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 <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] !== 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 <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] !== 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 <ariel@mashraki.co.il>
  * @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<b?a.concat(e(b-a.length,c)):a}).slice(0,Math.ceil(a.length/b)):a}return a.isMemoized("chunkBy",arguments)||a.memoize("chunkBy",arguments,this,f(b,c,d))}}]),b.module("a8m.concat",[]).filter("concat",[function(){return function(a,b){if(y(b))return a;if(D(a))return C(b)?a.concat(d(b)):a.concat(b);if(C(a)){var c=d(a);return C(b)?c.concat(d(b)):c.concat(b)}return a}}]),b.module("a8m.contains",[]).filter({contains:["$parse",t],some:["$parse",t]}),b.module("a8m.count-by",[]).filter("countBy",["$parse",function(a){return function(b,c){var e,f={},g=a(c);return b=C(b)?d(b):b,!D(b)||y(c)?b:(b.forEach(function(a){e=g(a),f[e]||(f[e]=0),f[e]++}),f)}}]),b.module("a8m.defaults",[]).filter("defaults",["$parse",function(a){return function(b,c){if(b=C(b)?d(b):b,!D(b)||!C(c))return b;var e=j(c);return b.forEach(function(b){e.forEach(function(d){var e=a(d),f=e.assign;y(e(b))&&f(b,e(c))})}),b}}]),b.module("a8m.every",[]).filter("every",["$parse",function(a){return function(b,c){return b=C(b)?d(b):b,!D(b)||y(c)?!0:b.every(function(b){return C(b)||z(c)?a(c)(b):b===c})}}]),b.module("a8m.filter-by",[]).filter("filterBy",["$parse",function(a){return function(b,e,f){var g;return f=A(f)||B(f)?String(f).toLowerCase():c,b=C(b)?d(b):b,!D(b)||y(f)?b:b.filter(function(b){return e.some(function(c){if(~c.indexOf("+")){var d=c.replace(new RegExp("\\s","g"),"").split("+");g=d.reduce(function(c,d,e){return 1===e?a(c)(b)+" "+a(d)(b):c+" "+a(d)(b)})}else g=a(c)(b);return A(g)||B(g)?String(g).toLowerCase().contains(f):!1})})}}]),b.module("a8m.first",[]).filter("first",["$parse",function(a){return function(b){var e,f,g;return b=C(b)?d(b):b,D(b)?(g=Array.prototype.slice.call(arguments,1),e=B(g[0])?g[0]:1,f=B(g[0])?B(g[1])?c:g[1]:g[0],g.length?h(b,e,f?a(f):f):b[0]):b}}]),b.module("a8m.flatten",[]).filter("flatten",function(){return function(a,b){return b=b||!1,a=C(a)?d(a):a,D(a)?b?[].concat.apply([],a):u(a,0):a}}),b.module("a8m.fuzzy-by",[]).filter("fuzzyBy",["$parse",function(a){return function(b,c,e,f){var h,i,j=f||!1;return b=C(b)?d(b):b,!D(b)||y(c)||y(e)?b:(i=a(c),b.filter(function(a){return h=i(a),A(h)?(h=j?h:h.toLowerCase(),e=j?e:e.toLowerCase(),g(h,e)!==!1):!1}))}}]),b.module("a8m.fuzzy",[]).filter("fuzzy",function(){return function(a,b,c){function e(a,b){var c,d,e=Object.keys(a);return 0<e.filter(function(e){return c=a[e],d?!0:A(c)?(c=f?c:c.toLowerCase(),d=g(c,b)!==!1):!1}).length}var f=c||!1;return a=C(a)?d(a):a,!D(a)||y(b)?a:(b=f?b:b.toLowerCase(),a.filter(function(a){return A(a)?(a=f?a:a.toLowerCase(),g(a,b)!==!1):C(a)?e(a,b):!1}))}}),b.module("a8m.group-by",["a8m.filter-watcher"]).filter("groupBy",["$parse","filterWatcher",function(a,b){return function(c,d){function e(a,b){var c,d={};return E(a,function(a){c=b(a),d[c]||(d[c]=[]),d[c].push(a)}),d}return!C(c)||y(d)?c:b.isMemoized("groupBy",arguments)||b.memoize("groupBy",arguments,this,e(c,a(d)))}}]),b.module("a8m.is-empty",[]).filter("isEmpty",function(){return function(a){return C(a)?!d(a).length:!a.length}}),b.module("a8m.join",[]).filter("join",function(){return function(a,b){return y(a)||!D(a)?a:(y(b)&&(b=" "),a.join(b))}}),b.module("a8m.last",[]).filter("last",["$parse",function(a){return function(b){var e,f,g,i=G(b);return i=C(i)?d(i):i,D(i)?(g=Array.prototype.slice.call(arguments,1),e=B(g[0])?g[0]:1,f=B(g[0])?B(g[1])?c:g[1]:g[0],g.length?h(i.reverse(),e,f?a(f):f).reverse():i[i.length-1]):i}}]),b.module("a8m.map",[]).filter("map",["$parse",function(a){return function(b,c){return b=C(b)?d(b):b,!D(b)||y(c)?b:b.map(function(b){return a(c)(b)})}}]),b.module("a8m.omit",[]).filter("omit",["$parse",function(a){return function(b,c){return b=C(b)?d(b):b,!D(b)||y(c)?b:b.filter(function(b){return!a(c)(b)})}}]),b.module("a8m.pick",[]).filter("pick",["$parse",function(a){return function(b,c){return b=C(b)?d(b):b,!D(b)||y(c)?b:b.filter(function(b){return a(c)(b)})}}]),b.module("a8m.range",[]).filter("range",function(){return function(a,b){for(var c=0;c<parseInt(b);c++)a.push(c);return a}}),b.module("a8m.remove-with",[]).filter("removeWith",function(){return function(a,b){return y(b)?a:(a=C(a)?d(a):a,a.filter(function(a){return!f(b,a)}))}}),b.module("a8m.remove",[]).filter("remove",function(){return function(a){a=C(a)?d(a):a;var b=Array.prototype.slice.call(arguments,1);return D(a)?a.filter(function(a){return!b.some(function(b){return H(b,a)})}):a}}),b.module("a8m.reverse",[]).filter("reverse",[function(){return function(a){return a=C(a)?d(a):a,A(a)?a.split("").reverse().join(""):D(a)?a.slice().reverse():a}}]),b.module("a8m.search-field",[]).filter("searchField",["$parse",function(a){return function(b){var c,e;b=C(b)?d(b):b;var f=Array.prototype.slice.call(arguments,1);return D(b)&&f.length?b.map(function(b){return e=f.map(function(d){return(c=a(d))(b)}).join(" "),F(b,{searchField:e})}):b}}]),b.module("a8m.to-array",[]).filter("toArray",function(){return function(a,b){return C(a)?b?Object.keys(a).map(function(b){return F(a[b],{$key:b})}):d(a):a}}),b.module("a8m.unique",[]).filter({unique:["$parse",v],uniq:["$parse",v]}),b.module("a8m.where",[]).filter("where",function(){return function(a,b){return y(b)?a:(a=C(a)?d(a):a,a.filter(function(a){return f(b,a)}))}}),b.module("a8m.xor",[]).filter("xor",["$parse",function(a){return function(b,c,e){function f(b,c){var d=a(e);return c.some(function(a){return e?H(d(a),d(b)):H(a,b)})}return e=e||!1,b=C(b)?d(b):b,c=C(c)?d(c):c,D(b)&&D(c)?b.concat(c).filter(function(a){return!(f(a,b)&&f(a,c))}):b}}]),b.module("a8m.math.byteFmt",["a8m.math"]).filter("byteFmt",["$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)+" 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<b.length;d++)for(var e=b[d].letters.split(""),f=0;f<e.length;f++)c[e[f]]=b[d].base;return function(b){return A(b)?a(b):b}}]),b.module("a8m.ltrim",[]).filter("ltrim",function(){return function(a,b){var c=b||"\\s";return A(a)?a.replace(new RegExp("^"+c+"+"),""):a}}),b.module("a8m.match",[]).filter("match",function(){return function(a,b,c){var d=new RegExp(b,c);return A(a)?a.match(d):null}}),b.module("a8m.repeat",[]).filter("repeat",[function(){return function(a,b,c){var d=~~b;return A(a)&&d?w(a,--b,c||""):a}}]),b.module("a8m.rtrim",[]).filter("rtrim",function(){return function(a,b){var c=b||"\\s";return A(a)?a.replace(new RegExp(c+"+$"),""):a}}),b.module("a8m.slugify",[]).filter("slugify",[function(){return function(a,b){var c=y(b)?"-":b;return A(a)?a.toLowerCase().replace(/\s+/g,c):a}}]),b.module("a8m.starts-with",[]).filter("startsWith",function(){return function(a,b,c){var d=c||!1;return!A(a)||y(b)?a:(a=d?a:a.toLowerCase(),!a.indexOf(d?b:b.toLowerCase()))}}),b.module("a8m.stringular",[]).filter("stringular",function(){return function(a){var b=Array.prototype.slice.call(arguments,1);return a.replace(/{(\d+)}/g,function(a,c){return y(b[c])?a:b[c]})}}),b.module("a8m.strip-tags",[]).filter("stripTags",function(){return function(a){return A(a)?a.replace(/<\S[^><]*>/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<b?a.concat(e(b-a.length,c)):a}).slice(0,Math.ceil(a.length/b)):a}return a.isMemoized("chunkBy",arguments)||a.memoize("chunkBy",arguments,this,f(b,c,d))}}]),b.module("a8m.concat",[]).filter("concat",[function(){return function(a,b){if(y(b))return a;if(D(a))return C(b)?a.concat(d(b)):a.concat(b);if(C(a)){var c=d(a);return C(b)?c.concat(d(b)):c.concat(b)}return a}}]),b.module("a8m.contains",[]).filter({contains:["$parse",t],some:["$parse",t]}),b.module("a8m.count-by",[]).filter("countBy",["$parse",function(a){return function(b,c){var e,f={},g=a(c);return b=C(b)?d(b):b,!D(b)||y(c)?b:(b.forEach(function(a){e=g(a),f[e]||(f[e]=0),f[e]++}),f)}}]),b.module("a8m.defaults",[]).filter("defaults",["$parse",function(a){return function(b,c){if(b=C(b)?d(b):b,!D(b)||!C(c))return b;var e=j(c);return b.forEach(function(b){e.forEach(function(d){var e=a(d),f=e.assign;y(e(b))&&f(b,e(c))})}),b}}]),b.module("a8m.every",[]).filter("every",["$parse",function(a){return function(b,c){return b=C(b)?d(b):b,!D(b)||y(c)?!0:b.every(function(b){return C(b)||z(c)?a(c)(b):b===c})}}]),b.module("a8m.filter-by",[]).filter("filterBy",["$parse",function(a){return function(b,e,f,g){var h;return f=A(f)||B(f)?String(f).toLowerCase():c,b=C(b)?d(b):b,!D(b)||y(f)?b:b.filter(function(b){return e.some(function(c){if(~c.indexOf("+")){var d=c.replace(/\s+/g,"").split("+");h=d.map(function(c){return a(c)(b)}).join(" ")}else h=a(c)(b);return A(h)||B(h)?(h=String(h).toLowerCase(),g?h===f:h.contains(f)):!1})})}}]),b.module("a8m.first",[]).filter("first",["$parse",function(a){return function(b){var e,f,g;return b=C(b)?d(b):b,D(b)?(g=Array.prototype.slice.call(arguments,1),e=B(g[0])?g[0]:1,f=B(g[0])?B(g[1])?c:g[1]:g[0],g.length?h(b,e,f?a(f):f):b[0]):b}}]),b.module("a8m.flatten",[]).filter("flatten",function(){return function(a,b){return b=b||!1,a=C(a)?d(a):a,D(a)?b?[].concat.apply([],a):u(a,0):a}}),b.module("a8m.fuzzy-by",[]).filter("fuzzyBy",["$parse",function(a){return function(b,c,e,f){var h,i,j=f||!1;return b=C(b)?d(b):b,!D(b)||y(c)||y(e)?b:(i=a(c),b.filter(function(a){return h=i(a),A(h)?(h=j?h:h.toLowerCase(),e=j?e:e.toLowerCase(),g(h,e)!==!1):!1}))}}]),b.module("a8m.fuzzy",[]).filter("fuzzy",function(){return function(a,b,c){function e(a,b){var c,d,e=Object.keys(a);return 0<e.filter(function(e){return c=a[e],d?!0:A(c)?(c=f?c:c.toLowerCase(),d=g(c,b)!==!1):!1}).length}var f=c||!1;return a=C(a)?d(a):a,!D(a)||y(b)?a:(b=f?b:b.toLowerCase(),a.filter(function(a){return A(a)?(a=f?a:a.toLowerCase(),g(a,b)!==!1):C(a)?e(a,b):!1}))}}),b.module("a8m.group-by",["a8m.filter-watcher"]).filter("groupBy",["$parse","filterWatcher",function(a,b){return function(c,d){function e(a,b){var c,d={};return E(a,function(a){c=b(a),d[c]||(d[c]=[]),d[c].push(a)}),d}return!C(c)||y(d)?c:b.isMemoized("groupBy",arguments)||b.memoize("groupBy",arguments,this,e(c,a(d)))}}]),b.module("a8m.is-empty",[]).filter("isEmpty",function(){return function(a){return C(a)?!d(a).length:!a.length}}),b.module("a8m.join",[]).filter("join",function(){return function(a,b){return y(a)||!D(a)?a:(y(b)&&(b=" "),a.join(b))}}),b.module("a8m.last",[]).filter("last",["$parse",function(a){return function(b){var e,f,g,i=G(b);return i=C(i)?d(i):i,D(i)?(g=Array.prototype.slice.call(arguments,1),e=B(g[0])?g[0]:1,f=B(g[0])?B(g[1])?c:g[1]:g[0],g.length?h(i.reverse(),e,f?a(f):f).reverse():i[i.length-1]):i}}]),b.module("a8m.map",[]).filter("map",["$parse",function(a){return function(b,c){return b=C(b)?d(b):b,!D(b)||y(c)?b:b.map(function(b){return a(c)(b)})}}]),b.module("a8m.omit",[]).filter("omit",["$parse",function(a){return function(b,c){return b=C(b)?d(b):b,!D(b)||y(c)?b:b.filter(function(b){return!a(c)(b)})}}]),b.module("a8m.pick",[]).filter("pick",["$parse",function(a){return function(b,c){return b=C(b)?d(b):b,!D(b)||y(c)?b:b.filter(function(b){return a(c)(b)})}}]),b.module("a8m.random",[]).filter("random",[function(){return function(a){return a=C(a)?d(a):a,D(a)?a[Math.floor(Math.random()*a.length)]:a}}]),b.module("a8m.range",[]).filter("range",function(){return function(a,b){for(var c=0;c<parseInt(b);c++)a.push(c);return a}}),b.module("a8m.remove-with",[]).filter("removeWith",function(){return function(a,b){return y(b)?a:(a=C(a)?d(a):a,a.filter(function(a){return!f(b,a)}))}}),b.module("a8m.remove",[]).filter("remove",function(){return function(a){a=C(a)?d(a):a;var b=Array.prototype.slice.call(arguments,1);return D(a)?a.filter(function(a){return!b.some(function(b){return H(b,a)})}):a}}),b.module("a8m.reverse",[]).filter("reverse",[function(){return function(a){return a=C(a)?d(a):a,A(a)?a.split("").reverse().join(""):D(a)?a.slice().reverse():a}}]),b.module("a8m.search-field",[]).filter("searchField",["$parse",function(a){return function(b){var c,e;b=C(b)?d(b):b;var f=Array.prototype.slice.call(arguments,1);return D(b)&&f.length?b.map(function(b){return e=f.map(function(d){return(c=a(d))(b)}).join(" "),F(b,{searchField:e})}):b}}]),b.module("a8m.to-array",[]).filter("toArray",function(){return function(a,b){return C(a)?b?Object.keys(a).map(function(b){return F(a[b],{$key:b})}):d(a):a}}),b.module("a8m.unique",[]).filter({unique:["$parse",v],uniq:["$parse",v]}),b.module("a8m.where",[]).filter("where",function(){return function(a,b){return y(b)?a:(a=C(a)?d(a):a,a.filter(function(a){return f(b,a)}))}}),b.module("a8m.xor",[]).filter("xor",["$parse",function(a){return function(b,c,e){function f(b,c){var d=a(e);return c.some(function(a){return e?H(d(a),d(b)):H(a,b)})}return e=e||!1,b=C(b)?d(b):b,c=C(c)?d(c):c,D(b)&&D(c)?b.concat(c).filter(function(a){return!(f(a,b)&&f(a,c))}):b}}]),b.module("a8m.math.byteFmt",["a8m.math"]).filter("byteFmt",["$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)+" 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<b.length;d++)for(var e=b[d].letters.split(""),f=0;f<e.length;f++)c[e[f]]=b[d].base;return function(b){return A(b)?a(b):b}}]),b.module("a8m.ltrim",[]).filter("ltrim",function(){return function(a,b){var c=b||"\\s";return A(a)?a.replace(new RegExp("^"+c+"+"),""):a}}),b.module("a8m.match",[]).filter("match",function(){return function(a,b,c){var d=new RegExp(b,c);return A(a)?a.match(d):null}}),b.module("a8m.repeat",[]).filter("repeat",[function(){return function(a,b,c){var d=~~b;return A(a)&&d?w(a,--b,c||""):a}}]),b.module("a8m.rtrim",[]).filter("rtrim",function(){return function(a,b){var c=b||"\\s";return A(a)?a.replace(new RegExp(c+"+$"),""):a}}),b.module("a8m.slugify",[]).filter("slugify",[function(){return function(a,b){var c=y(b)?"-":b;return A(a)?a.toLowerCase().replace(/\s+/g,c):a}}]),b.module("a8m.starts-with",[]).filter("startsWith",function(){return function(a,b,c){var d=c||!1;return!A(a)||y(b)?a:(a=d?a:a.toLowerCase(),!a.indexOf(d?b:b.toLowerCase()))}}),b.module("a8m.stringular",[]).filter("stringular",function(){return function(a){var b=Array.prototype.slice.call(arguments,1);return a.replace(/{(\d+)}/g,function(a,c){return y(b[c])?a:b[c]})}}),b.module("a8m.strip-tags",[]).filter("stripTags",function(){return function(a){return A(a)?a.replace(/<\S[^><]*>/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 7f18b4d..34b1ab9 100644
Binary files a/dist/angular-filter.zip and b/dist/angular-filter.zip differ
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);
+
+  });
+});