diff --git a/README.md b/README.md index 6336c4b..d0103b6 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,36 @@ Immutable.from([1, 2, 3]); Immutable([1, 2, 3]) ``` +## Immutable.sortBy + +As you may have read, you cannot call `Immutable([/* elements */]).sort()` without +receiving an error because of the mutable nature of the sort method. + +However with `sortBy` you will be able to sort immutable arrays of objects or just plain +types passing an attribute function. You will have two types of sorting, ascendant or +descendant order (based on [Lodash](https://lodash.com/docs/4.17.15#sortBy) +and [Ramda](https://ramdajs.com/docs/#sortBy) behaviors). + +The order types are two constants provided as `Immutable.OrderTypes`, which are: +- `DESC`: Descendant order. +- `ASC`: Ascendant order. It is used by default if you don't provide one. + +```javascript +var identityFn = function(x) { return x }; +var array = Immutable([3, 4, 1, 2]); + +var sortedAsc = Immutable.sortBy(array, identityFn); +// sortedAsc will be Immutable([1, 2, 3, 4]) + +var sortedDesc = Immutable.sortBy(array, identityFn, Immutable.OrderTypes.DESC); // or just 'DESC' +// sortedAsc will be Immutable([4, 3, 2, 1]) + +var people = Immutable([ { age: 17 }, { age: 16 }, { age: 40 } ]); +var byAge = function(obj) { return obj.age; }; +var sortedAscByAge = Immutable.sortBy(people, byAge); +// sortedByAge will be Immutable([ { age: 16 }, { age: 17 }, { age: 40 } ]) +``` + ## Immutable Array Like a regular Array, but immutable! You can construct these by passing diff --git a/src/seamless-immutable.js b/src/seamless-immutable.js index f9ead88..0a9b402 100644 --- a/src/seamless-immutable.js +++ b/src/seamless-immutable.js @@ -713,6 +713,45 @@ function immutableInit(config) { return staticWrapper; } + var OrderTypes = { + ASC: 'ASC', + DESC: 'DESC' + }; + + function basicSorter(a, b) { + return isEqual(a, b) ? 0 : (a > b ? 1 : (-1)); + } + + function sortBy(attribute, ordering) { + var orderType = ordering || OrderTypes.ASC; // Default param simulation on ES5 + var self = this; + if (!Array.isArray(self)) { + throw new TypeError('The first argument must be an array, you got ' + typeof self); + } else { + if (typeof attribute !== 'function') { + throw new TypeError('The attribute must be a function that returns the value used to order the array. You got ' + typeof attribute); + } else { + var mutableArray = asMutableArray.call(self); // No need to be deep, because sort will only modify the temporary array + mutableArray.sort(function (a, b) { + var finalOrder = OrderTypes.hasOwnProperty(orderType) && (orderType || OrderTypes.ASC); + var firstElement = !isObject(a) ? a : attribute(a); + var secondElement = !isObject(a) ? b : attribute(b); + return finalOrder === OrderTypes.ASC ? + basicSorter(firstElement, secondElement) + : basicSorter(secondElement, firstElement); + }); + var isAlreadySorted = mutableArray.slice(0).reduce(function (sorted, current, i, array) { + if (!sorted) { + array.splice(1); // Reduce early exit hack, i refuse to modify variables + } else { + return sorted && isEqual(current, self[i]); + } + }, true); + return isAlreadySorted ? self : makeImmutableArray(mutableArray); + } + } + } + // Export the library Immutable.from = Immutable; Immutable.isImmutable = isImmutable; @@ -728,6 +767,8 @@ function immutableInit(config) { Immutable.getIn = toStatic(getIn); Immutable.flatMap = toStatic(flatMap); Immutable.asObject = toStatic(asObject); + Immutable.sortBy = toStatic(sortBy); + Immutable.OrderTypes = OrderTypes; if (!globalConfig.use_static) { Immutable.static = immutableInit({ use_static: true diff --git a/test/ImmutableArray.spec.js b/test/ImmutableArray.spec.js index 8518aa0..f027876 100644 --- a/test/ImmutableArray.spec.js +++ b/test/ImmutableArray.spec.js @@ -4,7 +4,8 @@ var testAsObject = require("./ImmutableArray/test-asObject.js"); var testAsMutable = require("./ImmutableArray/test-asMutable.js"); var testSet = require("./ImmutableArray/test-set.js"); var testUpdate = require("./ImmutableArray/test-update.js"); -var testGetIn = require("./ImmutableArray/test-getIn.js"); +var testGetIn = require("./ImmutableArray/test-getIn.js"); +var testSortBy = require("./ImmutableArray/test-sortBy.js") var devBuild = require("../seamless-immutable.development.js"); var prodBuild = require("../seamless-immutable.production.min.js"); var getTestUtils = require("./TestUtils.js"); @@ -25,6 +26,7 @@ var getTestUtils = require("./TestUtils.js"); testSet(config); testUpdate(config); testGetIn(config); + testSortBy(config); }); }); }); diff --git a/test/ImmutableArray/test-sortBy.js b/test/ImmutableArray/test-sortBy.js new file mode 100644 index 0000000..1ba38fb --- /dev/null +++ b/test/ImmutableArray/test-sortBy.js @@ -0,0 +1,60 @@ +var JSC = require("jscheck"); +var assert = require("chai").assert; +var _ = require("lodash"); +var getTestUtils = require("../TestUtils.js"); + +module.exports = function(config) { + var Immutable = config.implementation; + var TestUtils = getTestUtils(Immutable); + var check = TestUtils.check; + var identityFn = function (x) { return x; } + + describe("#sortBy", function() { + it('should returns an immutable array', function() { + var array = Immutable(['foo', 'foo2', 'bar', 'bar2']); + var sorted = Immutable.sortBy(array, identityFn); + assert.equal(Immutable.isImmutable(sorted), true); + }) + + it('should returns the same array when it is already sorted', function() { + var array = Immutable([1, 2, 3, 4]); + var sorted = Immutable.sortBy(array, identityFn) + assert.equal(sorted, array) + }) + + it('should order the array in an ascendant way by default', function() { + var array = Immutable([3, 4, 1, 2]); + var sorted = Immutable.sortBy(array, identityFn); + TestUtils.assertJsonEqual(sorted, Immutable([1, 2, 3, 4])); + }) + + it('should order the array in a descendant way', function() { + var array = Immutable([3, 4, 1, 2]); + var sorted = Immutable.sortBy(array, identityFn, Immutable.OrderTypes.DESC); + TestUtils.assertJsonEqual(sorted, Immutable([4, 3, 2, 1])); + }) + + it('should throw an error when you pass in the attribute argument with a type different to a function', function() { + var array = Immutable([{ age: 1 }, { age: 2 }]); + var fnWithError = function() { + Immutable.sortBy(array, 'invalid'); + } + assert.throws(fnWithError, 'The attribute must be a function that returns the value used to order the array. You got string') + }) + + it('should throw an error if you pass something different to an array as the array param', function() { + var fnWithError = function() { + Immutable.sortBy({}, 'invalid'); + } + assert.throws(fnWithError, 'The first argument must be an array, you got object'); + }) + + it('should order an array with object by attribute correctly', function() { + var fn = function(person) { return person.age } + var people = Immutable([{ name: 'Josh', age: 34 }, { name: 'Melanie', age: 18 }, { name: 'Mark', age: 44 }]); + var sortedByAge = Immutable.sortBy(people, fn, Immutable.OrderTypes.ASC); + var expected = Immutable([{ name: 'Melanie', age: 18 }, { name: 'Josh', age: 34 }, { name: 'Mark', age: 44 }]); + TestUtils.assertJsonEqual(sortedByAge, expected); + }) + }); +};