5 (function (ng, undefined){
8 ng.module('smart-table', []).run(['$templateCache', function ($templateCache) {
9 $templateCache.put('template/smart-table/pagination.html',
10 '<nav ng-if="numPages && pages.length >= 2"><ul class="pagination">' +
11 '<li ng-repeat="page in pages" ng-class="{active: page==currentPage}"><a ng-click="selectPage(page)">{{page}}</a></li>' +
16 ng.module('smart-table')
17 .constant('stConfig', {
19 template: 'template/smart-table/pagination.html',
29 selectedClass: 'st-selected'
32 ascentClass: 'st-sort-ascent',
33 descentClass: 'st-sort-descent',
41 ng.module('smart-table')
42 .controller('stTableController', ['$scope', '$parse', '$filter', '$attrs', function StTableController ($scope, $parse, $filter, $attrs) {
43 var propertyName = $attrs.stTable;
44 var displayGetter = $parse(propertyName);
45 var displaySetter = displayGetter.assign;
47 var orderBy = $filter('orderBy');
48 var filter = $filter('filter');
49 var safeCopy = copyRefs(displayGetter($scope));
59 var pipeAfterSafeCopy = true;
63 function copyRefs (src) {
64 return src ? [].concat(src) : [];
67 function updateSafeCopy () {
68 safeCopy = copyRefs(safeGetter($scope));
69 if (pipeAfterSafeCopy === true) {
74 function deepDelete (object, path) {
75 if (path.indexOf('.') != -1) {
76 var partials = path.split('.');
77 var key = partials.pop();
78 var parentPath = partials.join('.');
79 var parentObject = $parse(parentPath)(object)
80 delete parentObject[key];
81 if (Object.keys(parentObject).length == 0) {
82 deepDelete(object, parentPath);
89 if ($attrs.stSafeSrc) {
90 safeGetter = $parse($attrs.stSafeSrc);
91 $scope.$watch(function () {
92 var safeSrc = safeGetter($scope);
93 return safeSrc && safeSrc.length ? safeSrc[0] : undefined;
94 }, function (newValue, oldValue) {
95 if (newValue !== oldValue) {
99 $scope.$watch(function () {
100 var safeSrc = safeGetter($scope);
101 return safeSrc ? safeSrc.length : 0;
102 }, function (newValue, oldValue) {
103 if (newValue !== safeCopy.length) {
107 $scope.$watch(function () {
108 return safeGetter($scope);
109 }, function (newValue, oldValue) {
110 if (newValue !== oldValue) {
111 tableState.pagination.start = 0;
119 * @param {Function | String} predicate - function or string which will be used as predicate for the sorting
120 * @param [reverse] - if you want to reverse the order
122 this.sortBy = function sortBy (predicate, reverse) {
123 tableState.sort.predicate = predicate;
124 tableState.sort.reverse = reverse === true;
126 if (ng.isFunction(predicate)) {
127 tableState.sort.functionName = predicate.name;
129 delete tableState.sort.functionName;
132 tableState.pagination.start = 0;
137 * search matching rows
138 * @param {String} input - the input string
139 * @param {String} [predicate] - the property name against you want to check the match, otherwise it will search on all properties
141 this.search = function search (input, predicate) {
142 var predicateObject = tableState.search.predicateObject || {};
143 var prop = predicate ? predicate : '$';
145 input = ng.isString(input) ? input.trim() : input;
146 $parse(prop).assign(predicateObject, input);
147 // to avoid to filter out null value
149 deepDelete(predicateObject, prop);
151 tableState.search.predicateObject = predicateObject;
152 tableState.pagination.start = 0;
157 * this will chain the operations of sorting and filtering based on the current table state (sort options, filtering, ect)
159 this.pipe = function pipe () {
160 var pagination = tableState.pagination;
162 filtered = tableState.search.predicateObject ? filter(safeCopy, tableState.search.predicateObject) : safeCopy;
163 if (tableState.sort.predicate) {
164 filtered = orderBy(filtered, tableState.sort.predicate, tableState.sort.reverse);
166 pagination.totalItemCount = filtered.length;
167 if (pagination.number !== undefined) {
168 pagination.numberOfPages = filtered.length > 0 ? Math.ceil(filtered.length / pagination.number) : 1;
169 pagination.start = pagination.start >= filtered.length ? (pagination.numberOfPages - 1) * pagination.number : pagination.start;
170 output = filtered.slice(pagination.start, pagination.start + parseInt(pagination.number));
172 displaySetter($scope, output || filtered);
176 * select a dataRow (it will add the attribute isSelected to the row object)
177 * @param {Object} row - the row to select
178 * @param {String} [mode] - "single" or "multiple" (multiple by default)
180 this.select = function select (row, mode) {
181 var rows = copyRefs(displayGetter($scope));
182 var index = rows.indexOf(row);
184 if (mode === 'single') {
185 row.isSelected = row.isSelected !== true;
187 lastSelected.isSelected = false;
189 lastSelected = row.isSelected === true ? row : undefined;
191 rows[index].isSelected = !rows[index].isSelected;
197 * take a slice of the current sorted/filtered collection (pagination)
199 * @param {Number} start - start index of the slice
200 * @param {Number} number - the number of item in the slice
202 this.slice = function splice (start, number) {
203 tableState.pagination.start = start;
204 tableState.pagination.number = number;
209 * return the current state of the table
210 * @returns {{sort: {}, search: {}, pagination: {start: number}}}
212 this.tableState = function getTableState () {
216 this.getFilteredCollection = function getFilteredCollection () {
217 return filtered || safeCopy;
221 * Use a different filter function than the angular FilterFilter
222 * @param filterName the name under which the custom filter is registered
224 this.setFilterFunction = function setFilterFunction (filterName) {
225 filter = $filter(filterName);
229 * Use a different function than the angular orderBy
230 * @param sortFunctionName the name under which the custom order function is registered
232 this.setSortFunction = function setSortFunction (sortFunctionName) {
233 orderBy = $filter(sortFunctionName);
237 * Usually when the safe copy is updated the pipe function is called.
238 * Calling this method will prevent it, which is something required when using a custom pipe function
240 this.preventPipeOnWatch = function preventPipe () {
241 pipeAfterSafeCopy = false;
244 .directive('stTable', function () {
247 controller: 'stTableController',
248 link: function (scope, element, attr, ctrl) {
250 if (attr.stSetFilter) {
251 ctrl.setFilterFunction(attr.stSetFilter);
254 if (attr.stSetSort) {
255 ctrl.setSortFunction(attr.stSetSort);
261 ng.module('smart-table')
262 .directive('stSearch', ['stConfig', '$timeout','$parse', function (stConfig, $timeout, $parse) {
265 link: function (scope, element, attr, ctrl) {
266 var tableCtrl = ctrl;
268 var throttle = attr.stDelay || stConfig.search.delay;
269 var event = attr.stInputEvent || stConfig.search.inputEvent;
271 attr.$observe('stSearch', function (newValue, oldValue) {
272 var input = element[0].value;
273 if (newValue !== oldValue && input) {
274 ctrl.tableState().search = {};
275 tableCtrl.search(input, newValue);
279 //table state -> view
280 scope.$watch(function () {
281 return ctrl.tableState().search;
282 }, function (newValue, oldValue) {
283 var predicateExpression = attr.stSearch || '$';
284 if (newValue.predicateObject && $parse(predicateExpression)(newValue.predicateObject) !== element[0].value) {
285 element[0].value = $parse(predicateExpression)(newValue.predicateObject) || '';
289 // view -> table state
290 element.bind(event, function (evt) {
291 evt = evt.originalEvent || evt;
292 if (promise !== null) {
293 $timeout.cancel(promise);
296 promise = $timeout(function () {
297 tableCtrl.search(evt.target.value, attr.stSearch || '');
305 ng.module('smart-table')
306 .directive('stSelectRow', ['stConfig', function (stConfig) {
313 link: function (scope, element, attr, ctrl) {
314 var mode = attr.stSelectMode || stConfig.select.mode;
315 element.bind('click', function () {
316 scope.$apply(function () {
317 ctrl.select(scope.row, mode);
321 scope.$watch('row.isSelected', function (newValue) {
322 if (newValue === true) {
323 element.addClass(stConfig.select.selectedClass);
325 element.removeClass(stConfig.select.selectedClass);
332 ng.module('smart-table')
333 .directive('stSort', ['stConfig', '$parse', '$timeout', function (stConfig, $parse, $timeout) {
337 link: function (scope, element, attr, ctrl) {
339 var predicate = attr.stSort;
340 var getter = $parse(predicate);
342 var classAscent = attr.stClassAscent || stConfig.sort.ascentClass;
343 var classDescent = attr.stClassDescent || stConfig.sort.descentClass;
344 var stateClasses = [classAscent, classDescent];
346 var skipNatural = attr.stSkipNatural !== undefined ? attr.stSkipNatural : stConfig.sort.skipNatural;
348 var throttle = attr.stDelay || stConfig.sort.delay;
350 if (attr.stSortDefault) {
351 sortDefault = scope.$eval(attr.stSortDefault) !== undefined ? scope.$eval(attr.stSortDefault) : attr.stSortDefault;
354 //view --> table state
358 predicate = ng.isFunction(getter(scope)) || ng.isArray(getter(scope)) ? getter(scope) : attr.stSort;
359 if (index % 3 === 0 && !!skipNatural !== true) {
362 ctrl.tableState().sort = {};
363 ctrl.tableState().pagination.start = 0;
364 func = ctrl.pipe.bind(ctrl);
366 func = ctrl.sortBy.bind(ctrl, predicate, index % 2 === 0);
368 if (promise !== null) {
369 $timeout.cancel(promise);
374 promise = $timeout(func, throttle);
378 element.bind('click', function sortClick () {
385 index = sortDefault === 'reverse' ? 1 : 0;
389 //table state --> view
390 scope.$watch(function () {
391 return ctrl.tableState().sort;
392 }, function (newValue) {
393 if (newValue.predicate !== predicate) {
396 .removeClass(classAscent)
397 .removeClass(classDescent);
399 index = newValue.reverse === true ? 2 : 1;
401 .removeClass(stateClasses[index % 2])
402 .addClass(stateClasses[index - 1]);
409 ng.module('smart-table')
410 .directive('stPagination', ['stConfig', function (stConfig) {
416 stDisplayedPages: '=?',
419 templateUrl: function (element, attrs) {
420 if (attrs.stTemplate) {
421 return attrs.stTemplate;
423 return stConfig.pagination.template;
425 link: function (scope, element, attrs, ctrl) {
427 scope.stItemsByPage = scope.stItemsByPage ? +(scope.stItemsByPage) : stConfig.pagination.itemsByPage;
428 scope.stDisplayedPages = scope.stDisplayedPages ? +(scope.stDisplayedPages) : stConfig.pagination.displayedPages;
430 scope.currentPage = 1;
434 var paginationState = ctrl.tableState().pagination;
438 var prevPage = scope.currentPage;
439 scope.totalItemCount = paginationState.totalItemCount;
440 scope.currentPage = Math.floor(paginationState.start / paginationState.number) + 1;
442 start = Math.max(start, scope.currentPage - Math.abs(Math.floor(scope.stDisplayedPages / 2)));
443 end = start + scope.stDisplayedPages;
445 if (end > paginationState.numberOfPages) {
446 end = paginationState.numberOfPages + 1;
447 start = Math.max(1, end - scope.stDisplayedPages);
451 scope.numPages = paginationState.numberOfPages;
453 for (i = start; i < end; i++) {
457 if (prevPage !== scope.currentPage) {
458 scope.stPageChange({newPage: scope.currentPage});
462 //table state --> view
463 scope.$watch(function () {
464 return ctrl.tableState().pagination;
467 //scope --> table state (--> view)
468 scope.$watch('stItemsByPage', function (newValue, oldValue) {
469 if (newValue !== oldValue) {
474 scope.$watch('stDisplayedPages', redraw);
476 //view -> table state
477 scope.selectPage = function (page) {
478 if (page > 0 && page <= scope.numPages) {
479 ctrl.slice((page - 1) * scope.stItemsByPage, scope.stItemsByPage);
483 if (!ctrl.tableState().pagination.number) {
484 ctrl.slice(0, scope.stItemsByPage);
490 ng.module('smart-table')
491 .directive('stPipe', ['stConfig', '$timeout', function (config, $timeout) {
499 pre: function (scope, element, attrs, ctrl) {
501 var pipePromise = null;
503 if (ng.isFunction(scope.stPipe)) {
504 ctrl.preventPipeOnWatch();
505 ctrl.pipe = function () {
507 if (pipePromise !== null) {
508 $timeout.cancel(pipePromise)
511 pipePromise = $timeout(function () {
512 scope.stPipe(ctrl.tableState(), ctrl);
513 }, config.pipe.delay);
520 post: function (scope, element, attrs, ctrl) {