diff options
| author | Misko Hevery | 2011-11-03 20:53:33 -0700 |
|---|---|---|
| committer | Misko Hevery | 2011-11-14 20:31:13 -0800 |
| commit | dd9151e522220b438074e55c72f47ed2a8da9933 (patch) | |
| tree | 9b184e61f9c771cac9dcaca6e897ea2b588324ff | |
| parent | 3972d2a89bfcfe177b12bb225302fc2937a1dbab (diff) | |
| download | angular.js-dd9151e522220b438074e55c72f47ed2a8da9933.tar.bz2 | |
refacter(filters): convert filter/limitTo/orderBy from type augmentation to filters
| -rw-r--r-- | angularFiles.js | 3 | ||||
| -rw-r--r-- | src/apis.js | 377 | ||||
| -rw-r--r-- | src/service/filter.js | 9 | ||||
| -rw-r--r-- | src/service/filter/filter.js | 164 | ||||
| -rw-r--r-- | src/service/filter/limitTo.js | 87 | ||||
| -rw-r--r-- | src/service/filter/orderBy.js | 137 | ||||
| -rw-r--r-- | test/ApiSpecs.js | 146 | ||||
| -rw-r--r-- | test/service/filter/filterSpec.js | 68 | ||||
| -rw-r--r-- | test/service/filter/filtersSpec.js | 2 | ||||
| -rw-r--r-- | test/service/filter/limitToSpec.js | 52 | ||||
| -rw-r--r-- | test/service/filter/orderBySpec.js | 37 |
11 files changed, 555 insertions, 527 deletions
diff --git a/angularFiles.js b/angularFiles.js index f2563094..894eba82 100644 --- a/angularFiles.js +++ b/angularFiles.js @@ -15,7 +15,10 @@ angularFiles = { 'src/service/document.js', 'src/service/exceptionHandler.js', 'src/service/filter.js', + 'src/service/filter/filter.js', 'src/service/filter/filters.js', + 'src/service/filter/limitTo.js', + 'src/service/filter/orderBy.js', 'src/service/formFactory.js', 'src/service/location.js', 'src/service/log.js', diff --git a/src/apis.js b/src/apis.js index db600013..269dc9a1 100644 --- a/src/apis.js +++ b/src/apis.js @@ -275,166 +275,6 @@ var angularArray = { }, - /** - * @ngdoc function - * @name angular.Array.filter - * @function - * - * @description - * Selects a subset of items from `array` and returns it as a new array. - * - * Note: This function is used to augment the `Array` type in Angular expressions. See - * {@link angular.Array} for more information about Angular arrays. - * - * @param {Array} array The source array. - * @param {string|Object|function()} expression The predicate to be used for selecting items from - * `array`. - * - * Can be one of: - * - * - `string`: Predicate that results in a substring match using the value of `expression` - * string. All strings or objects with string properties in `array` that contain this string - * will be returned. The predicate can be negated by prefixing the string with `!`. - * - * - `Object`: A pattern object can be used to filter specific properties on objects contained - * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items - * which have property `name` containing "M" and property `phone` containing "1". A special - * property name `$` can be used (as in `{$:"text"}`) to accept a match against any - * property of the object. That's equivalent to the simple substring match with a `string` - * as described above. - * - * - `function`: A predicate function can be used to write arbitrary filters. The function is - * called for each element of `array`. The final result is an array of those elements that - * the predicate returned true for. - * - * @example - <doc:example> - <doc:source> - <div ng:init="friends = [{name:'John', phone:'555-1276'}, - {name:'Mary', phone:'800-BIG-MARY'}, - {name:'Mike', phone:'555-4321'}, - {name:'Adam', phone:'555-5678'}, - {name:'Julie', phone:'555-8765'}]"></div> - - Search: <input ng:model="searchText"/> - <table id="searchTextResults"> - <tr><th>Name</th><th>Phone</th><tr> - <tr ng:repeat="friend in friends.$filter(searchText)"> - <td>{{friend.name}}</td> - <td>{{friend.phone}}</td> - <tr> - </table> - <hr> - Any: <input ng:model="search.$"/> <br> - Name only <input ng:model="search.name"/><br> - Phone only <input ng:model="search.phone"/><br> - <table id="searchObjResults"> - <tr><th>Name</th><th>Phone</th><tr> - <tr ng:repeat="friend in friends.$filter(search)"> - <td>{{friend.name}}</td> - <td>{{friend.phone}}</td> - <tr> - </table> - </doc:source> - <doc:scenario> - it('should search across all fields when filtering with a string', function() { - input('searchText').enter('m'); - expect(repeater('#searchTextResults tr', 'friend in friends').column('name')). - toEqual(['Mary', 'Mike', 'Adam']); - - input('searchText').enter('76'); - expect(repeater('#searchTextResults tr', 'friend in friends').column('name')). - toEqual(['John', 'Julie']); - }); - - it('should search in specific fields when filtering with a predicate object', function() { - input('search.$').enter('i'); - expect(repeater('#searchObjResults tr', 'friend in friends').column('name')). - toEqual(['Mary', 'Mike', 'Julie']); - }); - </doc:scenario> - </doc:example> - */ - 'filter':function(array, expression) { - var predicates = []; - predicates.check = function(value) { - for (var j = 0; j < predicates.length; j++) { - if(!predicates[j](value)) { - return false; - } - } - return true; - }; - var search = function(obj, text){ - if (text.charAt(0) === '!') { - return !search(obj, text.substr(1)); - } - switch (typeof obj) { - case "boolean": - case "number": - case "string": - return ('' + obj).toLowerCase().indexOf(text) > -1; - case "object": - for ( var objKey in obj) { - if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { - return true; - } - } - return false; - case "array": - for ( var i = 0; i < obj.length; i++) { - if (search(obj[i], text)) { - return true; - } - } - return false; - default: - return false; - } - }; - switch (typeof expression) { - case "boolean": - case "number": - case "string": - expression = {$:expression}; - case "object": - for (var key in expression) { - if (key == '$') { - (function() { - var text = (''+expression[key]).toLowerCase(); - if (!text) return; - predicates.push(function(value) { - return search(value, text); - }); - })(); - } else { - (function() { - var path = key; - var text = (''+expression[key]).toLowerCase(); - if (!text) return; - predicates.push(function(value) { - return search(getter(value, path), text); - }); - })(); - } - } - break; - case 'function': - predicates.push(expression); - break; - default: - return array; - } - var filtered = []; - for ( var j = 0; j < array.length; j++) { - var value = array[j]; - if (predicates.check(value)) { - filtered.push(value); - } - } - return filtered; - }, - /** * @ngdoc function @@ -577,223 +417,6 @@ var angularArray = { } }); return count; - }, - - - /** - * @ngdoc function - * @name angular.Array.orderBy - * @function - * - * @description - * Orders a specified `array` by the `expression` predicate. - * - * Note: this function is used to augment the `Array` type in Angular expressions. See - * {@link angular.Array} for more informaton about Angular arrays. - * - * @param {Array} array The array to sort. - * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be - * used by the comparator to determine the order of elements. - * - * Can be one of: - * - * - `function`: Getter function. The result of this function will be sorted using the - * `<`, `=`, `>` operator. - * - `string`: An Angular expression which evaluates to an object to order by, such as 'name' - * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control - * ascending or descending sort order (for example, +name or -name). - * - `Array`: An array of function or string predicates. The first predicate in the array - * is used for sorting, but when two items are equivalent, the next predicate is used. - * - * @param {boolean=} reverse Reverse the order the array. - * @returns {Array} Sorted copy of the source array. - * - * @example - <doc:example> - <doc:source> - <script> - function Ctrl() { - this.friends = - [{name:'John', phone:'555-1212', age:10}, - {name:'Mary', phone:'555-9876', age:19}, - {name:'Mike', phone:'555-4321', age:21}, - {name:'Adam', phone:'555-5678', age:35}, - {name:'Julie', phone:'555-8765', age:29}] - this.predicate = '-age'; - } - </script> - <div ng:controller="Ctrl"> - <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> - <hr/> - [ <a href="" ng:click="predicate=''">unsorted</a> ] - <table class="friend"> - <tr> - <th><a href="" ng:click="predicate = 'name'; reverse=false">Name</a> - (<a href ng:click="predicate = '-name'; reverse=false">^</a>)</th> - <th><a href="" ng:click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th> - <th><a href="" ng:click="predicate = 'age'; reverse=!reverse">Age</a></th> - <tr> - <tr ng:repeat="friend in friends.$orderBy(predicate, reverse)"> - <td>{{friend.name}}</td> - <td>{{friend.phone}}</td> - <td>{{friend.age}}</td> - <tr> - </table> - </div> - </doc:source> - <doc:scenario> - it('should be reverse ordered by aged', function() { - expect(binding('predicate')).toBe('Sorting predicate = -age; reverse = '); - expect(repeater('table.friend', 'friend in friends').column('friend.age')). - toEqual(['35', '29', '21', '19', '10']); - expect(repeater('table.friend', 'friend in friends').column('friend.name')). - toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']); - }); - - it('should reorder the table when user selects different predicate', function() { - element('.doc-example-live a:contains("Name")').click(); - expect(repeater('table.friend', 'friend in friends').column('friend.name')). - toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']); - expect(repeater('table.friend', 'friend in friends').column('friend.age')). - toEqual(['35', '10', '29', '19', '21']); - - element('.doc-example-live a:contains("Phone")').click(); - expect(repeater('table.friend', 'friend in friends').column('friend.phone')). - toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']); - expect(repeater('table.friend', 'friend in friends').column('friend.name')). - toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']); - }); - </doc:scenario> - </doc:example> - */ - 'orderBy':function(array, sortPredicate, reverseOrder) { - if (!sortPredicate) return array; - sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; - sortPredicate = map(sortPredicate, function(predicate){ - var descending = false, get = predicate || identity; - if (isString(predicate)) { - if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { - descending = predicate.charAt(0) == '-'; - predicate = predicate.substring(1); - } - get = expressionCompile(predicate); - } - return reverseComparator(function(a,b){ - return compare(get(a),get(b)); - }, descending); - }); - var arrayCopy = []; - for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } - return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); - - function comparator(o1, o2){ - for ( var i = 0; i < sortPredicate.length; i++) { - var comp = sortPredicate[i](o1, o2); - if (comp !== 0) return comp; - } - return 0; - } - function reverseComparator(comp, descending) { - return toBoolean(descending) - ? function(a,b){return comp(b,a);} - : comp; - } - function compare(v1, v2){ - var t1 = typeof v1; - var t2 = typeof v2; - if (t1 == t2) { - if (t1 == "string") v1 = v1.toLowerCase(); - if (t1 == "string") v2 = v2.toLowerCase(); - if (v1 === v2) return 0; - return v1 < v2 ? -1 : 1; - } else { - return t1 < t2 ? -1 : 1; - } - } - }, - - - /** - * @ngdoc function - * @name angular.Array.limitTo - * @function - * - * @description - * Creates a new array containing only a specified number of elements in an array. The elements - * are taken from either the beginning or the end of the source array, as specified by the - * value and sign (positive or negative) of `limit`. - * - * Note: This function is used to augment the `Array` type in Angular expressions. See - * {@link angular.Array} for more information about Angular arrays. - * - * @param {Array} array Source array to be limited. - * @param {string|Number} limit The length of the returned array. If the `limit` number is - * positive, `limit` number of items from the beginning of the source array are copied. - * If the number is negative, `limit` number of items from the end of the source array are - * copied. The `limit` will be trimmed if it exceeds `array.length` - * @returns {Array} A new sub-array of length `limit` or less if input array had less than `limit` - * elements. - * - * @example - <doc:example> - <doc:source> - <script> - function Ctrl() { - this.numbers = [1,2,3,4,5,6,7,8,9]; - this.limit = 3; - } - </script> - <div ng:controller="Ctrl"> - Limit {{numbers}} to: <input type="integer" ng:model="limit"/> - <p>Output: {{ numbers.$limitTo(limit) | json }}</p> - </div> - </doc:source> - <doc:scenario> - it('should limit the numer array to first three items', function() { - expect(element('.doc-example-live input[ng\\:model=limit]').val()).toBe('3'); - expect(binding('numbers.$limitTo(limit) | json')).toEqual('[1,2,3]'); - }); - - it('should update the output when -3 is entered', function() { - input('limit').enter(-3); - expect(binding('numbers.$limitTo(limit) | json')).toEqual('[7,8,9]'); - }); - - it('should not exceed the maximum size of input array', function() { - input('limit').enter(100); - expect(binding('numbers.$limitTo(limit) | json')).toEqual('[1,2,3,4,5,6,7,8,9]'); - }); - </doc:scenario> - </doc:example> - */ - limitTo: function(array, limit) { - limit = parseInt(limit, 10); - var out = [], - i, n; - - // check that array is iterable - if (!array || !(array instanceof Array)) - return out; - - // if abs(limit) exceeds maximum length, trim it - if (limit > array.length) - limit = array.length; - else if (limit < -array.length) - limit = -array.length; - - if (limit > 0) { - i = 0; - n = limit; - } else { - i = array.length + limit; - n = array.length; - } - - for (; i<n; i++) { - out.push(array[i]); - } - - return out; } }; diff --git a/src/service/filter.js b/src/service/filter.js index 7b85c23d..9d6a4001 100644 --- a/src/service/filter.js +++ b/src/service/filter.js @@ -17,11 +17,14 @@ function $FilterProvider($provide) { //////////////////////////////////////// $provide.filter('currency', currencyFilter); - $provide.filter('number', numberFilter); $provide.filter('date', dateFilter); + $provide.filter('filter', filterFilter); + $provide.filter('html', htmlFilter); $provide.filter('json', jsonFilter); + $provide.filter('limitTo', limitToFilter); + $provide.filter('linky', linkyFilter); $provide.filter('lowercase', lowercaseFilter); + $provide.filter('number', numberFilter); + $provide.filter('orderBy', orderByFilter); $provide.filter('uppercase', uppercaseFilter); - $provide.filter('html', htmlFilter); - $provide.filter('linky', linkyFilter); } diff --git a/src/service/filter/filter.js b/src/service/filter/filter.js new file mode 100644 index 00000000..0a0f5706 --- /dev/null +++ b/src/service/filter/filter.js @@ -0,0 +1,164 @@ +'use strict'; + +/** + * @ngdoc function + * @name angular.Array.filter + * @function + * + * @description + * Selects a subset of items from `array` and returns it as a new array. + * + * Note: This function is used to augment the `Array` type in Angular expressions. See + * {@link angular.Array} for more information about Angular arrays. + * + * @param {Array} array The source array. + * @param {string|Object|function()} expression The predicate to be used for selecting items from + * `array`. + * + * Can be one of: + * + * - `string`: Predicate that results in a substring match using the value of `expression` + * string. All strings or objects with string properties in `array` that contain this string + * will be returned. The predicate can be negated by prefixing the string with `!`. + * + * - `Object`: A pattern object can be used to filter specific properties on objects contained + * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items + * which have property `name` containing "M" and property `phone` containing "1". A special + * property name `$` can be used (as in `{$:"text"}`) to accept a match against any + * property of the object. That's equivalent to the simple substring match with a `string` + * as described above. + * + * - `function`: A predicate function can be used to write arbitrary filters. The function is + * called for each element of `array`. The final result is an array of those elements that + * the predicate returned true for. + * + * @example + <doc:example> + <doc:source> + <div ng:init="friends = [{name:'John', phone:'555-1276'}, + {name:'Mary', phone:'800-BIG-MARY'}, + {name:'Mike', phone:'555-4321'}, + {name:'Adam', phone:'555-5678'}, + {name:'Julie', phone:'555-8765'}]"></div> + + Search: <input ng:model="searchText"/> + <table id="searchTextResults"> + <tr><th>Name</th><th>Phone</th><tr> + <tr ng:repeat="friend in friends.$filter(searchText)"> + <td>{{friend.name}}</td> + <td>{{friend.phone}}</td> + <tr> + </table> + <hr> + Any: <input ng:model="search.$"/> <br> + Name only <input ng:model="search.name"/><br> + Phone only <input ng:model="search.phone"/><br> + <table id="searchObjResults"> + <tr><th>Name</th><th>Phone</th><tr> + <tr ng:repeat="friend in friends.$filter(search)"> + <td>{{friend.name}}</td> + <td>{{friend.phone}}</td> + <tr> + </table> + </doc:source> + <doc:scenario> + it('should search across all fields when filtering with a string', function() { + input('searchText').enter('m'); + expect(repeater('#searchTextResults tr', 'friend in friends').column('name')). + toEqual(['Mary', 'Mike', 'Adam']); + + input('searchText').enter('76'); + expect(repeater('#searchTextResults tr', 'friend in friends').column('name')). + toEqual(['John', 'Julie']); + }); + + it('should search in specific fields when filtering with a predicate object', function() { + input('search.$').enter('i'); + expect(repeater('#searchObjResults tr', 'friend in friends').column('name')). + toEqual(['Mary', 'Mike', 'Julie']); + }); + </doc:scenario> + </doc:example> + */ +function filterFilter() { + return function(array, expression) { + if (!(array instanceof Array)) return array; + var predicates = []; + predicates.check = function(value) { + for (var j = 0; j < predicates.length; j++) { + if(!predicates[j](value)) { + return false; + } + } + return true; + }; + var search = function(obj, text){ + if (text.charAt(0) === '!') { + return !search(obj, text.substr(1)); + } + switch (typeof obj) { + case "boolean": + case "number": + case "string": + return ('' + obj).toLowerCase().indexOf(text) > -1; + case "object": + for ( var objKey in obj) { + if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { + return true; + } + } + return false; + case "array": + for ( var i = 0; i < obj.length; i++) { + if (search(obj[i], text)) { + return true; + } + } + return false; + default: + return false; + } + }; + switch (typeof expression) { + case "boolean": + case "number": + case "string": + expression = {$:expression}; + case "object": + for (var key in expression) { + if (key == '$') { + (function() { + var text = (''+expression[key]).toLowerCase(); + if (!text) return; + predicates.push(function(value) { + return search(value, text); + }); + })(); + } else { + (function() { + var path = key; + var text = (''+expression[key]).toLowerCase(); + if (!text) return; + predicates.push(function(value) { + return search(getter(value, path), text); + }); + })(); + } + } + break; + case 'function': + predicates.push(expression); + break; + default: + return array; + } + var filtered = []; + for ( var j = 0; j < array.length; j++) { + var value = array[j]; + if (predicates.check(value)) { + filtered.push(value); + } + } + return filtered; + } +} diff --git a/src/service/filter/limitTo.js b/src/service/filter/limitTo.js new file mode 100644 index 00000000..9bb5cf4d --- /dev/null +++ b/src/service/filter/limitTo.js @@ -0,0 +1,87 @@ +'use strict'; + +/** + * @ngdoc function + * @name angular.Array.limitTo + * @function + * + * @description + * Creates a new array containing only a specified number of elements in an array. The elements + * are taken from either the beginning or the end of the source array, as specified by the + * value and sign (positive or negative) of `limit`. + * + * Note: This function is used to augment the `Array` type in Angular expressions. See + * {@link angular.Array} for more information about Angular arrays. + * + * @param {Array} array Source array to be limited. + * @param {string|Number} limit The length of the returned array. If the `limit` number is + * positive, `limit` number of items from the beginning of the source array are copied. + * If the number is negative, `limit` number of items from the end of the source array are + * copied. The `limit` will be trimmed if it exceeds `array.length` + * @returns {Array} A new sub-array of length `limit` or less if input array had less than `limit` + * elements. + * + * @example + <doc:example> + <doc:source> + <script> + function Ctrl() { + this.numbers = [1,2,3,4,5,6,7,8,9]; + this.limit = 3; + } + </script> + <div ng:controller="Ctrl"> + Limit {{numbers}} to: <input type="integer" ng:model="limit"/> + <p>Output: {{ numbers.$limitTo(limit) | json }}</p> + </div> + </doc:source> + <doc:scenario> + it('should limit the numer array to first three items', function() { + expect(element('.doc-example-live input[ng\\:model=limit]').val()).toBe('3'); + expect(binding('numbers.$limitTo(limit) | json')).toEqual('[1,2,3]'); + }); + + it('should update the output when -3 is entered', function() { + input('limit').enter(-3); + expect(binding('numbers.$limitTo(limit) | json')).toEqual('[7,8,9]'); + }); + + it('should not exceed the maximum size of input array', function() { + input('limit').enter(100); + expect(binding('numbers.$limitTo(limit) | json')).toEqual('[1,2,3,4,5,6,7,8,9]'); + }); + </doc:scenario> + </doc:example> + */ +function limitToFilter(){ + return function(array, limit) { + if (!(array instanceof Array)) return array; + limit = parseInt(limit, 10); + var out = [], + i, n; + + // check that array is iterable + if (!array || !(array instanceof Array)) + return out; + + // if abs(limit) exceeds maximum length, trim it + if (limit > array.length) + limit = array.length; + else if (limit < -array.length) + limit = -array.length; + + if (limit > 0) { + i = 0; + n = limit; + } else { + i = array.length + limit; + n = array.length; + } + + for (; i<n; i++) { + out.push(array[i]); + } + + return out; + } +} diff --git a/src/service/filter/orderBy.js b/src/service/filter/orderBy.js new file mode 100644 index 00000000..07c69af3 --- /dev/null +++ b/src/service/filter/orderBy.js @@ -0,0 +1,137 @@ +'use strict'; + +/** + * @ngdoc function + * @name angular.Array.orderBy + * @function + * + * @description + * Orders a specified `array` by the `expression` predicate. + * + * Note: this function is used to augment the `Array` type in Angular expressions. See + * {@link angular.Array} for more informaton about Angular arrays. + * + * @param {Array} array The array to sort. + * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be + * used by the comparator to determine the order of elements. + * + * Can be one of: + * + * - `function`: Getter function. The result of this function will be sorted using the + * `<`, `=`, `>` operator. + * - `string`: An Angular expression which evaluates to an object to order by, such as 'name' + * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control + * ascending or descending sort order (for example, +name or -name). + * - `Array`: An array of function or string predicates. The first predicate in the array + * is used for sorting, but when two items are equivalent, the next predicate is used. + * + * @param {boolean=} reverse Reverse the order the array. + * @returns {Array} Sorted copy of the source array. + * + * @example + <doc:example> + <doc:source> + <script> + function Ctrl() { + this.friends = + [{name:'John', phone:'555-1212', age:10}, + {name:'Mary', phone:'555-9876', age:19}, + {name:'Mike', phone:'555-4321', age:21}, + {name:'Adam', phone:'555-5678', age:35}, + {name:'Julie', phone:'555-8765', age:29}] + this.predicate = '-age'; + } + </script> + <div ng:controller="Ctrl"> + <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> + <hr/> + [ <a href="" ng:click="predicate=''">unsorted</a> ] + <table class="friend"> + <tr> + <th><a href="" ng:click="predicate = 'name'; reverse=false">Name</a> + (<a href ng:click="predicate = '-name'; reverse=false">^</a>)</th> + <th><a href="" ng:click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th> + <th><a href="" ng:click="predicate = 'age'; reverse=!reverse">Age</a></th> + <tr> + <tr ng:repeat="friend in friends.$orderBy(predicate, reverse)"> + <td>{{friend.name}}</td> + <td>{{friend.phone}}</td> + <td>{{friend.age}}</td> + <tr> + </table> + </div> + </doc:source> + <doc:scenario> + it('should be reverse ordered by aged', function() { + expect(binding('predicate')).toBe('Sorting predicate = -age; reverse = '); + expect(repeater('table.friend', 'friend in friends').column('friend.age')). + toEqual(['35', '29', '21', '19', '10']); + expect(repeater('table.friend', 'friend in friends').column('friend.name')). + toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']); + }); + + it('should reorder the table when user selects different predicate', function() { + element('.doc-example-live a:contains("Name")').click(); + expect(repeater('table.friend', 'friend in friends').column('friend.name')). + toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']); + expect(repeater('table.friend', 'friend in friends').column('friend.age')). + toEqual(['35', '10', '29', '19', '21']); + + element('.doc-example-live a:contains("Phone")').click(); + expect(repeater('table.friend', 'friend in friends').column('friend.phone')). + toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']); + expect(repeater('table.friend', 'friend in friends').column('friend.name')). + toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']); + }); + </doc:scenario> + </doc:example> + */ +orderByFilter.$inject = ['$parse']; +function orderByFilter($parse){ + return function(array, sortPredicate, reverseOrder) { + if (!(array instanceof Array)) return array; + if (!sortPredicate) return array; + sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; + sortPredicate = map(sortPredicate, function(predicate){ + var descending = false, get = predicate || identity; + if (isString(predicate)) { + if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { + descending = predicate.charAt(0) == '-'; + predicate = predicate.substring(1); + } + get = $parse(predicate); + } + return reverseComparator(function(a,b){ + return compare(get(a),get(b)); + }, descending); + }); + var arrayCopy = []; + for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } + return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); + + function comparator(o1, o2){ + for ( var i = 0; i < sortPredicate.length; i++) { + var comp = sortPredicate[i](o1, o2); + if (comp !== 0) return comp; + } + return 0; + } + function reverseComparator(comp, descending) { + return toBoolean(descending) + ? function(a,b){return comp(b,a);} + : comp; + } + function compare(v1, v2){ + var t1 = typeof v1; + var t2 = typeof v2; + if (t1 == t2) { + if (t1 == "string") v1 = v1.toLowerCase(); + if (t1 == "string") v2 = v2.toLowerCase(); + if (v1 === v2) return 0; + return v1 < v2 ? -1 : 1; + } else { + return t1 < t2 ? -1 : 1; + } + } + } +} diff --git a/test/ApiSpecs.js b/test/ApiSpecs.js index 7b4a04fb..0fa26e09 100644 --- a/test/ApiSpecs.js +++ b/test/ApiSpecs.js @@ -105,117 +105,6 @@ describe('api', function() { }); - describe('filter', function() { - it('should filter by string', function() { - var items = ["MIsKO", {name:"shyam"}, ["adam"], 1234]; - assertEquals(4, angular.Array.filter(items, "").length); - assertEquals(4, angular.Array.filter(items, undefined).length); - - assertEquals(1, angular.Array.filter(items, 'iSk').length); - assertEquals("MIsKO", angular.Array.filter(items, 'isk')[0]); - - assertEquals(1, angular.Array.filter(items, 'yam').length); - assertEquals(items[1], angular.Array.filter(items, 'yam')[0]); - - assertEquals(1, angular.Array.filter(items, 'da').length); - assertEquals(items[2], angular.Array.filter(items, 'da')[0]); - - assertEquals(1, angular.Array.filter(items, '34').length); - assertEquals(1234, angular.Array.filter(items, '34')[0]); - - assertEquals(0, angular.Array.filter(items, "I don't exist").length); - }); - - it('should not read $ properties', function() { - assertEquals("", "".charAt(0)); // assumption - var items = [{$name:"misko"}]; - assertEquals(0, angular.Array.filter(items, "misko").length); - }); - - it('should filter on specific property', function() { - var items = [{ignore:"a", name:"a"}, {ignore:"a", name:"abc"}]; - assertEquals(2, angular.Array.filter(items, {}).length); - - assertEquals(2, angular.Array.filter(items, {name:'a'}).length); - - assertEquals(1, angular.Array.filter(items, {name:'b'}).length); - assertEquals("abc", angular.Array.filter(items, {name:'b'})[0].name); - }); - - it('should take function as predicate', function() { - var items = [{name:"a"}, {name:"abc", done:true}]; - assertEquals(1, angular.Array.filter(items, function(i) {return i.done;}).length); - }); - - it('should take object as perdicate', function() { - var items = [{first:"misko", last:"hevery"}, - {first:"adam", last:"abrons"}]; - - assertEquals(2, angular.Array.filter(items, {first:'', last:''}).length); - assertEquals(1, angular.Array.filter(items, {first:'', last:'hevery'}).length); - assertEquals(0, angular.Array.filter(items, {first:'adam', last:'hevery'}).length); - assertEquals(1, angular.Array.filter(items, {first:'misko', last:'hevery'}).length); - assertEquals(items[0], angular.Array.filter(items, {first:'misko', last:'hevery'})[0]); - }); - - it('should support negation operator', function() { - var items = ["misko", "adam"]; - - assertEquals(1, angular.Array.filter(items, '!isk').length); - assertEquals(items[1], angular.Array.filter(items, '!isk')[0]); - }); - }); - - - describe('limit', function() { - var items; - - beforeEach(function() { - items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; - }); - - - it('should return the first X items when X is positive', function() { - expect(angular.Array.limitTo(items, 3)).toEqual(['a', 'b', 'c']); - expect(angular.Array.limitTo(items, '3')).toEqual(['a', 'b', 'c']); - }); - - - it('should return the last X items when X is negative', function() { - expect(angular.Array.limitTo(items, -3)).toEqual(['f', 'g', 'h']); - expect(angular.Array.limitTo(items, '-3')).toEqual(['f', 'g', 'h']); - }); - - - it('should return an empty array when X cannot be parsed', function() { - expect(angular.Array.limitTo(items, 'bogus')).toEqual([]); - expect(angular.Array.limitTo(items, 'null')).toEqual([]); - expect(angular.Array.limitTo(items, 'undefined')).toEqual([]); - expect(angular.Array.limitTo(items, null)).toEqual([]); - expect(angular.Array.limitTo(items, undefined)).toEqual([]); - }); - - - it('should return an empty array when input is not Array type', function() { - expect(angular.Array.limitTo('bogus', 1)).toEqual([]); - expect(angular.Array.limitTo(null, 1)).toEqual([]); - expect(angular.Array.limitTo(undefined, 1)).toEqual([]); - expect(angular.Array.limitTo(null, 1)).toEqual([]); - expect(angular.Array.limitTo(undefined, 1)).toEqual([]); - expect(angular.Array.limitTo({}, 1)).toEqual([]); - }); - - - it('should return a copy of input array if X is exceeds array length', function () { - expect(angular.Array.limitTo(items, 19)).toEqual(items); - expect(angular.Array.limitTo(items, '9')).toEqual(items); - expect(angular.Array.limitTo(items, -9)).toEqual(items); - expect(angular.Array.limitTo(items, '-9')).toEqual(items); - - expect(angular.Array.limitTo(items, 9)).not.toBe(items); - }); - }); - it('add', function() { var add = angular.Array.add; @@ -232,41 +121,6 @@ describe('api', function() { }); - describe('orderBy', function() { - var orderBy; - beforeEach(function() { - orderBy = angular.Array.orderBy; - }); - - it('should return same array if predicate is falsy', function() { - var array = [1, 2, 3]; - expect(orderBy(array)).toBe(array); - }); - - it('shouldSortArrayInReverse', function() { - assertJsonEquals([{a:15},{a:2}], angular.Array.orderBy([{a:15},{a:2}], 'a', true)); - assertJsonEquals([{a:15},{a:2}], angular.Array.orderBy([{a:15},{a:2}], 'a', "T")); - assertJsonEquals([{a:15},{a:2}], angular.Array.orderBy([{a:15},{a:2}], 'a', "reverse")); - }); - - it('should sort array by predicate', function() { - assertJsonEquals([{a:2, b:1},{a:15, b:1}], - angular.Array.orderBy([{a:15, b:1},{a:2, b:1}], ['a', 'b'])); - assertJsonEquals([{a:2, b:1},{a:15, b:1}], - angular.Array.orderBy([{a:15, b:1},{a:2, b:1}], ['b', 'a'])); - assertJsonEquals([{a:15, b:1},{a:2, b:1}], - angular.Array.orderBy([{a:15, b:1},{a:2, b:1}], ['+b', '-a'])); - }); - - it('should use function', function() { - expect( - orderBy( - [{a:15, b:1},{a:2, b:1}], - function(value) { return value.a; })). - toEqual([{a:2, b:1},{a:15, b:1}]); - }); - - }); }); diff --git a/test/service/filter/filterSpec.js b/test/service/filter/filterSpec.js new file mode 100644 index 00000000..9c43144c --- /dev/null +++ b/test/service/filter/filterSpec.js @@ -0,0 +1,68 @@ +'use strict'; + +describe('Filter: filter', function() { + var filter; + + beforeEach(inject(function($filter){ + filter = $filter('filter'); + })); + + it('should filter by string', function() { + var items = ["MIsKO", {name:"shyam"}, ["adam"], 1234]; + assertEquals(4, filter(items, "").length); + assertEquals(4, filter(items, undefined).length); + + assertEquals(1, filter(items, 'iSk').length); + assertEquals("MIsKO", filter(items, 'isk')[0]); + + assertEquals(1, filter(items, 'yam').length); + assertEquals(items[1], filter(items, 'yam')[0]); + + assertEquals(1, filter(items, 'da').length); + assertEquals(items[2], filter(items, 'da')[0]); + + assertEquals(1, filter(items, '34').length); + assertEquals(1234, filter(items, '34')[0]); + + assertEquals(0, filter(items, "I don't exist").length); + }); + + it('should not read $ properties', function() { + assertEquals("", "".charAt(0)); // assumption + var items = [{$name:"misko"}]; + assertEquals(0, filter(items, "misko").length); + }); + + it('should filter on specific property', function() { + var items = [{ignore:"a", name:"a"}, {ignore:"a", name:"abc"}]; + assertEquals(2, filter(items, {}).length); + + assertEquals(2, filter(items, {name:'a'}).length); + + assertEquals(1, filter(items, {name:'b'}).length); + assertEquals("abc", filter(items, {name:'b'})[0].name); + }); + + it('should take function as predicate', function() { + var items = [{name:"a"}, {name:"abc", done:true}]; + assertEquals(1, filter(items, function(i) {return i.done;}).length); + }); + + it('should take object as perdicate', function() { + var items = [{first:"misko", last:"hevery"}, + {first:"adam", last:"abrons"}]; + + assertEquals(2, filter(items, {first:'', last:''}).length); + assertEquals(1, filter(items, {first:'', last:'hevery'}).length); + assertEquals(0, filter(items, {first:'adam', last:'hevery'}).length); + assertEquals(1, filter(items, {first:'misko', last:'hevery'}).length); + assertEquals(items[0], filter(items, {first:'misko', last:'hevery'})[0]); + }); + + it('should support negation operator', function() { + var items = ["misko", "adam"]; + + assertEquals(1, filter(items, '!isk').length); + assertEquals(items[1], filter(items, '!isk')[0]); + }); +}); diff --git a/test/service/filter/filtersSpec.js b/test/service/filter/filtersSpec.js index 39d034f5..ca510a72 100644 --- a/test/service/filter/filtersSpec.js +++ b/test/service/filter/filtersSpec.js @@ -1,6 +1,6 @@ 'use strict'; -describe('filter', function() { +describe('filters', function() { var filter; diff --git a/test/service/filter/limitToSpec.js b/test/service/filter/limitToSpec.js new file mode 100644 index 00000000..b0977235 --- /dev/null +++ b/test/service/filter/limitToSpec.js @@ -0,0 +1,52 @@ +'use strict'; + +describe('Filter: limitTo', function() { + var items; + var limitTo; + + beforeEach(inject(function($filter) { + items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; + limitTo = $filter('limitTo'); + })); + + + it('should return the first X items when X is positive', function() { + expect(limitTo(items, 3)).toEqual(['a', 'b', 'c']); + expect(limitTo(items, '3')).toEqual(['a', 'b', 'c']); + }); + + + it('should return the last X items when X is negative', function() { + expect(limitTo(items, -3)).toEqual(['f', 'g', 'h']); + expect(limitTo(items, '-3')).toEqual(['f', 'g', 'h']); + }); + + + it('should return an empty array when X cannot be parsed', function() { + expect(limitTo(items, 'bogus')).toEqual([]); + expect(limitTo(items, 'null')).toEqual([]); + expect(limitTo(items, 'undefined')).toEqual([]); + expect(limitTo(items, null)).toEqual([]); + expect(limitTo(items, undefined)).toEqual([]); + }); + + + it('should return an empty array when input is not Array type', function() { + expect(limitTo('bogus', 1)).toEqual('bogus'); + expect(limitTo(null, 1)).toEqual(null); + expect(limitTo(undefined, 1)).toEqual(undefined); + expect(limitTo(null, 1)).toEqual(null); + expect(limitTo(undefined, 1)).toEqual(undefined); + expect(limitTo({}, 1)).toEqual({}); + }); + + + it('should return a copy of input array if X is exceeds array length', function () { + expect(limitTo(items, 19)).toEqual(items); + expect(limitTo(items, '9')).toEqual(items); + expect(limitTo(items, -9)).toEqual(items); + expect(limitTo(items, '-9')).toEqual(items); + + expect(limitTo(items, 9)).not.toBe(items); + }); +}); diff --git a/test/service/filter/orderBySpec.js b/test/service/filter/orderBySpec.js new file mode 100644 index 00000000..f59fad55 --- /dev/null +++ b/test/service/filter/orderBySpec.js @@ -0,0 +1,37 @@ +'use strict'; + +describe('Filter: orderBy', function() { + var orderBy; + beforeEach(inject(function($filter) { + orderBy = $filter('orderBy'); + })); + + it('should return same array if predicate is falsy', function() { + var array = [1, 2, 3]; + expect(orderBy(array)).toBe(array); + }); + + it('shouldSortArrayInReverse', function() { + assertJsonEquals([{a:15},{a:2}], orderBy([{a:15},{a:2}], 'a', true)); + assertJsonEquals([{a:15},{a:2}], orderBy([{a:15},{a:2}], 'a', "T")); + assertJsonEquals([{a:15},{a:2}], orderBy([{a:15},{a:2}], 'a', "reverse")); + }); + + it('should sort array by predicate', function() { + assertJsonEquals([{a:2, b:1},{a:15, b:1}], + orderBy([{a:15, b:1},{a:2, b:1}], ['a', 'b'])); + assertJsonEquals([{a:2, b:1},{a:15, b:1}], + orderBy([{a:15, b:1},{a:2, b:1}], ['b', 'a'])); + assertJsonEquals([{a:15, b:1},{a:2, b:1}], + orderBy([{a:15, b:1},{a:2, b:1}], ['+b', '-a'])); + }); + + it('should use function', function() { + expect( + orderBy( + [{a:15, b:1},{a:2, b:1}], + function(value) { return value.a; })). + toEqual([{a:2, b:1},{a:15, b:1}]); + }); + +}); |
