angular-data-grid-new
Version:
Light, flexible and performant Data Grid for AngularJS apps, with built-in sorting, pagination and filtering options, unified API for client-side and server-side data fetching, seamless synchronization with browser address bar and total freedom in mark-
447 lines (396 loc) • 19.6 kB
JavaScript
(function () {
'use strict';
angular
.module('dataGrid', [])
.filter('startFrom', function () {
return function (input, start) {
if (input) {
start = +start;
return input.slice(start);
}
return [];
}
})
.controller('gridController', ['$scope', '$rootScope', '$element', '$filter', '$location', 'filtersFactory', function ($scope, $rootScope, $element, $filter, $location, filtersFactory) {
// values by default
$scope._gridOptions = $scope.$eval($element.attr('grid-options'));
$scope._gridActions = $scope.$eval($element.attr('grid-actions'));
$scope.serverPagination = $element.attr('server-pagination') === 'true';
$scope.getDataDelay = $element.attr('get-delay') || 350;
if (!$scope._gridActions) {
$scope.$parent.$eval($element.attr('grid-actions') + '= {}');
$scope._gridActions = $scope.$parent.$eval($element.attr('grid-actions'));
}
$scope._gridOptions.grid = $scope;
$scope.filtered = $scope._gridOptions.data.slice();
$scope.paginationOptions = $scope._gridOptions.pagination ? angular.copy($scope._gridOptions.pagination) : {};
$scope.defaultsPaginationOptions = {
itemsPerPage: $scope.paginationOptions.itemsPerPage,
currentPage: $scope.paginationOptions.currentPage || 1
};
$scope.paginationOptions = angular.copy($scope.defaultsPaginationOptions);
$scope.sortOptions = $scope._gridOptions.sort ? angular.copy($scope._gridOptions.sort) : {};
$scope.customFilters = $scope._gridOptions.customFilters ? angular.copy($scope._gridOptions.customFilters) : {};
$scope.urlSync = $scope._gridOptions.urlSync;
$scope.$watchCollection('_gridOptions.data', function (newValue) {
if (newValue && newValue.length > -1) {
$scope.sortCache = {};
$scope.filtered = $scope._gridOptions.data.slice();
$scope.filters.forEach(function (filter) {
if (filter.filterType === 'select') {
$scope[filter.model + 'Options'] = generateOptions($scope.filtered, filter.filterBy);
}
});
if ($scope.urlSync) {
parseUrl();
} else {
applyFilters();
}
}
});
$scope.sort = function (predicate, isDefaultSort) {
if (!isDefaultSort) {
var direction = $scope.sortOptions.predicate === predicate && $scope.sortOptions.direction === 'desc' ? 'asc' : 'desc';
$scope.sortOptions.direction = direction;
$scope.sortOptions.predicate = predicate;
}
$scope.paginationOptions.currentPage = 1;
$scope.reloadGrid(isDefaultSort);
};
$scope.filter = function () {
$scope.paginationOptions.currentPage = 1;
$scope.reloadGrid();
};
$scope.$on('$locationChangeSuccess', function () {
onChangeStateOrLocation()
});
$scope.$on("$stateChangeSuccess", function (event, toState) {
onChangeStateOrLocation()
});
$scope.reloadGrid = function (isDefaultSort) {
if ($scope.urlSync || $scope.serverPagination) {
changePath(isDefaultSort);
} else {
applyFilters();
}
$rootScope.$broadcast('gridReloaded');
};
$scope._gridActions.refresh = $scope.reloadGrid;
$scope._gridActions.filter = $scope.filter;
$scope._gridActions.sort = $scope.sort;
function onChangeStateOrLocation(){
if ($scope.urlSync || $scope.serverPagination) {
if ($scope.serverPagination) {
clearTimeout($scope.getDataTimeout);
$scope.getDataTimeout = setTimeout(getData, $scope.getDataDelay);
}
if ($scope.filtered) {
parseUrl();
}
}
}
function changePath(isDefaultSort) {
var path, needApplyFilters = false;
path = 'page=' + $scope.paginationOptions.currentPage;
if ($scope.paginationOptions.itemsPerPage !== $scope.defaultsPaginationOptions.itemsPerPage) {
path += '&itemsPerPage=' + $scope.paginationOptions.itemsPerPage;
}
if ($scope.sortOptions.predicate) {
path += '&sort=' + encodeURIComponent($scope.sortOptions.predicate + "-" + $scope.sortOptions.direction);
}
//custom filters
$scope.filters.forEach(function (filter) {
var urlName = filter.model,
value = filter.isInScope ? $scope.$eval(urlName) : $scope.$parent.$eval(urlName);
if (filter.disableUrl) {
needApplyFilters = true;
return;
}
if (value) {
var strValue;
if (value instanceof Date) {
if (isNaN(value.getTime())) {
return;
}
strValue = value.getFullYear() + '-';
strValue += value.getMonth() < 9 ? '0' + (value.getMonth() + 1) + '-' : (value.getMonth() + 1) + '-';
strValue += value.getDate() < 10 ? '0' + value.getDate() : value.getDate();
value = strValue;
}
path += '&' + encodeURIComponent(urlName) + '=' + encodeURIComponent(value);
}
});
if (needApplyFilters) {
applyFilters();
}
$location.search(path);
if (isDefaultSort) {
$scope.$apply();
}
}
function parseUrl() {
var params = $location.search(),
customParams = {};
Object.keys(params).forEach(function(key) {
if (key !== 'page' && key !== 'sort' && key !== 'itemsPerPage') {
customParams[key] = params[key];
}
});
//custom filters
$scope.filters.forEach(function (filter) {
var urlName = filter.model,
value = customParams[urlName];
if (filter.disableUrl) {
return;
}
//datepicker-specific
if (~filter.filterType.toLowerCase().indexOf('date')) {
$scope.$parent.__evaltmp = value ? new Date(value) : null;
$scope.$parent.$eval(urlName + '=__evaltmp');
return;
}
if (filter.filterType === 'select' && !value) {
value = '';
}
if (value) {
if (filter.isInScope) {
$scope.__evaltmp = value;
$scope.$eval(urlName + '=__evaltmp');
} else {
$scope.$parent.__evaltmp = value;
$scope.$parent.$eval(urlName + '=__evaltmp');
}
}
});
//pagination options
$scope.paginationOptions.itemsPerPage = $scope.defaultsPaginationOptions.itemsPerPage;
$scope.paginationOptions.currentPage = $scope.defaultsPaginationOptions.currentPage;
if (params.itemsPerPage) {
$scope.paginationOptions.itemsPerPage = params.itemsPerPage;
}
if (params.page) {
if (!$scope.serverPagination && ((params.page - 1) * $scope.paginationOptions.itemsPerPage > $scope.filtered.length)) {
$scope.paginationOptions.currentPage = 1;
} else {
$scope.paginationOptions.currentPage = params.page;
}
}
//sort options
if (params.sort) {
var sort = params.sort.split('-');
$scope.sortOptions.predicate = decodeURIComponent(sort[0]);
$scope.sortOptions.direction = decodeURIComponent(sort[1]);
}
if (!$scope.serverPagination) {
applyFilters();
}
}
function getData() {
var url = '';
var params = $location.search();
Object.keys(params).forEach(function(key) {
url += key + '=' + params[key] + '&';
});
url = url.slice(0, -1);
if (!url && $scope.sortOptions.predicate) {
$scope.sort($scope.sortOptions.predicate, true);
} else {
$scope._gridOptions.getData('?' + url, function (data, totalItems) {
$scope.filtered = data;
$scope.paginationOptions.totalItems = totalItems;
});
}
// -> to promise
//$scope._gridOptions.getData('?' + url).then(function (data, totalItems) {
// $scope.filtered = data;
// $scope.paginationOptions.totalItems = totalItems;
//});
}
function applyFilters() {
var time = Date.now(), sorted = false;
//TO REMOVE ?
$scope._time = {};
if ($scope.sortOptions.predicate && $scope.sortCache && $scope.sortCache.predicate === $scope.sortOptions.predicate
&& $scope.sortCache.direction === $scope.sortOptions.direction) {
$scope.filtered = $scope.sortCache.data.slice();
sorted = true;
} else {
$scope.filtered = $scope._gridOptions.data.slice();
}
$scope._time.copy = Date.now() - time;
var time2 = Date.now();
applyCustomFilters();
$scope._time.filters = Date.now() - time2;
var time3 = Date.now();
if ($scope.sortOptions.predicate && !sorted) {
$scope.filtered = $filter('orderBy')($scope.filtered, $scope.sortOptions.predicate, $scope.sortOptions.direction === 'desc');
$scope.sortCache = {
data: $scope.filtered.slice(),
predicate: $scope.sortOptions.predicate,
direction: $scope.sortOptions.direction
}
}
$scope._time.sort = Date.now() - time3;
$scope._time.all = Date.now() - time;
$scope.paginationOptions.totalItems = $scope.filtered.length;
}
function applyCustomFilters() {
$scope.filters.forEach(function (filter) {
var predicate = filter.filterBy,
urlName = filter.model,
value = filter.isInScope ? $scope.$eval(urlName) : $scope.$parent.$eval(urlName),
type = filter.filterType;
if ($scope.customFilters[urlName]) {
$scope.filtered = $scope.customFilters[urlName]($scope.filtered, value, predicate);
} else if (value && type) {
var filterFunc = filtersFactory.getFilterByType(type);
if (filterFunc) {
$scope.filtered = filterFunc($scope.filtered, value, predicate);
}
}
});
}
}])
.directive('gridItem', ['$compile', function ($compile) {
return {
restrict: 'EA',
terminal:true,
scope: false,
link: function ($scope, element, attrs, ctrl, transclude) {
if ($scope.serverPagination) {
element.attr('ng-repeat', "item in filtered");
} else {
element.attr('ng-repeat', "item in filtered | startFrom:(paginationOptions.currentPage-1)*paginationOptions.itemsPerPage | limitTo:paginationOptions.itemsPerPage track by $index");
}
element.removeAttr('grid-item');
var html = element[0].outerHTML;
element.replaceWith($compile(html)($scope));
}
}
}])
.directive('gridData', ['$compile', '$animate', function ($compile) {
return {
restrict: 'EA',
//transclude: true,
//replace: true,
scope: true,
controller: 'gridController',
link: function ($scope, $element, attrs) {
var filters = [],
directiveElement = $element.parent(),
gridId = attrs.id,
serverPagination = attrs.serverPagination === 'true';
$scope.serverPagination = serverPagination;
angular.forEach(angular.element(directiveElement[0].querySelectorAll('[sortable]')), function (sortable) {
var element = angular.element(sortable),
predicate = element.attr('sortable');
element.attr('ng-class', "{'sort-ascent' : sortOptions.predicate ==='" +
predicate + "' && sortOptions.direction === 'asc', 'sort-descent' : sortOptions.predicate === '" +
predicate + "' && sortOptions.direction === 'desc'}");
element.attr('ng-click', "sort('" + predicate + "')");
$compile(element)($scope);
});
angular.forEach(document.querySelectorAll('[filter-by]'), function (filter) {
var element = angular.element(filter),
predicate = element.attr('filter-by'),
dataGridElement = document.querySelectorAll('[grid-data]')[0],
isInScope = dataGridElement.querySelectorAll('[filter-by="'+ predicate+'"]').length > 0,
filterType = element.attr('filter-type') || '',
urlName = element.attr('ng-model'),
disableUrl = element.attr('disable-url');
if (gridId && element.attr('grid-id') && gridId != element.attr('grid-id')) {
return;
}
if (filterType !== 'select') {
} else {
$scope[urlName + 'Options'] = generateOptions($scope.$eval($element.attr('grid-options') + '.data'), predicate);
}
if (~filterType.indexOf('date') && !element.attr('ng-focus')
&& !element.attr('ng-blur')) {
element.attr('ng-focus', "filter('{" + urlName + " : " + "this." + urlName + "}')");
element.attr('ng-blur', "filter('{" + urlName + " : " + "this." + urlName + "}')");
//$compile(element)($scope);
}
if (!urlName) {
urlName = predicate;
element.attr('ng-model', predicate);
element.attr('ng-change', 'filter()');
//$compile(element)($scope);
}
//$compile(element)($scope);
filters.push({
model: urlName,
isInScope: isInScope,
filterBy: predicate,
filterType: filterType,
disableUrl: disableUrl
});
});
$scope.filters = filters;
}
}
}])
.factory('filtersFactory', function () {
function selectFilter(items, value, predicate) {
return items.filter(function (item) {
return value && item[predicate] ? item[predicate] === value : true;
});
}
function textFilter(items, value, predicate) {
return items.filter(function (item) {
return value && item[predicate] ? ~(item[predicate] + '').toLowerCase().indexOf((value + '').toLowerCase()) : !!item[predicate];
});
}
function dateToFilter(items, value, predicate) {
value = new Date(value).getTime();
return items.filter(function (item) {
return value && item[predicate] ? item[predicate] <= value + 86399999 : true;
});
}
function dateFromFilter(items, value, predicate) {
value = new Date(value).getTime();
return items.filter(function (item) {
return value && item[predicate] ? item[predicate] >= value : true;
});
}
return {
getFilterByType: function (type) {
switch (type) {
case 'select' :
{
return selectFilter;
}
case 'text' :
{
return textFilter;
}
case 'dateTo':
{
return dateToFilter;
}
case 'dateFrom':
{
return dateFromFilter;
}
default :
{
return null;
}
}
}
}
});
function generateOptions(values, predicate) {
var array = [];
if (values) {
values.forEach(function (item) {
if (!~array.indexOf(item[predicate])) {
array.push(item[predicate]);
}
});
return array.map(function (option) {
return {text: option, value: option};
});
}
}
})();