<!DOCTYPE html>
<html ng-app="myApp">
<head>
<script data-require="angular.js@1.3.13" data-semver="1.3.13" src="https://code.angularjs.org/1.3.13/angular.js"></script>
<link data-require="bootstrap-css@*" data-semver="3.3.1" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
<link rel="stylesheet" href="style.css" />
<script src="angular-filter.js"></script>
<script src="smart-table.debug.js"></script>
<script src="script.js"></script>
</head>
<body ng-controller="mainCtrl as mc">
<select name="group" ng-model="mc.groupProperty">
<option value="">select a property to group</option>
<option value="name"> name </option>
<option value="age"> age </option>
<option value="saved"> saved </option>
</select>
<table class="table" st-table="mc.displayed">
<thead>
<tr>
<th st-sort="id">id</th>
<th st-sort="name">name</th>
<th st-sort="age">age</th>
<th st-sort="saved">saved people</th>
</tr>
<tr>
<th>
<input st-search="'id'" />
</th>
<th>
<input st-search="'name'" />
</th>
<th>
<input st-search="'age'" />
</th>
<th>
<input st-search="'saved'" />
</th>
</tr>
</thead>
<tbody ng-if="!mc.isLoading" ng-repeat="(key, value) in mc.displayed | groupBy: mc.groupProperty">
<tr ng-if="key !== 'undefined'" class="text-center text-uppercase text-danger">
<td colspan="4">{{key}}</td>
</tr>
<tr ng-repeat="item in value">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.age}}</td>
<td>{{item.saved}}</td>
</tr>
</tbody>
<tbody ng-if="mc.isLoading">
<tr>
<td colspan="4" class="text-center">Loading ...</td>
</tr>
</tbody>
<tfoot>
<tr>
<td class="text-center" st-pagination="" st-item-by-page="10" colspan="4"></td>
</tr>
</tfoot>
</table>
</body>
</html>
angular.module('myApp',['smart-table','angular.filter'])
.controller('mainCtrl',[function(){
var ctrl = this;
this.groupProperty='';
// the database (normally on your server)
this.displayed = [];
function createRandomItem(id) {
var heroes = ['Batman', 'Superman', 'Robin', 'Thor', 'Hulk', 'Niki Larson', 'Stark', 'Bob Leponge'];
return {
id: id,
name: heroes[Math.floor(Math.random() * 7)],
age: Math.floor(Math.random() * 1000),
saved: Math.floor(Math.random() * 10000)
};
}
for (var i = 0; i < 100; i++) {
this.displayed.push(createRandomItem(i));
}
}])
/* Styles go here */
/**
* @version 1.4.11
* @license MIT
*/
(function (ng, undefined){
'use strict';
ng.module('smart-table', []).run(['$templateCache', function ($templateCache) {
$templateCache.put('template/smart-table/pagination.html',
'<div class="pagination" ng-if="pages.length >= 2"><ul class="pagination">' +
'<li ng-repeat="page in pages" ng-class="{active: page==currentPage}"><a ng-click="selectPage(page)">{{page}}</a></li>' +
'</ul></div>');
}]);
ng.module('smart-table')
.controller('stTableController', ['$scope', '$parse', '$filter', '$attrs', function StTableController($scope, $parse, $filter, $attrs) {
var propertyName = $attrs.stTable;
var displayGetter = $parse(propertyName);
var displaySetter = displayGetter.assign;
var safeGetter;
var orderBy = $filter('orderBy');
var filter = $filter('filter');
var safeCopy = copyRefs(displayGetter($scope));
var tableState = {
sort: {},
search: {},
pagination: {
start: 0
}
};
var pipeAfterSafeCopy = true;
var ctrl = this;
var lastSelected;
function copyRefs(src) {
return src ? [].concat(src) : [];
}
function updateSafeCopy() {
safeCopy = copyRefs(safeGetter($scope));
if (pipeAfterSafeCopy === true) {
ctrl.pipe();
}
}
if ($attrs.stSafeSrc) {
safeGetter = $parse($attrs.stSafeSrc);
$scope.$watch(function () {
var safeSrc = safeGetter($scope);
return safeSrc ? safeSrc.length : 0;
}, function (newValue, oldValue) {
if (newValue !== safeCopy.length) {
updateSafeCopy();
}
});
$scope.$watch(function () {
return safeGetter($scope);
}, function (newValue, oldValue) {
if (newValue !== oldValue) {
updateSafeCopy();
}
});
}
/**
* sort the rows
* @param {Function | String} predicate - function or string which will be used as predicate for the sorting
* @param [reverse] - if you want to reverse the order
*/
this.sortBy = function sortBy(predicate, reverse) {
tableState.sort.predicate = predicate;
tableState.sort.reverse = reverse === true;
if (ng.isFunction(predicate)) {
tableState.sort.functionName = predicate.name;
} else {
delete tableState.sort.functionName;
}
tableState.pagination.start = 0;
return this.pipe();
};
/**
* search matching rows
* @param {String} input - the input string
* @param {String} [predicate] - the property name against you want to check the match, otherwise it will search on all properties
*/
this.search = function search(input, predicate) {
var predicateObject = tableState.search.predicateObject || {};
var prop = predicate ? predicate : '$';
input = ng.isString(input) ? input.trim() : input;
predicateObject[prop] = input;
// to avoid to filter out null value
if (!input) {
delete predicateObject[prop];
}
tableState.search.predicateObject = predicateObject;
tableState.pagination.start = 0;
return this.pipe();
};
/**
* this will chain the operations of sorting and filtering based on the current table state (sort options, filtering, ect)
*/
this.pipe = function pipe() {
var pagination = tableState.pagination;
var filtered = tableState.search.predicateObject ? filter(safeCopy, tableState.search.predicateObject) : safeCopy;
if (tableState.sort.predicate) {
filtered = orderBy(filtered, tableState.sort.predicate, tableState.sort.reverse);
}
if (pagination.number !== undefined) {
pagination.numberOfPages = filtered.length > 0 ? Math.ceil(filtered.length / pagination.number) : 1;
pagination.start = pagination.start >= filtered.length ? (pagination.numberOfPages - 1) * pagination.number : pagination.start;
filtered = filtered.slice(pagination.start, pagination.start + parseInt(pagination.number));
}
displaySetter($scope, filtered);
};
/**
* select a dataRow (it will add the attribute isSelected to the row object)
* @param {Object} row - the row to select
* @param {String} [mode] - "single" or "multiple" (multiple by default)
*/
this.select = function select(row, mode) {
var rows = safeCopy;
var index = rows.indexOf(row);
if (index !== -1) {
if (mode === 'single') {
row.isSelected = row.isSelected !== true;
if (lastSelected) {
lastSelected.isSelected = false;
}
lastSelected = row.isSelected === true ? row : undefined;
} else {
rows[index].isSelected = !rows[index].isSelected;
}
}
};
/**
* take a slice of the current sorted/filtered collection (pagination)
*
* @param {Number} start - start index of the slice
* @param {Number} number - the number of item in the slice
*/
this.slice = function splice(start, number) {
tableState.pagination.start = start;
tableState.pagination.number = number;
return this.pipe();
};
/**
* return the current state of the table
* @returns {{sort: {}, search: {}, pagination: {start: number}}}
*/
this.tableState = function getTableState() {
return tableState;
};
/**
* Use a different filter function than the angular FilterFilter
* @param filterName the name under which the custom filter is registered
*/
this.setFilterFunction = function setFilterFunction(filterName) {
filter = $filter(filterName);
};
/**
*User a different function than the angular orderBy
* @param sortFunctionName the name under which the custom order function is registered
*/
this.setSortFunction = function setSortFunction(sortFunctionName) {
orderBy = $filter(sortFunctionName);
};
/**
* Usually when the safe copy is updated the pipe function is called.
* Calling this method will prevent it, which is something required when using a custom pipe function
*/
this.preventPipeOnWatch = function preventPipe() {
pipeAfterSafeCopy = false;
};
}])
.directive('stTable', function () {
return {
restrict: 'A',
controller: 'stTableController',
link: function (scope, element, attr, ctrl) {
if (attr.stSetFilter) {
ctrl.setFilterFunction(attr.stSetFilter);
}
if (attr.stSetSort) {
ctrl.setSortFunction(attr.stSetSort);
}
}
};
});
ng.module('smart-table')
.directive('stSearch', ['$timeout', function ($timeout) {
return {
require: '^stTable',
scope: {
predicate: '=?stSearch'
},
link: function (scope, element, attr, ctrl) {
var tableCtrl = ctrl;
var promise = null;
var throttle = attr.stDelay || 400;
scope.$watch('predicate', function (newValue, oldValue) {
if (newValue !== oldValue) {
ctrl.tableState().search = {};
tableCtrl.search(element[0].value || '', newValue);
}
});
//table state -> view
scope.$watch(function () {
return ctrl.tableState().search;
}, function (newValue, oldValue) {
var predicateExpression = scope.predicate || '$';
if (newValue.predicateObject && newValue.predicateObject[predicateExpression] !== element[0].value) {
element[0].value = newValue.predicateObject[predicateExpression] || '';
}
}, true);
// view -> table state
element.bind('input', function (evt) {
evt = evt.originalEvent || evt;
if (promise !== null) {
$timeout.cancel(promise);
}
promise = $timeout(function () {
tableCtrl.search(evt.target.value, scope.predicate || '');
promise = null;
}, throttle);
});
}
};
}]);
ng.module('smart-table')
.directive('stSelectRow', function () {
return {
restrict: 'A',
require: '^stTable',
scope: {
row: '=stSelectRow'
},
link: function (scope, element, attr, ctrl) {
var mode = attr.stSelectMode || 'single';
element.bind('click', function () {
scope.$apply(function () {
ctrl.select(scope.row, mode);
});
});
scope.$watch('row.isSelected', function (newValue) {
if (newValue === true) {
element.addClass('st-selected');
} else {
element.removeClass('st-selected');
}
});
}
};
});
ng.module('smart-table')
.directive('stSort', ['$parse', function ($parse) {
return {
restrict: 'A',
require: '^stTable',
link: function (scope, element, attr, ctrl) {
var predicate = attr.stSort;
var getter = $parse(predicate);
var index = 0;
var classAscent = attr.stClassAscent || 'st-sort-ascent';
var classDescent = attr.stClassDescent || 'st-sort-descent';
var stateClasses = [classAscent, classDescent];
var sortDefault;
if (attr.stSortDefault) {
sortDefault = scope.$eval(attr.stSortDefault) !== undefined ? scope.$eval(attr.stSortDefault) : attr.stSortDefault;
}
//view --> table state
function sort() {
index++;
predicate = ng.isFunction(getter(scope)) ? getter(scope) : attr.stSort;
if (index % 3 === 0 && attr.stSkipNatural === undefined) {
//manual reset
index = 0;
ctrl.tableState().sort = {};
ctrl.tableState().pagination.start = 0;
ctrl.pipe();
} else {
ctrl.sortBy(predicate, index % 2 === 0);
}
}
element.bind('click', function sortClick() {
if (predicate) {
scope.$apply(sort);
}
});
if (sortDefault) {
index = attr.stSortDefault === 'reverse' ? 1 : 0;
sort();
}
//table state --> view
scope.$watch(function () {
return ctrl.tableState().sort;
}, function (newValue) {
if (newValue.predicate !== predicate) {
index = 0;
element
.removeClass(classAscent)
.removeClass(classDescent);
} else {
index = newValue.reverse === true ? 2 : 1;
element
.removeClass(stateClasses[index % 2])
.addClass(stateClasses[index - 1]);
}
}, true);
}
};
}]);
ng.module('smart-table')
.directive('stPagination', function () {
return {
restrict: 'EA',
require: '^stTable',
scope: {
stItemsByPage: '=?',
stDisplayedPages: '=?',
stPageChange: '&'
},
templateUrl: function (element, attrs) {
if (attrs.stTemplate) {
return attrs.stTemplate;
}
return 'template/smart-table/pagination.html';
},
link: function (scope, element, attrs, ctrl) {
scope.stItemsByPage = scope.stItemsByPage ? +(scope.stItemsByPage) : 10;
scope.stDisplayedPages = scope.stDisplayedPages ? +(scope.stDisplayedPages) : 5;
scope.currentPage = 1;
scope.pages = [];
function redraw() {
var paginationState = ctrl.tableState().pagination;
var start = 1;
var end;
var i;
var prevPage = scope.currentPage;
scope.currentPage = Math.floor(paginationState.start / paginationState.number) + 1;
start = Math.max(start, scope.currentPage - Math.abs(Math.floor(scope.stDisplayedPages / 2)));
end = start + scope.stDisplayedPages;
if (end > paginationState.numberOfPages) {
end = paginationState.numberOfPages + 1;
start = Math.max(1, end - scope.stDisplayedPages);
}
scope.pages = [];
scope.numPages = paginationState.numberOfPages;
for (i = start; i < end; i++) {
scope.pages.push(i);
}
if (prevPage!==scope.currentPage) {
scope.stPageChange({newPage: scope.currentPage});
}
}
//table state --> view
scope.$watch(function () {
return ctrl.tableState().pagination;
}, redraw, true);
//scope --> table state (--> view)
scope.$watch('stItemsByPage', function (newValue, oldValue) {
if (newValue !== oldValue) {
scope.selectPage(1);
}
});
scope.$watch('stDisplayedPages', redraw);
//view -> table state
scope.selectPage = function (page) {
if (page > 0 && page <= scope.numPages) {
ctrl.slice((page - 1) * scope.stItemsByPage, scope.stItemsByPage);
}
};
if(!ctrl.tableState().pagination.number){
ctrl.slice(0, scope.stItemsByPage);
}
}
};
});
ng.module('smart-table')
.directive('stPipe', function () {
return {
require: 'stTable',
scope: {
stPipe: '='
},
link: {
pre: function (scope, element, attrs, ctrl) {
if (ng.isFunction(scope.stPipe)) {
ctrl.preventPipeOnWatch();
ctrl.pipe = function () {
return scope.stPipe(ctrl.tableState(), ctrl);
}
}
},
post: function (scope, element, attrs, ctrl) {
ctrl.pipe();
}
}
};
});
})(angular);
/**
* Bunch of useful filters for angularJS(with no external dependencies!)
* @version v0.5.2 - 2015-01-17 * @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 concat
* @kind function
*
* @description
* get (array/object, object/array) and return merged collection
*
*/
angular.module('a8m.concat', [])
//TODO(Ariel):unique option ? or use unique filter to filter result
.filter('concat', [function () {
return function (collection, joined) {
if (isUndefined(joined)) {
return collection;
}
if (isArray(collection)) {
return (isObject(joined))
? collection.concat(toArray(joined))
: collection.concat(joined);
}
if (isObject(collection)) {
var array = toArray(collection);
return (isObject(joined))
? array.concat(toArray(joined))
: array.concat(joined);
}
return collection;
};
}
]);
/**
* @ngdoc filter
* @name contains
* @kind function
*
* @description
* Checks if given expression is present in one or more object in the collection
*/
angular.module('a8m.contains', [])
.filter({
contains: ['$parse', containsFilter],
some: ['$parse', containsFilter]
});
function containsFilter( $parse ) {
return function (collection, expression) {
collection = (isObject(collection)) ? toArray(collection) : collection;
if(!isArray(collection) || isUndefined(expression)) {
return false;
}
return collection.some(function(elm) {
return (isObject(elm) || isFunction(expression))
? $parse(expression)(elm)
: elm === expression;
});
}
}
/**
* @ngdoc filter
* @name countBy
* @kind function
*
* @description
* Sorts a list into groups and returns a count for the number of objects in each group.
*/
angular.module('a8m.count-by', [])
.filter('countBy', [ '$parse', function ( $parse ) {
return function (collection, property) {
var result = {},
get = $parse(property),
prop;
collection = (isObject(collection)) ? toArray(collection) : collection;
if(!isArray(collection) || isUndefined(property)) {
return collection;
}
collection.forEach( function( elm ) {
prop = get(elm);
if(!result[prop]) {
result[prop] = 0;
}
result[prop]++;
});
return result;
}
}]);
/**
* @ngdoc filter
* @name defaults
* @kind function
*
* @description
* defaultsFilter allows to specify a default fallback value for properties that resolve to undefined.
*/
angular.module('a8m.defaults', [])
.filter('defaults', ['$parse', function( $parse ) {
return function(collection, defaults) {
collection = (isObject(collection)) ? toArray(collection) : collection;
if(!isArray(collection) || !isObject(defaults)) {
return collection;
}
var keys = deepKeys(defaults);
collection.forEach(function(elm) {
//loop through all the keys
keys.forEach(function(key) {
var getter = $parse(key);
var setter = getter.assign;
//if it's not exist
if(isUndefined(getter(elm))) {
//get from defaults, and set to the returned object
setter(elm, getter(defaults))
}
});
});
return collection;
}
}]);
/**
* @ngdoc filter
* @name every
* @kind function
*
* @description
* Checks if given expression is present in all members in the collection
*
*/
angular.module('a8m.every', [])
.filter('every', ['$parse', function($parse) {
return function (collection, expression) {
collection = (isObject(collection)) ? toArray(collection) : collection;
if(!isArray(collection) || isUndefined(expression)) {
return true;
}
return collection.every( function(elm) {
return (isObject(elm) || isFunction(expression))
? $parse(expression)(elm)
: elm === expression;
});
}
}]);
/**
* @ngdoc filter
* @name filterBy
* @kind function
*
* @description
* filter by specific properties, avoid the rest
*/
angular.module('a8m.filter-by', [])
.filter('filterBy', ['$parse', function( $parse ) {
return function(collection, properties, search) {
var comparator;
search = (isString(search) || isNumber(search)) ?
String(search).toLowerCase() : undefined;
collection = (isObject(collection)) ? toArray(collection) : collection;
if(!isArray(collection) || isUndefined(search)) {
return collection;
}
return collection.filter(function(elm) {
return properties.some(function(prop) {
/**
* check if there is concatenate properties
* example:
* object: { first: 'foo', last:'bar' }
* filterBy: ['first + last'] => search by full name(i.e 'foo bar')
*/
if(!~prop.indexOf('+')) {
comparator = $parse(prop)(elm)
} else {
var propList = prop.replace(new RegExp('\\s', 'g'), '').split('+');
comparator = propList.reduce(function(prev, cur, index) {
return (index === 1) ? $parse(prev)(elm) + ' ' + $parse(cur)(elm) :
prev + ' ' + $parse(cur)(elm);
});
}
return (isString(comparator) || isNumber(comparator))
? String(comparator).toLowerCase().contains(search)
: false;
});
});
}
}]);
/**
* @ngdoc filter
* @name first
* @kind function
*
* @description
* Gets the first element or first n elements of an array
* if callback is provided, is returns as long the callback return truthy
*/
angular.module('a8m.first', [])
.filter('first', ['$parse', function( $parse ) {
return function(collection) {
var n,
getter,
args;
collection = (isObject(collection))
? toArray(collection)
: collection;
if(!isArray(collection)) {
return collection;
}
args = Array.prototype.slice.call(arguments, 1);
n = (isNumber(args[0])) ? args[0] : 1;
getter = (!isNumber(args[0])) ? args[0] : (!isNumber(args[1])) ? args[1] : undefined;
return (args.length) ? getFirstMatches(collection, n,(getter) ? $parse(getter) : getter) :
collection[0];
}
}]);
/**
* @ngdoc filter
* @name flatten
* @kind function
*
* @description
* Flattens a nested array (the nesting can be to any depth).
* If you pass shallow, the array will only be flattened a single level
*/
angular.module('a8m.flatten', [])
.filter('flatten', function () {
return function(collection, shallow) {
shallow = shallow || false;
collection = (isObject(collection))
? toArray(collection)
: collection;
if(!isArray(collection)) {
return collection;
}
return !shallow
? flatten(collection, 0)
: [].concat.apply([], collection);
}
});
/**
* flatten nested array (the nesting can be to any depth).
* @param array {Array}
* @param i {int}
* @returns {Array}
* @private
*/
function flatten(array, i) {
i = i || 0;
if(i >= array.length)
return array;
if(isArray(array[i])) {
return flatten(array.slice(0,i)
.concat(array[i], array.slice(i+1)), i);
}
return flatten(array, i+1);
}
/**
* @ngdoc filter
* @name fuzzyByKey
* @kind function
*
* @description
* fuzzy string searching by key
*/
angular.module('a8m.fuzzy-by', [])
.filter('fuzzyBy', ['$parse', function ( $parse ) {
return function (collection, property, search, csensitive) {
var sensitive = csensitive || false,
prop, getter;
collection = (isObject(collection)) ? toArray(collection) : collection;
if(!isArray(collection) || isUndefined(property)
|| isUndefined(search)) {
return collection;
}
getter = $parse(property);
return collection.filter(function(elm) {
prop = getter(elm);
if(!isString(prop)) {
return false;
}
prop = (sensitive) ? prop : prop.toLowerCase();
search = (sensitive) ? search : search.toLowerCase();
return hasApproxPattern(prop, search) !== false
})
}
}]);
/**
* @ngdoc filter
* @name fuzzy
* @kind function
*
* @description
* fuzzy string searching for array of strings, objects
*/
angular.module('a8m.fuzzy', [])
.filter('fuzzy', function () {
return function (collection, search, csensitive) {
var sensitive = csensitive || false;
collection = (isObject(collection)) ? toArray(collection) : collection;
if(!isArray(collection) || isUndefined(search)) {
return collection;
}
search = (sensitive) ? search : search.toLowerCase();
return collection.filter(function(elm) {
if(isString(elm)) {
elm = (sensitive) ? elm : elm.toLowerCase();
return hasApproxPattern(elm, search) !== false
}
return (isObject(elm)) ? _hasApproximateKey(elm, search) : false;
});
/**
* checks if object has key{string} that match
* to fuzzy search pattern
* @param object
* @param search
* @returns {boolean}
* @private
*/
function _hasApproximateKey(object, search) {
var properties = Object.keys(object),
prop, flag;
return 0 < properties.filter(function (elm) {
prop = object[elm];
//avoid iteration if we found some key that equal[performance]
if(flag) return true;
if (isString(prop)) {
prop = (sensitive) ? prop : prop.toLowerCase();
return flag = (hasApproxPattern(prop, search) !== false);
}
return false;
}).length;
}
}
});
/**
* @ngdoc filter
* @name groupBy
* @kind function
*
* @description
* Create an object composed of keys generated from the result of running each element of a collection,
* each key is an array of the elements.
*/
angular.module('a8m.group-by', [ 'a8m.filter-watcher' ])
.filter('groupBy', [ '$parse', 'filterWatcher', function ( $parse, filterWatcher ) {
return function (collection, property) {
if(!isObject(collection) || isUndefined(property)) {
return collection;
}
var getterFn = $parse(property);
return filterWatcher.isMemoized('groupBy', arguments) ||
filterWatcher.memoize('groupBy', arguments, this,
_groupBy(collection, getterFn));
/**
* groupBy function
* @param collection
* @param getter
* @returns {{}}
*/
function _groupBy(collection, getter) {
var result = {};
var prop;
forEach( collection, function( elm ) {
prop = getter(elm);
if(!result[prop]) {
result[prop] = [];
}
result[prop].push(elm);
});
return result;
}
}
}]);
/**
* @ngdoc filter
* @name isEmpty
* @kind function
*
* @description
* get collection or string and return if it empty
*/
angular.module('a8m.is-empty', [])
.filter('isEmpty', function () {
return function(collection) {
return (isObject(collection)) ?
!toArray(collection).length :
!collection.length;
}
});
/**
* @ngdoc filter
* @name join
* @kind function
*
* @description
* join a collection by a provided delimiter (space by default)
*/
angular.module('a8m.join', [])
.filter('join', function () {
return function (input, delimiter) {
if (isUndefined(input) || !isArray(input)) {
return input;
}
if (isUndefined(delimiter)) {
delimiter = ' ';
}
return input.join(delimiter);
};
})
;
/**
* @ngdoc filter
* @name last
* @kind function
*
* @description
* Gets the last element or last n elements of an array
* if callback is provided, is returns as long the callback return truthy
*/
angular.module('a8m.last', [])
.filter('last', ['$parse', function( $parse ) {
return function(collection) {
var n
, getter
, args
//cuz reverse change our src collection
//and we don't want side effects
, reversed = copy(collection);
reversed = (isObject(reversed))
? toArray(reversed)
: reversed;
if(!isArray(reversed)) {
return reversed;
}
args = Array.prototype.slice.call(arguments, 1);
n = (isNumber(args[0])) ? args[0] : 1;
getter = (!isNumber(args[0])) ? args[0] : (!isNumber(args[1])) ? args[1] : undefined;
return (args.length) ?
//send reversed collection as arguments, and reverse it back as result
getFirstMatches(reversed.reverse(), n,(getter) ? $parse(getter) : getter).reverse() :
//get the last element
reversed[reversed.length-1];
}
}]);
/**
* @ngdoc filter
* @name map
* @kind function
*
* @description
* Returns a new collection of the results of each expression execution.
*/
angular.module('a8m.map', [])
.filter('map', ['$parse', function($parse) {
return function (collection, expression) {
collection = (isObject(collection))
? toArray(collection)
: collection;
if(!isArray(collection) || isUndefined(expression)) {
return collection;
}
return collection.map(function (elm) {
return $parse(expression)(elm);
});
}
}]);
/**
* @ngdoc filter
* @name omit
* @kind function
*
* @description
* filter collection by expression
*/
angular.module('a8m.omit', [])
.filter('omit', ['$parse', function($parse) {
return function (collection, expression) {
collection = (isObject(collection))
? toArray(collection)
: collection;
if(!isArray(collection) || isUndefined(expression)) {
return collection;
}
return collection.filter(function (elm) {
return !($parse(expression)(elm));
});
}
}]);
/**
* @ngdoc filter
* @name omit
* @kind function
*
* @description
* filter collection by expression
*/
angular.module('a8m.pick', [])
.filter('pick', ['$parse', function($parse) {
return function (collection, expression) {
collection = (isObject(collection))
? toArray(collection)
: collection;
if(!isArray(collection) || isUndefined(expression)) {
return collection;
}
return collection.filter(function (elm) {
return $parse(expression)(elm);
});
}
}]);
/**
* @ngdoc filter
* @name removeWith
* @kind function
*
* @description
* get collection and properties object, and removed elements
* with this properties
*/
angular.module('a8m.remove-with', [])
.filter('removeWith', function() {
return function (collection, object) {
if(isUndefined(object)) {
return collection;
}
collection = isObject(collection)
? toArray(collection)
: collection;
return collection.filter(function (elm) {
return !objectContains(object, elm);
});
}
});
/**
* @ngdoc filter
* @name remove
* @kind function
*
* @description
* remove specific members from collection
*/
angular.module('a8m.remove', [])
.filter('remove', function () {
return function (collection) {
collection = (isObject(collection)) ? toArray(collection) : collection;
var args = Array.prototype.slice.call(arguments, 1);
if(!isArray(collection)) {
return collection;
}
return collection.filter( function( member ) {
return !args.some(function(nest) {
return equals(nest, member);
})
});
}
});
/**
* @ngdoc filter
* @name reverse
* @kind function
*
* @description
* Reverses a string or collection
*/
angular.module('a8m.reverse', [])
.filter('reverse',[ function () {
return function (input) {
input = (isObject(input)) ? toArray(input) : input;
if(isString(input)) {
return input.split('').reverse().join('');
}
return (isArray(input)) ? input.slice().reverse() : input;
}
}]);
/**
* @ngdoc filter
* @name searchField
* @kind function
*
* @description
* for each member, join several strings field and add them to
* new field called 'searchField' (use for search filtering)
*/
angular.module('a8m.search-field', [])
.filter('searchField', ['$parse', function ($parse) {
return function (collection) {
var get, field;
collection = (isObject(collection)) ? toArray(collection) : collection;
var args = Array.prototype.slice.call(arguments, 1);
if(!isArray(collection) || !args.length) {
return collection;
}
return collection.map(function(member) {
field = args.map(function(field) {
get = $parse(field);
return get(member);
}).join(' ');
return extend(member, { searchField: field });
});
}
}]);
/**
* @ngdoc filter
* @name toArray
* @kind function
*
* @description
* Convert objects into stable arrays.
* if addKey set to true,the filter also attaches a new property
* $key to the value containing the original key that was used in
* the object we are iterating over to reference the property
*/
angular.module('a8m.to-array', [])
.filter('toArray', function() {
return function (collection, addKey) {
if(!isObject(collection)) {
return collection;
}
return (!addKey) ? toArray(collection) :
Object.keys(collection).map(function (key) {
return extend(collection[key], { $key: key });
});
}
});
/**
* @ngdoc filter
* @name unique/uniq
* @kind function
*
* @description
* get collection and filter duplicate members
* if uniqueFilter get a property(nested to) as argument it's
* filter by this property as unique identifier
*/
angular.module('a8m.unique', [])
.filter({
unique: ['$parse', uniqFilter],
uniq: ['$parse', uniqFilter]
});
function uniqFilter($parse) {
return function (collection, property) {
collection = (isObject(collection)) ? toArray(collection) : collection;
if (!isArray(collection)) {
return collection;
}
//store all unique identifiers
var uniqueItems = [],
get = $parse(property);
return (isUndefined(property))
//if it's kind of primitive array
? collection.filter(function (elm, pos, self) {
return self.indexOf(elm) === pos;
})
//else compare with equals
: collection.filter(function (elm) {
var prop = get(elm);
if(some(uniqueItems, prop)) {
return false;
}
uniqueItems.push(prop);
return true;
});
//checked if the unique identifier is already exist
function some(array, member) {
if(isUndefined(member)) {
return false;
}
return array.some(function(el) {
return equals(el, member);
});
}
}
}
/**
* @ngdoc filter
* @name where
* @kind function
*
* @description
* of each element in a collection to the given properties object,
* returning an array of all elements that have equivalent property values.
*
*/
angular.module('a8m.where', [])
.filter('where', function() {
return function (collection, object) {
if(isUndefined(object)) {
return collection;
}
collection = (isObject(collection))
? toArray(collection)
: collection;
return collection.filter(function (elm) {
return objectContains(object, elm);
});
}
});
/**
* @ngdoc filter
* @name xor
* @kind function
*
* @description
* Exclusive or filter by expression
*/
angular.module('a8m.xor', [])
.filter('xor', ['$parse', function($parse) {
return function (col1, col2, expression) {
expression = expression || false;
col1 = (isObject(col1)) ? toArray(col1) : col1;
col2 = (isObject(col2)) ? toArray(col2) : col2;
if(!isArray(col1) || !isArray(col2)) return col1;
return col1.concat(col2)
.filter(function(elm) {
return !(some(elm, col1) && some(elm, col2));
});
function some(el, col) {
var getter = $parse(expression);
return col.some(function(dElm) {
return expression
? equals(getter(dElm), getter(el))
: equals(dElm, el);
});
}
}
}]);
/**
* @ngdoc filter
* @name formatBytes
* @kind function
*
* @description
* Convert bytes into appropriate display
* 1024 bytes => 1 KB
*/
angular.module('a8m.math.byteFmt', ['a8m.math'])
.filter('byteFmt', ['$math', function ($math) {
return function (bytes, decimal) {
if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 &&
isNumber(bytes) && isFinite(bytes)) {
if(bytes < 1024) { // within 1 KB so B
return convertToDecimal(bytes, decimal, $math) + ' B';
} else if(bytes < 1048576) { // within 1 MB so KB
return convertToDecimal((bytes / 1024), decimal, $math) + ' KB';
} else if(bytes < 1073741824){ // within 1 GB so MB
return convertToDecimal((bytes / 1048576), decimal, $math) + ' MB';
} else { // GB or more
return convertToDecimal((bytes / 1073741824), decimal, $math) + ' GB';
}
} else {
return "NaN";
}
}
}]);
/**
* @ngdoc filter
* @name degrees
* @kind function
*
* @description
* Convert angle from radians to degrees
*
*/
angular.module('a8m.math.degrees', ['a8m.math'])
.filter('degrees', ['$math', function ($math) {
return function (radians, decimal) {
// if decimal is not an integer greater than -1, we cannot do. quit with error "NaN"
// if degrees is not a real number, we cannot do also. quit with error "NaN"
if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 &&
isNumber(radians) && isFinite(radians)) {
var degrees = (radians * 180) / $math.PI;
return $math.round(degrees * $math.pow(10,decimal)) / ($math.pow(10,decimal));
} else {
return "NaN";
}
}
}]);
/**
* @ngdoc filter
* @name formatBytes
* @kind function
*
* @description
* Convert bytes into appropriate display
* 1024 kilobytes => 1 MB
*/
angular.module('a8m.math.kbFmt', ['a8m.math'])
.filter('kbFmt', ['$math', function ($math) {
return function (bytes, decimal) {
if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 &&
isNumber(bytes) && isFinite(bytes)) {
if(bytes < 1024) { // within 1 MB so KB
return convertToDecimal(bytes, decimal, $math) + ' KB';
} else if(bytes < 1048576) { // within 1 GB so MB
return convertToDecimal((bytes / 1024), decimal, $math) + ' MB';
} else {
return convertToDecimal((bytes / 1048576), decimal, $math) + ' GB';
}
} else {
return "NaN";
}
}
}]);
/**
* @ngdoc module
* @name math
* @description
* reference to global Math object
*/
angular.module('a8m.math', [])
.factory('$math', ['$window', function ($window) {
return $window.Math;
}]);
/**
* @ngdoc filter
* @name max
* @kind function
*
* @description
* Math.max will get an array and return the max value. if an expression
* is provided, will return max value by expression.
*/
angular.module('a8m.math.max', ['a8m.math'])
.filter('max', ['$math', '$parse', function ($math, $parse) {
return function (input, expression) {
if(!isArray(input)) {
return input;
}
return isUndefined(expression)
? $math.max.apply($math, input)
: input[indexByMax(input, expression)];
};
/**
* @private
* @param array
* @param exp
* @returns {number|*|Number}
*/
function indexByMax(array, exp) {
var mappedArray = array.map(function(elm){
return $parse(exp)(elm);
});
return mappedArray.indexOf($math.max.apply($math, mappedArray));
}
}]);
/**
* @ngdoc filter
* @name min
* @kind function
*
* @description
* Math.min will get an array and return the min value. if an expression
* is provided, will return min value by expression.
*/
angular.module('a8m.math.min', ['a8m.math'])
.filter('min', ['$math', '$parse', function ($math, $parse) {
return function (input, expression) {
if(!isArray(input)) {
return input;
}
return isUndefined(expression)
? $math.min.apply($math, input)
: input[indexByMin(input, expression)];
};
/**
* @private
* @param array
* @param exp
* @returns {number|*|Number}
*/
function indexByMin(array, exp) {
var mappedArray = array.map(function(elm){
return $parse(exp)(elm);
});
return mappedArray.indexOf($math.min.apply($math, mappedArray));
}
}]);
/**
* @ngdoc filter
* @name Percent
* @kind function
*
* @description
* percentage between two numbers
*
*/
angular.module('a8m.math.percent', ['a8m.math'])
.filter('percent', ['$math', '$window', function ($math, $window) {
return function (input, divided, round) {
var divider = (isString(input)) ? $window.Number(input) : input;
divided = divided || 100;
round = round || false;
if (!isNumber(divider) || $window.isNaN(divider)) return input;
return (round) ? $math.round((divider / divided) * 100) :
((divider / divided) * 100);
}
}]);
/**
* @ngdoc filter
* @name toRadians
* @kind function
*
* @description
* Convert angle from degrees to radians
*
*/
angular.module('a8m.math.radians', ['a8m.math'])
.filter('radians', ['$math', function ($math) {
return function (degrees, decimal) {
// if decimal is not an integer greater than -1, we cannot do. quit with error "NaN"
// if degrees is not a real number, we cannot do also. quit with error "NaN"
if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 &&
isNumber(degrees) && isFinite(degrees)) {
var radians = (degrees * 3.14159265359) / 180;
return $math.round(radians * $math.pow(10,decimal)) / ($math.pow(10,decimal));
} else {
return "NaN";
}
}
}]);
/**
* @ngdoc filter
* @name Radix
* @kind function
*
* @description
* converting decimal numbers to different bases(radix)
*/
angular.module('a8m.math.radix', [])
.filter('radix', function () {
return function (input, radix) {
var RANGE = /^[2-9]$|^[1-2]\d$|^3[0-6]$/;
if(!isNumber(input) || !RANGE.test(radix)) {
return input;
}
return input.toString(radix).toUpperCase();
}
});
/**
* @ngdoc filter
* @name formatBytes
* @kind function
*
* @description
* Convert number into abbreviations.
* i.e: K for one thousand, M for Million, B for billion
* e.g: number of users:235,221, decimal:1 => 235.2 K
*/
angular.module('a8m.math.shortFmt', ['a8m.math'])
.filter('shortFmt', ['$math', function ($math) {
return function (number, decimal) {
if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 &&
isNumber(number) && isFinite(number)){
if(number < 1e3) {
return number;
} else if(number < 1e6) {
return convertToDecimal((number / 1e3), decimal, $math) + ' K';
} else if(number < 1e9){
return convertToDecimal((number / 1e6), decimal, $math) + ' M';
} else {
return convertToDecimal((number / 1e9), decimal, $math) + ' B';
}
}else{
return "NaN";
}
}
}]);
/**
* @ngdoc filter
* @name sum
* @kind function
*
* @description
* Sum up all values within an array
*
*/
angular.module('a8m.math.sum', [])
.filter('sum', function () {
return function (input, initial) {
return (!isArray(input)) ? input :
input.reduce(function(prev, curr) {
return prev + curr;
}, initial || 0);
}
});
/**
* @ngdoc filter
* @name endsWith
* @kind function
*
* @description
* checks whether string ends with the ends parameter.
*/
angular.module('a8m.ends-with', [])
.filter('endsWith', function () {
return function (input, ends, csensitive) {
var sensitive = csensitive || false,
position;
if(!isString(input) || isUndefined(ends)) {
return input;
}
input = (sensitive) ? input : input.toLowerCase();
position = input.length - ends.length;
return input.indexOf((sensitive) ? ends : ends.toLowerCase(), position) !== -1;
}
});
/**
* @ngdoc filter
* @name ltrim
* @kind function
*
* @description
* Left trim. Similar to trimFilter, but only for left side.
*/
angular.module('a8m.ltrim', [])
.filter('ltrim', function () {
return function(input, chars) {
var trim = chars || '\\s';
if(!isString(input)) {
return input;
}
return input.replace(new RegExp('^' + trim + '+'), '');
}
});
/**
* @ngdoc filter
* @name repeat
* @kind function
*
* @description
* Repeats a string n times
*/
angular.module('a8m.repeat', [])
.filter('repeat',[ function () {
return function (input, n, separator) {
var times = ~~n;
if(!isString(input)) {
return input;
}
return (!times) ? input : strRepeat(input, --n, separator || '');
}
}]);
/**
* Repeats a string n times with given separator
* @param str string to repeat
* @param n number of times
* @param sep separator
* @returns {*}
*/
function strRepeat(str, n, sep) {
if(!n) {
return str;
}
return str + sep + strRepeat(str, --n, sep);
}
/**
* @ngdoc filter
* @name rtrim
* @kind function
*
* @description
* Right trim. Similar to trimFilter, but only for right side.
*/
angular.module('a8m.rtrim', [])
.filter('rtrim', function () {
return function(input, chars) {
var trim = chars || '\\s';
if(!isString(input)) {
return input;
}
return input.replace(new RegExp(trim + '+$'), '');
}
});
/**
* @ngdoc filter
* @name slugify
* @kind function
*
* @description
* remove spaces from string, replace with "-" or given argument
*/
angular.module('a8m.slugify', [])
.filter('slugify',[ function () {
return function (input, sub) {
var replace = (isUndefined(sub)) ? '-' : sub;
if(isString(input)) {
return input.toLowerCase()
.replace(/\s+/g, replace);
}
return input;
}
}]);
/**
* @ngdoc filter
* @name startWith
* @kind function
*
* @description
* checks whether string starts with the starts parameter.
*/
angular.module('a8m.starts-with', [])
.filter('startsWith', function () {
return function (input, start, csensitive) {
var sensitive = csensitive || false;
if(!isString(input) || isUndefined(start)) {
return input;
}
input = (sensitive) ? input : input.toLowerCase();
return !input.indexOf((sensitive) ? start : start.toLowerCase());
}
});
/**
* @ngdoc filter
* @name stringular
* @kind function
*
* @description
* get string with {n} and replace match with enumeration values
*/
angular.module('a8m.stringular', [])
.filter('stringular', function () {
return function(input) {
var args = Array.prototype.slice.call(arguments, 1);
return input.replace(/{(\d+)}/g, function (match, number) {
return isUndefined(args[number]) ? match : args[number];
});
}
});
/**
* @ngdoc filter
* @name stripTags
* @kind function
*
* @description
* strip html tags from string
*/
angular.module('a8m.strip-tags', [])
.filter('stripTags', function () {
return function(input) {
if(isString(input)) {
return input.replace(/<\S[^><]*>/g, '');
}
return input;
}
});
/**
* @ngdoc filter
* @name trim
* @kind function
*
* @description
* Strip whitespace (or other characters) from the beginning and end of a string
*/
angular.module('a8m.trim', [])
.filter('trim', function () {
return function(input, chars) {
var trim = chars || '\\s';
if(!isString(input)) {
return input;
}
return input.replace(new RegExp('^' + trim + '+|' + trim + '+$', 'g'), '');
}
});
/**
* @ngdoc filter
* @name truncate
* @kind function
*
* @description
* truncates a string given a specified length, providing a custom string to denote an omission.
*/
angular.module('a8m.truncate', [])
.filter('truncate', function () {
return function(input, length, suffix, preserve) {
length = isUndefined(length) ? input.length : length;
preserve = preserve || false;
suffix = suffix || '';
if(!isString(input) || (input.length <= length)) return input;
return input.substring(0, (preserve) ?
((input.indexOf(' ', length) === -1) ? input.length : input.indexOf(' ', length)) :
length) + suffix;
};
});
/**
* @ngdoc filter
* @name ucfirst
* @kind function
*
* @description
* ucfirst
*
*/
angular.module('a8m.ucfirst', [])
.filter('ucfirst', [function() {
return function(input) {
if(!isString(input)) {
return input;
}
return input.split(' ').map(function (ch) {
return ch.charAt(0).toUpperCase() + ch.substring(1);
}).join(' ');
}
}]);
/**
* @ngdoc filter
* @name uriComponentEncode
* @kind function
*
* @description
* get string as parameter and return encoded string
*/
angular.module('a8m.uri-component-encode', [])
.filter('uriComponentEncode',['$window', function ($window) {
return function (input) {
if(isString(input)) {
return $window.encodeURIComponent(input);
}
return input;
}
}]);
/**
* @ngdoc filter
* @name uriEncode
* @kind function
*
* @description
* get string as parameter and return encoded string
*/
angular.module('a8m.uri-encode', [])
.filter('uriEncode',['$window', function ($window) {
return function (input) {
if(isString(input)) {
return $window.encodeURI(input);
}
return input;
}
}]);
/**
* @ngdoc filter
* @name wrap
* @kind function
*
* @description
* Wrap a string with another string
*/
angular.module('a8m.wrap', [])
.filter('wrap', function () {
return function(input, wrap, ends) {
if(!isString(input) || isUndefined(wrap)) {
return input;
}
return [wrap, input, ends || wrap].join('');
}
});
/**
* @ngdoc provider
* @name filterWatcher
* @kind function
*
* @description
* store specific filters result in $$cache, based on scope life time(avoid memory leak).
* on scope.$destroy remove it's cache from $$cache container
*/
angular.module('a8m.filter-watcher', [])
.provider('filterWatcher', function() {
this.$get = ['$window', '$rootScope', function($window, $rootScope) {
/**
* Cache storing
* @type {Object}
*/
var $$cache = {};
/**
* Scope listeners container
* scope.$destroy => remove all cache keys
* bind to current scope.
* @type {Object}
*/
var $$listeners = {};
/**
* $timeout without triggering the digest cycle
* @type {function}
*/
var $$timeout = $window.setTimeout;
/**
* @description
* get `HashKey` string based on the given arguments.
* @param fName
* @param args
* @returns {string}
*/
function getHashKey(fName, args) {
return [fName, angular.toJson(args)]
.join('#')
.replace(/"/g,'');
}
/**
* @description
* fir on $scope.$destroy,
* remove cache based scope from `$$cache`,
* and remove itself from `$$listeners`
* @param event
*/
function removeCache(event) {
var id = event.targetScope.$id;
forEach($$listeners[id], function(key) {
delete $$cache[key];
});
delete $$listeners[id];
}
/**
* @description
* for angular version that greater than v.1.3.0
* if clear cache when the digest cycle end.
*/
function cleanStateless() {
$$timeout(function() {
if(!$rootScope.$$phase)
$$cache = {};
});
}
/**
* @description
* Store hashKeys in $$listeners container
* on scope.$destroy, remove them all(bind an event).
* @param scope
* @param hashKey
* @returns {*}
*/
function addListener(scope, hashKey) {
var id = scope.$id;
if(isUndefined($$listeners[id])) {
scope.$on('$destroy', removeCache);
$$listeners[id] = [];
}
return $$listeners[id].push(hashKey);
}
/**
* @description
* return the `cacheKey` or undefined.
* @param filterName
* @param args
* @returns {*}
*/
function $$isMemoized(filterName, args) {
var hashKey = getHashKey(filterName, args);
return $$cache[hashKey];
}
/**
* @description
* store `result` in `$$cache` container, based on the hashKey.
* add $destroy listener and return result
* @param filterName
* @param args
* @param scope
* @param result
* @returns {*}
*/
function $$memoize(filterName, args, scope, result) {
var hashKey = getHashKey(filterName, args);
//store result in `$$cache` container
$$cache[hashKey] = result;
// for 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.strip-tags',
'a8m.stringular',
'a8m.truncate',
'a8m.starts-with',
'a8m.ends-with',
'a8m.wrap',
'a8m.trim',
'a8m.ltrim',
'a8m.rtrim',
'a8m.repeat',
'a8m.to-array',
'a8m.concat',
'a8m.contains',
'a8m.unique',
'a8m.is-empty',
'a8m.after',
'a8m.after-where',
'a8m.before',
'a8m.before-where',
'a8m.defaults',
'a8m.where',
'a8m.reverse',
'a8m.remove',
'a8m.remove-with',
'a8m.group-by',
'a8m.count-by',
'a8m.search-field',
'a8m.fuzzy-by',
'a8m.fuzzy',
'a8m.omit',
'a8m.pick',
'a8m.every',
'a8m.filter-by',
'a8m.xor',
'a8m.map',
'a8m.first',
'a8m.last',
'a8m.flatten',
'a8m.join',
'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 );