merlin/merlin/static/merlin/js/lib/angular-filter.js

2186 lines
47 KiB
JavaScript

/**
* Bunch of useful filters for angularJS(with no external dependencies!)
* @version v0.5.1 - 2014-11-12 * @link https://github.com/a8m/angular-filter
* @author Ariel Mashraki <ariel@mashraki.co.il>
* @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] || (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.templates
* @kind function
*
* @description
* reference to templates 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 concat
* @kind function
*
* @description
* get (array/object, object/array) and return merged collection
*
*/
angular.module('a8m.concat', [])
//TODO:unique option ? or use unique filter to filter result
.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 true;
}
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 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 omit
* @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 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';
}
} else {
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';
}
} else {
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));
} else {
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';
}
}else{
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 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';
if(!isString(input)) {
return input;
}
return input.replace(new RegExp('^' + trim + '+'), '');
}
});
/**
* @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';
if(!isString(input)) {
return input;
}
return input.replace(new RegExp(trim + '+$'), '');
}
});
/**
* @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;
if(isString(input)) {
return input.toLowerCase()
.replace(/\s+/g, replace);
}
return 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) {
if(isString(input)) {
return input.replace(/<\S[^><]*>/g, '');
}
return 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';
if(!isString(input)) {
return input;
}
return input.replace(new RegExp('^' + trim + '+|' + trim + '+$', 'g'), '');
}
});
/**
* @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 angular.isString(input) ? input.split(' ')
.map(function (char) {
return char.charAt(0).toUpperCase() + char.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) {
if(isString(input)) {
return $window.encodeURIComponent(input);
}
return 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) {
if(isString(input)) {
return $window.encodeURI(input);
}
return 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) {
if(!isString(input) || isUndefined(wrap)) {
return input;
}
return [wrap, input, ends || wrap].join('');
}
});
/**
* @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, JSON.stringify(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 templates version that greater than v.1.3.0
* if clear cache when the digest cycle 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 templates 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 templates.filters
* @description
* Bunch of useful filters for angularJS
*/
angular.module('angular.filter', [
'a8m.ucfirst',
'a8m.uri-encode',
'a8m.uri-component-encode',
'a8m.slugify',
'a8m.strip-tags',
'a8m.stringular',
'a8m.truncate',
'a8m.starts-with',
'a8m.ends-with',
'a8m.wrap',
'a8m.trim',
'a8m.ltrim',
'a8m.rtrim',
'a8m.repeat',
'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.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.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 );