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}]); +  }); + +}); | 
