From 58c0e4ec3679c8994f381102ebe41c0a19ee61f9 Mon Sep 17 00:00:00 2001 From: blackheartadhar Date: Mon, 21 Dec 2015 14:12:51 +0600 Subject: [PATCH] Adde 'random' filter which can get random value from a collection --- dist/angular-filter.js | 4276 +++++++++++++------------ dist/angular-filter.min.js | 4 +- dist/angular-filter.zip | Bin 81020 -> 83684 bytes src/_filter/collection/random.js | 18 + test/spec/filter/collection/random.js | 44 + 5 files changed, 2213 insertions(+), 2129 deletions(-) create mode 100644 src/_filter/collection/random.js create mode 100644 test/spec/filter/collection/random.js diff --git a/dist/angular-filter.js b/dist/angular-filter.js index f50d9f9..0dfc47b 100644 --- a/dist/angular-filter.js +++ b/dist/angular-filter.js @@ -1,1626 +1,1648 @@ -/** - * Bunch of useful filters for angularJS(with no external dependencies!) - * @version v0.5.7 - 2015-10-04 * @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 (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) { - 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(new RegExp('\\s', 'g'), '').split('+'); - comparator = propList.reduce(function(prev, cur, index) { - return (index === 1) ? $parse(prev)(elm) + ' ' + $parse(cur)(elm) : - prev + ' ' + $parse(cur)(elm); - }); - } - - return (isString(comparator) || isNumber(comparator)) - ? String(comparator).toLowerCase().contains(search) - : false; - }); - }); - } - }]); - -/** - * @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) { - for (var i = 0; i < parseInt(total); i++) { - input.push(i); - } - 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.8 - 2015-12-21 * @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 (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 random + * @kind function + * + * @description + * Return a random value from collection + */ +angular.module('a8m.random', []) + .filter('random',[ function () { + return function (input) { + input = isObject(input) ? toArray(input) : input; + + return isArray(input) + ? input[Math.floor(Math.random() * input.length)] + : input; + } + }]); + +/** + * @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) { + for (var i = 0; i < parseInt(total); i++) { + input.push(i); + } + 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; + } + }); + /** * @ngdoc filter * @name latinize @@ -1742,27 +1764,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 @@ -1782,145 +1804,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 @@ -1940,348 +1962,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 11714a5..46f04ed 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.7 - 2015-10-04 * @link https://github.com/a8m/angular-filter + * @version v0.5.8 - 2015-12-21 * @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-1==d.map(function(d){return b[d]!==c&&b[d]==a[d]}).indexOf(!1)}function g(a,b){if(""===b)return a;var c=a.indexOf(b.charAt(0));return-1===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)?b>d&&c(a):b>d;return d=e?d+1:d,e})}function i(a,b,c){return c.round(a*c.pow(10,b))/c.pow(10,b)}function j(a,b,c){b=b||[];var d=Object.keys(a);return d.forEach(function(d){if(C(a[d])&&!D(a[d])){var e=c?c+"."+d:c;j(a[d],b,e||d)}else{var f=c?c+"."+d:d;b.push(f)}}),b}function k(a){return a&&a.$evalAsync&&a.$watch}function l(){return function(a,b){return a>b}}function m(){return function(a,b){return a>=b}}function n(){return function(a,b){return b>a}}function o(){return function(a,b){return b>=a}}function p(){return function(a,b){return a==b}}function q(){return function(a,b){return a!=b}}function r(){return function(a,b){return a===b}}function s(){return function(a,b){return a!==b}}function t(a){return function(b,c){return b=C(b)?d(b):b,!D(b)||y(c)?!1:b.some(function(b){return C(b)||z(c)?a(c)(b):b===c})}}function u(a,b){return b=b||0,b>=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)?!1: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)?!1:(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-1!==String.prototype.indexOf.apply(this,arguments)}),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(-1===c?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,-1===c?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)?1024>b?i(b,c,a)+" B":1048576>b?i(b/1024,c,a)+" KB":1073741824>b?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)?1024>b?i(b,c,a)+" KB":1048576>b?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)?1e3>b?b:1e6>b?i(b/1e3,c,a)+" K":1e9>b?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,-1!==a.indexOf(e?b:b.toLowerCase(),d))}}),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?-1===a.indexOf(" ",b)?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-1==d.map(function(d){return b[d]!==c&&b[d]==a[d]}).indexOf(!1)}function g(a,b){if(""===b)return a;var c=a.indexOf(b.charAt(0));return-1===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)?b>d&&c(a):b>d;return d=e?d+1:d,e})}function i(a,b,c){return c.round(a*c.pow(10,b))/c.pow(10,b)}function j(a,b,c){b=b||[];var d=Object.keys(a);return d.forEach(function(d){if(C(a[d])&&!D(a[d])){var e=c?c+"."+d:c;j(a[d],b,e||d)}else{var f=c?c+"."+d:d;b.push(f)}}),b}function k(a){return a&&a.$evalAsync&&a.$watch}function l(){return function(a,b){return a>b}}function m(){return function(a,b){return a>=b}}function n(){return function(a,b){return b>a}}function o(){return function(a,b){return b>=a}}function p(){return function(a,b){return a==b}}function q(){return function(a,b){return a!=b}}function r(){return function(a,b){return a===b}}function s(){return function(a,b){return a!==b}}function t(a){return function(b,c){return b=C(b)?d(b):b,!D(b)||y(c)?!1:b.some(function(b){return C(b)||z(c)?a(c)(b):b===c})}}function u(a,b){return b=b||0,b>=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)?!1: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)?!1:(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-1!==String.prototype.indexOf.apply(this,arguments)}),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(-1===c?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,-1===c?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)?1024>b?i(b,c,a)+" B":1048576>b?i(b/1024,c,a)+" KB":1073741824>b?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)?1024>b?i(b,c,a)+" KB":1048576>b?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)?1e3>b?b:1e6>b?i(b/1e3,c,a)+" K":1e9>b?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,-1!==a.indexOf(e?b:b.toLowerCase(),d))}}),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?-1===a.indexOf(" ",b)?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 7f18b4d1c7273c8e7034837e721274b3deb912f8..34b1ab951d874418c54d8d230a39ed989d643ce1 100644 GIT binary patch delta 16212 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 diff --git a/src/_filter/collection/random.js b/src/_filter/collection/random.js new file mode 100644 index 0000000..ab1a485 --- /dev/null +++ b/src/_filter/collection/random.js @@ -0,0 +1,18 @@ +/** + * @ngdoc filter + * @name random + * @kind function + * + * @description + * Return a random value from collection + */ +angular.module('a8m.random', []) + .filter('random',[ function () { + return function (input) { + input = isObject(input) ? toArray(input) : input; + + return isArray(input) + ? input[Math.floor(Math.random() * input.length)] + : input; + } + }]); diff --git a/test/spec/filter/collection/random.js b/test/spec/filter/collection/random.js new file mode 100644 index 0000000..cf88b20 --- /dev/null +++ b/test/spec/filter/collection/random.js @@ -0,0 +1,44 @@ +'use strict'; + +describe('randomFilter', function() { + var filter; + + beforeEach(module('a8m.random')); + + beforeEach(inject(function ($filter) { + filter = $filter('random'); + })); + + it('should get array as collection and return a random value from the collection', function() { + + var numbArray = [10, 1, 2, 4, 9, 7, 5]; + var stringArray = ['foo', 'bar', 'baz']; + var mixedArray = [1, 'foo', 9, 'bar', 4, 'baz']; + + expect(numbArray).toContain(filter(numbArray)); + expect(stringArray).toContain(filter(stringArray)); + expect(mixedArray).toContain(filter(mixedArray)); + + }); + + it('should get object as collection and return a random value from the collection', function() { + + var object = { + 0: { id: 1 }, + 1: { id: 2 }, + 2: { id: 3 } + }; + + expect(toArray(object)).toContain(filter(object)); + + }); + + it('should get !collection and return it as-is', function() { + + expect(filter(999)).toEqual(999); + expect(filter('hello world!')).toEqual('hello world!'); + expect(filter(!1)).toBeFalsy(); + expect(null).toEqual(null); + + }); +});