diff --git a/FEATURES.md b/FEATURES.md index dc4082c798a..d7a64f15337 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -31,6 +31,10 @@ for a detailed explanation. Makes ember test helpers (`fillIn`, `click`, `triggerEvent` ...) fire native javascript events instead of `jQuery.Event`s, maching more closely app's real usage. +* `ember-route-serializers` + + Deprecates `Route#serialize` and introduces a `serialize` option to the router DSL as a replacement (as per the [Route Serializers RFC](https://github.com/emberjs/rfcs/blob/master/text/0120-route-serializers.md)). + * `ember-runtime-computed-uniq-by` Introduces a computed and enumerable method "uniqBy" that allows creation of a new enumerable with unique values as determined by the given property key. diff --git a/features.json b/features.json index de6cc760119..77a61829944 100644 --- a/features.json +++ b/features.json @@ -8,6 +8,7 @@ "ember-metal-ember-assign": true, "ember-htmlbars-local-lookup": true, "ember-application-engines": null, + "ember-route-serializers": null, "ember-glimmer": null, "ember-runtime-computed-uniq-by": null, "ember-improved-instrumentation": null diff --git a/packages/ember-routing/lib/system/dsl.js b/packages/ember-routing/lib/system/dsl.js index 292dc719a4d..a20ccdbffcb 100644 --- a/packages/ember-routing/lib/system/dsl.js +++ b/packages/ember-routing/lib/system/dsl.js @@ -1,4 +1,5 @@ import { assert, deprecate } from 'ember-metal/debug'; +import isEnabled from 'ember-metal/features'; /** @module ember @@ -11,6 +12,10 @@ function DSL(name, options) { this.matches = []; this.explicitIndex = undefined; this.options = options; + + if (isEnabled('ember-route-serializers')) { + this.router = options && options.router; + } } export default DSL; @@ -41,6 +46,10 @@ DSL.prototype = { createRoute(this, `${name}_error`, { path: dummyErrorRoute }); } + if (isEnabled('ember-route-serializers') && options.serialize && this.router) { + this.router._serializeMethods[name] = options.serialize; + } + if (callback) { var fullName = getFullName(this, name, options.resetNamespace); var dsl = new DSL(fullName, this.options); diff --git a/packages/ember-routing/lib/system/route.js b/packages/ember-routing/lib/system/route.js index 23cdff9cd32..7c1442d7720 100644 --- a/packages/ember-routing/lib/system/route.js +++ b/packages/ember-routing/lib/system/route.js @@ -31,10 +31,41 @@ import { } from 'ember-routing/utils'; import { getOwner } from 'container/owner'; import isEmpty from 'ember-metal/is_empty'; +import symbol from 'ember-metal/symbol'; var slice = Array.prototype.slice; function K() { return this; } +function defaultSerialize(model, params) { + if (params.length < 1) { return; } + if (!model) { return; } + + var name = params[0]; + var object = {}; + + if (params.length === 1) { + if (name in model) { + object[name] = get(model, name); + } else if (/_id$/.test(name)) { + object[name] = get(model, 'id'); + } + } else { + object = getProperties(model, params); + } + + return object; +} + +const DEFAULT_SERIALIZE = symbol('DEFAULT_SERIALIZE'); + +if (isEnabled('ember-route-serializers')) { + defaultSerialize[DEFAULT_SERIALIZE] = true; +} + +export function hasDefaultSerialize(route) { + return !!route.serialize[DEFAULT_SERIALIZE]; +} + /** @module ember @submodule ember-routing @@ -1583,25 +1614,7 @@ var Route = EmberObject.extend(ActionHandler, Evented, { @return {Object} the serialized parameters @public */ - serialize(model, params) { - if (params.length < 1) { return; } - if (!model) { return; } - - var name = params[0]; - var object = {}; - - if (params.length === 1) { - if (name in model) { - object[name] = get(model, name); - } else if (/_id$/.test(name)) { - object[name] = get(model, 'id'); - } - } else { - object = getProperties(model, params); - } - - return object; - }, + serialize: defaultSerialize, /** A hook you can use to setup the controller for the current route. diff --git a/packages/ember-routing/lib/system/router.js b/packages/ember-routing/lib/system/router.js index 59f45403187..ab968f60719 100644 --- a/packages/ember-routing/lib/system/router.js +++ b/packages/ember-routing/lib/system/router.js @@ -1,6 +1,7 @@ import Logger from 'ember-metal/logger'; -import { assert, info } from 'ember-metal/debug'; +import { assert, info, deprecate } from 'ember-metal/debug'; import EmberError from 'ember-metal/error'; +import isEnabled from 'ember-metal/features'; import { get } from 'ember-metal/property_get'; import { set } from 'ember-metal/property_set'; import { defineProperty } from 'ember-metal/properties'; @@ -10,6 +11,7 @@ import assign from 'ember-metal/assign'; import run from 'ember-metal/run_loop'; import EmberObject from 'ember-runtime/system/object'; import Evented from 'ember-runtime/mixins/evented'; +import { hasDefaultSerialize } from 'ember-routing/system/route'; import EmberRouterDSL from 'ember-routing/system/dsl'; import EmberLocation from 'ember-routing/location/api'; import { @@ -102,10 +104,15 @@ var EmberRouter = EmberObject.extend(Evented, { _buildDSL() { let moduleBasedResolver = this._hasModuleBasedResolver(); - - return new EmberRouterDSL(null, { + let options = { enableLoadingSubstates: !!moduleBasedResolver - }); + }; + + if (isEnabled('ember-route-serializers')) { + options.router = this; + } + + return new EmberRouterDSL(null, options); }, init() { @@ -115,6 +122,10 @@ var EmberRouter = EmberObject.extend(Evented, { this._qpCache = new EmptyObject(); this._resetQueuedQueryParameterChanges(); this._handledErrors = dictionary(null); + + if (isEnabled('ember-route-serializers')) { + this._serializeMethods = new EmptyObject(); + } }, /* @@ -548,6 +559,18 @@ var EmberRouter = EmberObject.extend(Evented, { } } + if (isEnabled('ember-route-serializers')) { + deprecate( + `Defining a serialize function on route '${name}' is deprecated. Instead, define it in the router's map as an option.`, + hasDefaultSerialize(handler), + { id: 'ember-routing.serialize-function', until: '3.0.0', url: 'http://emberjs.com/deprecations/v2.x#toc_route-serialize' } + ); + + if (this._serializeMethods[name]) { + handler.serialize = this._serializeMethods[name]; + } + } + handler.routeName = name; return handler; }; diff --git a/packages/ember/tests/helpers/link_to_test.js b/packages/ember/tests/helpers/link_to_test.js index e371755413e..b56922bce35 100644 --- a/packages/ember/tests/helpers/link_to_test.js +++ b/packages/ember/tests/helpers/link_to_test.js @@ -742,12 +742,6 @@ QUnit.test('The {{link-to}} helper moves into the named route with context', fun } }); - App.ItemRoute = Route.extend({ - serialize(object) { - return { id: object.id }; - } - }); - bootApplication(); run(function() { @@ -925,22 +919,36 @@ QUnit.test('Issue 4201 - Shorthand for route.index shouldn\'t throw errors about QUnit.test('The {{link-to}} helper unwraps controllers', function() { expect(5); - Router.map(function() { - this.route('filter', { path: '/filters/:filter' }); - }); - var indexObject = { filter: 'popular' }; - App.FilterRoute = Route.extend({ - model(params) { - return indexObject; - }, + function serializeFilterRoute(passedObject) { + equal(passedObject, indexObject, 'The unwrapped object is passed'); + return { filter: 'popular' }; + } - serialize(passedObject) { - equal(passedObject, indexObject, 'The unwrapped object is passed'); - return { filter: 'popular' }; - } - }); + if (isEnabled('ember-route-serializers')) { + Router.map(function() { + this.route('filter', { path: '/filters/:filter', serialize: serializeFilterRoute }); + }); + + App.FilterRoute = Route.extend({ + model(params) { + return indexObject; + } + }); + } else { + Router.map(function() { + this.route('filter', { path: '/filters/:filter' }); + }); + + App.FilterRoute = Route.extend({ + model(params) { + return indexObject; + }, + + serialize: serializeFilterRoute + }); + } App.IndexRoute = Route.extend({ model() { @@ -1272,6 +1280,7 @@ QUnit.test('The non-block form {{link-to}} helper updates the link text when it QUnit.test('The non-block form {{link-to}} helper moves into the named route with context', function() { expect(5); + Router.map(function(match) { this.route('item', { path: '/item/:id' }); }); @@ -1286,12 +1295,6 @@ QUnit.test('The non-block form {{link-to}} helper moves into the named route wit } }); - App.ItemRoute = Route.extend({ - serialize(object) { - return { id: object.id }; - } - }); - Ember.TEMPLATES.index = compile('
{{model.name}}
{{#link-to \'index\' id=\'home-link\'}}Home{{/link-to}}'); diff --git a/packages/ember/tests/routing/basic_test.js b/packages/ember/tests/routing/basic_test.js index fc577cad18f..027ab9cb4ce 100644 --- a/packages/ember/tests/routing/basic_test.js +++ b/packages/ember/tests/routing/basic_test.js @@ -1137,11 +1137,56 @@ asyncTest('Moving from one page to another triggers the correct callbacks', func }); asyncTest('Nested callbacks are not exited when moving to siblings', function() { - Router.map(function() { - this.route('root', { path: '/' }, function() { - this.route('special', { path: '/specials/:menu_item_id', resetNamespace: true }); + function serializeRootRoute() { + rootSerialize++; + return this._super(...arguments); + } + + if (isEnabled('ember-route-serializers')) { + Router.map(function() { + this.route('root', { path: '/', serialize: serializeRootRoute }, function() { + this.route('special', { path: '/specials/:menu_item_id', resetNamespace: true }); + }); }); - }); + + App.RootRoute = Route.extend({ + model() { + rootModel++; + return this._super(...arguments); + }, + + setupController() { + rootSetup++; + }, + + renderTemplate() { + rootRender++; + } + }); + } else { + Router.map(function() { + this.route('root', { path: '/' }, function() { + this.route('special', { path: '/specials/:menu_item_id', resetNamespace: true }); + }); + }); + + App.RootRoute = Route.extend({ + model() { + rootModel++; + return this._super(...arguments); + }, + + setupController() { + rootSetup++; + }, + + renderTemplate() { + rootRender++; + }, + + serialize: serializeRootRoute + }); + } var currentPath; @@ -1165,26 +1210,6 @@ asyncTest('Nested callbacks are not exited when moving to siblings', function() }); - App.RootRoute = Route.extend({ - model() { - rootModel++; - return this._super(...arguments); - }, - - serialize() { - rootSerialize++; - return this._super(...arguments); - }, - - setupController() { - rootSetup++; - }, - - renderTemplate() { - rootRender++; - } - }); - App.HomeRoute = Route.extend({ }); @@ -1892,10 +1917,6 @@ QUnit.test('Redirecting to the current target with a different context aborts th } else { this.transitionTo('bar.baz', model); } - }, - - serialize(params) { - return params; } }); @@ -2309,12 +2330,6 @@ QUnit.test('Nested index route is not overriden by parent\'s implicit index rout }); }); - App.Route = Route.extend({ - serialize(model) { - return { category: model.category }; - } - }); - bootApplication(); run(function() { @@ -2324,6 +2339,34 @@ QUnit.test('Nested index route is not overriden by parent\'s implicit index rout deepEqual(router.location.path, '/posts/emberjs'); }); +if (isEnabled('ember-route-serializers')) { + QUnit.test('Custom Route#serialize method still works [DEPRECATED]', function() { + Router.map(function() { + this.route('posts', function() { + this.route('index', { + path: ':category' + }); + }); + }); + + App.PostsIndexRoute = Route.extend({ + serialize(model) { + return { category: model.category }; + } + }); + + bootApplication(); + + run(function() { + expectDeprecation(function() { + router.transitionTo('posts', { category: 'emberjs' }); + }, 'Defining a serialize function on route \'posts\' is deprecated. Instead, define it in the router\'s map as an option.'); + }); + + deepEqual(router.location.path, '/posts/emberjs'); + }); +} + QUnit.test('Application template does not duplicate when re-rendered', function() { Ember.TEMPLATES.application = compile('