From 0e8be8057f5e1b9ee55621fcb57ef01b6e0b2e97 Mon Sep 17 00:00:00 2001 From: Automacao Date: Fri, 29 Jul 2016 16:24:50 +0100 Subject: [PATCH 1/2] Check if collection is array in remove-with --- src/_filter/collection/remove-with.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/_filter/collection/remove-with.js b/src/_filter/collection/remove-with.js index f26bc54..d076199 100644 --- a/src/_filter/collection/remove-with.js +++ b/src/_filter/collection/remove-with.js @@ -9,16 +9,20 @@ */ angular.module('a8m.remove-with', []) - .filter('removeWith', function() { + .filter('removeWith', function () { return function (collection, object) { - if(isUndefined(object)) { + if (isUndefined(object)) { return collection; } collection = isObject(collection) ? toArray(collection) : collection; + if (!isArray(collection)) { + return collection; + } + return collection.filter(function (elm) { return !objectContains(object, elm); }); From 2ea3aa8f5f65b0c063cf517dfd42665f67b708ee Mon Sep 17 00:00:00 2001 From: Automacao Date: Fri, 29 Jul 2016 16:26:11 +0100 Subject: [PATCH 2/2] added build --- dist/angular-filter.js | 4270 ++++++++++++++++++------------------ dist/angular-filter.min.js | 4 +- dist/angular-filter.zip | Bin 81208 -> 83424 bytes 3 files changed, 2139 insertions(+), 2135 deletions(-) diff --git a/dist/angular-filter.js b/dist/angular-filter.js index b1663b0..b725d05 100644 --- a/dist/angular-filter.js +++ b/dist/angular-filter.js @@ -1,1632 +1,1636 @@ -/** - * Bunch of useful filters for angularJS(with no external dependencies!) - * @version v0.5.9 - 2016-07-15 * @link https://github.com/a8m/angular-filter - * @author Ariel Mashraki - * @license MIT License, http://www.opensource.org/licenses/MIT - */ -(function ( window, angular, undefined ) { -/*jshint globalstrict:true*/ -'use strict'; - -var isDefined = angular.isDefined, - isUndefined = angular.isUndefined, - isFunction = angular.isFunction, - isString = angular.isString, - isNumber = angular.isNumber, - isObject = angular.isObject, - isArray = angular.isArray, - forEach = angular.forEach, - extend = angular.extend, - copy = angular.copy, - equals = angular.equals; - - -/** - * @description - * get an object and return array of values - * @param object - * @returns {Array} - */ -function toArray(object) { - return isArray(object) - ? object - : Object.keys(object).map(function(key) { - return object[key]; - }); -} - -/** - * @param value - * @returns {boolean} - */ -function isNull(value) { - return value === null; -} - -/** - * @description - * return if object contains partial object - * @param partial{object} - * @param object{object} - * @returns {boolean} - */ -function objectContains(partial, object) { - var keys = Object.keys(partial); - - return keys.map(function(el) { - return (object[el] !== undefined) && (object[el] == partial[el]); - }).indexOf(false) == -1; - -} - -/** - * @description - * search for approximate pattern in string - * @param word - * @param pattern - * @returns {*} - */ -function hasApproxPattern(word, pattern) { - if(pattern === '') - return word; - - var index = word.indexOf(pattern.charAt(0)); - - if(index === -1) - return false; - - return hasApproxPattern(word.substr(index+1), pattern.substr(1)) -} - -/** - * @description - * return the first n element of an array, - * if expression provided, is returns as long the expression return truthy - * @param array - * @param n {number} - * @param expression {$parse} - * @return array or single object - */ -function getFirstMatches(array, n, expression) { - var count = 0; - - return array.filter(function(elm) { - var rest = isDefined(expression) ? (count < n && expression(elm)) : count < n; - count = rest ? count+1 : count; - - return rest; - }); -} -/** - * Polyfill to ECMA6 String.prototype.contains - */ -if (!String.prototype.contains) { - String.prototype.contains = function() { - return String.prototype.indexOf.apply(this, arguments) !== -1; - }; -} - -/** - * @param num {Number} - * @param decimal {Number} - * @param $math - * @returns {Number} - */ -function convertToDecimal(num, decimal, $math){ - return $math.round(num * $math.pow(10,decimal)) / ($math.pow(10,decimal)); -} - -/** - * @description - * Get an object, and return an array composed of it's properties names(nested too). - * @param obj {Object} - * @param stack {Array} - * @param parent {String} - * @returns {Array} - * @example - * parseKeys({ a:1, b: { c:2, d: { e: 3 } } }) ==> ["a", "b.c", "b.d.e"] - */ -function deepKeys(obj, stack, parent) { - stack = stack || []; - var keys = Object.keys(obj); - - keys.forEach(function(el) { - //if it's a nested object - if(isObject(obj[el]) && !isArray(obj[el])) { - //concatenate the new parent if exist - var p = parent ? parent + '.' + el : parent; - deepKeys(obj[el], stack, p || el); - } else { - //create and save the key - var key = parent ? parent + '.' + el : el; - stack.push(key) - } - }); - return stack -} - -/** - * @description - * Test if given object is a Scope instance - * @param obj - * @returns {Boolean} - */ -function isScope(obj) { - return obj && obj.$evalAsync && obj.$watch; -} - -/** - * @ngdoc filter - * @name a8m.angular - * @kind function - * - * @description - * reference to angular function - */ - -angular.module('a8m.angular', []) - - .filter('isUndefined', function () { - return function (input) { - return angular.isUndefined(input); - } - }) - .filter('isDefined', function() { - return function (input) { - return angular.isDefined(input); - } - }) - .filter('isFunction', function() { - return function (input) { - return angular.isFunction(input); - } - }) - .filter('isString', function() { - return function (input) { - return angular.isString(input) - } - }) - .filter('isNumber', function() { - return function (input) { - return angular.isNumber(input); - } - }) - .filter('isArray', function() { - return function (input) { - return angular.isArray(input); - } - }) - .filter('isObject', function() { - return function (input) { - return angular.isObject(input); - } - }) - .filter('isEqual', function() { - return function (o1, o2) { - return angular.equals(o1, o2); - } - }); - -/** - * @ngdoc filter - * @name a8m.conditions - * @kind function - * - * @description - * reference to math conditions - */ - angular.module('a8m.conditions', []) - - .filter({ - isGreaterThan : isGreaterThanFilter, - '>' : isGreaterThanFilter, - - isGreaterThanOrEqualTo : isGreaterThanOrEqualToFilter, - '>=' : isGreaterThanOrEqualToFilter, - - isLessThan : isLessThanFilter, - '<' : isLessThanFilter, - - isLessThanOrEqualTo : isLessThanOrEqualToFilter, - '<=' : isLessThanOrEqualToFilter, - - isEqualTo : isEqualToFilter, - '==' : isEqualToFilter, - - isNotEqualTo : isNotEqualToFilter, - '!=' : isNotEqualToFilter, - - isIdenticalTo : isIdenticalToFilter, - '===' : isIdenticalToFilter, - - isNotIdenticalTo : isNotIdenticalToFilter, - '!==' : isNotIdenticalToFilter - }); - - function isGreaterThanFilter() { - return function (input, check) { - return input > check; - }; - } - - function isGreaterThanOrEqualToFilter() { - return function (input, check) { - return input >= check; - }; - } - - function isLessThanFilter() { - return function (input, check) { - return input < check; - }; - } - - function isLessThanOrEqualToFilter() { - return function (input, check) { - return input <= check; - }; - } - - function isEqualToFilter() { - return function (input, check) { - return input == check; - }; - } - - function isNotEqualToFilter() { - return function (input, check) { - return input != check; - }; - } - - function isIdenticalToFilter() { - return function (input, check) { - return input === check; - }; - } - - function isNotIdenticalToFilter() { - return function (input, check) { - return input !== check; - }; - } -/** - * @ngdoc filter - * @name isNull - * @kind function - * - * @description - * checks if value is null or not - * @return Boolean - */ -angular.module('a8m.is-null', []) - .filter('isNull', function () { - return function(input) { - return isNull(input); - } - }); - -/** - * @ngdoc filter - * @name after-where - * @kind function - * - * @description - * get a collection and properties object, and returns all of the items - * in the collection after the first that found with the given properties. - * - */ -angular.module('a8m.after-where', []) - .filter('afterWhere', function() { - return function (collection, object) { - - collection = isObject(collection) - ? toArray(collection) - : collection; - - if(!isArray(collection) || isUndefined(object)) return collection; - - var index = collection.map( function( elm ) { - return objectContains(object, elm); - }).indexOf( true ); - - return collection.slice((index === -1) ? 0 : index); - } - }); - -/** - * @ngdoc filter - * @name after - * @kind function - * - * @description - * get a collection and specified count, and returns all of the items - * in the collection after the specified count. - * - */ - -angular.module('a8m.after', []) - .filter('after', function() { - return function (collection, count) { - collection = isObject(collection) - ? toArray(collection) - : collection; - - return (isArray(collection)) - ? collection.slice(count) - : collection; - } - }); - -/** - * @ngdoc filter - * @name before-where - * @kind function - * - * @description - * get a collection and properties object, and returns all of the items - * in the collection before the first that found with the given properties. - */ -angular.module('a8m.before-where', []) - .filter('beforeWhere', function() { - return function (collection, object) { - - collection = isObject(collection) - ? toArray(collection) - : collection; - - if(!isArray(collection) || isUndefined(object)) return collection; - - var index = collection.map( function( elm ) { - return objectContains(object, elm); - }).indexOf( true ); - - return collection.slice(0, (index === -1) ? collection.length : ++index); - } - }); - -/** - * @ngdoc filter - * @name before - * @kind function - * - * @description - * get a collection and specified count, and returns all of the items - * in the collection before the specified count. - */ -angular.module('a8m.before', []) - .filter('before', function() { - return function (collection, count) { - collection = isObject(collection) - ? toArray(collection) - : collection; - - return (isArray(collection)) - ? collection.slice(0, (!count) ? count : --count) - : collection; - } - }); - -/** - * @ngdoc filter - * @name chunkBy - * @kind function - * - * @description - * Collect data into fixed-length chunks or blocks - */ - -angular.module('a8m.chunk-by', ['a8m.filter-watcher']) - .filter('chunkBy', ['filterWatcher', function (filterWatcher) { - return function (array, n, fillVal) { - - return filterWatcher.isMemoized('chunkBy', arguments) || - filterWatcher.memoize('chunkBy', arguments, this, - _chunkBy(array, n, fillVal)); - /** - * @description - * Get array with size `n` in `val` inside it. - * @param n - * @param val - * @returns {Array} - */ - function fill(n, val) { - var ret = []; - while (n--) ret[n] = val; - return ret; - } - - function _chunkBy(array, n, fillVal) { - if (!isArray(array)) return array; - return array.map(function (el, i, self) { - i = i * n; - el = self.slice(i, i + n); - return !isUndefined(fillVal) && el.length < n - ? el.concat(fill(n - el.length, fillVal)) - : el; - }).slice(0, Math.ceil(array.length / n)); - } - } - }]); - -/** - * @ngdoc filter - * @name concat - * @kind function - * - * @description - * get (array/object, object/array) and return merged collection - */ -angular.module('a8m.concat', []) - .filter('concat', [function () { - return function (collection, joined) { - - if (isUndefined(joined)) return collection; - - if (isArray(collection)) { - return isObject(joined) - ? collection.concat(toArray(joined)) - : collection.concat(joined); - } - - if (isObject(collection)) { - var array = toArray(collection); - return (isObject(joined)) - ? array.concat(toArray(joined)) - : array.concat(joined); - } - return collection; - }; - } -]); - -/** - * @ngdoc filter - * @name contains - * @kind function - * - * @description - * Checks if given expression is present in one or more object in the collection - */ -angular.module('a8m.contains', []) - .filter({ - contains: ['$parse', containsFilter], - some: ['$parse', containsFilter] - }); - -function containsFilter($parse) { - return function (collection, expression) { - - collection = isObject(collection) ? toArray(collection) : collection; - - if(!isArray(collection) || isUndefined(expression)) { - return false; - } - - return collection.some(function(elm) { - return ((isString(expression) && isObject(elm)) || isFunction(expression)) - ? $parse(expression)(elm) - : elm === expression; - }); - - } - } - -/** - * @ngdoc filter - * @name countBy - * @kind function - * - * @description - * Sorts a list into groups and returns a count for the number of objects in each group. - */ - -angular.module('a8m.count-by', []) - - .filter('countBy', [ '$parse', function ( $parse ) { - return function (collection, property) { - - var result = {}, - get = $parse(property), - prop; - - collection = (isObject(collection)) ? toArray(collection) : collection; - - if(!isArray(collection) || isUndefined(property)) { - return collection; - } - - collection.forEach( function( elm ) { - prop = get(elm); - - if(!result[prop]) { - result[prop] = 0; - } - - result[prop]++; - }); - - return result; - } - }]); - -/** - * @ngdoc filter - * @name defaults - * @kind function - * - * @description - * defaultsFilter allows to specify a default fallback value for properties that resolve to undefined. - */ -angular.module('a8m.defaults', []) - .filter('defaults', ['$parse', function( $parse ) { - return function(collection, defaults) { - - collection = isObject(collection) ? toArray(collection) : collection; - - if(!isArray(collection) || !isObject(defaults)) { - return collection; - } - - var keys = deepKeys(defaults); - - collection.forEach(function(elm) { - //loop through all the keys - keys.forEach(function(key) { - var getter = $parse(key); - var setter = getter.assign; - //if it's not exist - if(isUndefined(getter(elm))) { - //get from defaults, and set to the returned object - setter(elm, getter(defaults)) - } - }); - }); - - return collection; - } - }]); -/** - * @ngdoc filter - * @name every - * @kind function - * - * @description - * Checks if given expression is present in all members in the collection - * - */ -angular.module('a8m.every', []) - .filter('every', ['$parse', function($parse) { - return function (collection, expression) { - collection = isObject(collection) ? toArray(collection) : collection; - - if(!isArray(collection) || isUndefined(expression)) { - return true; - } - - return collection.every( function(elm) { - return (isObject(elm) || isFunction(expression)) - ? $parse(expression)(elm) - : elm === expression; - }); - } - }]); - -/** - * @ngdoc filter - * @name filterBy - * @kind function - * - * @description - * filter by specific properties, avoid the rest - */ -angular.module('a8m.filter-by', []) - .filter('filterBy', ['$parse', function( $parse ) { - return function(collection, properties, search, strict) { - var comparator; - - search = (isString(search) || isNumber(search)) ? - String(search).toLowerCase() : undefined; - - collection = isObject(collection) ? toArray(collection) : collection; - - if(!isArray(collection) || isUndefined(search)) { - return collection; - } - - return collection.filter(function(elm) { - return properties.some(function(prop) { - - /** - * check if there is concatenate properties - * example: - * object: { first: 'foo', last:'bar' } - * filterBy: ['first + last'] => search by full name(i.e 'foo bar') - */ - if(!~prop.indexOf('+')) { - comparator = $parse(prop)(elm) - } else { - var propList = prop.replace(/\s+/g, '').split('+'); - comparator = propList - .map(function(prop) { return $parse(prop)(elm); }) - .join(' '); - } - - if (!isString(comparator) && !isNumber(comparator)) { - return false; - } - - comparator = String(comparator).toLowerCase(); - - return strict ? comparator === search : comparator.contains(search); - }); - }); - } - }]); - -/** - * @ngdoc filter - * @name first - * @kind function - * - * @description - * Gets the first element or first n elements of an array - * if callback is provided, is returns as long the callback return truthy - */ -angular.module('a8m.first', []) - .filter('first', ['$parse', function( $parse ) { - return function(collection) { - var n - , getter - , args; - - collection = isObject(collection) - ? toArray(collection) - : collection; - - if(!isArray(collection)) { - return collection; - } - - args = Array.prototype.slice.call(arguments, 1); - n = (isNumber(args[0])) ? args[0] : 1; - getter = (!isNumber(args[0])) ? args[0] : (!isNumber(args[1])) ? args[1] : undefined; - - return (args.length) ? getFirstMatches(collection, n,(getter) ? $parse(getter) : getter) : - collection[0]; - } - }]); - -/** - * @ngdoc filter - * @name flatten - * @kind function - * - * @description - * Flattens a nested array (the nesting can be to any depth). - * If you pass shallow, the array will only be flattened a single level - */ -angular.module('a8m.flatten', []) - .filter('flatten', function () { - return function(collection, shallow) { - - shallow = shallow || false; - collection = isObject(collection) - ? toArray(collection) - : collection; - - if(!isArray(collection)) { - return collection; - } - - return !shallow - ? flatten(collection, 0) - : [].concat.apply([], collection); - } - }); - -/** - * flatten nested array (the nesting can be to any depth). - * @param array {Array} - * @param i {int} - * @returns {Array} - * @private - */ -function flatten(array, i) { - i = i || 0; - - if(i >= array.length) - return array; - - if(isArray(array[i])) { - return flatten(array.slice(0,i) - .concat(array[i], array.slice(i+1)), i); - } - return flatten(array, i+1); -} - -/** - * @ngdoc filter - * @name fuzzyByKey - * @kind function - * - * @description - * fuzzy string searching by key - */ -angular.module('a8m.fuzzy-by', []) - .filter('fuzzyBy', ['$parse', function ( $parse ) { - return function (collection, property, search, csensitive) { - - var sensitive = csensitive || false, - prop, getter; - - collection = isObject(collection) ? toArray(collection) : collection; - - if(!isArray(collection) || isUndefined(property) - || isUndefined(search)) { - return collection; - } - - getter = $parse(property); - - return collection.filter(function(elm) { - - prop = getter(elm); - if(!isString(prop)) { - return false; - } - - prop = (sensitive) ? prop : prop.toLowerCase(); - search = (sensitive) ? search : search.toLowerCase(); - - return hasApproxPattern(prop, search) !== false - }) - } - - }]); -/** - * @ngdoc filter - * @name fuzzy - * @kind function - * - * @description - * fuzzy string searching for array of strings, objects - */ -angular.module('a8m.fuzzy', []) - .filter('fuzzy', function () { - return function (collection, search, csensitive) { - var sensitive = csensitive || false; - collection = isObject(collection) ? toArray(collection) : collection; - - if(!isArray(collection) || isUndefined(search)) { - return collection; - } - - search = (sensitive) ? search : search.toLowerCase(); - - return collection.filter(function(elm) { - if(isString(elm)) { - elm = (sensitive) ? elm : elm.toLowerCase(); - return hasApproxPattern(elm, search) !== false - } - return (isObject(elm)) ? _hasApproximateKey(elm, search) : false; - }); - - /** - * checks if object has key{string} that match - * to fuzzy search pattern - * @param object - * @param search - * @returns {boolean} - * @private - */ - function _hasApproximateKey(object, search) { - var properties = Object.keys(object), - prop, flag; - return 0 < properties.filter(function (elm) { - prop = object[elm]; - - //avoid iteration if we found some key that equal[performance] - if(flag) return true; - - if (isString(prop)) { - prop = (sensitive) ? prop : prop.toLowerCase(); - return flag = (hasApproxPattern(prop, search) !== false); - } - - return false; - - }).length; - } - } - }); - -/** - * @ngdoc filter - * @name groupBy - * @kind function - * - * @description - * Create an object composed of keys generated from the result of running each element of a collection, - * each key is an array of the elements. - */ - -angular.module('a8m.group-by', [ 'a8m.filter-watcher' ]) - .filter('groupBy', [ '$parse', 'filterWatcher', function ( $parse, filterWatcher ) { - return function (collection, property) { - - if(!isObject(collection) || isUndefined(property)) { - return collection; - } - - return filterWatcher.isMemoized('groupBy', arguments) || - filterWatcher.memoize('groupBy', arguments, this, - _groupBy(collection, $parse(property))); - - /** - * groupBy function - * @param collection - * @param getter - * @returns {{}} - */ - function _groupBy(collection, getter) { - var result = {}; - var prop; - - forEach( collection, function( elm ) { - prop = getter(elm); - - if(!result[prop]) { - result[prop] = []; - } - result[prop].push(elm); - }); - return result; - } - } - }]); - -/** - * @ngdoc filter - * @name isEmpty - * @kind function - * - * @description - * get collection or string and return if it empty - */ -angular.module('a8m.is-empty', []) - .filter('isEmpty', function () { - return function(collection) { - return isObject(collection) - ? !toArray(collection).length - : !collection.length; - } - }); - -/** - * @ngdoc filter - * @name join - * @kind function - * - * @description - * join a collection by a provided delimiter (space by default) - */ -angular.module('a8m.join', []) - .filter('join', function () { - return function (input, delimiter) { - if (isUndefined(input) || !isArray(input)) { - return input; - } - if (isUndefined(delimiter)) delimiter = ' '; - - return input.join(delimiter); - }; - }) -; - -/** - * @ngdoc filter - * @name last - * @kind function - * - * @description - * Gets the last element or last n elements of an array - * if callback is provided, is returns as long the callback return truthy - */ -angular.module('a8m.last', []) - .filter('last', ['$parse', function( $parse ) { - return function(collection) { - var n - , getter - , args - //cuz reverse change our src collection - //and we don't want side effects - , reversed = copy(collection); - - reversed = isObject(reversed) - ? toArray(reversed) - : reversed; - - if(!isArray(reversed)) { - return reversed; - } - - args = Array.prototype.slice.call(arguments, 1); - n = (isNumber(args[0])) ? args[0] : 1; - getter = (!isNumber(args[0])) ? args[0] : (!isNumber(args[1])) ? args[1] : undefined; - - return (args.length) - //send reversed collection as arguments, and reverse it back as result - ? getFirstMatches(reversed.reverse(), n,(getter) ? $parse(getter) : getter).reverse() - //get the last element - : reversed[reversed.length-1]; - } - }]); - -/** - * @ngdoc filter - * @name map - * @kind function - * - * @description - * Returns a new collection of the results of each expression execution. - */ -angular.module('a8m.map', []) - .filter('map', ['$parse', function($parse) { - return function (collection, expression) { - - collection = isObject(collection) - ? toArray(collection) - : collection; - - if(!isArray(collection) || isUndefined(expression)) { - return collection; - } - - return collection.map(function (elm) { - return $parse(expression)(elm); - }); - } - }]); - -/** - * @ngdoc filter - * @name omit - * @kind function - * - * @description - * filter collection by expression - */ - -angular.module('a8m.omit', []) - - .filter('omit', ['$parse', function($parse) { - return function (collection, expression) { - - collection = isObject(collection) - ? toArray(collection) - : collection; - - if(!isArray(collection) || isUndefined(expression)) { - return collection; - } - - return collection.filter(function (elm) { - return !($parse(expression)(elm)); - }); - } - }]); - -/** - * @ngdoc filter - * @name pick - * @kind function - * - * @description - * filter collection by expression - */ - -angular.module('a8m.pick', []) - - .filter('pick', ['$parse', function($parse) { - return function (collection, expression) { - - collection = isObject(collection) - ? toArray(collection) - : collection; - - if(!isArray(collection) || isUndefined(expression)) { - return collection; - } - - return collection.filter(function (elm) { - return $parse(expression)(elm); - }); - } - }]); - -/** - * @ngdoc filter - * @name range - * @kind function - * - * @description - * rangeFilter provides some support for a for loop using numbers - */ -angular.module('a8m.range', []) - .filter('range', function () { - return function (input, total, start, increment, cb) { - start = start || 0; - increment = increment || 1; - for (var i = 0; i < parseInt(total); i++) { - var j = start + i * increment; - input.push(isFunction(cb) ? cb(j) : j); - } - return input; - }; - }); -/** - * @ngdoc filter - * @name removeWith - * @kind function - * - * @description - * get collection and properties object, and removed elements - * with this properties - */ - -angular.module('a8m.remove-with', []) - .filter('removeWith', function() { - return function (collection, object) { - - if(isUndefined(object)) { - return collection; - } - collection = isObject(collection) - ? toArray(collection) - : collection; - - return collection.filter(function (elm) { - return !objectContains(object, elm); - }); - } - }); - - -/** - * @ngdoc filter - * @name remove - * @kind function - * - * @description - * remove specific members from collection - */ - -angular.module('a8m.remove', []) - - .filter('remove', function () { - return function (collection) { - collection = isObject(collection) ? toArray(collection) : collection; - var args = Array.prototype.slice.call(arguments, 1); - - if(!isArray(collection)) { - return collection; - } - - return collection.filter( function( member ) { - return !args.some(function(nest) { - return equals(nest, member); - }) - }); - } - }); - -/** - * @ngdoc filter - * @name reverse - * @kind function - * - * @description - * Reverses a string or collection - */ -angular.module('a8m.reverse', []) - .filter('reverse',[ function () { - return function (input) { - input = isObject(input) ? toArray(input) : input; - - if(isString(input)) { - return input.split('').reverse().join(''); - } - - return isArray(input) - ? input.slice().reverse() - : input; - } - }]); - -/** - * @ngdoc filter - * @name searchField - * @kind function - * - * @description - * for each member, join several strings field and add them to - * new field called 'searchField' (use for search filtering) - */ -angular.module('a8m.search-field', []) - .filter('searchField', ['$parse', function ($parse) { - return function (collection) { - - var get, field; - - collection = isObject(collection) ? toArray(collection) : collection; - - var args = Array.prototype.slice.call(arguments, 1); - - if(!isArray(collection) || !args.length) { - return collection; - } - - return collection.map(function(member) { - - field = args.map(function(field) { - get = $parse(field); - return get(member); - }).join(' '); - - return extend(member, { searchField: field }); - }); - } - }]); - -/** - * @ngdoc filter - * @name toArray - * @kind function - * - * @description - * Convert objects into stable arrays. - * if addKey set to true,the filter also attaches a new property - * $key to the value containing the original key that was used in - * the object we are iterating over to reference the property - */ -angular.module('a8m.to-array', []) - .filter('toArray', function() { - return function (collection, addKey) { - - if(!isObject(collection)) { - return collection; - } - - return !addKey - ? toArray(collection) - : Object.keys(collection).map(function (key) { - return extend(collection[key], { $key: key }); - }); - } - }); - -/** - * @ngdoc filter - * @name unique/uniq - * @kind function - * - * @description - * get collection and filter duplicate members - * if uniqueFilter get a property(nested to) as argument it's - * filter by this property as unique identifier - */ - -angular.module('a8m.unique', []) - .filter({ - unique: ['$parse', uniqFilter], - uniq: ['$parse', uniqFilter] - }); - -function uniqFilter($parse) { - return function (collection, property) { - - collection = isObject(collection) ? toArray(collection) : collection; - - if (!isArray(collection)) { - return collection; - } - - //store all unique identifiers - var uniqueItems = [], - get = $parse(property); - - return (isUndefined(property)) - //if it's kind of primitive array - ? collection.filter(function (elm, pos, self) { - return self.indexOf(elm) === pos; - }) - //else compare with equals - : collection.filter(function (elm) { - var prop = get(elm); - if(some(uniqueItems, prop)) { - return false; - } - uniqueItems.push(prop); - return true; - }); - - //checked if the unique identifier is already exist - function some(array, member) { - if(isUndefined(member)) { - return false; - } - return array.some(function(el) { - return equals(el, member); - }); - } - } -} - -/** - * @ngdoc filter - * @name where - * @kind function - * - * @description - * of each element in a collection to the given properties object, - * returning an array of all elements that have equivalent property values. - * - */ -angular.module('a8m.where', []) - .filter('where', function() { - return function (collection, object) { - if(isUndefined(object)) return collection; - collection = isObject(collection) - ? toArray(collection) - : collection; - - return collection.filter(function (elm) { - return objectContains(object, elm); - }); - } - }); - -/** - * @ngdoc filter - * @name xor - * @kind function - * - * @description - * Exclusive or filter by expression - */ - -angular.module('a8m.xor', []) - - .filter('xor', ['$parse', function($parse) { - return function (col1, col2, expression) { - - expression = expression || false; - - col1 = isObject(col1) ? toArray(col1) : col1; - col2 = isObject(col2) ? toArray(col2) : col2; - - if(!isArray(col1) || !isArray(col2)) return col1; - - return col1.concat(col2) - .filter(function(elm) { - return !(some(elm, col1) && some(elm, col2)); - }); - - function some(el, col) { - var getter = $parse(expression); - return col.some(function(dElm) { - return expression - ? equals(getter(dElm), getter(el)) - : equals(dElm, el); - }); - } - } - }]); - -/** - * @ngdoc filter - * @name formatBytes - * @kind function - * - * @description - * Convert bytes into appropriate display - * 1024 bytes => 1 KB - */ -angular.module('a8m.math.byteFmt', ['a8m.math']) - .filter('byteFmt', ['$math', function ($math) { - return function (bytes, decimal) { - - if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && - isNumber(bytes) && isFinite(bytes)) { - if(bytes < 1024) { // within 1 KB so B - return convertToDecimal(bytes, decimal, $math) + ' B'; - } else if(bytes < 1048576) { // within 1 MB so KB - return convertToDecimal((bytes / 1024), decimal, $math) + ' KB'; - } else if(bytes < 1073741824){ // within 1 GB so MB - return convertToDecimal((bytes / 1048576), decimal, $math) + ' MB'; - } else { // GB or more - return convertToDecimal((bytes / 1073741824), decimal, $math) + ' GB'; - } - - } - return "NaN"; - } - }]); -/** - * @ngdoc filter - * @name degrees - * @kind function - * - * @description - * Convert angle from radians to degrees - */ -angular.module('a8m.math.degrees', ['a8m.math']) - .filter('degrees', ['$math', function ($math) { - return function (radians, decimal) { - // if decimal is not an integer greater than -1, we cannot do. quit with error "NaN" - // if degrees is not a real number, we cannot do also. quit with error "NaN" - if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && - isNumber(radians) && isFinite(radians)) { - var degrees = (radians * 180) / $math.PI; - return $math.round(degrees * $math.pow(10,decimal)) / ($math.pow(10,decimal)); - } else { - return "NaN"; - } - } - }]); - - - -/** - * @ngdoc filter - * @name formatBytes - * @kind function - * - * @description - * Convert bytes into appropriate display - * 1024 kilobytes => 1 MB - */ -angular.module('a8m.math.kbFmt', ['a8m.math']) - .filter('kbFmt', ['$math', function ($math) { - return function (bytes, decimal) { - - if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && - isNumber(bytes) && isFinite(bytes)) { - if(bytes < 1024) { // within 1 MB so KB - return convertToDecimal(bytes, decimal, $math) + ' KB'; - } else if(bytes < 1048576) { // within 1 GB so MB - return convertToDecimal((bytes / 1024), decimal, $math) + ' MB'; - } else { - return convertToDecimal((bytes / 1048576), decimal, $math) + ' GB'; - } - } - return "NaN"; - } - }]); -/** - * @ngdoc module - * @name math - * @description - * reference to global Math object - */ -angular.module('a8m.math', []) - .factory('$math', ['$window', function ($window) { - return $window.Math; - }]); - -/** - * @ngdoc filter - * @name max - * @kind function - * - * @description - * Math.max will get an array and return the max value. if an expression - * is provided, will return max value by expression. - */ -angular.module('a8m.math.max', ['a8m.math']) - .filter('max', ['$math', '$parse', function ($math, $parse) { - return function (input, expression) { - - if(!isArray(input)) { - return input; - } - return isUndefined(expression) - ? $math.max.apply($math, input) - : input[indexByMax(input, expression)]; - }; - - /** - * @private - * @param array - * @param exp - * @returns {number|*|Number} - */ - function indexByMax(array, exp) { - var mappedArray = array.map(function(elm){ - return $parse(exp)(elm); - }); - return mappedArray.indexOf($math.max.apply($math, mappedArray)); - } - }]); -/** - * @ngdoc filter - * @name min - * @kind function - * - * @description - * Math.min will get an array and return the min value. if an expression - * is provided, will return min value by expression. - */ -angular.module('a8m.math.min', ['a8m.math']) - .filter('min', ['$math', '$parse', function ($math, $parse) { - return function (input, expression) { - - if(!isArray(input)) { - return input; - } - return isUndefined(expression) - ? $math.min.apply($math, input) - : input[indexByMin(input, expression)]; - }; - - /** - * @private - * @param array - * @param exp - * @returns {number|*|Number} - */ - function indexByMin(array, exp) { - var mappedArray = array.map(function(elm){ - return $parse(exp)(elm); - }); - return mappedArray.indexOf($math.min.apply($math, mappedArray)); - } - }]); -/** - * @ngdoc filter - * @name Percent - * @kind function - * - * @description - * percentage between two numbers - */ -angular.module('a8m.math.percent', ['a8m.math']) - .filter('percent', ['$math', '$window', function ($math, $window) { - return function (input, divided, round) { - - var divider = isString(input) ? $window.Number(input) : input; - divided = divided || 100; - round = round || false; - - if (!isNumber(divider) || $window.isNaN(divider)) return input; - - return round - ? $math.round((divider / divided) * 100) - : (divider / divided) * 100; - } - }]); - -/** - * @ngdoc filter - * @name toRadians - * @kind function - * - * @description - * Convert angle from degrees to radians - */ -angular.module('a8m.math.radians', ['a8m.math']) - .filter('radians', ['$math', function ($math) { - return function (degrees, decimal) { - // if decimal is not an integer greater than -1, we cannot do. quit with error "NaN" - // if degrees is not a real number, we cannot do also. quit with error "NaN" - if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && - isNumber(degrees) && isFinite(degrees)) { - var radians = (degrees * 3.14159265359) / 180; - return $math.round(radians * $math.pow(10,decimal)) / ($math.pow(10,decimal)); - } - return "NaN"; - } - }]); - - - -/** - * @ngdoc filter - * @name Radix - * @kind function - * - * @description - * converting decimal numbers to different bases(radix) - */ -angular.module('a8m.math.radix', []) - .filter('radix', function () { - return function (input, radix) { - var RANGE = /^[2-9]$|^[1-2]\d$|^3[0-6]$/; - - if(!isNumber(input) || !RANGE.test(radix)) { - return input; - } - - return input.toString(radix).toUpperCase(); - } - }); - -/** - * @ngdoc filter - * @name formatBytes - * @kind function - * - * @description - * Convert number into abbreviations. - * i.e: K for one thousand, M for Million, B for billion - * e.g: number of users:235,221, decimal:1 => 235.2 K - */ -angular.module('a8m.math.shortFmt', ['a8m.math']) - .filter('shortFmt', ['$math', function ($math) { - return function (number, decimal) { - if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && - isNumber(number) && isFinite(number)){ - if(number < 1e3) { - return number; - } else if(number < 1e6) { - return convertToDecimal((number / 1e3), decimal, $math) + ' K'; - } else if(number < 1e9){ - return convertToDecimal((number / 1e6), decimal, $math) + ' M'; - } else { - return convertToDecimal((number / 1e9), decimal, $math) + ' B'; - } - - } - return "NaN"; - } - }]); -/** - * @ngdoc filter - * @name sum - * @kind function - * - * @description - * Sum up all values within an array - */ -angular.module('a8m.math.sum', []) - .filter('sum', function () { - return function (input, initial) { - return !isArray(input) - ? input - : input.reduce(function(prev, curr) { - return prev + curr; - }, initial || 0); - } - }); - -/** - * @ngdoc filter - * @name endsWith - * @kind function - * - * @description - * checks whether string ends with the ends parameter. - */ -angular.module('a8m.ends-with', []) - - .filter('endsWith', function () { - return function (input, ends, csensitive) { - - var sensitive = csensitive || false, - position; - - if(!isString(input) || isUndefined(ends)) { - return input; - } - - input = (sensitive) ? input : input.toLowerCase(); - position = input.length - ends.length; - - return input.indexOf((sensitive) ? ends : ends.toLowerCase(), position) !== -1; - } - }); - +/** + * Bunch of useful filters for angularJS(with no external dependencies!) + * @version v0.5.9 - 2016-07-29 * @link https://github.com/a8m/angular-filter + * @author Ariel Mashraki + * @license MIT License, http://www.opensource.org/licenses/MIT + */ +(function ( window, angular, undefined ) { +/*jshint globalstrict:true*/ +'use strict'; + +var isDefined = angular.isDefined, + isUndefined = angular.isUndefined, + isFunction = angular.isFunction, + isString = angular.isString, + isNumber = angular.isNumber, + isObject = angular.isObject, + isArray = angular.isArray, + forEach = angular.forEach, + extend = angular.extend, + copy = angular.copy, + equals = angular.equals; + + +/** + * @description + * get an object and return array of values + * @param object + * @returns {Array} + */ +function toArray(object) { + return isArray(object) + ? object + : Object.keys(object).map(function(key) { + return object[key]; + }); +} + +/** + * @param value + * @returns {boolean} + */ +function isNull(value) { + return value === null; +} + +/** + * @description + * return if object contains partial object + * @param partial{object} + * @param object{object} + * @returns {boolean} + */ +function objectContains(partial, object) { + var keys = Object.keys(partial); + + return keys.map(function(el) { + return (object[el] !== undefined) && (object[el] == partial[el]); + }).indexOf(false) == -1; + +} + +/** + * @description + * search for approximate pattern in string + * @param word + * @param pattern + * @returns {*} + */ +function hasApproxPattern(word, pattern) { + if(pattern === '') + return word; + + var index = word.indexOf(pattern.charAt(0)); + + if(index === -1) + return false; + + return hasApproxPattern(word.substr(index+1), pattern.substr(1)) +} + +/** + * @description + * return the first n element of an array, + * if expression provided, is returns as long the expression return truthy + * @param array + * @param n {number} + * @param expression {$parse} + * @return array or single object + */ +function getFirstMatches(array, n, expression) { + var count = 0; + + return array.filter(function(elm) { + var rest = isDefined(expression) ? (count < n && expression(elm)) : count < n; + count = rest ? count+1 : count; + + return rest; + }); +} +/** + * Polyfill to ECMA6 String.prototype.contains + */ +if (!String.prototype.contains) { + String.prototype.contains = function() { + return String.prototype.indexOf.apply(this, arguments) !== -1; + }; +} + +/** + * @param num {Number} + * @param decimal {Number} + * @param $math + * @returns {Number} + */ +function convertToDecimal(num, decimal, $math){ + return $math.round(num * $math.pow(10,decimal)) / ($math.pow(10,decimal)); +} + +/** + * @description + * Get an object, and return an array composed of it's properties names(nested too). + * @param obj {Object} + * @param stack {Array} + * @param parent {String} + * @returns {Array} + * @example + * parseKeys({ a:1, b: { c:2, d: { e: 3 } } }) ==> ["a", "b.c", "b.d.e"] + */ +function deepKeys(obj, stack, parent) { + stack = stack || []; + var keys = Object.keys(obj); + + keys.forEach(function(el) { + //if it's a nested object + if(isObject(obj[el]) && !isArray(obj[el])) { + //concatenate the new parent if exist + var p = parent ? parent + '.' + el : parent; + deepKeys(obj[el], stack, p || el); + } else { + //create and save the key + var key = parent ? parent + '.' + el : el; + stack.push(key) + } + }); + return stack +} + +/** + * @description + * Test if given object is a Scope instance + * @param obj + * @returns {Boolean} + */ +function isScope(obj) { + return obj && obj.$evalAsync && obj.$watch; +} + +/** + * @ngdoc filter + * @name a8m.angular + * @kind function + * + * @description + * reference to angular function + */ + +angular.module('a8m.angular', []) + + .filter('isUndefined', function () { + return function (input) { + return angular.isUndefined(input); + } + }) + .filter('isDefined', function() { + return function (input) { + return angular.isDefined(input); + } + }) + .filter('isFunction', function() { + return function (input) { + return angular.isFunction(input); + } + }) + .filter('isString', function() { + return function (input) { + return angular.isString(input) + } + }) + .filter('isNumber', function() { + return function (input) { + return angular.isNumber(input); + } + }) + .filter('isArray', function() { + return function (input) { + return angular.isArray(input); + } + }) + .filter('isObject', function() { + return function (input) { + return angular.isObject(input); + } + }) + .filter('isEqual', function() { + return function (o1, o2) { + return angular.equals(o1, o2); + } + }); + +/** + * @ngdoc filter + * @name a8m.conditions + * @kind function + * + * @description + * reference to math conditions + */ + angular.module('a8m.conditions', []) + + .filter({ + isGreaterThan : isGreaterThanFilter, + '>' : isGreaterThanFilter, + + isGreaterThanOrEqualTo : isGreaterThanOrEqualToFilter, + '>=' : isGreaterThanOrEqualToFilter, + + isLessThan : isLessThanFilter, + '<' : isLessThanFilter, + + isLessThanOrEqualTo : isLessThanOrEqualToFilter, + '<=' : isLessThanOrEqualToFilter, + + isEqualTo : isEqualToFilter, + '==' : isEqualToFilter, + + isNotEqualTo : isNotEqualToFilter, + '!=' : isNotEqualToFilter, + + isIdenticalTo : isIdenticalToFilter, + '===' : isIdenticalToFilter, + + isNotIdenticalTo : isNotIdenticalToFilter, + '!==' : isNotIdenticalToFilter + }); + + function isGreaterThanFilter() { + return function (input, check) { + return input > check; + }; + } + + function isGreaterThanOrEqualToFilter() { + return function (input, check) { + return input >= check; + }; + } + + function isLessThanFilter() { + return function (input, check) { + return input < check; + }; + } + + function isLessThanOrEqualToFilter() { + return function (input, check) { + return input <= check; + }; + } + + function isEqualToFilter() { + return function (input, check) { + return input == check; + }; + } + + function isNotEqualToFilter() { + return function (input, check) { + return input != check; + }; + } + + function isIdenticalToFilter() { + return function (input, check) { + return input === check; + }; + } + + function isNotIdenticalToFilter() { + return function (input, check) { + return input !== check; + }; + } +/** + * @ngdoc filter + * @name isNull + * @kind function + * + * @description + * checks if value is null or not + * @return Boolean + */ +angular.module('a8m.is-null', []) + .filter('isNull', function () { + return function(input) { + return isNull(input); + } + }); + +/** + * @ngdoc filter + * @name after-where + * @kind function + * + * @description + * get a collection and properties object, and returns all of the items + * in the collection after the first that found with the given properties. + * + */ +angular.module('a8m.after-where', []) + .filter('afterWhere', function() { + return function (collection, object) { + + collection = isObject(collection) + ? toArray(collection) + : collection; + + if(!isArray(collection) || isUndefined(object)) return collection; + + var index = collection.map( function( elm ) { + return objectContains(object, elm); + }).indexOf( true ); + + return collection.slice((index === -1) ? 0 : index); + } + }); + +/** + * @ngdoc filter + * @name after + * @kind function + * + * @description + * get a collection and specified count, and returns all of the items + * in the collection after the specified count. + * + */ + +angular.module('a8m.after', []) + .filter('after', function() { + return function (collection, count) { + collection = isObject(collection) + ? toArray(collection) + : collection; + + return (isArray(collection)) + ? collection.slice(count) + : collection; + } + }); + +/** + * @ngdoc filter + * @name before-where + * @kind function + * + * @description + * get a collection and properties object, and returns all of the items + * in the collection before the first that found with the given properties. + */ +angular.module('a8m.before-where', []) + .filter('beforeWhere', function() { + return function (collection, object) { + + collection = isObject(collection) + ? toArray(collection) + : collection; + + if(!isArray(collection) || isUndefined(object)) return collection; + + var index = collection.map( function( elm ) { + return objectContains(object, elm); + }).indexOf( true ); + + return collection.slice(0, (index === -1) ? collection.length : ++index); + } + }); + +/** + * @ngdoc filter + * @name before + * @kind function + * + * @description + * get a collection and specified count, and returns all of the items + * in the collection before the specified count. + */ +angular.module('a8m.before', []) + .filter('before', function() { + return function (collection, count) { + collection = isObject(collection) + ? toArray(collection) + : collection; + + return (isArray(collection)) + ? collection.slice(0, (!count) ? count : --count) + : collection; + } + }); + +/** + * @ngdoc filter + * @name chunkBy + * @kind function + * + * @description + * Collect data into fixed-length chunks or blocks + */ + +angular.module('a8m.chunk-by', ['a8m.filter-watcher']) + .filter('chunkBy', ['filterWatcher', function (filterWatcher) { + return function (array, n, fillVal) { + + return filterWatcher.isMemoized('chunkBy', arguments) || + filterWatcher.memoize('chunkBy', arguments, this, + _chunkBy(array, n, fillVal)); + /** + * @description + * Get array with size `n` in `val` inside it. + * @param n + * @param val + * @returns {Array} + */ + function fill(n, val) { + var ret = []; + while (n--) ret[n] = val; + return ret; + } + + function _chunkBy(array, n, fillVal) { + if (!isArray(array)) return array; + return array.map(function (el, i, self) { + i = i * n; + el = self.slice(i, i + n); + return !isUndefined(fillVal) && el.length < n + ? el.concat(fill(n - el.length, fillVal)) + : el; + }).slice(0, Math.ceil(array.length / n)); + } + } + }]); + +/** + * @ngdoc filter + * @name concat + * @kind function + * + * @description + * get (array/object, object/array) and return merged collection + */ +angular.module('a8m.concat', []) + .filter('concat', [function () { + return function (collection, joined) { + + if (isUndefined(joined)) return collection; + + if (isArray(collection)) { + return isObject(joined) + ? collection.concat(toArray(joined)) + : collection.concat(joined); + } + + if (isObject(collection)) { + var array = toArray(collection); + return (isObject(joined)) + ? array.concat(toArray(joined)) + : array.concat(joined); + } + return collection; + }; + } +]); + +/** + * @ngdoc filter + * @name contains + * @kind function + * + * @description + * Checks if given expression is present in one or more object in the collection + */ +angular.module('a8m.contains', []) + .filter({ + contains: ['$parse', containsFilter], + some: ['$parse', containsFilter] + }); + +function containsFilter($parse) { + return function (collection, expression) { + + collection = isObject(collection) ? toArray(collection) : collection; + + if(!isArray(collection) || isUndefined(expression)) { + return false; + } + + return collection.some(function(elm) { + return ((isString(expression) && isObject(elm)) || isFunction(expression)) + ? $parse(expression)(elm) + : elm === expression; + }); + + } + } + +/** + * @ngdoc filter + * @name countBy + * @kind function + * + * @description + * Sorts a list into groups and returns a count for the number of objects in each group. + */ + +angular.module('a8m.count-by', []) + + .filter('countBy', [ '$parse', function ( $parse ) { + return function (collection, property) { + + var result = {}, + get = $parse(property), + prop; + + collection = (isObject(collection)) ? toArray(collection) : collection; + + if(!isArray(collection) || isUndefined(property)) { + return collection; + } + + collection.forEach( function( elm ) { + prop = get(elm); + + if(!result[prop]) { + result[prop] = 0; + } + + result[prop]++; + }); + + return result; + } + }]); + +/** + * @ngdoc filter + * @name defaults + * @kind function + * + * @description + * defaultsFilter allows to specify a default fallback value for properties that resolve to undefined. + */ +angular.module('a8m.defaults', []) + .filter('defaults', ['$parse', function( $parse ) { + return function(collection, defaults) { + + collection = isObject(collection) ? toArray(collection) : collection; + + if(!isArray(collection) || !isObject(defaults)) { + return collection; + } + + var keys = deepKeys(defaults); + + collection.forEach(function(elm) { + //loop through all the keys + keys.forEach(function(key) { + var getter = $parse(key); + var setter = getter.assign; + //if it's not exist + if(isUndefined(getter(elm))) { + //get from defaults, and set to the returned object + setter(elm, getter(defaults)) + } + }); + }); + + return collection; + } + }]); +/** + * @ngdoc filter + * @name every + * @kind function + * + * @description + * Checks if given expression is present in all members in the collection + * + */ +angular.module('a8m.every', []) + .filter('every', ['$parse', function($parse) { + return function (collection, expression) { + collection = isObject(collection) ? toArray(collection) : collection; + + if(!isArray(collection) || isUndefined(expression)) { + return true; + } + + return collection.every( function(elm) { + return (isObject(elm) || isFunction(expression)) + ? $parse(expression)(elm) + : elm === expression; + }); + } + }]); + +/** + * @ngdoc filter + * @name filterBy + * @kind function + * + * @description + * filter by specific properties, avoid the rest + */ +angular.module('a8m.filter-by', []) + .filter('filterBy', ['$parse', function( $parse ) { + return function(collection, properties, search, strict) { + var comparator; + + search = (isString(search) || isNumber(search)) ? + String(search).toLowerCase() : undefined; + + collection = isObject(collection) ? toArray(collection) : collection; + + if(!isArray(collection) || isUndefined(search)) { + return collection; + } + + return collection.filter(function(elm) { + return properties.some(function(prop) { + + /** + * check if there is concatenate properties + * example: + * object: { first: 'foo', last:'bar' } + * filterBy: ['first + last'] => search by full name(i.e 'foo bar') + */ + if(!~prop.indexOf('+')) { + comparator = $parse(prop)(elm) + } else { + var propList = prop.replace(/\s+/g, '').split('+'); + comparator = propList + .map(function(prop) { return $parse(prop)(elm); }) + .join(' '); + } + + if (!isString(comparator) && !isNumber(comparator)) { + return false; + } + + comparator = String(comparator).toLowerCase(); + + return strict ? comparator === search : comparator.contains(search); + }); + }); + } + }]); + +/** + * @ngdoc filter + * @name first + * @kind function + * + * @description + * Gets the first element or first n elements of an array + * if callback is provided, is returns as long the callback return truthy + */ +angular.module('a8m.first', []) + .filter('first', ['$parse', function( $parse ) { + return function(collection) { + var n + , getter + , args; + + collection = isObject(collection) + ? toArray(collection) + : collection; + + if(!isArray(collection)) { + return collection; + } + + args = Array.prototype.slice.call(arguments, 1); + n = (isNumber(args[0])) ? args[0] : 1; + getter = (!isNumber(args[0])) ? args[0] : (!isNumber(args[1])) ? args[1] : undefined; + + return (args.length) ? getFirstMatches(collection, n,(getter) ? $parse(getter) : getter) : + collection[0]; + } + }]); + +/** + * @ngdoc filter + * @name flatten + * @kind function + * + * @description + * Flattens a nested array (the nesting can be to any depth). + * If you pass shallow, the array will only be flattened a single level + */ +angular.module('a8m.flatten', []) + .filter('flatten', function () { + return function(collection, shallow) { + + shallow = shallow || false; + collection = isObject(collection) + ? toArray(collection) + : collection; + + if(!isArray(collection)) { + return collection; + } + + return !shallow + ? flatten(collection, 0) + : [].concat.apply([], collection); + } + }); + +/** + * flatten nested array (the nesting can be to any depth). + * @param array {Array} + * @param i {int} + * @returns {Array} + * @private + */ +function flatten(array, i) { + i = i || 0; + + if(i >= array.length) + return array; + + if(isArray(array[i])) { + return flatten(array.slice(0,i) + .concat(array[i], array.slice(i+1)), i); + } + return flatten(array, i+1); +} + +/** + * @ngdoc filter + * @name fuzzyByKey + * @kind function + * + * @description + * fuzzy string searching by key + */ +angular.module('a8m.fuzzy-by', []) + .filter('fuzzyBy', ['$parse', function ( $parse ) { + return function (collection, property, search, csensitive) { + + var sensitive = csensitive || false, + prop, getter; + + collection = isObject(collection) ? toArray(collection) : collection; + + if(!isArray(collection) || isUndefined(property) + || isUndefined(search)) { + return collection; + } + + getter = $parse(property); + + return collection.filter(function(elm) { + + prop = getter(elm); + if(!isString(prop)) { + return false; + } + + prop = (sensitive) ? prop : prop.toLowerCase(); + search = (sensitive) ? search : search.toLowerCase(); + + return hasApproxPattern(prop, search) !== false + }) + } + + }]); +/** + * @ngdoc filter + * @name fuzzy + * @kind function + * + * @description + * fuzzy string searching for array of strings, objects + */ +angular.module('a8m.fuzzy', []) + .filter('fuzzy', function () { + return function (collection, search, csensitive) { + var sensitive = csensitive || false; + collection = isObject(collection) ? toArray(collection) : collection; + + if(!isArray(collection) || isUndefined(search)) { + return collection; + } + + search = (sensitive) ? search : search.toLowerCase(); + + return collection.filter(function(elm) { + if(isString(elm)) { + elm = (sensitive) ? elm : elm.toLowerCase(); + return hasApproxPattern(elm, search) !== false + } + return (isObject(elm)) ? _hasApproximateKey(elm, search) : false; + }); + + /** + * checks if object has key{string} that match + * to fuzzy search pattern + * @param object + * @param search + * @returns {boolean} + * @private + */ + function _hasApproximateKey(object, search) { + var properties = Object.keys(object), + prop, flag; + return 0 < properties.filter(function (elm) { + prop = object[elm]; + + //avoid iteration if we found some key that equal[performance] + if(flag) return true; + + if (isString(prop)) { + prop = (sensitive) ? prop : prop.toLowerCase(); + return flag = (hasApproxPattern(prop, search) !== false); + } + + return false; + + }).length; + } + } + }); + +/** + * @ngdoc filter + * @name groupBy + * @kind function + * + * @description + * Create an object composed of keys generated from the result of running each element of a collection, + * each key is an array of the elements. + */ + +angular.module('a8m.group-by', [ 'a8m.filter-watcher' ]) + .filter('groupBy', [ '$parse', 'filterWatcher', function ( $parse, filterWatcher ) { + return function (collection, property) { + + if(!isObject(collection) || isUndefined(property)) { + return collection; + } + + return filterWatcher.isMemoized('groupBy', arguments) || + filterWatcher.memoize('groupBy', arguments, this, + _groupBy(collection, $parse(property))); + + /** + * groupBy function + * @param collection + * @param getter + * @returns {{}} + */ + function _groupBy(collection, getter) { + var result = {}; + var prop; + + forEach( collection, function( elm ) { + prop = getter(elm); + + if(!result[prop]) { + result[prop] = []; + } + result[prop].push(elm); + }); + return result; + } + } + }]); + +/** + * @ngdoc filter + * @name isEmpty + * @kind function + * + * @description + * get collection or string and return if it empty + */ +angular.module('a8m.is-empty', []) + .filter('isEmpty', function () { + return function(collection) { + return isObject(collection) + ? !toArray(collection).length + : !collection.length; + } + }); + +/** + * @ngdoc filter + * @name join + * @kind function + * + * @description + * join a collection by a provided delimiter (space by default) + */ +angular.module('a8m.join', []) + .filter('join', function () { + return function (input, delimiter) { + if (isUndefined(input) || !isArray(input)) { + return input; + } + if (isUndefined(delimiter)) delimiter = ' '; + + return input.join(delimiter); + }; + }) +; + +/** + * @ngdoc filter + * @name last + * @kind function + * + * @description + * Gets the last element or last n elements of an array + * if callback is provided, is returns as long the callback return truthy + */ +angular.module('a8m.last', []) + .filter('last', ['$parse', function( $parse ) { + return function(collection) { + var n + , getter + , args + //cuz reverse change our src collection + //and we don't want side effects + , reversed = copy(collection); + + reversed = isObject(reversed) + ? toArray(reversed) + : reversed; + + if(!isArray(reversed)) { + return reversed; + } + + args = Array.prototype.slice.call(arguments, 1); + n = (isNumber(args[0])) ? args[0] : 1; + getter = (!isNumber(args[0])) ? args[0] : (!isNumber(args[1])) ? args[1] : undefined; + + return (args.length) + //send reversed collection as arguments, and reverse it back as result + ? getFirstMatches(reversed.reverse(), n,(getter) ? $parse(getter) : getter).reverse() + //get the last element + : reversed[reversed.length-1]; + } + }]); + +/** + * @ngdoc filter + * @name map + * @kind function + * + * @description + * Returns a new collection of the results of each expression execution. + */ +angular.module('a8m.map', []) + .filter('map', ['$parse', function($parse) { + return function (collection, expression) { + + collection = isObject(collection) + ? toArray(collection) + : collection; + + if(!isArray(collection) || isUndefined(expression)) { + return collection; + } + + return collection.map(function (elm) { + return $parse(expression)(elm); + }); + } + }]); + +/** + * @ngdoc filter + * @name omit + * @kind function + * + * @description + * filter collection by expression + */ + +angular.module('a8m.omit', []) + + .filter('omit', ['$parse', function($parse) { + return function (collection, expression) { + + collection = isObject(collection) + ? toArray(collection) + : collection; + + if(!isArray(collection) || isUndefined(expression)) { + return collection; + } + + return collection.filter(function (elm) { + return !($parse(expression)(elm)); + }); + } + }]); + +/** + * @ngdoc filter + * @name pick + * @kind function + * + * @description + * filter collection by expression + */ + +angular.module('a8m.pick', []) + + .filter('pick', ['$parse', function($parse) { + return function (collection, expression) { + + collection = isObject(collection) + ? toArray(collection) + : collection; + + if(!isArray(collection) || isUndefined(expression)) { + return collection; + } + + return collection.filter(function (elm) { + return $parse(expression)(elm); + }); + } + }]); + +/** + * @ngdoc filter + * @name range + * @kind function + * + * @description + * rangeFilter provides some support for a for loop using numbers + */ +angular.module('a8m.range', []) + .filter('range', function () { + return function (input, total, start, increment, cb) { + start = start || 0; + increment = increment || 1; + for (var i = 0; i < parseInt(total); i++) { + var j = start + i * increment; + input.push(isFunction(cb) ? cb(j) : j); + } + return input; + }; + }); +/** + * @ngdoc filter + * @name removeWith + * @kind function + * + * @description + * get collection and properties object, and removed elements + * with this properties + */ + +angular.module('a8m.remove-with', []) + .filter('removeWith', function () { + return function (collection, object) { + + if (isUndefined(object)) { + return collection; + } + collection = isObject(collection) + ? toArray(collection) + : collection; + + if (!isArray(collection)) { + return collection; + } + + return collection.filter(function (elm) { + return !objectContains(object, elm); + }); + } + }); + + +/** + * @ngdoc filter + * @name remove + * @kind function + * + * @description + * remove specific members from collection + */ + +angular.module('a8m.remove', []) + + .filter('remove', function () { + return function (collection) { + collection = isObject(collection) ? toArray(collection) : collection; + var args = Array.prototype.slice.call(arguments, 1); + + if(!isArray(collection)) { + return collection; + } + + return collection.filter( function( member ) { + return !args.some(function(nest) { + return equals(nest, member); + }) + }); + } + }); + +/** + * @ngdoc filter + * @name reverse + * @kind function + * + * @description + * Reverses a string or collection + */ +angular.module('a8m.reverse', []) + .filter('reverse',[ function () { + return function (input) { + input = isObject(input) ? toArray(input) : input; + + if(isString(input)) { + return input.split('').reverse().join(''); + } + + return isArray(input) + ? input.slice().reverse() + : input; + } + }]); + +/** + * @ngdoc filter + * @name searchField + * @kind function + * + * @description + * for each member, join several strings field and add them to + * new field called 'searchField' (use for search filtering) + */ +angular.module('a8m.search-field', []) + .filter('searchField', ['$parse', function ($parse) { + return function (collection) { + + var get, field; + + collection = isObject(collection) ? toArray(collection) : collection; + + var args = Array.prototype.slice.call(arguments, 1); + + if(!isArray(collection) || !args.length) { + return collection; + } + + return collection.map(function(member) { + + field = args.map(function(field) { + get = $parse(field); + return get(member); + }).join(' '); + + return extend(member, { searchField: field }); + }); + } + }]); + +/** + * @ngdoc filter + * @name toArray + * @kind function + * + * @description + * Convert objects into stable arrays. + * if addKey set to true,the filter also attaches a new property + * $key to the value containing the original key that was used in + * the object we are iterating over to reference the property + */ +angular.module('a8m.to-array', []) + .filter('toArray', function() { + return function (collection, addKey) { + + if(!isObject(collection)) { + return collection; + } + + return !addKey + ? toArray(collection) + : Object.keys(collection).map(function (key) { + return extend(collection[key], { $key: key }); + }); + } + }); + +/** + * @ngdoc filter + * @name unique/uniq + * @kind function + * + * @description + * get collection and filter duplicate members + * if uniqueFilter get a property(nested to) as argument it's + * filter by this property as unique identifier + */ + +angular.module('a8m.unique', []) + .filter({ + unique: ['$parse', uniqFilter], + uniq: ['$parse', uniqFilter] + }); + +function uniqFilter($parse) { + return function (collection, property) { + + collection = isObject(collection) ? toArray(collection) : collection; + + if (!isArray(collection)) { + return collection; + } + + //store all unique identifiers + var uniqueItems = [], + get = $parse(property); + + return (isUndefined(property)) + //if it's kind of primitive array + ? collection.filter(function (elm, pos, self) { + return self.indexOf(elm) === pos; + }) + //else compare with equals + : collection.filter(function (elm) { + var prop = get(elm); + if(some(uniqueItems, prop)) { + return false; + } + uniqueItems.push(prop); + return true; + }); + + //checked if the unique identifier is already exist + function some(array, member) { + if(isUndefined(member)) { + return false; + } + return array.some(function(el) { + return equals(el, member); + }); + } + } +} + +/** + * @ngdoc filter + * @name where + * @kind function + * + * @description + * of each element in a collection to the given properties object, + * returning an array of all elements that have equivalent property values. + * + */ +angular.module('a8m.where', []) + .filter('where', function() { + return function (collection, object) { + if(isUndefined(object)) return collection; + collection = isObject(collection) + ? toArray(collection) + : collection; + + return collection.filter(function (elm) { + return objectContains(object, elm); + }); + } + }); + +/** + * @ngdoc filter + * @name xor + * @kind function + * + * @description + * Exclusive or filter by expression + */ + +angular.module('a8m.xor', []) + + .filter('xor', ['$parse', function($parse) { + return function (col1, col2, expression) { + + expression = expression || false; + + col1 = isObject(col1) ? toArray(col1) : col1; + col2 = isObject(col2) ? toArray(col2) : col2; + + if(!isArray(col1) || !isArray(col2)) return col1; + + return col1.concat(col2) + .filter(function(elm) { + return !(some(elm, col1) && some(elm, col2)); + }); + + function some(el, col) { + var getter = $parse(expression); + return col.some(function(dElm) { + return expression + ? equals(getter(dElm), getter(el)) + : equals(dElm, el); + }); + } + } + }]); + +/** + * @ngdoc filter + * @name formatBytes + * @kind function + * + * @description + * Convert bytes into appropriate display + * 1024 bytes => 1 KB + */ +angular.module('a8m.math.byteFmt', ['a8m.math']) + .filter('byteFmt', ['$math', function ($math) { + return function (bytes, decimal) { + + if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && + isNumber(bytes) && isFinite(bytes)) { + if(bytes < 1024) { // within 1 KB so B + return convertToDecimal(bytes, decimal, $math) + ' B'; + } else if(bytes < 1048576) { // within 1 MB so KB + return convertToDecimal((bytes / 1024), decimal, $math) + ' KB'; + } else if(bytes < 1073741824){ // within 1 GB so MB + return convertToDecimal((bytes / 1048576), decimal, $math) + ' MB'; + } else { // GB or more + return convertToDecimal((bytes / 1073741824), decimal, $math) + ' GB'; + } + + } + return "NaN"; + } + }]); +/** + * @ngdoc filter + * @name degrees + * @kind function + * + * @description + * Convert angle from radians to degrees + */ +angular.module('a8m.math.degrees', ['a8m.math']) + .filter('degrees', ['$math', function ($math) { + return function (radians, decimal) { + // if decimal is not an integer greater than -1, we cannot do. quit with error "NaN" + // if degrees is not a real number, we cannot do also. quit with error "NaN" + if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && + isNumber(radians) && isFinite(radians)) { + var degrees = (radians * 180) / $math.PI; + return $math.round(degrees * $math.pow(10,decimal)) / ($math.pow(10,decimal)); + } else { + return "NaN"; + } + } + }]); + + + +/** + * @ngdoc filter + * @name formatBytes + * @kind function + * + * @description + * Convert bytes into appropriate display + * 1024 kilobytes => 1 MB + */ +angular.module('a8m.math.kbFmt', ['a8m.math']) + .filter('kbFmt', ['$math', function ($math) { + return function (bytes, decimal) { + + if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && + isNumber(bytes) && isFinite(bytes)) { + if(bytes < 1024) { // within 1 MB so KB + return convertToDecimal(bytes, decimal, $math) + ' KB'; + } else if(bytes < 1048576) { // within 1 GB so MB + return convertToDecimal((bytes / 1024), decimal, $math) + ' MB'; + } else { + return convertToDecimal((bytes / 1048576), decimal, $math) + ' GB'; + } + } + return "NaN"; + } + }]); +/** + * @ngdoc module + * @name math + * @description + * reference to global Math object + */ +angular.module('a8m.math', []) + .factory('$math', ['$window', function ($window) { + return $window.Math; + }]); + +/** + * @ngdoc filter + * @name max + * @kind function + * + * @description + * Math.max will get an array and return the max value. if an expression + * is provided, will return max value by expression. + */ +angular.module('a8m.math.max', ['a8m.math']) + .filter('max', ['$math', '$parse', function ($math, $parse) { + return function (input, expression) { + + if(!isArray(input)) { + return input; + } + return isUndefined(expression) + ? $math.max.apply($math, input) + : input[indexByMax(input, expression)]; + }; + + /** + * @private + * @param array + * @param exp + * @returns {number|*|Number} + */ + function indexByMax(array, exp) { + var mappedArray = array.map(function(elm){ + return $parse(exp)(elm); + }); + return mappedArray.indexOf($math.max.apply($math, mappedArray)); + } + }]); +/** + * @ngdoc filter + * @name min + * @kind function + * + * @description + * Math.min will get an array and return the min value. if an expression + * is provided, will return min value by expression. + */ +angular.module('a8m.math.min', ['a8m.math']) + .filter('min', ['$math', '$parse', function ($math, $parse) { + return function (input, expression) { + + if(!isArray(input)) { + return input; + } + return isUndefined(expression) + ? $math.min.apply($math, input) + : input[indexByMin(input, expression)]; + }; + + /** + * @private + * @param array + * @param exp + * @returns {number|*|Number} + */ + function indexByMin(array, exp) { + var mappedArray = array.map(function(elm){ + return $parse(exp)(elm); + }); + return mappedArray.indexOf($math.min.apply($math, mappedArray)); + } + }]); +/** + * @ngdoc filter + * @name Percent + * @kind function + * + * @description + * percentage between two numbers + */ +angular.module('a8m.math.percent', ['a8m.math']) + .filter('percent', ['$math', '$window', function ($math, $window) { + return function (input, divided, round) { + + var divider = isString(input) ? $window.Number(input) : input; + divided = divided || 100; + round = round || false; + + if (!isNumber(divider) || $window.isNaN(divider)) return input; + + return round + ? $math.round((divider / divided) * 100) + : (divider / divided) * 100; + } + }]); + +/** + * @ngdoc filter + * @name toRadians + * @kind function + * + * @description + * Convert angle from degrees to radians + */ +angular.module('a8m.math.radians', ['a8m.math']) + .filter('radians', ['$math', function ($math) { + return function (degrees, decimal) { + // if decimal is not an integer greater than -1, we cannot do. quit with error "NaN" + // if degrees is not a real number, we cannot do also. quit with error "NaN" + if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && + isNumber(degrees) && isFinite(degrees)) { + var radians = (degrees * 3.14159265359) / 180; + return $math.round(radians * $math.pow(10,decimal)) / ($math.pow(10,decimal)); + } + return "NaN"; + } + }]); + + + +/** + * @ngdoc filter + * @name Radix + * @kind function + * + * @description + * converting decimal numbers to different bases(radix) + */ +angular.module('a8m.math.radix', []) + .filter('radix', function () { + return function (input, radix) { + var RANGE = /^[2-9]$|^[1-2]\d$|^3[0-6]$/; + + if(!isNumber(input) || !RANGE.test(radix)) { + return input; + } + + return input.toString(radix).toUpperCase(); + } + }); + +/** + * @ngdoc filter + * @name formatBytes + * @kind function + * + * @description + * Convert number into abbreviations. + * i.e: K for one thousand, M for Million, B for billion + * e.g: number of users:235,221, decimal:1 => 235.2 K + */ +angular.module('a8m.math.shortFmt', ['a8m.math']) + .filter('shortFmt', ['$math', function ($math) { + return function (number, decimal) { + if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && + isNumber(number) && isFinite(number)){ + if(number < 1e3) { + return number; + } else if(number < 1e6) { + return convertToDecimal((number / 1e3), decimal, $math) + ' K'; + } else if(number < 1e9){ + return convertToDecimal((number / 1e6), decimal, $math) + ' M'; + } else { + return convertToDecimal((number / 1e9), decimal, $math) + ' B'; + } + + } + return "NaN"; + } + }]); +/** + * @ngdoc filter + * @name sum + * @kind function + * + * @description + * Sum up all values within an array + */ +angular.module('a8m.math.sum', []) + .filter('sum', function () { + return function (input, initial) { + return !isArray(input) + ? input + : input.reduce(function(prev, curr) { + return prev + curr; + }, initial || 0); + } + }); + +/** + * @ngdoc filter + * @name endsWith + * @kind function + * + * @description + * checks whether string ends with the ends parameter. + */ +angular.module('a8m.ends-with', []) + + .filter('endsWith', function () { + return function (input, ends, csensitive) { + + var sensitive = csensitive || false, + position; + + if(!isString(input) || isUndefined(ends)) { + return input; + } + + input = (sensitive) ? input : input.toLowerCase(); + position = input.length - ends.length; + + return input.indexOf((sensitive) ? ends : ends.toLowerCase(), position) !== -1; + } + }); + /** * @ngdoc filter * @name latinize @@ -1748,27 +1752,27 @@ angular.module('a8m.latinize', []) : input; } }]); - -/** - * @ngdoc filter - * @name ltrim - * @kind function - * - * @description - * Left trim. Similar to trimFilter, but only for left side. - */ -angular.module('a8m.ltrim', []) - .filter('ltrim', function () { - return function(input, chars) { - - var trim = chars || '\\s'; - - return isString(input) - ? input.replace(new RegExp('^' + trim + '+'), '') - : input; - } - }); - + +/** + * @ngdoc filter + * @name ltrim + * @kind function + * + * @description + * Left trim. Similar to trimFilter, but only for left side. + */ +angular.module('a8m.ltrim', []) + .filter('ltrim', function () { + return function(input, chars) { + + var trim = chars || '\\s'; + + return isString(input) + ? input.replace(new RegExp('^' + trim + '+'), '') + : input; + } + }); + /** * @ngdoc filter * @name match @@ -1788,145 +1792,145 @@ angular.module('a8m.match', []) : null; } }); - -/** - * @ngdoc filter - * @name repeat - * @kind function - * - * @description - * Repeats a string n times - */ -angular.module('a8m.repeat', []) - .filter('repeat',[ function () { - return function (input, n, separator) { - - var times = ~~n; - - if(!isString(input)) { - return input; - } - - return !times - ? input - : strRepeat(input, --n, separator || ''); - } - }]); - -/** - * Repeats a string n times with given separator - * @param str string to repeat - * @param n number of times - * @param sep separator - * @returns {*} - */ -function strRepeat(str, n, sep) { - if(!n) { - return str; - } - return str + sep + strRepeat(str, --n, sep); -} -/** -* @ngdoc filter -* @name rtrim -* @kind function -* -* @description -* Right trim. Similar to trimFilter, but only for right side. -*/ -angular.module('a8m.rtrim', []) - .filter('rtrim', function () { - return function(input, chars) { - - var trim = chars || '\\s'; - - return isString(input) - ? input.replace(new RegExp(trim + '+$'), '') - : input; - } - }); - -/** - * @ngdoc filter - * @name slugify - * @kind function - * - * @description - * remove spaces from string, replace with "-" or given argument - */ -angular.module('a8m.slugify', []) - .filter('slugify',[ function () { - return function (input, sub) { - - var replace = (isUndefined(sub)) ? '-' : sub; - - return isString(input) - ? input.toLowerCase().replace(/\s+/g, replace) - : input; - } - }]); - -/** - * @ngdoc filter - * @name startWith - * @kind function - * - * @description - * checks whether string starts with the starts parameter. - */ -angular.module('a8m.starts-with', []) - .filter('startsWith', function () { - return function (input, start, csensitive) { - - var sensitive = csensitive || false; - - if(!isString(input) || isUndefined(start)) { - return input; - } - - input = (sensitive) ? input : input.toLowerCase(); - - return !input.indexOf((sensitive) ? start : start.toLowerCase()); - } - }); - -/** - * @ngdoc filter - * @name stringular - * @kind function - * - * @description - * get string with {n} and replace match with enumeration values - */ -angular.module('a8m.stringular', []) - .filter('stringular', function () { - return function(input) { - - var args = Array.prototype.slice.call(arguments, 1); - - return input.replace(/{(\d+)}/g, function (match, number) { - return isUndefined(args[number]) ? match : args[number]; - }); - } - }); - -/** - * @ngdoc filter - * @name stripTags - * @kind function - * - * @description - * strip html tags from string - */ -angular.module('a8m.strip-tags', []) - .filter('stripTags', function () { - return function(input) { - return isString(input) - ? input.replace(/<\S[^><]*>/g, '') - : input; - } - }); - + +/** + * @ngdoc filter + * @name repeat + * @kind function + * + * @description + * Repeats a string n times + */ +angular.module('a8m.repeat', []) + .filter('repeat',[ function () { + return function (input, n, separator) { + + var times = ~~n; + + if(!isString(input)) { + return input; + } + + return !times + ? input + : strRepeat(input, --n, separator || ''); + } + }]); + +/** + * Repeats a string n times with given separator + * @param str string to repeat + * @param n number of times + * @param sep separator + * @returns {*} + */ +function strRepeat(str, n, sep) { + if(!n) { + return str; + } + return str + sep + strRepeat(str, --n, sep); +} +/** +* @ngdoc filter +* @name rtrim +* @kind function +* +* @description +* Right trim. Similar to trimFilter, but only for right side. +*/ +angular.module('a8m.rtrim', []) + .filter('rtrim', function () { + return function(input, chars) { + + var trim = chars || '\\s'; + + return isString(input) + ? input.replace(new RegExp(trim + '+$'), '') + : input; + } + }); + +/** + * @ngdoc filter + * @name slugify + * @kind function + * + * @description + * remove spaces from string, replace with "-" or given argument + */ +angular.module('a8m.slugify', []) + .filter('slugify',[ function () { + return function (input, sub) { + + var replace = (isUndefined(sub)) ? '-' : sub; + + return isString(input) + ? input.toLowerCase().replace(/\s+/g, replace) + : input; + } + }]); + +/** + * @ngdoc filter + * @name startWith + * @kind function + * + * @description + * checks whether string starts with the starts parameter. + */ +angular.module('a8m.starts-with', []) + .filter('startsWith', function () { + return function (input, start, csensitive) { + + var sensitive = csensitive || false; + + if(!isString(input) || isUndefined(start)) { + return input; + } + + input = (sensitive) ? input : input.toLowerCase(); + + return !input.indexOf((sensitive) ? start : start.toLowerCase()); + } + }); + +/** + * @ngdoc filter + * @name stringular + * @kind function + * + * @description + * get string with {n} and replace match with enumeration values + */ +angular.module('a8m.stringular', []) + .filter('stringular', function () { + return function(input) { + + var args = Array.prototype.slice.call(arguments, 1); + + return input.replace(/{(\d+)}/g, function (match, number) { + return isUndefined(args[number]) ? match : args[number]; + }); + } + }); + +/** + * @ngdoc filter + * @name stripTags + * @kind function + * + * @description + * strip html tags from string + */ +angular.module('a8m.strip-tags', []) + .filter('stripTags', function () { + return function(input) { + return isString(input) + ? input.replace(/<\S[^><]*>/g, '') + : input; + } + }); + /** * @ngdoc filter * @name test @@ -1946,348 +1950,348 @@ angular.module('a8m.test', []) : input; } }); - -/** - * @ngdoc filter - * @name trim - * @kind function - * - * @description - * Strip whitespace (or other characters) from the beginning and end of a string - */ -angular.module('a8m.trim', []) - .filter('trim', function () { - return function(input, chars) { - - var trim = chars || '\\s'; - - return isString(input) - ? input.replace(new RegExp('^' + trim + '+|' + trim + '+$', 'g'), '') - : input; - } - }); - -/** - * @ngdoc filter - * @name truncate - * @kind function - * - * @description - * truncates a string given a specified length, providing a custom string to denote an omission. - */ -angular.module('a8m.truncate', []) - .filter('truncate', function () { - return function(input, length, suffix, preserve) { - - length = isUndefined(length) ? input.length : length; - preserve = preserve || false; - suffix = suffix || ''; - - if(!isString(input) || (input.length <= length)) return input; - - return input.substring(0, (preserve) - ? ((input.indexOf(' ', length) === -1) ? input.length : input.indexOf(' ', length)) - : length) + suffix; - }; - }); - -/** - * @ngdoc filter - * @name ucfirst - * @kind function - * - * @description - * ucfirst - */ -angular.module('a8m.ucfirst', []) - .filter('ucfirst', [function() { - return function(input) { - return isString(input) - ? input - .split(' ') - .map(function (ch) { - return ch.charAt(0).toUpperCase() + ch.substring(1); - }) - .join(' ') - : input; - } - }]); - -/** - * @ngdoc filter - * @name uriComponentEncode - * @kind function - * - * @description - * get string as parameter and return encoded string - */ -angular.module('a8m.uri-component-encode', []) - .filter('uriComponentEncode',['$window', function ($window) { - return function (input) { - return isString(input) - ? $window.encodeURIComponent(input) - : input; - } - }]); - -/** - * @ngdoc filter - * @name uriEncode - * @kind function - * - * @description - * get string as parameter and return encoded string - */ -angular.module('a8m.uri-encode', []) - .filter('uriEncode',['$window', function ($window) { - return function (input) { - return isString(input) - ? $window.encodeURI(input) - : input; - } - }]); - -/** - * @ngdoc filter - * @name wrap - * @kind function - * - * @description - * Wrap a string with another string - */ -angular.module('a8m.wrap', []) - .filter('wrap', function () { - return function(input, wrap, ends) { - return isString(input) && isDefined(wrap) - ? [wrap, input, ends || wrap].join('') - : input; - } - }); - -/** - * @ngdoc provider - * @name filterWatcher - * @kind function - * - * @description - * store specific filters result in $$cache, based on scope life time(avoid memory leak). - * on scope.$destroy remove it's cache from $$cache container - */ - -angular.module('a8m.filter-watcher', []) - .provider('filterWatcher', function() { - - this.$get = ['$window', '$rootScope', function($window, $rootScope) { - - /** - * Cache storing - * @type {Object} - */ - var $$cache = {}; - - /** - * Scope listeners container - * scope.$destroy => remove all cache keys - * bind to current scope. - * @type {Object} - */ - var $$listeners = {}; - - /** - * $timeout without triggering the digest cycle - * @type {function} - */ - var $$timeout = $window.setTimeout; - - /** - * @description - * get `HashKey` string based on the given arguments. - * @param fName - * @param args - * @returns {string} - */ - function getHashKey(fName, args) { - function replacerFactory() { - var cache = []; - return function(key, val) { - if(isObject(val) && !isNull(val)) { - if (~cache.indexOf(val)) return '[Circular]'; - cache.push(val) - } - if($window == val) return '$WINDOW'; - if($window.document == val) return '$DOCUMENT'; - if(isScope(val)) return '$SCOPE'; - return val; - } - } - return [fName, JSON.stringify(args, replacerFactory())] - .join('#') - .replace(/"/g,''); - } - - /** - * @description - * fir on $scope.$destroy, - * remove cache based scope from `$$cache`, - * and remove itself from `$$listeners` - * @param event - */ - function removeCache(event) { - var id = event.targetScope.$id; - forEach($$listeners[id], function(key) { - delete $$cache[key]; - }); - delete $$listeners[id]; - } - - /** - * @description - * for angular version that greater than v.1.3.0 - * it clear cache when the digest cycle is end. - */ - function cleanStateless() { - $$timeout(function() { - if(!$rootScope.$$phase) - $$cache = {}; - }, 2000); - } - - /** - * @description - * Store hashKeys in $$listeners container - * on scope.$destroy, remove them all(bind an event). - * @param scope - * @param hashKey - * @returns {*} - */ - function addListener(scope, hashKey) { - var id = scope.$id; - if(isUndefined($$listeners[id])) { - scope.$on('$destroy', removeCache); - $$listeners[id] = []; - } - return $$listeners[id].push(hashKey); - } - - /** - * @description - * return the `cacheKey` or undefined. - * @param filterName - * @param args - * @returns {*} - */ - function $$isMemoized(filterName, args) { - var hashKey = getHashKey(filterName, args); - return $$cache[hashKey]; - } - - /** - * @description - * store `result` in `$$cache` container, based on the hashKey. - * add $destroy listener and return result - * @param filterName - * @param args - * @param scope - * @param result - * @returns {*} - */ - function $$memoize(filterName, args, scope, result) { - var hashKey = getHashKey(filterName, args); - //store result in `$$cache` container - $$cache[hashKey] = result; - // for angular versions that less than 1.3 - // add to `$destroy` listener, a cleaner callback - if(isScope(scope)) { - addListener(scope, hashKey); - } else { - cleanStateless(); - } - return result; - } - - return { - isMemoized: $$isMemoized, - memoize: $$memoize - } - }]; - }); - - -/** - * @ngdoc module - * @name angular.filters - * @description - * Bunch of useful filters for angularJS - */ - -angular.module('angular.filter', [ - - 'a8m.ucfirst', - 'a8m.uri-encode', - 'a8m.uri-component-encode', - 'a8m.slugify', - 'a8m.latinize', - 'a8m.strip-tags', - 'a8m.stringular', - 'a8m.truncate', - 'a8m.starts-with', - 'a8m.ends-with', - 'a8m.wrap', - 'a8m.trim', - 'a8m.ltrim', - 'a8m.rtrim', - 'a8m.repeat', - 'a8m.test', - 'a8m.match', - - 'a8m.to-array', - 'a8m.concat', - 'a8m.contains', - 'a8m.unique', - 'a8m.is-empty', - 'a8m.after', - 'a8m.after-where', - 'a8m.before', - 'a8m.before-where', - 'a8m.defaults', - 'a8m.where', - 'a8m.reverse', - 'a8m.remove', - 'a8m.remove-with', - 'a8m.group-by', - 'a8m.count-by', - 'a8m.chunk-by', - 'a8m.search-field', - 'a8m.fuzzy-by', - 'a8m.fuzzy', - 'a8m.omit', - 'a8m.pick', - 'a8m.every', - 'a8m.filter-by', - 'a8m.xor', - 'a8m.map', - 'a8m.first', - 'a8m.last', - 'a8m.flatten', - 'a8m.join', - 'a8m.range', - - 'a8m.math', - 'a8m.math.max', - 'a8m.math.min', - 'a8m.math.percent', - 'a8m.math.radix', - 'a8m.math.sum', - 'a8m.math.degrees', - 'a8m.math.radians', - 'a8m.math.byteFmt', - 'a8m.math.kbFmt', - 'a8m.math.shortFmt', - - 'a8m.angular', - 'a8m.conditions', - 'a8m.is-null', - - 'a8m.filter-watcher' -]); + +/** + * @ngdoc filter + * @name trim + * @kind function + * + * @description + * Strip whitespace (or other characters) from the beginning and end of a string + */ +angular.module('a8m.trim', []) + .filter('trim', function () { + return function(input, chars) { + + var trim = chars || '\\s'; + + return isString(input) + ? input.replace(new RegExp('^' + trim + '+|' + trim + '+$', 'g'), '') + : input; + } + }); + +/** + * @ngdoc filter + * @name truncate + * @kind function + * + * @description + * truncates a string given a specified length, providing a custom string to denote an omission. + */ +angular.module('a8m.truncate', []) + .filter('truncate', function () { + return function(input, length, suffix, preserve) { + + length = isUndefined(length) ? input.length : length; + preserve = preserve || false; + suffix = suffix || ''; + + if(!isString(input) || (input.length <= length)) return input; + + return input.substring(0, (preserve) + ? ((input.indexOf(' ', length) === -1) ? input.length : input.indexOf(' ', length)) + : length) + suffix; + }; + }); + +/** + * @ngdoc filter + * @name ucfirst + * @kind function + * + * @description + * ucfirst + */ +angular.module('a8m.ucfirst', []) + .filter('ucfirst', [function() { + return function(input) { + return isString(input) + ? input + .split(' ') + .map(function (ch) { + return ch.charAt(0).toUpperCase() + ch.substring(1); + }) + .join(' ') + : input; + } + }]); + +/** + * @ngdoc filter + * @name uriComponentEncode + * @kind function + * + * @description + * get string as parameter and return encoded string + */ +angular.module('a8m.uri-component-encode', []) + .filter('uriComponentEncode',['$window', function ($window) { + return function (input) { + return isString(input) + ? $window.encodeURIComponent(input) + : input; + } + }]); + +/** + * @ngdoc filter + * @name uriEncode + * @kind function + * + * @description + * get string as parameter and return encoded string + */ +angular.module('a8m.uri-encode', []) + .filter('uriEncode',['$window', function ($window) { + return function (input) { + return isString(input) + ? $window.encodeURI(input) + : input; + } + }]); + +/** + * @ngdoc filter + * @name wrap + * @kind function + * + * @description + * Wrap a string with another string + */ +angular.module('a8m.wrap', []) + .filter('wrap', function () { + return function(input, wrap, ends) { + return isString(input) && isDefined(wrap) + ? [wrap, input, ends || wrap].join('') + : input; + } + }); + +/** + * @ngdoc provider + * @name filterWatcher + * @kind function + * + * @description + * store specific filters result in $$cache, based on scope life time(avoid memory leak). + * on scope.$destroy remove it's cache from $$cache container + */ + +angular.module('a8m.filter-watcher', []) + .provider('filterWatcher', function() { + + this.$get = ['$window', '$rootScope', function($window, $rootScope) { + + /** + * Cache storing + * @type {Object} + */ + var $$cache = {}; + + /** + * Scope listeners container + * scope.$destroy => remove all cache keys + * bind to current scope. + * @type {Object} + */ + var $$listeners = {}; + + /** + * $timeout without triggering the digest cycle + * @type {function} + */ + var $$timeout = $window.setTimeout; + + /** + * @description + * get `HashKey` string based on the given arguments. + * @param fName + * @param args + * @returns {string} + */ + function getHashKey(fName, args) { + function replacerFactory() { + var cache = []; + return function(key, val) { + if(isObject(val) && !isNull(val)) { + if (~cache.indexOf(val)) return '[Circular]'; + cache.push(val) + } + if($window == val) return '$WINDOW'; + if($window.document == val) return '$DOCUMENT'; + if(isScope(val)) return '$SCOPE'; + return val; + } + } + return [fName, JSON.stringify(args, replacerFactory())] + .join('#') + .replace(/"/g,''); + } + + /** + * @description + * fir on $scope.$destroy, + * remove cache based scope from `$$cache`, + * and remove itself from `$$listeners` + * @param event + */ + function removeCache(event) { + var id = event.targetScope.$id; + forEach($$listeners[id], function(key) { + delete $$cache[key]; + }); + delete $$listeners[id]; + } + + /** + * @description + * for angular version that greater than v.1.3.0 + * it clear cache when the digest cycle is end. + */ + function cleanStateless() { + $$timeout(function() { + if(!$rootScope.$$phase) + $$cache = {}; + }, 2000); + } + + /** + * @description + * Store hashKeys in $$listeners container + * on scope.$destroy, remove them all(bind an event). + * @param scope + * @param hashKey + * @returns {*} + */ + function addListener(scope, hashKey) { + var id = scope.$id; + if(isUndefined($$listeners[id])) { + scope.$on('$destroy', removeCache); + $$listeners[id] = []; + } + return $$listeners[id].push(hashKey); + } + + /** + * @description + * return the `cacheKey` or undefined. + * @param filterName + * @param args + * @returns {*} + */ + function $$isMemoized(filterName, args) { + var hashKey = getHashKey(filterName, args); + return $$cache[hashKey]; + } + + /** + * @description + * store `result` in `$$cache` container, based on the hashKey. + * add $destroy listener and return result + * @param filterName + * @param args + * @param scope + * @param result + * @returns {*} + */ + function $$memoize(filterName, args, scope, result) { + var hashKey = getHashKey(filterName, args); + //store result in `$$cache` container + $$cache[hashKey] = result; + // for angular versions that less than 1.3 + // add to `$destroy` listener, a cleaner callback + if(isScope(scope)) { + addListener(scope, hashKey); + } else { + cleanStateless(); + } + return result; + } + + return { + isMemoized: $$isMemoized, + memoize: $$memoize + } + }]; + }); + + +/** + * @ngdoc module + * @name angular.filters + * @description + * Bunch of useful filters for angularJS + */ + +angular.module('angular.filter', [ + + 'a8m.ucfirst', + 'a8m.uri-encode', + 'a8m.uri-component-encode', + 'a8m.slugify', + 'a8m.latinize', + 'a8m.strip-tags', + 'a8m.stringular', + 'a8m.truncate', + 'a8m.starts-with', + 'a8m.ends-with', + 'a8m.wrap', + 'a8m.trim', + 'a8m.ltrim', + 'a8m.rtrim', + 'a8m.repeat', + 'a8m.test', + 'a8m.match', + + 'a8m.to-array', + 'a8m.concat', + 'a8m.contains', + 'a8m.unique', + 'a8m.is-empty', + 'a8m.after', + 'a8m.after-where', + 'a8m.before', + 'a8m.before-where', + 'a8m.defaults', + 'a8m.where', + 'a8m.reverse', + 'a8m.remove', + 'a8m.remove-with', + 'a8m.group-by', + 'a8m.count-by', + 'a8m.chunk-by', + 'a8m.search-field', + 'a8m.fuzzy-by', + 'a8m.fuzzy', + 'a8m.omit', + 'a8m.pick', + 'a8m.every', + 'a8m.filter-by', + 'a8m.xor', + 'a8m.map', + 'a8m.first', + 'a8m.last', + 'a8m.flatten', + 'a8m.join', + 'a8m.range', + + 'a8m.math', + 'a8m.math.max', + 'a8m.math.min', + 'a8m.math.percent', + 'a8m.math.radix', + 'a8m.math.sum', + 'a8m.math.degrees', + 'a8m.math.radians', + 'a8m.math.byteFmt', + 'a8m.math.kbFmt', + 'a8m.math.shortFmt', + + 'a8m.angular', + 'a8m.conditions', + 'a8m.is-null', + + 'a8m.filter-watcher' +]); })( window, window.angular ); \ No newline at end of file diff --git a/dist/angular-filter.min.js b/dist/angular-filter.min.js index 6ba20d7..ba69aac 100644 --- a/dist/angular-filter.min.js +++ b/dist/angular-filter.min.js @@ -1,6 +1,6 @@ /** * Bunch of useful filters for angularJS(with no external dependencies!) - * @version v0.5.9 - 2016-07-15 * @link https://github.com/a8m/angular-filter + * @version v0.5.9 - 2016-07-29 * @link https://github.com/a8m/angular-filter * @author Ariel Mashraki * @license MIT License, http://www.opensource.org/licenses/MIT - */!function(a,b,c){"use strict";function d(a){return D(a)?a:Object.keys(a).map(function(b){return a[b]})}function e(a){return null===a}function f(a,b){var d=Object.keys(a);return d.map(function(d){return b[d]!==c&&b[d]==a[d]}).indexOf(!1)==-1}function g(a,b){if(""===b)return a;var c=a.indexOf(b.charAt(0));return c!==-1&&g(a.substr(c+1),b.substr(1))}function h(a,b,c){var d=0;return a.filter(function(a){var e=x(c)?db}}function m(){return function(a,b){return a>=b}}function n(){return function(a,b){return a=a.length?a:D(a[b])?u(a.slice(0,b).concat(a[b],a.slice(b+1)),b):u(a,b+1)}function v(a){return function(b,c){function e(a,b){return!y(b)&&a.some(function(a){return H(a,b)})}if(b=C(b)?d(b):b,!D(b))return b;var f=[],g=a(c);return y(c)?b.filter(function(a,b,c){return c.indexOf(a)===b}):b.filter(function(a){var b=g(a);return!e(f,b)&&(f.push(b),!0)})}}function w(a,b,c){return b?a+c+w(a,--b,c):a}var x=b.isDefined,y=b.isUndefined,z=b.isFunction,A=b.isString,B=b.isNumber,C=b.isObject,D=b.isArray,E=b.forEach,F=b.extend,G=b.copy,H=b.equals;String.prototype.contains||(String.prototype.contains=function(){return String.prototype.indexOf.apply(this,arguments)!==-1}),b.module("a8m.angular",[]).filter("isUndefined",function(){return function(a){return b.isUndefined(a)}}).filter("isDefined",function(){return function(a){return b.isDefined(a)}}).filter("isFunction",function(){return function(a){return b.isFunction(a)}}).filter("isString",function(){return function(a){return b.isString(a)}}).filter("isNumber",function(){return function(a){return b.isNumber(a)}}).filter("isArray",function(){return function(a){return b.isArray(a)}}).filter("isObject",function(){return function(a){return b.isObject(a)}}).filter("isEqual",function(){return function(a,c){return b.equals(a,c)}}),b.module("a8m.conditions",[]).filter({isGreaterThan:l,">":l,isGreaterThanOrEqualTo:m,">=":m,isLessThan:n,"<":n,isLessThanOrEqualTo:o,"<=":o,isEqualTo:p,"==":p,isNotEqualTo:q,"!=":q,isIdenticalTo:r,"===":r,isNotIdenticalTo:s,"!==":s}),b.module("a8m.is-null",[]).filter("isNull",function(){return function(a){return e(a)}}),b.module("a8m.after-where",[]).filter("afterWhere",function(){return function(a,b){if(a=C(a)?d(a):a,!D(a)||y(b))return a;var c=a.map(function(a){return f(b,a)}).indexOf(!0);return a.slice(c===-1?0:c)}}),b.module("a8m.after",[]).filter("after",function(){return function(a,b){return a=C(a)?d(a):a,D(a)?a.slice(b):a}}),b.module("a8m.before-where",[]).filter("beforeWhere",function(){return function(a,b){if(a=C(a)?d(a):a,!D(a)||y(b))return a;var c=a.map(function(a){return f(b,a)}).indexOf(!0);return a.slice(0,c===-1?a.length:++c)}}),b.module("a8m.before",[]).filter("before",function(){return function(a,b){return a=C(a)?d(a):a,D(a)?a.slice(0,b?--b:b):a}}),b.module("a8m.chunk-by",["a8m.filter-watcher"]).filter("chunkBy",["filterWatcher",function(a){return function(b,c,d){function e(a,b){for(var c=[];a--;)c[a]=b;return c}function f(a,b,c){return D(a)?a.map(function(a,d,f){return d*=b,a=f.slice(d,d+b),!y(c)&&a.length=0&&B(b)&&isFinite(b)?b<1024?i(b,c,a)+" B":b<1048576?i(b/1024,c,a)+" KB":b<1073741824?i(b/1048576,c,a)+" MB":i(b/1073741824,c,a)+" GB":"NaN"}}]),b.module("a8m.math.degrees",["a8m.math"]).filter("degrees",["$math",function(a){return function(b,c){if(B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)){var d=180*b/a.PI;return a.round(d*a.pow(10,c))/a.pow(10,c)}return"NaN"}}]),b.module("a8m.math.kbFmt",["a8m.math"]).filter("kbFmt",["$math",function(a){return function(b,c){return B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)?b<1024?i(b,c,a)+" KB":b<1048576?i(b/1024,c,a)+" MB":i(b/1048576,c,a)+" GB":"NaN"}}]),b.module("a8m.math",[]).factory("$math",["$window",function(a){return a.Math}]),b.module("a8m.math.max",["a8m.math"]).filter("max",["$math","$parse",function(a,b){function c(c,d){var e=c.map(function(a){return b(d)(a)});return e.indexOf(a.max.apply(a,e))}return function(b,d){return D(b)?y(d)?a.max.apply(a,b):b[c(b,d)]:b}}]),b.module("a8m.math.min",["a8m.math"]).filter("min",["$math","$parse",function(a,b){function c(c,d){var e=c.map(function(a){return b(d)(a)});return e.indexOf(a.min.apply(a,e))}return function(b,d){return D(b)?y(d)?a.min.apply(a,b):b[c(b,d)]:b}}]),b.module("a8m.math.percent",["a8m.math"]).filter("percent",["$math","$window",function(a,b){return function(c,d,e){var f=A(c)?b.Number(c):c;return d=d||100,e=e||!1,!B(f)||b.isNaN(f)?c:e?a.round(f/d*100):f/d*100}}]),b.module("a8m.math.radians",["a8m.math"]).filter("radians",["$math",function(a){return function(b,c){if(B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)){var d=3.14159265359*b/180;return a.round(d*a.pow(10,c))/a.pow(10,c)}return"NaN"}}]),b.module("a8m.math.radix",[]).filter("radix",function(){return function(a,b){var c=/^[2-9]$|^[1-2]\d$|^3[0-6]$/;return B(a)&&c.test(b)?a.toString(b).toUpperCase():a}}),b.module("a8m.math.shortFmt",["a8m.math"]).filter("shortFmt",["$math",function(a){return function(b,c){return B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)?b<1e3?b:b<1e6?i(b/1e3,c,a)+" K":b<1e9?i(b/1e6,c,a)+" M":i(b/1e9,c,a)+" B":"NaN"}}]),b.module("a8m.math.sum",[]).filter("sum",function(){return function(a,b){return D(a)?a.reduce(function(a,b){return a+b},b||0):a}}),b.module("a8m.ends-with",[]).filter("endsWith",function(){return function(a,b,c){var d,e=c||!1;return!A(a)||y(b)?a:(a=e?a:a.toLowerCase(),d=a.length-b.length,a.indexOf(e?b:b.toLowerCase(),d)!==-1)}}),b.module("a8m.latinize",[]).filter("latinize",[function(){function a(a){return a.replace(/[^\u0000-\u007E]/g,function(a){return c[a]||a})}for(var b=[{base:"A",letters:"AⒶAÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ"},{base:"AA",letters:"Ꜳ"},{base:"AE",letters:"ÆǼǢ"},{base:"AO",letters:"Ꜵ"},{base:"AU",letters:"Ꜷ"},{base:"AV",letters:"ꜸꜺ"},{base:"AY",letters:"Ꜽ"},{base:"B",letters:"BⒷBḂḄḆɃƂƁ"},{base:"C",letters:"CⒸCĆĈĊČÇḈƇȻꜾ"},{base:"D",letters:"DⒹDḊĎḌḐḒḎĐƋƊƉꝹ"},{base:"DZ",letters:"DZDŽ"},{base:"Dz",letters:"DzDž"},{base:"E",letters:"EⒺEÈÉÊỀẾỄỂẼĒḔḖĔĖËẺĚȄȆẸỆȨḜĘḘḚƐƎ"},{base:"F",letters:"FⒻFḞƑꝻ"},{base:"G",letters:"GⒼGǴĜḠĞĠǦĢǤƓꞠꝽꝾ"},{base:"H",letters:"HⒽHĤḢḦȞḤḨḪĦⱧⱵꞍ"},{base:"I",letters:"IⒾIÌÍÎĨĪĬİÏḮỈǏȈȊỊĮḬƗ"},{base:"J",letters:"JⒿJĴɈ"},{base:"K",letters:"KⓀKḰǨḲĶḴƘⱩꝀꝂꝄꞢ"},{base:"L",letters:"LⓁLĿĹĽḶḸĻḼḺŁȽⱢⱠꝈꝆꞀ"},{base:"LJ",letters:"LJ"},{base:"Lj",letters:"Lj"},{base:"M",letters:"MⓂMḾṀṂⱮƜ"},{base:"N",letters:"NⓃNǸŃÑṄŇṆŅṊṈȠƝꞐꞤ"},{base:"NJ",letters:"NJ"},{base:"Nj",letters:"Nj"},{base:"O",letters:"OⓄOÒÓÔỒỐỖỔÕṌȬṎŌṐṒŎȮȰÖȪỎŐǑȌȎƠỜỚỠỞỢỌỘǪǬØǾƆƟꝊꝌ"},{base:"OI",letters:"Ƣ"},{base:"OO",letters:"Ꝏ"},{base:"OU",letters:"Ȣ"},{base:"OE",letters:"ŒŒ"},{base:"oe",letters:"œœ"},{base:"P",letters:"PⓅPṔṖƤⱣꝐꝒꝔ"},{base:"Q",letters:"QⓆQꝖꝘɊ"},{base:"R",letters:"RⓇRŔṘŘȐȒṚṜŖṞɌⱤꝚꞦꞂ"},{base:"S",letters:"SⓈSẞŚṤŜṠŠṦṢṨȘŞⱾꞨꞄ"},{base:"T",letters:"TⓉTṪŤṬȚŢṰṮŦƬƮȾꞆ"},{base:"TZ",letters:"Ꜩ"},{base:"U",letters:"UⓊUÙÚÛŨṸŪṺŬÜǛǗǕǙỦŮŰǓȔȖƯỪỨỮỬỰỤṲŲṶṴɄ"},{base:"V",letters:"VⓋVṼṾƲꝞɅ"},{base:"VY",letters:"Ꝡ"},{base:"W",letters:"WⓌWẀẂŴẆẄẈⱲ"},{base:"X",letters:"XⓍXẊẌ"},{base:"Y",letters:"YⓎYỲÝŶỸȲẎŸỶỴƳɎỾ"},{base:"Z",letters:"ZⓏZŹẐŻŽẒẔƵȤⱿⱫꝢ"},{base:"a",letters:"aⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐ"},{base:"aa",letters:"ꜳ"},{base:"ae",letters:"æǽǣ"},{base:"ao",letters:"ꜵ"},{base:"au",letters:"ꜷ"},{base:"av",letters:"ꜹꜻ"},{base:"ay",letters:"ꜽ"},{base:"b",letters:"bⓑbḃḅḇƀƃɓ"},{base:"c",letters:"cⓒcćĉċčçḉƈȼꜿↄ"},{base:"d",letters:"dⓓdḋďḍḑḓḏđƌɖɗꝺ"},{base:"dz",letters:"dzdž"},{base:"e",letters:"eⓔeèéêềếễểẽēḕḗĕėëẻěȅȇẹệȩḝęḙḛɇɛǝ"},{base:"f",letters:"fⓕfḟƒꝼ"},{base:"g",letters:"gⓖgǵĝḡğġǧģǥɠꞡᵹꝿ"},{base:"h",letters:"hⓗhĥḣḧȟḥḩḫẖħⱨⱶɥ"},{base:"hv",letters:"ƕ"},{base:"i",letters:"iⓘiìíîĩīĭïḯỉǐȉȋịįḭɨı"},{base:"j",letters:"jⓙjĵǰɉ"},{base:"k",letters:"kⓚkḱǩḳķḵƙⱪꝁꝃꝅꞣ"},{base:"l",letters:"lⓛlŀĺľḷḹļḽḻſłƚɫⱡꝉꞁꝇ"},{base:"lj",letters:"lj"},{base:"m",letters:"mⓜmḿṁṃɱɯ"},{base:"n",letters:"nⓝnǹńñṅňṇņṋṉƞɲʼnꞑꞥ"},{base:"nj",letters:"nj"},{base:"o",letters:"oⓞoòóôồốỗổõṍȭṏōṑṓŏȯȱöȫỏőǒȍȏơờớỡởợọộǫǭøǿɔꝋꝍɵ"},{base:"oi",letters:"ƣ"},{base:"ou",letters:"ȣ"},{base:"oo",letters:"ꝏ"},{base:"p",letters:"pⓟpṕṗƥᵽꝑꝓꝕ"},{base:"q",letters:"qⓠqɋꝗꝙ"},{base:"r",letters:"rⓡrŕṙřȑȓṛṝŗṟɍɽꝛꞧꞃ"},{base:"s",letters:"sⓢsßśṥŝṡšṧṣṩșşȿꞩꞅẛ"},{base:"t",letters:"tⓣtṫẗťṭțţṱṯŧƭʈⱦꞇ"},{base:"tz",letters:"ꜩ"},{base:"u",letters:"uⓤuùúûũṹūṻŭüǜǘǖǚủůűǔȕȗưừứữửựụṳųṷṵʉ"},{base:"v",letters:"vⓥvṽṿʋꝟʌ"},{base:"vy",letters:"ꝡ"},{base:"w",letters:"wⓦwẁẃŵẇẅẘẉⱳ"},{base:"x",letters:"xⓧxẋẍ"},{base:"y",letters:"yⓨyỳýŷỹȳẏÿỷẙỵƴɏỿ"},{base:"z",letters:"zⓩzźẑżžẓẕƶȥɀⱬꝣ"}],c={},d=0;d<]*>/g,""):a}}),b.module("a8m.test",[]).filter("test",function(){return function(a,b,c){var d=new RegExp(b,c);return A(a)?d.test(a):a}}),b.module("a8m.trim",[]).filter("trim",function(){return function(a,b){var c=b||"\\s";return A(a)?a.replace(new RegExp("^"+c+"+|"+c+"+$","g"),""):a}}),b.module("a8m.truncate",[]).filter("truncate",function(){return function(a,b,c,d){return b=y(b)?a.length:b,d=d||!1,c=c||"",!A(a)||a.length<=b?a:a.substring(0,d?a.indexOf(" ",b)===-1?a.length:a.indexOf(" ",b):b)+c}}),b.module("a8m.ucfirst",[]).filter("ucfirst",[function(){return function(a){return A(a)?a.split(" ").map(function(a){return a.charAt(0).toUpperCase()+a.substring(1)}).join(" "):a}}]),b.module("a8m.uri-component-encode",[]).filter("uriComponentEncode",["$window",function(a){return function(b){return A(b)?a.encodeURIComponent(b):b}}]),b.module("a8m.uri-encode",[]).filter("uriEncode",["$window",function(a){return function(b){return A(b)?a.encodeURI(b):b}}]),b.module("a8m.wrap",[]).filter("wrap",function(){return function(a,b,c){return A(a)&&x(b)?[b,a,c||b].join(""):a}}),b.module("a8m.filter-watcher",[]).provider("filterWatcher",function(){this.$get=["$window","$rootScope",function(a,b){function c(b,c){function d(){var b=[];return function(c,d){if(C(d)&&!e(d)){if(~b.indexOf(d))return"[Circular]";b.push(d)}return a==d?"$WINDOW":a.document==d?"$DOCUMENT":k(d)?"$SCOPE":d}}return[b,JSON.stringify(c,d())].join("#").replace(/"/g,"")}function d(a){var b=a.targetScope.$id;E(l[b],function(a){delete j[a]}),delete l[b]}function f(){m(function(){b.$$phase||(j={})},2e3)}function g(a,b){var c=a.$id;return y(l[c])&&(a.$on("$destroy",d),l[c]=[]),l[c].push(b)}function h(a,b){var d=c(a,b);return j[d]}function i(a,b,d,e){var h=c(a,b);return j[h]=e,k(d)?g(d,h):f(),e}var j={},l={},m=a.setTimeout;return{isMemoized:h,memoize:i}}]}),b.module("angular.filter",["a8m.ucfirst","a8m.uri-encode","a8m.uri-component-encode","a8m.slugify","a8m.latinize","a8m.strip-tags","a8m.stringular","a8m.truncate","a8m.starts-with","a8m.ends-with","a8m.wrap","a8m.trim","a8m.ltrim","a8m.rtrim","a8m.repeat","a8m.test","a8m.match","a8m.to-array","a8m.concat","a8m.contains","a8m.unique","a8m.is-empty","a8m.after","a8m.after-where","a8m.before","a8m.before-where","a8m.defaults","a8m.where","a8m.reverse","a8m.remove","a8m.remove-with","a8m.group-by","a8m.count-by","a8m.chunk-by","a8m.search-field","a8m.fuzzy-by","a8m.fuzzy","a8m.omit","a8m.pick","a8m.every","a8m.filter-by","a8m.xor","a8m.map","a8m.first","a8m.last","a8m.flatten","a8m.join","a8m.range","a8m.math","a8m.math.max","a8m.math.min","a8m.math.percent","a8m.math.radix","a8m.math.sum","a8m.math.degrees","a8m.math.radians","a8m.math.byteFmt","a8m.math.kbFmt","a8m.math.shortFmt","a8m.angular","a8m.conditions","a8m.is-null","a8m.filter-watcher"])}(window,window.angular); \ No newline at end of file + */!function(a,b,c){"use strict";function d(a){return D(a)?a:Object.keys(a).map(function(b){return a[b]})}function e(a){return null===a}function f(a,b){var d=Object.keys(a);return d.map(function(d){return b[d]!==c&&b[d]==a[d]}).indexOf(!1)==-1}function g(a,b){if(""===b)return a;var c=a.indexOf(b.charAt(0));return c!==-1&&g(a.substr(c+1),b.substr(1))}function h(a,b,c){var d=0;return a.filter(function(a){var e=x(c)?db}}function m(){return function(a,b){return a>=b}}function n(){return function(a,b){return a=a.length?a:D(a[b])?u(a.slice(0,b).concat(a[b],a.slice(b+1)),b):u(a,b+1)}function v(a){return function(b,c){function e(a,b){return!y(b)&&a.some(function(a){return H(a,b)})}if(b=C(b)?d(b):b,!D(b))return b;var f=[],g=a(c);return y(c)?b.filter(function(a,b,c){return c.indexOf(a)===b}):b.filter(function(a){var b=g(a);return!e(f,b)&&(f.push(b),!0)})}}function w(a,b,c){return b?a+c+w(a,--b,c):a}var x=b.isDefined,y=b.isUndefined,z=b.isFunction,A=b.isString,B=b.isNumber,C=b.isObject,D=b.isArray,E=b.forEach,F=b.extend,G=b.copy,H=b.equals;String.prototype.contains||(String.prototype.contains=function(){return String.prototype.indexOf.apply(this,arguments)!==-1}),b.module("a8m.angular",[]).filter("isUndefined",function(){return function(a){return b.isUndefined(a)}}).filter("isDefined",function(){return function(a){return b.isDefined(a)}}).filter("isFunction",function(){return function(a){return b.isFunction(a)}}).filter("isString",function(){return function(a){return b.isString(a)}}).filter("isNumber",function(){return function(a){return b.isNumber(a)}}).filter("isArray",function(){return function(a){return b.isArray(a)}}).filter("isObject",function(){return function(a){return b.isObject(a)}}).filter("isEqual",function(){return function(a,c){return b.equals(a,c)}}),b.module("a8m.conditions",[]).filter({isGreaterThan:l,">":l,isGreaterThanOrEqualTo:m,">=":m,isLessThan:n,"<":n,isLessThanOrEqualTo:o,"<=":o,isEqualTo:p,"==":p,isNotEqualTo:q,"!=":q,isIdenticalTo:r,"===":r,isNotIdenticalTo:s,"!==":s}),b.module("a8m.is-null",[]).filter("isNull",function(){return function(a){return e(a)}}),b.module("a8m.after-where",[]).filter("afterWhere",function(){return function(a,b){if(a=C(a)?d(a):a,!D(a)||y(b))return a;var c=a.map(function(a){return f(b,a)}).indexOf(!0);return a.slice(c===-1?0:c)}}),b.module("a8m.after",[]).filter("after",function(){return function(a,b){return a=C(a)?d(a):a,D(a)?a.slice(b):a}}),b.module("a8m.before-where",[]).filter("beforeWhere",function(){return function(a,b){if(a=C(a)?d(a):a,!D(a)||y(b))return a;var c=a.map(function(a){return f(b,a)}).indexOf(!0);return a.slice(0,c===-1?a.length:++c)}}),b.module("a8m.before",[]).filter("before",function(){return function(a,b){return a=C(a)?d(a):a,D(a)?a.slice(0,b?--b:b):a}}),b.module("a8m.chunk-by",["a8m.filter-watcher"]).filter("chunkBy",["filterWatcher",function(a){return function(b,c,d){function e(a,b){for(var c=[];a--;)c[a]=b;return c}function f(a,b,c){return D(a)?a.map(function(a,d,f){return d*=b,a=f.slice(d,d+b),!y(c)&&a.length=0&&B(b)&&isFinite(b)?b<1024?i(b,c,a)+" B":b<1048576?i(b/1024,c,a)+" KB":b<1073741824?i(b/1048576,c,a)+" MB":i(b/1073741824,c,a)+" GB":"NaN"}}]),b.module("a8m.math.degrees",["a8m.math"]).filter("degrees",["$math",function(a){return function(b,c){if(B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)){var d=180*b/a.PI;return a.round(d*a.pow(10,c))/a.pow(10,c)}return"NaN"}}]),b.module("a8m.math.kbFmt",["a8m.math"]).filter("kbFmt",["$math",function(a){return function(b,c){return B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)?b<1024?i(b,c,a)+" KB":b<1048576?i(b/1024,c,a)+" MB":i(b/1048576,c,a)+" GB":"NaN"}}]),b.module("a8m.math",[]).factory("$math",["$window",function(a){return a.Math}]),b.module("a8m.math.max",["a8m.math"]).filter("max",["$math","$parse",function(a,b){function c(c,d){var e=c.map(function(a){return b(d)(a)});return e.indexOf(a.max.apply(a,e))}return function(b,d){return D(b)?y(d)?a.max.apply(a,b):b[c(b,d)]:b}}]),b.module("a8m.math.min",["a8m.math"]).filter("min",["$math","$parse",function(a,b){function c(c,d){var e=c.map(function(a){return b(d)(a)});return e.indexOf(a.min.apply(a,e))}return function(b,d){return D(b)?y(d)?a.min.apply(a,b):b[c(b,d)]:b}}]),b.module("a8m.math.percent",["a8m.math"]).filter("percent",["$math","$window",function(a,b){return function(c,d,e){var f=A(c)?b.Number(c):c;return d=d||100,e=e||!1,!B(f)||b.isNaN(f)?c:e?a.round(f/d*100):f/d*100}}]),b.module("a8m.math.radians",["a8m.math"]).filter("radians",["$math",function(a){return function(b,c){if(B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)){var d=3.14159265359*b/180;return a.round(d*a.pow(10,c))/a.pow(10,c)}return"NaN"}}]),b.module("a8m.math.radix",[]).filter("radix",function(){return function(a,b){var c=/^[2-9]$|^[1-2]\d$|^3[0-6]$/;return B(a)&&c.test(b)?a.toString(b).toUpperCase():a}}),b.module("a8m.math.shortFmt",["a8m.math"]).filter("shortFmt",["$math",function(a){return function(b,c){return B(c)&&isFinite(c)&&c%1===0&&c>=0&&B(b)&&isFinite(b)?b<1e3?b:b<1e6?i(b/1e3,c,a)+" K":b<1e9?i(b/1e6,c,a)+" M":i(b/1e9,c,a)+" B":"NaN"}}]),b.module("a8m.math.sum",[]).filter("sum",function(){return function(a,b){return D(a)?a.reduce(function(a,b){return a+b},b||0):a}}),b.module("a8m.ends-with",[]).filter("endsWith",function(){return function(a,b,c){var d,e=c||!1;return!A(a)||y(b)?a:(a=e?a:a.toLowerCase(),d=a.length-b.length,a.indexOf(e?b:b.toLowerCase(),d)!==-1)}}),b.module("a8m.latinize",[]).filter("latinize",[function(){function a(a){return a.replace(/[^\u0000-\u007E]/g,function(a){return c[a]||a})}for(var b=[{base:"A",letters:"AⒶAÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ"},{base:"AA",letters:"Ꜳ"},{base:"AE",letters:"ÆǼǢ"},{base:"AO",letters:"Ꜵ"},{base:"AU",letters:"Ꜷ"},{base:"AV",letters:"ꜸꜺ"},{base:"AY",letters:"Ꜽ"},{base:"B",letters:"BⒷBḂḄḆɃƂƁ"},{base:"C",letters:"CⒸCĆĈĊČÇḈƇȻꜾ"},{base:"D",letters:"DⒹDḊĎḌḐḒḎĐƋƊƉꝹ"},{base:"DZ",letters:"DZDŽ"},{base:"Dz",letters:"DzDž"},{base:"E",letters:"EⒺEÈÉÊỀẾỄỂẼĒḔḖĔĖËẺĚȄȆẸỆȨḜĘḘḚƐƎ"},{base:"F",letters:"FⒻFḞƑꝻ"},{base:"G",letters:"GⒼGǴĜḠĞĠǦĢǤƓꞠꝽꝾ"},{base:"H",letters:"HⒽHĤḢḦȞḤḨḪĦⱧⱵꞍ"},{base:"I",letters:"IⒾIÌÍÎĨĪĬİÏḮỈǏȈȊỊĮḬƗ"},{base:"J",letters:"JⒿJĴɈ"},{base:"K",letters:"KⓀKḰǨḲĶḴƘⱩꝀꝂꝄꞢ"},{base:"L",letters:"LⓁLĿĹĽḶḸĻḼḺŁȽⱢⱠꝈꝆꞀ"},{base:"LJ",letters:"LJ"},{base:"Lj",letters:"Lj"},{base:"M",letters:"MⓂMḾṀṂⱮƜ"},{base:"N",letters:"NⓃNǸŃÑṄŇṆŅṊṈȠƝꞐꞤ"},{base:"NJ",letters:"NJ"},{base:"Nj",letters:"Nj"},{base:"O",letters:"OⓄOÒÓÔỒỐỖỔÕṌȬṎŌṐṒŎȮȰÖȪỎŐǑȌȎƠỜỚỠỞỢỌỘǪǬØǾƆƟꝊꝌ"},{base:"OI",letters:"Ƣ"},{base:"OO",letters:"Ꝏ"},{base:"OU",letters:"Ȣ"},{base:"OE",letters:"ŒŒ"},{base:"oe",letters:"œœ"},{base:"P",letters:"PⓅPṔṖƤⱣꝐꝒꝔ"},{base:"Q",letters:"QⓆQꝖꝘɊ"},{base:"R",letters:"RⓇRŔṘŘȐȒṚṜŖṞɌⱤꝚꞦꞂ"},{base:"S",letters:"SⓈSẞŚṤŜṠŠṦṢṨȘŞⱾꞨꞄ"},{base:"T",letters:"TⓉTṪŤṬȚŢṰṮŦƬƮȾꞆ"},{base:"TZ",letters:"Ꜩ"},{base:"U",letters:"UⓊUÙÚÛŨṸŪṺŬÜǛǗǕǙỦŮŰǓȔȖƯỪỨỮỬỰỤṲŲṶṴɄ"},{base:"V",letters:"VⓋVṼṾƲꝞɅ"},{base:"VY",letters:"Ꝡ"},{base:"W",letters:"WⓌWẀẂŴẆẄẈⱲ"},{base:"X",letters:"XⓍXẊẌ"},{base:"Y",letters:"YⓎYỲÝŶỸȲẎŸỶỴƳɎỾ"},{base:"Z",letters:"ZⓏZŹẐŻŽẒẔƵȤⱿⱫꝢ"},{base:"a",letters:"aⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐ"},{base:"aa",letters:"ꜳ"},{base:"ae",letters:"æǽǣ"},{base:"ao",letters:"ꜵ"},{base:"au",letters:"ꜷ"},{base:"av",letters:"ꜹꜻ"},{base:"ay",letters:"ꜽ"},{base:"b",letters:"bⓑbḃḅḇƀƃɓ"},{base:"c",letters:"cⓒcćĉċčçḉƈȼꜿↄ"},{base:"d",letters:"dⓓdḋďḍḑḓḏđƌɖɗꝺ"},{base:"dz",letters:"dzdž"},{base:"e",letters:"eⓔeèéêềếễểẽēḕḗĕėëẻěȅȇẹệȩḝęḙḛɇɛǝ"},{base:"f",letters:"fⓕfḟƒꝼ"},{base:"g",letters:"gⓖgǵĝḡğġǧģǥɠꞡᵹꝿ"},{base:"h",letters:"hⓗhĥḣḧȟḥḩḫẖħⱨⱶɥ"},{base:"hv",letters:"ƕ"},{base:"i",letters:"iⓘiìíîĩīĭïḯỉǐȉȋịįḭɨı"},{base:"j",letters:"jⓙjĵǰɉ"},{base:"k",letters:"kⓚkḱǩḳķḵƙⱪꝁꝃꝅꞣ"},{base:"l",letters:"lⓛlŀĺľḷḹļḽḻſłƚɫⱡꝉꞁꝇ"},{base:"lj",letters:"lj"},{base:"m",letters:"mⓜmḿṁṃɱɯ"},{base:"n",letters:"nⓝnǹńñṅňṇņṋṉƞɲʼnꞑꞥ"},{base:"nj",letters:"nj"},{base:"o",letters:"oⓞoòóôồốỗổõṍȭṏōṑṓŏȯȱöȫỏőǒȍȏơờớỡởợọộǫǭøǿɔꝋꝍɵ"},{base:"oi",letters:"ƣ"},{base:"ou",letters:"ȣ"},{base:"oo",letters:"ꝏ"},{base:"p",letters:"pⓟpṕṗƥᵽꝑꝓꝕ"},{base:"q",letters:"qⓠqɋꝗꝙ"},{base:"r",letters:"rⓡrŕṙřȑȓṛṝŗṟɍɽꝛꞧꞃ"},{base:"s",letters:"sⓢsßśṥŝṡšṧṣṩșşȿꞩꞅẛ"},{base:"t",letters:"tⓣtṫẗťṭțţṱṯŧƭʈⱦꞇ"},{base:"tz",letters:"ꜩ"},{base:"u",letters:"uⓤuùúûũṹūṻŭüǜǘǖǚủůűǔȕȗưừứữửựụṳųṷṵʉ"},{base:"v",letters:"vⓥvṽṿʋꝟʌ"},{base:"vy",letters:"ꝡ"},{base:"w",letters:"wⓦwẁẃŵẇẅẘẉⱳ"},{base:"x",letters:"xⓧxẋẍ"},{base:"y",letters:"yⓨyỳýŷỹȳẏÿỷẙỵƴɏỿ"},{base:"z",letters:"zⓩzźẑżžẓẕƶȥɀⱬꝣ"}],c={},d=0;d<]*>/g,""):a}}),b.module("a8m.test",[]).filter("test",function(){return function(a,b,c){var d=new RegExp(b,c);return A(a)?d.test(a):a}}),b.module("a8m.trim",[]).filter("trim",function(){return function(a,b){var c=b||"\\s";return A(a)?a.replace(new RegExp("^"+c+"+|"+c+"+$","g"),""):a}}),b.module("a8m.truncate",[]).filter("truncate",function(){return function(a,b,c,d){return b=y(b)?a.length:b,d=d||!1,c=c||"",!A(a)||a.length<=b?a:a.substring(0,d?a.indexOf(" ",b)===-1?a.length:a.indexOf(" ",b):b)+c}}),b.module("a8m.ucfirst",[]).filter("ucfirst",[function(){return function(a){return A(a)?a.split(" ").map(function(a){return a.charAt(0).toUpperCase()+a.substring(1)}).join(" "):a}}]),b.module("a8m.uri-component-encode",[]).filter("uriComponentEncode",["$window",function(a){return function(b){return A(b)?a.encodeURIComponent(b):b}}]),b.module("a8m.uri-encode",[]).filter("uriEncode",["$window",function(a){return function(b){return A(b)?a.encodeURI(b):b}}]),b.module("a8m.wrap",[]).filter("wrap",function(){return function(a,b,c){return A(a)&&x(b)?[b,a,c||b].join(""):a}}),b.module("a8m.filter-watcher",[]).provider("filterWatcher",function(){this.$get=["$window","$rootScope",function(a,b){function c(b,c){function d(){var b=[];return function(c,d){if(C(d)&&!e(d)){if(~b.indexOf(d))return"[Circular]";b.push(d)}return a==d?"$WINDOW":a.document==d?"$DOCUMENT":k(d)?"$SCOPE":d}}return[b,JSON.stringify(c,d())].join("#").replace(/"/g,"")}function d(a){var b=a.targetScope.$id;E(l[b],function(a){delete j[a]}),delete l[b]}function f(){m(function(){b.$$phase||(j={})},2e3)}function g(a,b){var c=a.$id;return y(l[c])&&(a.$on("$destroy",d),l[c]=[]),l[c].push(b)}function h(a,b){var d=c(a,b);return j[d]}function i(a,b,d,e){var h=c(a,b);return j[h]=e,k(d)?g(d,h):f(),e}var j={},l={},m=a.setTimeout;return{isMemoized:h,memoize:i}}]}),b.module("angular.filter",["a8m.ucfirst","a8m.uri-encode","a8m.uri-component-encode","a8m.slugify","a8m.latinize","a8m.strip-tags","a8m.stringular","a8m.truncate","a8m.starts-with","a8m.ends-with","a8m.wrap","a8m.trim","a8m.ltrim","a8m.rtrim","a8m.repeat","a8m.test","a8m.match","a8m.to-array","a8m.concat","a8m.contains","a8m.unique","a8m.is-empty","a8m.after","a8m.after-where","a8m.before","a8m.before-where","a8m.defaults","a8m.where","a8m.reverse","a8m.remove","a8m.remove-with","a8m.group-by","a8m.count-by","a8m.chunk-by","a8m.search-field","a8m.fuzzy-by","a8m.fuzzy","a8m.omit","a8m.pick","a8m.every","a8m.filter-by","a8m.xor","a8m.map","a8m.first","a8m.last","a8m.flatten","a8m.join","a8m.range","a8m.math","a8m.math.max","a8m.math.min","a8m.math.percent","a8m.math.radix","a8m.math.sum","a8m.math.degrees","a8m.math.radians","a8m.math.byteFmt","a8m.math.kbFmt","a8m.math.shortFmt","a8m.angular","a8m.conditions","a8m.is-null","a8m.filter-watcher"])}(window,window.angular); \ No newline at end of file diff --git a/dist/angular-filter.zip b/dist/angular-filter.zip index c74b8c7cdebf100ddca7767863a3435de5a08f22..26aa43b5afc558de9040bd785496ac9ae24310ae 100644 GIT binary patch delta 15162 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 delta 17426 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 Rp8J&k#fM|U?M(?d^nX&!oWKA8