aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMisko Hevery2011-11-03 20:53:33 -0700
committerMisko Hevery2011-11-14 20:31:13 -0800
commitdd9151e522220b438074e55c72f47ed2a8da9933 (patch)
tree9b184e61f9c771cac9dcaca6e897ea2b588324ff
parent3972d2a89bfcfe177b12bb225302fc2937a1dbab (diff)
downloadangular.js-dd9151e522220b438074e55c72f47ed2a8da9933.tar.bz2
refacter(filters): convert filter/limitTo/orderBy from type augmentation to filters
-rw-r--r--angularFiles.js3
-rw-r--r--src/apis.js377
-rw-r--r--src/service/filter.js9
-rw-r--r--src/service/filter/filter.js164
-rw-r--r--src/service/filter/limitTo.js87
-rw-r--r--src/service/filter/orderBy.js137
-rw-r--r--test/ApiSpecs.js146
-rw-r--r--test/service/filter/filterSpec.js68
-rw-r--r--test/service/filter/filtersSpec.js2
-rw-r--r--test/service/filter/limitToSpec.js52
-rw-r--r--test/service/filter/orderBySpec.js37
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}]);
+ });
+
+});