UNPKG

ember-source

Version:

A JavaScript framework for creating ambitious web applications

1,078 lines (900 loc) 35.9 kB
import '../../../debug/index.js'; import { i as isElementDescriptor, c as computed, g as get, R as autoComputed } from '../../../../shared-chunks/cache-fCezwMOy.js'; import '../../../-internals/meta/lib/meta.js'; import '../../../../@glimmer/validator/index.js'; import '../../../../shared-chunks/mandatory-setter-CXNsxygN.js'; import { isDevelopingApp } from '@embroider/macros'; import '../../../../@glimmer/destroyable/index.js'; import '../../../../@glimmer/manager/index.js'; import '../../../../shared-chunks/env-CwR5CFCu.js'; import '../../index.js'; import '../../core.js'; import compare from '../../../utils/lib/compare.js'; import EmberArray, { A, uniqBy as uniqBy$1 } from '../../../array/index.js'; import { assert } from '../../../debug/lib/assert.js'; /** @module @ember/object */ function isNativeOrEmberArray(obj) { return Array.isArray(obj) || EmberArray.detect(obj); } function reduceMacro(dependentKey, callback, initialValue, name) { (isDevelopingApp() && !(!/[[\]{}]/g.test(dependentKey)) && assert(`Dependent key passed to \`${name}\` computed macro shouldn't contain brace expanding pattern.`, !/[[\]{}]/g.test(dependentKey))); return computed(`${dependentKey}.[]`, function () { let arr = get(this, dependentKey); if (arr === null || typeof arr !== 'object') { return initialValue; } return arr.reduce(callback, initialValue, this); }).readOnly(); } function arrayMacro(dependentKey, additionalDependentKeys, callback) { // This is a bit ugly let propertyName; if (/@each/.test(dependentKey)) { propertyName = dependentKey.replace(/\.@each.*$/, ''); } else { propertyName = dependentKey; dependentKey += '.[]'; } return computed(dependentKey, ...additionalDependentKeys, function () { let value = get(this, propertyName); if (isNativeOrEmberArray(value)) { return A(callback.call(this, value)); } else { return A(); } }).readOnly(); } function multiArrayMacro(_dependentKeys, callback, name) { (isDevelopingApp() && !(_dependentKeys.every(dependentKey => !/[[\]{}]/g.test(dependentKey))) && assert(`Dependent keys passed to \`${name}\` computed macro shouldn't contain brace expanding pattern.`, _dependentKeys.every(dependentKey => !/[[\]{}]/g.test(dependentKey)))); let dependentKeys = _dependentKeys.map(key => `${key}.[]`); return computed(...dependentKeys, function () { return A(callback.call(this, _dependentKeys)); }).readOnly(); } /** A computed property that returns the sum of the values in the dependent array. Example: ```javascript import { sum } from '@ember/object/computed'; class Invoice { lineItems = [1.00, 2.50, 9.99]; @sum('lineItems') total; } let invoice = new Invoice(); invoice.total; // 13.49 ``` @method sum @for @ember/object/computed @static @param {String} dependentKey @return {ComputedProperty} computes the sum of all values in the dependentKey's array @since 1.4.0 @public */ function sum(dependentKey) { (isDevelopingApp() && !(!isElementDescriptor(Array.prototype.slice.call(arguments))) && assert('You attempted to use @sum as a decorator directly, but it requires a `dependentKey` parameter', !isElementDescriptor(Array.prototype.slice.call(arguments)))); return reduceMacro(dependentKey, (sum, item) => sum + item, 0, 'sum'); } /** A computed property that calculates the maximum value in the dependent array. This will return `-Infinity` when the dependent array is empty. Example: ```javascript import { set } from '@ember/object'; import { mapBy, max } from '@ember/object/computed'; class Person { children = []; @mapBy('children', 'age') childAges; @max('childAges') maxChildAge; } let lordByron = new Person(); lordByron.maxChildAge; // -Infinity set(lordByron, 'children', [ { name: 'Augusta Ada Byron', age: 7 } ]); lordByron.maxChildAge; // 7 set(lordByron, 'children', [ ...lordByron.children, { name: 'Allegra Byron', age: 5 }, { name: 'Elizabeth Medora Leigh', age: 8 } ]); lordByron.maxChildAge; // 8 ``` If the types of the arguments are not numbers, they will be converted to numbers and the type of the return value will always be `Number`. For example, the max of a list of Date objects will be the highest timestamp as a `Number`. This behavior is consistent with `Math.max`. @method max @for @ember/object/computed @static @param {String} dependentKey @return {ComputedProperty} computes the largest value in the dependentKey's array @public */ function max(dependentKey) { (isDevelopingApp() && !(!isElementDescriptor(Array.prototype.slice.call(arguments))) && assert('You attempted to use @max as a decorator directly, but it requires a `dependentKey` parameter', !isElementDescriptor(Array.prototype.slice.call(arguments)))); return reduceMacro(dependentKey, (max, item) => Math.max(max, item), -Infinity, 'max'); } /** A computed property that calculates the minimum value in the dependent array. This will return `Infinity` when the dependent array is empty. Example: ```javascript import { set } from '@ember/object'; import { mapBy, min } from '@ember/object/computed'; class Person { children = []; @mapBy('children', 'age') childAges; @min('childAges') minChildAge; } let lordByron = Person.create({ children: [] }); lordByron.minChildAge; // Infinity set(lordByron, 'children', [ { name: 'Augusta Ada Byron', age: 7 } ]); lordByron.minChildAge; // 7 set(lordByron, 'children', [ ...lordByron.children, { name: 'Allegra Byron', age: 5 }, { name: 'Elizabeth Medora Leigh', age: 8 } ]); lordByron.minChildAge; // 5 ``` If the types of the arguments are not numbers, they will be converted to numbers and the type of the return value will always be `Number`. For example, the min of a list of Date objects will be the lowest timestamp as a `Number`. This behavior is consistent with `Math.min`. @method min @for @ember/object/computed @static @param {String} dependentKey @return {ComputedProperty} computes the smallest value in the dependentKey's array @public */ function min(dependentKey) { (isDevelopingApp() && !(!isElementDescriptor(Array.prototype.slice.call(arguments))) && assert('You attempted to use @min as a decorator directly, but it requires a `dependentKey` parameter', !isElementDescriptor(Array.prototype.slice.call(arguments)))); return reduceMacro(dependentKey, (min, item) => Math.min(min, item), Infinity, 'min'); } /** Returns an array mapped via the callback The callback method you provide should have the following signature: - `item` is the current item in the iteration. - `index` is the integer index of the current item in the iteration. ```javascript function mapCallback(item, index); ``` Example: ```javascript import { set } from '@ember/object'; import { map } from '@ember/object/computed'; class Hamster { constructor(chores) { set(this, 'chores', chores); } @map('chores', function(chore, index) { return `${chore.toUpperCase()}!`; }) excitingChores; }); let hamster = new Hamster(['clean', 'write more unit tests']); hamster.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 { set } from '@ember/object'; import { map } from '@ember/object/computed'; class Hamster { shouldUpperCase = false; constructor(chores) { set(this, 'chores', chores); } @map('chores', ['shouldUpperCase'], function(chore, index) { if (this.shouldUpperCase) { return `${chore.toUpperCase()}!`; } else { return `${chore}!`; } }) excitingChores; } let hamster = new Hamster(['clean', 'write more unit tests']); hamster.excitingChores; // ['clean!', 'write more unit tests!'] set(hamster, 'shouldUpperCase', true); hamster.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 {Function} callback @return {ComputedProperty} an array mapped via the callback @public */ function map(dependentKey, additionalDependentKeysOrCallback, callback) { (isDevelopingApp() && !(!isElementDescriptor(Array.prototype.slice.call(arguments))) && assert('You attempted to use @map as a decorator directly, but it requires atleast `dependentKey` and `callback` parameters', !isElementDescriptor(Array.prototype.slice.call(arguments)))); (isDevelopingApp() && !(typeof callback === 'function' || callback === undefined && typeof additionalDependentKeysOrCallback === 'function') && assert('The final parameter provided to map must be a callback function', typeof callback === 'function' || callback === undefined && typeof additionalDependentKeysOrCallback === 'function')); (isDevelopingApp() && !(Array.isArray(additionalDependentKeysOrCallback) || typeof additionalDependentKeysOrCallback === 'function') && assert('The second parameter provided to map must either be the callback or an array of additional dependent keys', Array.isArray(additionalDependentKeysOrCallback) || typeof additionalDependentKeysOrCallback === 'function')); let additionalDependentKeys; if (typeof additionalDependentKeysOrCallback === 'function') { callback = additionalDependentKeysOrCallback; additionalDependentKeys = []; } else { additionalDependentKeys = additionalDependentKeysOrCallback; } const cCallback = callback; (isDevelopingApp() && !(cCallback) && assert('[BUG] Missing callback', cCallback)); return arrayMacro(dependentKey, additionalDependentKeys, function (value) { // This is so dumb... return Array.isArray(value) ? value.map(cCallback, this) : value.map(cCallback, this); }); } /** Returns an array mapped to the specified key. Example: ```javascript import { set } from '@ember/object'; import { mapBy } from '@ember/object/computed'; class Person { children = []; @mapBy('children', 'age') childAges; } let lordByron = new Person(); lordByron.childAges; // [] set(lordByron, 'children', [ { name: 'Augusta Ada Byron', age: 7 } ]); lordByron.childAges; // [7] set(lordByron, 'children', [ ...lordByron.children, { name: 'Allegra Byron', age: 5 }, { name: 'Elizabeth Medora Leigh', age: 8 } ]); lordByron.childAges; // [7, 5, 8] ``` @method mapBy @for @ember/object/computed @static @param {String} dependentKey @param {String} propertyKey @return {ComputedProperty} an array mapped to the specified key @public */ function mapBy(dependentKey, propertyKey) { (isDevelopingApp() && !(!isElementDescriptor(Array.prototype.slice.call(arguments))) && assert('You attempted to use @mapBy as a decorator directly, but it requires `dependentKey` and `propertyKey` parameters', !isElementDescriptor(Array.prototype.slice.call(arguments)))); (isDevelopingApp() && !(typeof propertyKey === 'string') && assert('`mapBy` computed macro expects a property string for its second argument, ' + 'perhaps you meant to use "map"', typeof propertyKey === 'string')); (isDevelopingApp() && !(!/[[\]{}]/g.test(dependentKey)) && assert(`Dependent key passed to \`mapBy\` computed macro shouldn't contain brace expanding pattern.`, !/[[\]{}]/g.test(dependentKey))); return map(`${dependentKey}.@each.${propertyKey}`, item => get(item, propertyKey)); } /** Filters the array by the callback, like the `Array.prototype.filter` method. The callback method you provide should have the following signature: - `item` is the current item in the iteration. - `index` is the integer index of the current item in the iteration. - `array` is the dependant array itself. ```javascript function filterCallback(item, index, array); ``` In the callback, return a truthy value that coerces to true to keep the element, or a falsy to reject it. Example: ```javascript import { set } from '@ember/object'; import { filter } from '@ember/object/computed'; class Hamster { constructor(chores) { set(this, 'chores', chores); } @filter('chores', function(chore, index, array) { return !chore.done; }) remainingChores; } let hamster = Hamster.create([ { name: 'cook', done: true }, { name: 'clean', done: true }, { name: 'write more unit tests', done: false } ]); hamster.remainingChores; // [{name: 'write more unit tests', done: false}] ``` You can also use `@each.property` in your dependent key, the callback will still use the underlying array: ```javascript import { set } from '@ember/object'; import { filter } from '@ember/object/computed'; class Hamster { constructor(chores) { set(this, 'chores', chores); } @filter('chores.@each.done', function(chore, index, array) { return !chore.done; }) remainingChores; } let hamster = new Hamster([ { name: 'cook', done: true }, { name: 'clean', done: true }, { name: 'write more unit tests', done: false } ]); hamster.remainingChores; // [{name: 'write more unit tests', done: false}] set(hamster.chores[2], 'done', true); hamster.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'; class Hamster { constructor(chores) { set(this, 'chores', chores); } doneKey = 'finished'; @filter('chores', ['doneKey'], function(chore, index, array) { return !chore[this.doneKey]; }) remainingChores; } let hamster = new Hamster([ { name: 'cook', finished: true }, { name: 'clean', finished: true }, { name: 'write more unit tests', finished: false } ]); hamster.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 {Function} callback @return {ComputedProperty} the filtered array @public */ function filter(dependentKey, additionalDependentKeysOrCallback, callback) { (isDevelopingApp() && !(!isElementDescriptor(Array.prototype.slice.call(arguments))) && assert('You attempted to use @filter as a decorator directly, but it requires atleast `dependentKey` and `callback` parameters', !isElementDescriptor(Array.prototype.slice.call(arguments)))); (isDevelopingApp() && !(typeof callback === 'function' || callback === undefined && typeof additionalDependentKeysOrCallback === 'function') && assert('The final parameter provided to filter must be a callback function', typeof callback === 'function' || callback === undefined && typeof additionalDependentKeysOrCallback === 'function')); (isDevelopingApp() && !(Array.isArray(additionalDependentKeysOrCallback) || typeof additionalDependentKeysOrCallback === 'function') && assert('The second parameter provided to filter must either be the callback or an array of additional dependent keys', Array.isArray(additionalDependentKeysOrCallback) || typeof additionalDependentKeysOrCallback === 'function')); let additionalDependentKeys; if (typeof additionalDependentKeysOrCallback === 'function') { callback = additionalDependentKeysOrCallback; additionalDependentKeys = []; } else { additionalDependentKeys = additionalDependentKeysOrCallback; } const cCallback = callback; return arrayMacro(dependentKey, additionalDependentKeys, function (value) { // This is a really silly way to keep TS happy return Array.isArray(value) ? value.filter(cCallback, this) : value.filter(cCallback, this); }); } /** Filters the array by the property and value. Example: ```javascript import { set } from '@ember/object'; import { filterBy } from '@ember/object/computed'; class Hamster { constructor(chores) { set(this, 'chores', chores); } @filterBy('chores', 'done', false) remainingChores; } let hamster = new Hamster([ { name: 'cook', done: true }, { name: 'clean', done: true }, { name: 'write more unit tests', done: false } ]); hamster.remainingChores; // [{ name: 'write more unit tests', done: false }] ``` @method filterBy @for @ember/object/computed @static @param {String} dependentKey @param {String} propertyKey @param {*} value @return {ComputedProperty} the filtered array @public */ function filterBy(dependentKey, propertyKey, value) { (isDevelopingApp() && !(!isElementDescriptor(Array.prototype.slice.call(arguments))) && assert('You attempted to use @filterBy as a decorator directly, but it requires atleast `dependentKey` and `propertyKey` parameters', !isElementDescriptor(Array.prototype.slice.call(arguments)))); (isDevelopingApp() && !(!/[[\]{}]/g.test(dependentKey)) && assert(`Dependent key passed to \`filterBy\` computed macro shouldn't contain brace expanding pattern.`, !/[[\]{}]/g.test(dependentKey))); let callback; if (arguments.length === 2) { callback = item => get(item, propertyKey); } else { callback = item => get(item, propertyKey) === value; } return filter(`${dependentKey}.@each.${propertyKey}`, callback); } /** A computed property which returns a new array with all the unique elements from one or more dependent arrays. Example: ```javascript import { set } from '@ember/object'; import { uniq } from '@ember/object/computed'; class Hamster { constructor(fruits) { set(this, 'fruits', fruits); } @uniq('fruits') uniqueFruits; } let hamster = new Hamster([ 'banana', 'grape', 'kale', 'banana' ]); hamster.uniqueFruits; // ['banana', 'grape', 'kale'] ``` @method uniq @for @ember/object/computed @static @param {String} propertyKey* @return {ComputedProperty} computes a new array with all the unique elements from the dependent array @public */ function uniq(dependentKey, ...additionalDependentKeys) { (isDevelopingApp() && !(!isElementDescriptor(Array.prototype.slice.call(arguments))) && assert('You attempted to use @uniq/@union as a decorator directly, but it requires atleast one dependent key parameter', !isElementDescriptor(Array.prototype.slice.call(arguments)))); let args = [dependentKey, ...additionalDependentKeys]; return multiArrayMacro(args, function (dependentKeys) { let uniq = A(); let seen = new Set(); dependentKeys.forEach(dependentKey => { let value = get(this, dependentKey); if (isNativeOrEmberArray(value)) { value.forEach(item => { if (!seen.has(item)) { seen.add(item); uniq.push(item); } }); } }); return uniq; }, 'uniq'); } /** A computed property which returns a new array with all the unique elements from an array, with uniqueness determined by specific key. Example: ```javascript import { set } from '@ember/object'; import { uniqBy } from '@ember/object/computed'; class Hamster { constructor(fruits) { set(this, 'fruits', fruits); } @uniqBy('fruits', 'id') uniqueFruits; } let hamster = new Hamster([ { id: 1, 'banana' }, { id: 2, 'grape' }, { id: 3, 'peach' }, { id: 1, 'banana' } ]); hamster.uniqueFruits; // [ { id: 1, 'banana' }, { id: 2, 'grape' }, { id: 3, 'peach' }] ``` @method uniqBy @for @ember/object/computed @static @param {String} dependentKey @param {String} propertyKey @return {ComputedProperty} computes a new array with all the unique elements from the dependent array @public */ function uniqBy(dependentKey, propertyKey) { (isDevelopingApp() && !(!isElementDescriptor(Array.prototype.slice.call(arguments))) && assert('You attempted to use @uniqBy as a decorator directly, but it requires `dependentKey` and `propertyKey` parameters', !isElementDescriptor(Array.prototype.slice.call(arguments)))); (isDevelopingApp() && !(!/[[\]{}]/g.test(dependentKey)) && assert(`Dependent key passed to \`uniqBy\` computed macro shouldn't contain brace expanding pattern.`, !/[[\]{}]/g.test(dependentKey))); return computed(`${dependentKey}.[]`, function () { let list = get(this, dependentKey); return isNativeOrEmberArray(list) ? uniqBy$1(list, propertyKey) : A(); }).readOnly(); } /** A computed property which returns a new array with all the unique elements from one or more dependent arrays. Example: ```javascript import { set } from '@ember/object'; import { union } from '@ember/object/computed'; class Hamster { constructor(fruits, vegetables) { set(this, 'fruits', fruits); set(this, 'vegetables', vegetables); } @union('fruits', 'vegetables') uniqueFruits; }); let hamster = new, Hamster( [ 'banana', 'grape', 'kale', 'banana', 'tomato' ], [ 'tomato', 'carrot', 'lettuce' ] ); hamster.uniqueFruits; // ['banana', 'grape', 'kale', 'tomato', 'carrot', 'lettuce'] ``` @method union @for @ember/object/computed @static @param {String} propertyKey* @return {ComputedProperty} computes a new array with all the unique elements from one or more dependent arrays. @public */ let union = uniq; /** A computed property which returns a new array with all the elements two or more dependent arrays have in common. Example: ```javascript import { set } from '@ember/object'; import { intersect } from '@ember/object/computed'; class FriendGroups { constructor(adaFriends, charlesFriends) { set(this, 'adaFriends', adaFriends); set(this, 'charlesFriends', charlesFriends); } @intersect('adaFriends', 'charlesFriends') friendsInCommon; } let groups = new FriendGroups( ['Charles Babbage', 'John Hobhouse', 'William King', 'Mary Somerville'], ['William King', 'Mary Somerville', 'Ada Lovelace', 'George Peacock'] ); groups.friendsInCommon; // ['William King', 'Mary Somerville'] ``` @method intersect @for @ember/object/computed @static @param {String} propertyKey* @return {ComputedProperty} computes a new array with all the duplicated elements from the dependent arrays @public */ function intersect(dependentKey, ...additionalDependentKeys) { (isDevelopingApp() && !(!isElementDescriptor(Array.prototype.slice.call(arguments))) && assert('You attempted to use @intersect as a decorator directly, but it requires atleast one dependent key parameter', !isElementDescriptor(Array.prototype.slice.call(arguments)))); let args = [dependentKey, ...additionalDependentKeys]; return multiArrayMacro(args, function (dependentKeys) { let arrays = dependentKeys.map(dependentKey => { let array = get(this, dependentKey); return Array.isArray(array) ? array : []; }); let firstArray = arrays.pop(); (isDevelopingApp() && !(firstArray) && assert('Attempted to apply multiArrayMacro for intersect without any dependentKeys', firstArray)); let results = firstArray.filter(candidate => { for (let array of arrays) { let found = false; for (let item of array) { if (item === candidate) { found = true; break; } } if (found === false) { return false; } } return true; }); return A(results); }, 'intersect'); } /** A computed property which returns a new array with all the properties from the first dependent array that are not in the second dependent array. Example: ```javascript import { set } from '@ember/object'; import { setDiff } from '@ember/object/computed'; class Hamster { constructor(likes, fruits) { set(this, 'likes', likes); set(this, 'fruits', fruits); } @setDiff('likes', 'fruits') wants; } let hamster = new Hamster( [ 'banana', 'grape', 'kale' ], [ 'grape', 'kale', ] ); hamster.wants; // ['banana'] ``` @method setDiff @for @ember/object/computed @static @param {String} setAProperty @param {String} setBProperty @return {ComputedProperty} computes a new array with all the items from the first dependent array that are not in the second dependent array @public */ function setDiff(setAProperty, setBProperty) { (isDevelopingApp() && !(!isElementDescriptor(Array.prototype.slice.call(arguments))) && assert('You attempted to use @setDiff as a decorator directly, but it requires atleast one dependent key parameter', !isElementDescriptor(Array.prototype.slice.call(arguments)))); (isDevelopingApp() && !(arguments.length === 2) && assert('`setDiff` computed macro requires exactly two dependent arrays.', arguments.length === 2)); (isDevelopingApp() && !(!/[[\]{}]/g.test(setAProperty) && !/[[\]{}]/g.test(setBProperty)) && assert(`Dependent keys passed to \`setDiff\` computed macro shouldn't contain brace expanding pattern.`, !/[[\]{}]/g.test(setAProperty) && !/[[\]{}]/g.test(setBProperty))); return computed(`${setAProperty}.[]`, `${setBProperty}.[]`, function () { let setA = get(this, setAProperty); let setB = get(this, setBProperty); if (!isNativeOrEmberArray(setA)) { return A(); } if (!isNativeOrEmberArray(setB)) { return setA; } return setA.filter(x => setB.indexOf(x) === -1); }).readOnly(); } /** A computed property that returns the array of values for the provided dependent properties. Example: ```javascript import { set } from '@ember/object'; import { collect } from '@ember/object/computed'; class Hamster { @collect('hat', 'shirt') clothes; } let hamster = new Hamster(); hamster.clothes; // [null, null] set(hamster, 'hat', 'Camp Hat'); set(hamster, 'shirt', 'Camp Shirt'); hamster.clothes; // ['Camp Hat', 'Camp Shirt'] ``` @method collect @for @ember/object/computed @static @param {String} dependentKey* @return {ComputedProperty} computed property which maps values of all passed in properties to an array. @public */ function collect(dependentKey, ...additionalDependentKeys) { (isDevelopingApp() && !(!isElementDescriptor(Array.prototype.slice.call(arguments))) && assert('You attempted to use @collect as a decorator directly, but it requires atleast one dependent key parameter', !isElementDescriptor(Array.prototype.slice.call(arguments)))); let dependentKeys = [dependentKey, ...additionalDependentKeys]; return multiArrayMacro(dependentKeys, function () { let res = dependentKeys.map(key => { let val = get(this, key); return val === undefined ? null : val; }); return A(res); }, 'collect'); } // (UN)SAFETY: we use `any` here to match how TS defines the sorting for arrays. // Additionally, since we're using it with *decorators*, we don't have any way // to plumb through the relationship between the types in a way that would be // variance-safe. /** 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. 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 In the first form, the callback method you provide should have the following signature: ```javascript function sortCallback(itemA, itemB); ``` - `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`. 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: ```javascript import { set } from '@ember/object'; import { sort } from '@ember/object/computed'; class ToDoList { constructor(todos) { set(this, 'todos', todos); } // using a custom sort function @sort('todos', function(a, b){ if (a.priority > b.priority) { return 1; } else if (a.priority < b.priority) { return -1; } return 0; }) priorityTodos; } let todoList = new ToDoList([ { name: 'Unit Test', priority: 2 }, { name: 'Documentation', priority: 3 }, { name: 'Release', priority: 1 } ]); todoList.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 EmberObject, { set } from '@ember/object'; import { sort } from '@ember/object/computed'; class ToDoList { sortKey = 'priority'; constructor(todos) { set(this, 'todos', todos); } // using a custom sort function @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; }) sortedTodos; }); let todoList = new ToDoList([ { name: 'Unit Test', priority: 2 }, { name: 'Documentation', priority: 3 }, { name: 'Release', priority: 1 } ]); todoList.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 { set } from '@ember/object'; import { sort } from '@ember/object/computed'; class ToDoList { constructor(todos) { set(this, 'todos', todos); } // using standard ascending sort todosSorting = ['name']; @sort('todos', 'todosSorting') sortedTodos; // using descending sort todosSortingDesc = ['name:desc']; @sort('todos', 'todosSortingDesc') sortedTodosDesc; } let todoList = new ToDoList([ { name: 'Unit Test', priority: 2 }, { name: 'Documentation', priority: 3 }, { name: 'Release', priority: 1 } ]); todoList.sortedTodos; // [{ name:'Documentation', priority:3 }, { name:'Release', priority:1 }, { name:'Unit Test', priority:2 }] todoList.sortedTodosDesc; // [{ name:'Unit Test', priority:2 }, { name:'Release', priority:1 }, { name:'Documentation', priority:3 }] ``` @method sort @for @ember/object/computed @static @param {String} itemsKey @param {String|Function|Array} sortDefinitionOrDependentKeys The key of the sort definition (an array of sort properties), the sort function, or an array of additional dependent keys @param {Function?} sortDefinition the sort function (when used with additional dependent keys) @return {ComputedProperty} computes a new sorted array based on the sort property array or callback function @public */ function sort(itemsKey, additionalDependentKeysOrDefinition, sortDefinition) { (isDevelopingApp() && !(!isElementDescriptor(Array.prototype.slice.call(arguments))) && assert('You attempted to use @sort as a decorator directly, but it requires atleast an `itemsKey` parameter', !isElementDescriptor(Array.prototype.slice.call(arguments)))); if (isDevelopingApp()) { let argumentsValid = false; if (arguments.length === 2) { argumentsValid = typeof itemsKey === 'string' && (typeof additionalDependentKeysOrDefinition === 'string' || typeof additionalDependentKeysOrDefinition === 'function'); } if (arguments.length === 3) { argumentsValid = typeof itemsKey === 'string' && Array.isArray(additionalDependentKeysOrDefinition) && typeof sortDefinition === 'function'; } (isDevelopingApp() && !(argumentsValid) && assert('The `sort` computed macro can either be used with an array of sort properties or with a sort function. If used with an array of sort properties, it must receive exactly two arguments: the key of the array to sort, and the key of the array of sort properties. If used with a sort function, it may receive up to three arguments: the key of the array to sort, an optional additional array of dependent keys for the computed property, and the sort function.', argumentsValid)); } let additionalDependentKeys; let sortDefinitionOrString; if (Array.isArray(additionalDependentKeysOrDefinition)) { additionalDependentKeys = additionalDependentKeysOrDefinition; sortDefinitionOrString = sortDefinition; } else { additionalDependentKeys = []; sortDefinitionOrString = additionalDependentKeysOrDefinition; } if (typeof sortDefinitionOrString === 'function') { return customSort(itemsKey, additionalDependentKeys, sortDefinitionOrString); } else { return propertySort(itemsKey, sortDefinitionOrString); } } function customSort(itemsKey, additionalDependentKeys, comparator) { return arrayMacro(itemsKey, additionalDependentKeys, function (value) { return value.slice().sort((x, y) => comparator.call(this, x, y)); }); } // This one needs to dynamically set up and tear down observers on the itemsKey // depending on the sortProperties function propertySort(itemsKey, sortPropertiesKey) { let cp = autoComputed(function (key) { let sortProperties = get(this, sortPropertiesKey); (isDevelopingApp() && !(function (arr) { return isNativeOrEmberArray(arr) && arr.every(s => typeof s === 'string'); }(sortProperties)) && assert(`The sort definition for '${key}' on ${this} must be a function or an array of strings`, function (arr) { return isNativeOrEmberArray(arr) && arr.every(s => typeof s === 'string'); }(sortProperties))); let itemsKeyIsAtThis = itemsKey === '@this'; let normalizedSortProperties = normalizeSortProperties(sortProperties); let items = itemsKeyIsAtThis ? this : get(this, itemsKey); if (!isNativeOrEmberArray(items)) { return A(); } if (normalizedSortProperties.length === 0) { return A(items.slice()); } else { return sortByNormalizedSortProperties(items, normalizedSortProperties); } }).readOnly(); return cp; } function normalizeSortProperties(sortProperties) { let callback = p => { let [prop, direction] = p.split(':'); direction = direction || 'asc'; // SAFETY: There will always be at least one value returned by split return [prop, direction]; }; // This nonsense is necessary since technically the two map implementations diverge. return Array.isArray(sortProperties) ? sortProperties.map(callback) : sortProperties.map(callback); } function sortByNormalizedSortProperties(items, normalizedSortProperties) { return A(items.slice().sort((itemA, itemB) => { for (let [prop, direction] of normalizedSortProperties) { let result = compare(get(itemA, prop), get(itemB, prop)); if (result !== 0) { return direction === 'desc' ? -1 * result : result; } } return 0; })); } export { collect, filter, filterBy, intersect, map, mapBy, max, min, setDiff, sort, sum, union, uniq, uniqBy };