/** * Bunch of useful filters for angularJS(with no external dependencies!) * @version v0.5.5 - 2015-08-07 * @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', []) .filter('chunkBy', [function () { /** * @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; } return function (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; } var getterFn = $parse(property); return filterWatcher.isMemoized('groupBy', arguments) || filterWatcher.memoize('groupBy', arguments, this, _groupBy(collection, getterFn)); /** * 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; } }); /** * @ngdoc filter * @name latinize * @kind function * * @description * remove accents/diacritics from a string */ angular.module('a8m.latinize', []) .filter('latinize',[ function () { var defaultDiacriticsRemovalap = [ {'base':'A', 'letters':'\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F'}, {'base':'AA','letters':'\uA732'}, {'base':'AE','letters':'\u00C6\u01FC\u01E2'}, {'base':'AO','letters':'\uA734'}, {'base':'AU','letters':'\uA736'}, {'base':'AV','letters':'\uA738\uA73A'}, {'base':'AY','letters':'\uA73C'}, {'base':'B', 'letters':'\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181'}, {'base':'C', 'letters':'\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E'}, {'base':'D', 'letters':'\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779'}, {'base':'DZ','letters':'\u01F1\u01C4'}, {'base':'Dz','letters':'\u01F2\u01C5'}, {'base':'E', 'letters':'\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E'}, {'base':'F', 'letters':'\u0046\u24BB\uFF26\u1E1E\u0191\uA77B'}, {'base':'G', 'letters':'\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E'}, {'base':'H', 'letters':'\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D'}, {'base':'I', 'letters':'\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197'}, {'base':'J', 'letters':'\u004A\u24BF\uFF2A\u0134\u0248'}, {'base':'K', 'letters':'\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2'}, {'base':'L', 'letters':'\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780'}, {'base':'LJ','letters':'\u01C7'}, {'base':'Lj','letters':'\u01C8'}, {'base':'M', 'letters':'\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C'}, {'base':'N', 'letters':'\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4'}, {'base':'NJ','letters':'\u01CA'}, {'base':'Nj','letters':'\u01CB'}, {'base':'O', 'letters':'\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C'}, {'base':'OI','letters':'\u01A2'}, {'base':'OO','letters':'\uA74E'}, {'base':'OU','letters':'\u0222'}, {'base':'OE','letters':'\u008C\u0152'}, {'base':'oe','letters':'\u009C\u0153'}, {'base':'P', 'letters':'\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754'}, {'base':'Q', 'letters':'\u0051\u24C6\uFF31\uA756\uA758\u024A'}, {'base':'R', 'letters':'\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782'}, {'base':'S', 'letters':'\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784'}, {'base':'T', 'letters':'\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786'}, {'base':'TZ','letters':'\uA728'}, {'base':'U', 'letters':'\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244'}, {'base':'V', 'letters':'\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245'}, {'base':'VY','letters':'\uA760'}, {'base':'W', 'letters':'\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72'}, {'base':'X', 'letters':'\u0058\u24CD\uFF38\u1E8A\u1E8C'}, {'base':'Y', 'letters':'\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE'}, {'base':'Z', 'letters':'\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762'}, {'base':'a', 'letters':'\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250'}, {'base':'aa','letters':'\uA733'}, {'base':'ae','letters':'\u00E6\u01FD\u01E3'}, {'base':'ao','letters':'\uA735'}, {'base':'au','letters':'\uA737'}, {'base':'av','letters':'\uA739\uA73B'}, {'base':'ay','letters':'\uA73D'}, {'base':'b', 'letters':'\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253'}, {'base':'c', 'letters':'\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184'}, {'base':'d', 'letters':'\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A'}, {'base':'dz','letters':'\u01F3\u01C6'}, {'base':'e', 'letters':'\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD'}, {'base':'f', 'letters':'\u0066\u24D5\uFF46\u1E1F\u0192\uA77C'}, {'base':'g', 'letters':'\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F'}, {'base':'h', 'letters':'\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265'}, {'base':'hv','letters':'\u0195'}, {'base':'i', 'letters':'\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131'}, {'base':'j', 'letters':'\u006A\u24D9\uFF4A\u0135\u01F0\u0249'}, {'base':'k', 'letters':'\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3'}, {'base':'l', 'letters':'\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747'}, {'base':'lj','letters':'\u01C9'}, {'base':'m', 'letters':'\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F'}, {'base':'n', 'letters':'\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5'}, {'base':'nj','letters':'\u01CC'}, {'base':'o', 'letters':'\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275'}, {'base':'oi','letters':'\u01A3'}, {'base':'ou','letters':'\u0223'}, {'base':'oo','letters':'\uA74F'}, {'base':'p','letters':'\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755'}, {'base':'q','letters':'\u0071\u24E0\uFF51\u024B\uA757\uA759'}, {'base':'r','letters':'\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783'}, {'base':'s','letters':'\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B'}, {'base':'t','letters':'\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787'}, {'base':'tz','letters':'\uA729'}, {'base':'u','letters': '\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289'}, {'base':'v','letters':'\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C'}, {'base':'vy','letters':'\uA761'}, {'base':'w','letters':'\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73'}, {'base':'x','letters':'\u0078\u24E7\uFF58\u1E8B\u1E8D'}, {'base':'y','letters':'\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF'}, {'base':'z','letters':'\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763'} ]; var diacriticsMap = {}; for (var i = 0; i < defaultDiacriticsRemovalap.length; i++) { var letters = defaultDiacriticsRemovalap[i].letters.split(""); for (var j = 0; j < letters.length ; j++){ diacriticsMap[letters[j]] = defaultDiacriticsRemovalap[i].base; } } // "what?" version ... http://jsperf.com/diacritics/12 function removeDiacritics (str) { return str.replace(/[^\u0000-\u007E]/g, function(a){ return diacriticsMap[a] || a; }); } return function (input) { return isString(input) ? removeDiacritics(input) : 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 * @kind function * * @description * Return the matched pattern in a string. */ angular.module('a8m.match', []) .filter('match', function () { return function (input, pattern, flag) { var reg = new RegExp(pattern, flag); return isString(input) ? input.match(reg) : 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 test * @kind function * * @description * test if a string match a pattern. */ angular.module('a8m.test', []) .filter('test', function () { return function (input, pattern, flag) { var reg = new RegExp(pattern, flag); return isString(input) ? reg.test(input) : 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) { return [fName, angular.toJson(args)] .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 = {}; }); } /** * @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 );