From 95973cf35a59fae073889d9a89a16694cb07803f Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Fri, 11 Jan 2019 15:46:58 -0800 Subject: [PATCH] review feedback --- .../@ember/-internals/metal/lib/computed.ts | 6 +- .../-internals/metal/tests/computed_test.js | 72 +++--- .../metal/tests/mixin/computed_test.js | 104 ++++---- .../-internals/routing/lib/system/route.ts | 226 +++++++++--------- .../routing/tests/system/route_test.js | 4 +- .../mixins/observable/propertyChanges_test.js | 28 ++- .../tests/system/object/computed_test.js | 14 +- .../lib/computed/reduce_computed_macros.js | 163 +++++++++++-- 8 files changed, 368 insertions(+), 249 deletions(-) diff --git a/packages/@ember/-internals/metal/lib/computed.ts b/packages/@ember/-internals/metal/lib/computed.ts index 6142029941a..9b9b50dc03a 100644 --- a/packages/@ember/-internals/metal/lib/computed.ts +++ b/packages/@ember/-internals/metal/lib/computed.ts @@ -1,5 +1,5 @@ import { meta as metaFor, peekMeta } from '@ember/-internals/meta'; -import { inspect } from '@ember/-internals/utils'; +import { inspect, toString } from '@ember/-internals/utils'; import { EMBER_METAL_TRACKED_PROPERTIES } from '@ember/canary-features'; import { assert, deprecate, warn } from '@ember/debug'; import EmberError from '@ember/error'; @@ -476,7 +476,9 @@ class ComputedProperty extends Descriptor implements DescriptorWithDependentKeys clobberSet(obj: object, keyName: string, value: any): any { deprecate( - `The ${keyName} computed property was just overriden. This removes the computed property and replaces it with a plain value, and has been deprecated. If you want this behavior, consider defining a setter which does it manually.`, + `The ${toString( + obj + )}#${keyName} computed property was just overriden. This removes the computed property and replaces it with a plain value, and has been deprecated. If you want this behavior, consider defining a setter which does it manually.`, false, { id: 'computed-property.override', diff --git a/packages/@ember/-internals/metal/tests/computed_test.js b/packages/@ember/-internals/metal/tests/computed_test.js index f37e707710f..d524a067d5b 100644 --- a/packages/@ember/-internals/metal/tests/computed_test.js +++ b/packages/@ember/-internals/metal/tests/computed_test.js @@ -77,15 +77,17 @@ moduleFor( } ['@test can override volatile computed property'](assert) { - expectDeprecation(() => { - let obj = {}; + let obj = {}; + expectDeprecation(() => { defineProperty(obj, 'foo', computed(function() {}).volatile()); + }, 'Setting a computed property as volatile has been deprecated. Instead, consider using a native getter with native class syntax.'); + expectDeprecation(() => { set(obj, 'foo', 'boom'); + }, /The \[object Object\]#foo computed property was just overriden./); - assert.equal(obj.foo, 'boom'); - }, 'Setting a computed property as volatile has been deprecated. Instead, consider using a native getter with native class syntax.'); + assert.equal(obj.foo, 'boom'); } ['@test defining computed property should invoke property on set'](assert) { @@ -740,23 +742,25 @@ moduleFor( } ['@test setter can be omited'](assert) { - expectDeprecation(() => { - let testObj = EmberObject.extend({ - a: '1', - b: '2', - aInt: computed('a', { - get(keyName) { - assert.equal(keyName, 'aInt', 'getter receives the keyName'); - return parseInt(this.get('a')); - }, - }), - }).create(); + let testObj = EmberObject.extend({ + a: '1', + b: '2', + aInt: computed('a', { + get(keyName) { + assert.equal(keyName, 'aInt', 'getter receives the keyName'); + return parseInt(this.get('a')); + }, + }), + }).create(); - assert.ok(testObj.get('aInt') === 1, 'getter works'); - assert.ok(testObj.get('a') === '1'); + assert.ok(testObj.get('aInt') === 1, 'getter works'); + assert.ok(testObj.get('a') === '1'); + + expectDeprecation(() => { testObj.set('aInt', '123'); - assert.ok(testObj.get('aInt') === '123', 'cp has been updated too'); - }, /The aInt computed property was just overriden/); + }, /The <\(unknown\):ember\d*>#aInt computed property was just overriden/); + + assert.ok(testObj.get('aInt') === '123', 'cp has been updated too'); } ['@test getter can be omited'](assert) { @@ -940,26 +944,26 @@ moduleFor( 'computed - default setter', class extends AbstractTestCase { ["@test when setting a value on a computed property that doesn't handle sets"](assert) { - expectDeprecation(() => { - let obj = {}; - let observerFired = false; + let obj = {}; + let observerFired = false; - defineProperty( - obj, - 'foo', - computed(function() { - return 'foo'; - }) - ); + defineProperty( + obj, + 'foo', + computed(function() { + return 'foo'; + }) + ); - addObserver(obj, 'foo', null, () => (observerFired = true)); + addObserver(obj, 'foo', null, () => (observerFired = true)); + expectDeprecation(() => { set(obj, 'foo', 'bar'); + }, /The \[object Object\]#foo computed property was just overriden./); - assert.equal(get(obj, 'foo'), 'bar', 'The set value is properly returned'); - assert.ok(typeof obj.foo === 'string', 'The computed property was removed'); - assert.ok(observerFired, 'The observer was still notified'); - }, /The foo computed property was just overriden./); + assert.equal(get(obj, 'foo'), 'bar', 'The set value is properly returned'); + assert.ok(typeof obj.foo === 'string', 'The computed property was removed'); + assert.ok(observerFired, 'The observer was still notified'); } } ); diff --git a/packages/@ember/-internals/metal/tests/mixin/computed_test.js b/packages/@ember/-internals/metal/tests/mixin/computed_test.js index 1a9a07afd6c..071464c5d86 100644 --- a/packages/@ember/-internals/metal/tests/mixin/computed_test.js +++ b/packages/@ember/-internals/metal/tests/mixin/computed_test.js @@ -110,62 +110,60 @@ moduleFor( } ['@test setter behavior works properly when overriding computed properties'](assert) { - expectDeprecation(() => { - let obj = {}; - - let MixinA = Mixin.create({ - cpWithSetter2: computed(K), - cpWithSetter3: computed(K), - cpWithoutSetter: computed(K), - }); - - let cpWasCalled = false; - - let MixinB = Mixin.create({ - cpWithSetter2: computed({ - get: K, - set() { - cpWasCalled = true; - }, - }), - - cpWithSetter3: computed({ - get: K, - set() { - cpWasCalled = true; - }, - }), - - cpWithoutSetter: computed(function() { + let obj = {}; + + let MixinA = Mixin.create({ + cpWithSetter2: computed(K), + cpWithSetter3: computed(K), + cpWithoutSetter: computed(K), + }); + + let cpWasCalled = false; + + let MixinB = Mixin.create({ + cpWithSetter2: computed({ + get: K, + set() { cpWasCalled = true; - }), - }); - - MixinA.apply(obj); - MixinB.apply(obj); - - set(obj, 'cpWithSetter2', 'test'); - assert.ok( - cpWasCalled, - 'The computed property setter was called when defined with two args' - ); - cpWasCalled = false; - - set(obj, 'cpWithSetter3', 'test'); - assert.ok( - cpWasCalled, - 'The computed property setter was called when defined with three args' - ); - cpWasCalled = false; + }, + }), + + cpWithSetter3: computed({ + get: K, + set() { + cpWasCalled = true; + }, + }), + cpWithoutSetter: computed(function() { + cpWasCalled = true; + }), + }); + + MixinA.apply(obj); + MixinB.apply(obj); + + set(obj, 'cpWithSetter2', 'test'); + assert.ok(cpWasCalled, 'The computed property setter was called when defined with two args'); + cpWasCalled = false; + + set(obj, 'cpWithSetter3', 'test'); + assert.ok( + cpWasCalled, + 'The computed property setter was called when defined with three args' + ); + cpWasCalled = false; + + expectDeprecation(() => { set(obj, 'cpWithoutSetter', 'test'); - assert.equal( - get(obj, 'cpWithoutSetter'), - 'test', - 'The default setter was called, the value is correct' - ); - assert.ok(!cpWasCalled, 'The default setter was called, not the CP itself'); - }, /The cpWithoutSetter computed property was just overriden./); + }, /The \[object Object\]#cpWithoutSetter computed property was just overriden./); + + assert.equal( + get(obj, 'cpWithoutSetter'), + 'test', + 'The default setter was called, the value is correct' + ); + assert.ok(!cpWasCalled, 'The default setter was called, not the CP itself'); } } ); diff --git a/packages/@ember/-internals/routing/lib/system/route.ts b/packages/@ember/-internals/routing/lib/system/route.ts index babf671acce..392247e6836 100644 --- a/packages/@ember/-internals/routing/lib/system/route.ts +++ b/packages/@ember/-internals/routing/lib/system/route.ts @@ -2185,134 +2185,128 @@ Route.reopen(ActionHandler, Evented, { @property _qp */ - _qp: computed({ - get(this: Route) { - let combinedQueryParameterConfiguration; + _qp: computed(function(this: Route) { + let combinedQueryParameterConfiguration; - let controllerName = this.controllerName || this.routeName; - let owner = getOwner(this); - let controller = owner.lookup(`controller:${controllerName}`); - let queryParameterConfiguraton = get(this, 'queryParams'); - let hasRouterDefinedQueryParams = Object.keys(queryParameterConfiguraton).length > 0; - - if (controller) { - // the developer has authored a controller class in their application for - // this route find its query params and normalize their object shape them - // merge in the query params for the route. As a mergedProperty, - // Route#queryParams is always at least `{}` - - let controllerDefinedQueryParameterConfiguration = get(controller, 'queryParams') || {}; - let normalizedControllerQueryParameterConfiguration = normalizeControllerQueryParams( - controllerDefinedQueryParameterConfiguration - ); - combinedQueryParameterConfiguration = mergeEachQueryParams( - normalizedControllerQueryParameterConfiguration, - queryParameterConfiguraton - ); - } else if (hasRouterDefinedQueryParams) { - // the developer has not defined a controller but *has* supplied route query params. - // Generate a class for them so we can later insert default values - controller = generateController(owner, controllerName); - combinedQueryParameterConfiguration = queryParameterConfiguraton; - } - - let qps = []; - let map = {}; - let propertyNames = []; - - for (let propName in combinedQueryParameterConfiguration) { - if (!combinedQueryParameterConfiguration.hasOwnProperty(propName)) { - continue; - } + let controllerName = this.controllerName || this.routeName; + let owner = getOwner(this); + let controller = owner.lookup(`controller:${controllerName}`); + let queryParameterConfiguraton = get(this, 'queryParams'); + let hasRouterDefinedQueryParams = Object.keys(queryParameterConfiguraton).length > 0; + + if (controller) { + // the developer has authored a controller class in their application for + // this route find its query params and normalize their object shape them + // merge in the query params for the route. As a mergedProperty, + // Route#queryParams is always at least `{}` + + let controllerDefinedQueryParameterConfiguration = get(controller, 'queryParams') || {}; + let normalizedControllerQueryParameterConfiguration = normalizeControllerQueryParams( + controllerDefinedQueryParameterConfiguration + ); + combinedQueryParameterConfiguration = mergeEachQueryParams( + normalizedControllerQueryParameterConfiguration, + queryParameterConfiguraton + ); + } else if (hasRouterDefinedQueryParams) { + // the developer has not defined a controller but *has* supplied route query params. + // Generate a class for them so we can later insert default values + controller = generateController(owner, controllerName); + combinedQueryParameterConfiguration = queryParameterConfiguraton; + } - // to support the dubious feature of using unknownProperty - // on queryParams configuration - if (propName === 'unknownProperty' || propName === '_super') { - // possible todo: issue deprecation warning? - continue; - } + let qps = []; + let map = {}; + let propertyNames = []; - let desc = combinedQueryParameterConfiguration[propName]; - let scope = desc.scope || 'model'; - let parts; + for (let propName in combinedQueryParameterConfiguration) { + if (!combinedQueryParameterConfiguration.hasOwnProperty(propName)) { + continue; + } - if (scope === 'controller') { - parts = []; - } + // to support the dubious feature of using unknownProperty + // on queryParams configuration + if (propName === 'unknownProperty' || propName === '_super') { + // possible todo: issue deprecation warning? + continue; + } - let urlKey = desc.as || this.serializeQueryParamKey(propName); - let defaultValue = get(controller, propName); + let desc = combinedQueryParameterConfiguration[propName]; + let scope = desc.scope || 'model'; + let parts; - if (Array.isArray(defaultValue)) { - defaultValue = emberA(defaultValue.slice()); - } + if (scope === 'controller') { + parts = []; + } - let type = desc.type || typeOf(defaultValue); - - let defaultValueSerialized = this.serializeQueryParam(defaultValue, urlKey, type); - let scopedPropertyName = `${controllerName}:${propName}`; - let qp = { - undecoratedDefaultValue: get(controller, propName), - defaultValue, - serializedDefaultValue: defaultValueSerialized, - serializedValue: defaultValueSerialized, - - type, - urlKey, - prop: propName, - scopedPropertyName, - controllerName, - route: this, - parts, // provided later when stashNames is called if 'model' scope - values: null, // provided later when setup is called. no idea why. - scope, - }; + let urlKey = desc.as || this.serializeQueryParamKey(propName); + let defaultValue = get(controller, propName); - map[propName] = map[urlKey] = map[scopedPropertyName] = qp; - qps.push(qp); - propertyNames.push(propName); + if (Array.isArray(defaultValue)) { + defaultValue = emberA(defaultValue.slice()); } - return { - qps, - map, - propertyNames, - states: { - /* - Called when a query parameter changes in the URL, this route cares - about that query parameter, but the route is not currently - in the active route hierarchy. - */ - inactive: (prop: string, value: unknown) => { - let qp = map[prop]; - this._qpChanged(prop, value, qp); - }, - /* - Called when a query parameter changes in the URL, this route cares - about that query parameter, and the route is currently - in the active route hierarchy. - */ - active: (prop: string, value: unknown) => { - let qp = map[prop]; - this._qpChanged(prop, value, qp); - return this._activeQPChanged(qp, value); - }, - /* - Called when a value of a query parameter this route handles changes in a controller - and the route is currently in the active route hierarchy. - */ - allowOverrides: (prop: string, value: unknown) => { - let qp = map[prop]; - this._qpChanged(prop, value, qp); - return this._updatingQPChanged(qp); - }, - }, + let type = desc.type || typeOf(defaultValue); + + let defaultValueSerialized = this.serializeQueryParam(defaultValue, urlKey, type); + let scopedPropertyName = `${controllerName}:${propName}`; + let qp = { + undecoratedDefaultValue: get(controller, propName), + defaultValue, + serializedDefaultValue: defaultValueSerialized, + serializedValue: defaultValueSerialized, + + type, + urlKey, + prop: propName, + scopedPropertyName, + controllerName, + route: this, + parts, // provided later when stashNames is called if 'model' scope + values: null, // provided later when setup is called. no idea why. + scope, }; - }, - set(key, value) { - defineProperty(this, key, null, value); - }, + map[propName] = map[urlKey] = map[scopedPropertyName] = qp; + qps.push(qp); + propertyNames.push(propName); + } + + return { + qps, + map, + propertyNames, + states: { + /* + Called when a query parameter changes in the URL, this route cares + about that query parameter, but the route is not currently + in the active route hierarchy. + */ + inactive: (prop: string, value: unknown) => { + let qp = map[prop]; + this._qpChanged(prop, value, qp); + }, + /* + Called when a query parameter changes in the URL, this route cares + about that query parameter, and the route is currently + in the active route hierarchy. + */ + active: (prop: string, value: unknown) => { + let qp = map[prop]; + this._qpChanged(prop, value, qp); + return this._activeQPChanged(qp, value); + }, + /* + Called when a value of a query parameter this route handles changes in a controller + and the route is currently in the active route hierarchy. + */ + allowOverrides: (prop: string, value: unknown) => { + let qp = map[prop]; + this._qpChanged(prop, value, qp); + return this._updatingQPChanged(qp); + }, + }, + }; }), /** diff --git a/packages/@ember/-internals/routing/tests/system/route_test.js b/packages/@ember/-internals/routing/tests/system/route_test.js index 90d5a29cd4a..870db3fbfea 100644 --- a/packages/@ember/-internals/routing/tests/system/route_test.js +++ b/packages/@ember/-internals/routing/tests/system/route_test.js @@ -3,6 +3,7 @@ import { runDestroy, buildOwner, moduleFor, AbstractTestCase } from 'internal-te import Service, { inject as injectService } from '@ember/service'; import { Object as EmberObject } from '@ember/-internals/runtime'; import EmberRoute from '../../lib/system/route'; +import { defineProperty } from '../../../metal'; let route, routeOne, routeTwo, lookupHash; @@ -53,7 +54,8 @@ moduleFor( let owner = buildOwner(ownerOptions); setOwner(route, owner); - route.set('_qp', null); + // Override the computed property by redefining it + defineProperty(route, '_qp', null, null); assert.equal(route.model({ post_id: 1 }), post); assert.equal(route.findModel('post', 1), post, '#findModel returns the correct post'); diff --git a/packages/@ember/-internals/runtime/tests/legacy_1x/mixins/observable/propertyChanges_test.js b/packages/@ember/-internals/runtime/tests/legacy_1x/mixins/observable/propertyChanges_test.js index d73e9faa7bf..a672cdd8754 100644 --- a/packages/@ember/-internals/runtime/tests/legacy_1x/mixins/observable/propertyChanges_test.js +++ b/packages/@ember/-internals/runtime/tests/legacy_1x/mixins/observable/propertyChanges_test.js @@ -112,8 +112,10 @@ moduleFor( ['@test should invalidate function property cache when notifyPropertyChange is called']( assert ) { + let a; + expectDeprecation(() => { - let a = ObservableObject.extend({ + a = ObservableObject.extend({ b: computed({ get() { return this._b; @@ -126,19 +128,19 @@ moduleFor( }).create({ _b: null, }); - - a.set('b', 'foo'); - assert.equal(a.get('b'), 'foo', 'should have set the correct value for property b'); - - a._b = 'bar'; - a.notifyPropertyChange('b'); - a.set('b', 'foo'); - assert.equal( - a.get('b'), - 'foo', - 'should have invalidated the cache so that the newly set value is actually set' - ); }, /Setting a computed property as volatile has been deprecated/); + + a.set('b', 'foo'); + assert.equal(a.get('b'), 'foo', 'should have set the correct value for property b'); + + a._b = 'bar'; + a.notifyPropertyChange('b'); + a.set('b', 'foo'); + assert.equal( + a.get('b'), + 'foo', + 'should have invalidated the cache so that the newly set value is actually set' + ); } } ); diff --git a/packages/@ember/-internals/runtime/tests/system/object/computed_test.js b/packages/@ember/-internals/runtime/tests/system/object/computed_test.js index d48ae6f9d7d..7bbf03b4bfd 100644 --- a/packages/@ember/-internals/runtime/tests/system/object/computed_test.js +++ b/packages/@ember/-internals/runtime/tests/system/object/computed_test.js @@ -358,21 +358,23 @@ moduleFor( } ['@test can declare dependent keys with .property()'](assert) { + let Obj; + expectDeprecation(() => { - let Obj = EmberObject.extend({ + Obj = EmberObject.extend({ foo: computed(function() { return this.bar; }).property('bar'), }); + }, /Setting dependency keys using the `.property\(\)` modifier has been deprecated/); - let obj = Obj.create({ bar: 1 }); + let obj = Obj.create({ bar: 1 }); - assert.equal(obj.get('foo'), 1); + assert.equal(obj.get('foo'), 1); - obj.set('bar', 2); + obj.set('bar', 2); - assert.equal(obj.get('foo'), 2); - }, /Setting dependency keys using the `.property\(\)` modifier has been deprecated/); + assert.equal(obj.get('foo'), 2); } } ); diff --git a/packages/@ember/object/lib/computed/reduce_computed_macros.js b/packages/@ember/object/lib/computed/reduce_computed_macros.js index c039665ade1..4f5a864bd40 100644 --- a/packages/@ember/object/lib/computed/reduce_computed_macros.js +++ b/packages/@ember/object/lib/computed/reduce_computed_macros.js @@ -213,11 +213,39 @@ export function min(dependentKey) { hamster.get('excitingChores'); // ['CLEAN!', 'WRITE MORE UNIT TESTS!'] ``` + You can optionally pass an array of additional dependent keys as the second + parameter to the macro, if your map function relies on any external values: + + ```javascript + import { map } from '@ember/object/computed'; + import EmberObject from '@ember/object'; + + let Hamster = EmberObject.extend({ + excitingChores: map('chores', ['shouldUpperCase'], function(chore, index) { + if (this.shouldUpperCase) { + return chore.toUpperCase() + '!'; + } else { + return chore + '!'; + } + }) + }); + + let hamster = Hamster.create({ + shouldUpperCase: false, + + chores: ['clean', 'write more unit tests'] + }); + + hamster.get('excitingChores'); // ['clean!', 'write more unit tests!'] + hamster.set('shouldUpperCase', true); + hamster.get('excitingChores'); // ['CLEAN!', 'WRITE MORE UNIT TESTS!'] + ``` + @method map @for @ember/object/computed @static @param {String} dependentKey - @param {Array} additionalDependentKeys optional array of additional dependent keys + @param {Array} [additionalDependentKeys] optional array of additional dependent keys @param {Function} callback @return {ComputedProperty} an array mapped via the callback @public @@ -349,12 +377,38 @@ export function mapBy(dependentKey, propertyKey) { hamster.get('remainingChores'); // [] ``` + Finally, you can optionally pass an array of additional dependent keys as the + second parameter to the macro, if your filter function relies on any external + values: + + ```javascript + import { filter } from '@ember/object/computed'; + import EmberObject from '@ember/object'; + + let Hamster = EmberObject.extend({ + remainingChores: filter('chores', ['doneKey'], function(chore, index, array) { + return !chore[this.doneKey]; + }) + }); + + let hamster = Hamster.create({ + doneKey: 'finished' + + chores: [ + { name: 'cook', finished: true }, + { name: 'clean', finished: true }, + { name: 'write more unit tests', finished: false } + ] + }); + + hamster.get('remainingChores'); // [{name: 'write more unit tests', finished: false}] + ``` @method filter @for @ember/object/computed @static @param {String} dependentKey - @param {Array} additionalDependentKeys optional array of additional dependent keys + @param {Array} [additionalDependentKeys] optional array of additional dependent keys @param {Function} callback @return {ComputedProperty} the filtered array @public @@ -754,9 +808,13 @@ export function collect(...dependentKeys) { /** A computed property which returns a new array with all the properties from the first dependent array sorted based on a property - or sort function. + or sort function. The sort macro can be used in two different ways: + + 1. By providing a sort callback function + 2. By providing an array of keys to sort the array - The callback method you provide should have the following signature: + In the first form, the callback method you provide should have the following + signature: ```javascript function(itemA, itemB); @@ -765,12 +823,14 @@ export function collect(...dependentKeys) { - `itemA` the first item to compare. - `itemB` the second item to compare. - This function should return negative number (e.g. `-1`) when `itemA` should come before - `itemB`. It should return positive number (e.g. `1`) when `itemA` should come after - `itemB`. If the `itemA` and `itemB` are equal this function should return `0`. + This function should return negative number (e.g. `-1`) when `itemA` should + come before `itemB`. It should return positive number (e.g. `1`) when `itemA` + should come after `itemB`. If the `itemA` and `itemB` are equal this function + should return `0`. - Therefore, if this function is comparing some numeric values, simple `itemA - itemB` or - `itemA.get( 'foo' ) - itemB.get( 'foo' )` can be used instead of series of `if`. + Therefore, if this function is comparing some numeric values, simple `itemA - + itemB` or `itemA.get( 'foo' ) - itemB.get( 'foo' )` can be used instead of + series of `if`. Example @@ -779,14 +839,6 @@ export function collect(...dependentKeys) { import EmberObject from '@ember/object'; let ToDoList = EmberObject.extend({ - // using standard ascending sort - todosSorting: Object.freeze(['name']), - sortedTodos: sort('todos', 'todosSorting'), - - // using descending sort - todosSortingDesc: Object.freeze(['name:desc']), - sortedTodosDesc: sort('todos', 'todosSortingDesc'), - // using a custom sort function priorityTodos: sort('todos', function(a, b){ if (a.priority > b.priority) { @@ -799,22 +851,85 @@ export function collect(...dependentKeys) { }) }); - let todoList = ToDoList.create({todos: [ - { name: 'Unit Test', priority: 2 }, - { name: 'Documentation', priority: 3 }, - { name: 'Release', priority: 1 } - ]}); + let todoList = ToDoList.create({ + todos: [ + { name: 'Unit Test', priority: 2 }, + { name: 'Documentation', priority: 3 }, + { name: 'Release', priority: 1 } + ] + }); + + todoList.get('priorityTodos'); // [{ name:'Release', priority:1 }, { name:'Unit Test', priority:2 }, { name:'Documentation', priority:3 }] + ``` + + You can also optionally pass an array of additional dependent keys as the + second parameter, if your sort function is dependent on additional values that + could changes: + + ```js + import { sort } from '@ember/object/computed'; + import EmberObject from '@ember/object'; + + let ToDoList = EmberObject.extend({ + // using a custom sort function + sortedTodos: sort('todos', ['sortKey'] function(a, b){ + if (a[this.sortKey] > b[this.sortKey]) { + return 1; + } else if (a[this.sortKey] < b[this.sortKey]) { + return -1; + } + + return 0; + }) + }); + + let todoList = ToDoList.create({ + sortKey: 'priority', + + todos: [ + { name: 'Unit Test', priority: 2 }, + { name: 'Documentation', priority: 3 }, + { name: 'Release', priority: 1 } + ] + }); + + todoList.get('priorityTodos'); // [{ name:'Release', priority:1 }, { name:'Unit Test', priority:2 }, { name:'Documentation', priority:3 }] + ``` + + In the second form, you should provide the key of the array of sort values as + the second parameter: + + ```javascript + import { sort } from '@ember/object/computed'; + import EmberObject from '@ember/object'; + + let ToDoList = EmberObject.extend({ + // using standard ascending sort + todosSorting: Object.freeze(['name']), + sortedTodos: sort('todos', 'todosSorting'), + + // using descending sort + todosSortingDesc: Object.freeze(['name:desc']), + sortedTodosDesc: sort('todos', 'todosSortingDesc'), + }); + + let todoList = ToDoList.create({ + todos: [ + { name: 'Unit Test', priority: 2 }, + { name: 'Documentation', priority: 3 }, + { name: 'Release', priority: 1 } + ] + }); todoList.get('sortedTodos'); // [{ name:'Documentation', priority:3 }, { name:'Release', priority:1 }, { name:'Unit Test', priority:2 }] todoList.get('sortedTodosDesc'); // [{ name:'Unit Test', priority:2 }, { name:'Release', priority:1 }, { name:'Documentation', priority:3 }] - todoList.get('priorityTodos'); // [{ name:'Release', priority:1 }, { name:'Unit Test', priority:2 }, { name:'Documentation', priority:3 }] ``` @method sort @for @ember/object/computed @static @param {String} itemsKey - @param {Array} additionalDependentKeys optional array of additional dependent keys + @param {Array} [additionalDependentKeys] optional array of additional dependent keys @param {String or Function} sortDefinition a dependent key to an array of sort properties (add `:desc` to the arrays sort properties to sort descending) or a function to use when sorting @return {ComputedProperty} computes a new sorted array based