Built motion from commit 1038d87.|0.0.141
[motion.git] / public / bower_components / angular-smart-table / smart-table.js
1 /** 
2 * @version 2.1.5
3 * @license MIT
4 */
5 (function (ng, undefined){
6     'use strict';
7
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>' +
12         '</ul></nav>');
13 }]);
14
15
16 ng.module('smart-table')
17   .constant('stConfig', {
18     pagination: {
19       template: 'template/smart-table/pagination.html',
20       itemsByPage: 10,
21       displayedPages: 5
22     },
23     search: {
24       delay: 400, // ms
25       inputEvent: 'input'
26     },
27     select: {
28       mode: 'single',
29       selectedClass: 'st-selected'
30     },
31     sort: {
32       ascentClass: 'st-sort-ascent',
33       descentClass: 'st-sort-descent',
34       skipNatural: false,
35       delay:300
36     },
37     pipe: {
38       delay: 100 //ms
39     }
40   });
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;
46     var safeGetter;
47     var orderBy = $filter('orderBy');
48     var filter = $filter('filter');
49     var safeCopy = copyRefs(displayGetter($scope));
50     var tableState = {
51       sort: {},
52       search: {},
53       pagination: {
54         start: 0,
55         totalItemCount: 0
56       }
57     };
58     var filtered;
59     var pipeAfterSafeCopy = true;
60     var ctrl = this;
61     var lastSelected;
62
63     function copyRefs (src) {
64       return src ? [].concat(src) : [];
65     }
66
67     function updateSafeCopy () {
68       safeCopy = copyRefs(safeGetter($scope));
69       if (pipeAfterSafeCopy === true) {
70         ctrl.pipe();
71       }
72     }
73
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);
83         }
84       } else {
85         delete object[path];
86       }
87     }
88
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) {
96           updateSafeCopy();
97         }
98       });
99       $scope.$watch(function () {
100         var safeSrc = safeGetter($scope);
101         return safeSrc ? safeSrc.length : 0;
102       }, function (newValue, oldValue) {
103         if (newValue !== safeCopy.length) {
104           updateSafeCopy();
105         }
106       });
107       $scope.$watch(function () {
108         return safeGetter($scope);
109       }, function (newValue, oldValue) {
110         if (newValue !== oldValue) {
111           tableState.pagination.start = 0;
112           updateSafeCopy();
113         }
114       });
115     }
116
117     /**
118      * sort the rows
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
121      */
122     this.sortBy = function sortBy (predicate, reverse) {
123       tableState.sort.predicate = predicate;
124       tableState.sort.reverse = reverse === true;
125
126       if (ng.isFunction(predicate)) {
127         tableState.sort.functionName = predicate.name;
128       } else {
129         delete tableState.sort.functionName;
130       }
131
132       tableState.pagination.start = 0;
133       return this.pipe();
134     };
135
136     /**
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
140      */
141     this.search = function search (input, predicate) {
142       var predicateObject = tableState.search.predicateObject || {};
143       var prop = predicate ? predicate : '$';
144
145       input = ng.isString(input) ? input.trim() : input;
146       $parse(prop).assign(predicateObject, input);
147       // to avoid to filter out null value
148       if (!input) {
149         deepDelete(predicateObject, prop);
150       }
151       tableState.search.predicateObject = predicateObject;
152       tableState.pagination.start = 0;
153       return this.pipe();
154     };
155
156     /**
157      * this will chain the operations of sorting and filtering based on the current table state (sort options, filtering, ect)
158      */
159     this.pipe = function pipe () {
160       var pagination = tableState.pagination;
161       var output;
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);
165       }
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));
171       }
172       displaySetter($scope, output || filtered);
173     };
174
175     /**
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)
179      */
180     this.select = function select (row, mode) {
181       var rows = copyRefs(displayGetter($scope));
182       var index = rows.indexOf(row);
183       if (index !== -1) {
184         if (mode === 'single') {
185           row.isSelected = row.isSelected !== true;
186           if (lastSelected) {
187             lastSelected.isSelected = false;
188           }
189           lastSelected = row.isSelected === true ? row : undefined;
190         } else {
191           rows[index].isSelected = !rows[index].isSelected;
192         }
193       }
194     };
195
196     /**
197      * take a slice of the current sorted/filtered collection (pagination)
198      *
199      * @param {Number} start - start index of the slice
200      * @param {Number} number - the number of item in the slice
201      */
202     this.slice = function splice (start, number) {
203       tableState.pagination.start = start;
204       tableState.pagination.number = number;
205       return this.pipe();
206     };
207
208     /**
209      * return the current state of the table
210      * @returns {{sort: {}, search: {}, pagination: {start: number}}}
211      */
212     this.tableState = function getTableState () {
213       return tableState;
214     };
215
216     this.getFilteredCollection = function getFilteredCollection () {
217       return filtered || safeCopy;
218     };
219
220     /**
221      * Use a different filter function than the angular FilterFilter
222      * @param filterName the name under which the custom filter is registered
223      */
224     this.setFilterFunction = function setFilterFunction (filterName) {
225       filter = $filter(filterName);
226     };
227
228     /**
229      * Use a different function than the angular orderBy
230      * @param sortFunctionName the name under which the custom order function is registered
231      */
232     this.setSortFunction = function setSortFunction (sortFunctionName) {
233       orderBy = $filter(sortFunctionName);
234     };
235
236     /**
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
239      */
240     this.preventPipeOnWatch = function preventPipe () {
241       pipeAfterSafeCopy = false;
242     };
243   }])
244   .directive('stTable', function () {
245     return {
246       restrict: 'A',
247       controller: 'stTableController',
248       link: function (scope, element, attr, ctrl) {
249
250         if (attr.stSetFilter) {
251           ctrl.setFilterFunction(attr.stSetFilter);
252         }
253
254         if (attr.stSetSort) {
255           ctrl.setSortFunction(attr.stSetSort);
256         }
257       }
258     };
259   });
260
261 ng.module('smart-table')
262   .directive('stSearch', ['stConfig', '$timeout','$parse', function (stConfig, $timeout, $parse) {
263     return {
264       require: '^stTable',
265       link: function (scope, element, attr, ctrl) {
266         var tableCtrl = ctrl;
267         var promise = null;
268         var throttle = attr.stDelay || stConfig.search.delay;
269         var event = attr.stInputEvent || stConfig.search.inputEvent;
270
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);
276           }
277         });
278
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) || '';
286           }
287         }, true);
288
289         // view -> table state
290         element.bind(event, function (evt) {
291           evt = evt.originalEvent || evt;
292           if (promise !== null) {
293             $timeout.cancel(promise);
294           }
295
296           promise = $timeout(function () {
297             tableCtrl.search(evt.target.value, attr.stSearch || '');
298             promise = null;
299           }, throttle);
300         });
301       }
302     };
303   }]);
304
305 ng.module('smart-table')
306   .directive('stSelectRow', ['stConfig', function (stConfig) {
307     return {
308       restrict: 'A',
309       require: '^stTable',
310       scope: {
311         row: '=stSelectRow'
312       },
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);
318           });
319         });
320
321         scope.$watch('row.isSelected', function (newValue) {
322           if (newValue === true) {
323             element.addClass(stConfig.select.selectedClass);
324           } else {
325             element.removeClass(stConfig.select.selectedClass);
326           }
327         });
328       }
329     };
330   }]);
331
332 ng.module('smart-table')
333   .directive('stSort', ['stConfig', '$parse', '$timeout', function (stConfig, $parse, $timeout) {
334     return {
335       restrict: 'A',
336       require: '^stTable',
337       link: function (scope, element, attr, ctrl) {
338
339         var predicate = attr.stSort;
340         var getter = $parse(predicate);
341         var index = 0;
342         var classAscent = attr.stClassAscent || stConfig.sort.ascentClass;
343         var classDescent = attr.stClassDescent || stConfig.sort.descentClass;
344         var stateClasses = [classAscent, classDescent];
345         var sortDefault;
346         var skipNatural = attr.stSkipNatural !== undefined ? attr.stSkipNatural : stConfig.sort.skipNatural;
347         var promise = null;
348         var throttle = attr.stDelay || stConfig.sort.delay;
349
350         if (attr.stSortDefault) {
351           sortDefault = scope.$eval(attr.stSortDefault) !== undefined ? scope.$eval(attr.stSortDefault) : attr.stSortDefault;
352         }
353
354         //view --> table state
355         function sort () {
356           index++;
357           var func;
358           predicate = ng.isFunction(getter(scope)) || ng.isArray(getter(scope)) ? getter(scope) : attr.stSort;
359           if (index % 3 === 0 && !!skipNatural !== true) {
360             //manual reset
361             index = 0;
362             ctrl.tableState().sort = {};
363             ctrl.tableState().pagination.start = 0;
364             func = ctrl.pipe.bind(ctrl);
365           } else {
366             func = ctrl.sortBy.bind(ctrl, predicate, index % 2 === 0);
367           }
368           if (promise !== null) {
369             $timeout.cancel(promise);
370           }
371           if (throttle < 0) {
372             scope.$apply(func);
373           } else {
374             promise = $timeout(func, throttle);
375           }
376         }
377
378         element.bind('click', function sortClick () {
379           if (predicate) {
380             sort();
381           }
382         });
383
384         if (sortDefault) {
385           index = sortDefault === 'reverse' ? 1 : 0;
386           sort();
387         }
388
389         //table state --> view
390         scope.$watch(function () {
391           return ctrl.tableState().sort;
392         }, function (newValue) {
393           if (newValue.predicate !== predicate) {
394             index = 0;
395             element
396               .removeClass(classAscent)
397               .removeClass(classDescent);
398           } else {
399             index = newValue.reverse === true ? 2 : 1;
400             element
401               .removeClass(stateClasses[index % 2])
402               .addClass(stateClasses[index - 1]);
403           }
404         }, true);
405       }
406     };
407   }]);
408
409 ng.module('smart-table')
410   .directive('stPagination', ['stConfig', function (stConfig) {
411     return {
412       restrict: 'EA',
413       require: '^stTable',
414       scope: {
415         stItemsByPage: '=?',
416         stDisplayedPages: '=?',
417         stPageChange: '&'
418       },
419       templateUrl: function (element, attrs) {
420         if (attrs.stTemplate) {
421           return attrs.stTemplate;
422         }
423         return stConfig.pagination.template;
424       },
425       link: function (scope, element, attrs, ctrl) {
426
427         scope.stItemsByPage = scope.stItemsByPage ? +(scope.stItemsByPage) : stConfig.pagination.itemsByPage;
428         scope.stDisplayedPages = scope.stDisplayedPages ? +(scope.stDisplayedPages) : stConfig.pagination.displayedPages;
429
430         scope.currentPage = 1;
431         scope.pages = [];
432
433         function redraw () {
434           var paginationState = ctrl.tableState().pagination;
435           var start = 1;
436           var end;
437           var i;
438           var prevPage = scope.currentPage;
439           scope.totalItemCount = paginationState.totalItemCount;
440           scope.currentPage = Math.floor(paginationState.start / paginationState.number) + 1;
441
442           start = Math.max(start, scope.currentPage - Math.abs(Math.floor(scope.stDisplayedPages / 2)));
443           end = start + scope.stDisplayedPages;
444
445           if (end > paginationState.numberOfPages) {
446             end = paginationState.numberOfPages + 1;
447             start = Math.max(1, end - scope.stDisplayedPages);
448           }
449
450           scope.pages = [];
451           scope.numPages = paginationState.numberOfPages;
452
453           for (i = start; i < end; i++) {
454             scope.pages.push(i);
455           }
456
457           if (prevPage !== scope.currentPage) {
458             scope.stPageChange({newPage: scope.currentPage});
459           }
460         }
461
462         //table state --> view
463         scope.$watch(function () {
464           return ctrl.tableState().pagination;
465         }, redraw, true);
466
467         //scope --> table state  (--> view)
468         scope.$watch('stItemsByPage', function (newValue, oldValue) {
469           if (newValue !== oldValue) {
470             scope.selectPage(1);
471           }
472         });
473
474         scope.$watch('stDisplayedPages', redraw);
475
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);
480           }
481         };
482
483         if (!ctrl.tableState().pagination.number) {
484           ctrl.slice(0, scope.stItemsByPage);
485         }
486       }
487     };
488   }]);
489
490 ng.module('smart-table')
491   .directive('stPipe', ['stConfig', '$timeout', function (config, $timeout) {
492     return {
493       require: 'stTable',
494       scope: {
495         stPipe: '='
496       },
497       link: {
498
499         pre: function (scope, element, attrs, ctrl) {
500
501           var pipePromise = null;
502
503           if (ng.isFunction(scope.stPipe)) {
504             ctrl.preventPipeOnWatch();
505             ctrl.pipe = function () {
506
507               if (pipePromise !== null) {
508                 $timeout.cancel(pipePromise)
509               }
510
511               pipePromise = $timeout(function () {
512                 scope.stPipe(ctrl.tableState(), ctrl);
513               }, config.pipe.delay);
514
515               return pipePromise;
516             }
517           }
518         },
519
520         post: function (scope, element, attrs, ctrl) {
521           ctrl.pipe();
522         }
523       }
524     };
525   }]);
526
527 })(angular);