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, totalItemCount: 0 } }; var filtered; 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(); } } function deepDelete (object, path) { if (path.indexOf('.') != -1) { var partials = path.split('.'); var key = partials.pop(); var parentPath = partials.join('.'); var parentObject = $parse(parentPath)(object) delete parentObject[key]; if (Object.keys(parentObject).length == 0) { deepDelete(object, parentPath); } } else { delete object[path]; } } if ($attrs.stSafeSrc) { safeGetter = $parse($attrs.stSafeSrc); $scope.$watch(function () { var safeSrc = safeGetter($scope); return safeSrc && safeSrc.length ? safeSrc[0] : undefined; }, function (newValue, oldValue) { if (newValue !== oldValue) { updateSafeCopy(); } }); $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) { tableState.pagination.start = 0; 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; $parse(prop).assign(predicateObject, input); // to avoid to filter out null value if (!input) { deepDelete(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 output; filtered = tableState.search.predicateObject ? filter(safeCopy, tableState.search.predicateObject) : safeCopy; if (tableState.sort.predicate) { filtered = orderBy(filtered, tableState.sort.predicate, tableState.sort.reverse); } pagination.totalItemCount = filtered.length; 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; output = filtered.slice(pagination.start, pagination.start + parseInt(pagination.number)); } displaySetter($scope, output || 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 = copyRefs(displayGetter($scope)); 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; }; this.getFilteredCollection = function getFilteredCollection () { return filtered || safeCopy; }; /** * 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); }; /** * Use 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); } } }; });