/* * Angular Material Data Table * https://github.com/daniel-nagy/md-data-table * @license MIT * v0.10.9 */ (function (window, angular, undefined) { 'use strict'; angular.module('md.table.templates', ['md-table-pagination.html', 'md-table-progress.html', 'arrow-up.svg', 'navigate-before.svg', 'navigate-first.svg', 'navigate-last.svg', 'navigate-next.svg']); angular.module('md-table-pagination.html', []).run(['$templateCache', function($templateCache) { $templateCache.put('md-table-pagination.html', '
\n' + '
{{$pagination.label.page}}
\n' + '\n' + ' \n' + ' \n' + ' {{page}}\n' + ' \n' + ' \n' + '
\n' + '\n' + '
\n' + '
{{$pagination.label.rowsPerPage}}
\n' + '\n' + ' \n' + ' {{::option.label ? option.label : option}}\n' + ' \n' + '
\n' + '\n' + '
\n' + '
{{$pagination.min()}} - {{$pagination.max()}} {{$pagination.label.of}} {{$pagination.total}}
\n' + '\n' + ' \n' + ' \n' + ' \n' + '\n' + ' \n' + ' \n' + ' \n' + '\n' + ' \n' + ' \n' + ' \n' + '\n' + ' \n' + ' \n' + ' \n' + '
'); }]); angular.module('md-table-progress.html', []).run(['$templateCache', function($templateCache) { $templateCache.put('md-table-progress.html', '\n' + ' \n' + ' \n' + ' \n' + ''); }]); angular.module('arrow-up.svg', []).run(['$templateCache', function($templateCache) { $templateCache.put('arrow-up.svg', ''); }]); angular.module('navigate-before.svg', []).run(['$templateCache', function($templateCache) { $templateCache.put('navigate-before.svg', ''); }]); angular.module('navigate-first.svg', []).run(['$templateCache', function($templateCache) { $templateCache.put('navigate-first.svg', ''); }]); angular.module('navigate-last.svg', []).run(['$templateCache', function($templateCache) { $templateCache.put('navigate-last.svg', ''); }]); angular.module('navigate-next.svg', []).run(['$templateCache', function($templateCache) { $templateCache.put('navigate-next.svg', ''); }]); angular.module('md.data.table', ['md.table.templates']); angular.module('md.data.table').directive('mdBody', mdBody); function mdBody() { function compile(tElement) { tElement.addClass('md-body'); } return { compile: compile, restrict: 'A' }; } angular.module('md.data.table').directive('mdCell', mdCell); function mdCell() { function compile(tElement) { var select = tElement.find('md-select'); if(select.length) { select.addClass('md-table-select').attr('md-container-class', 'md-table-select'); } tElement.addClass('md-cell'); return postLink; } // empty controller to be bind properties to in postLink function function Controller() { } function postLink(scope, element, attrs, ctrls) { var select = element.find('md-select'); var cellCtrl = ctrls.shift(); var tableCtrl = ctrls.shift(); if(attrs.ngClick) { element.addClass('md-clickable'); } if(select.length) { select.on('click', function (event) { event.stopPropagation(); }); element.addClass('md-clickable').on('click', function (event) { event.stopPropagation(); select[0].click(); }); } cellCtrl.getTable = tableCtrl.getElement; function getColumn() { return tableCtrl.$$columns[getIndex()]; } function getIndex() { return Array.prototype.indexOf.call(element.parent().children(), element[0]); } scope.$watch(getColumn, function (column) { if(!column) { return; } if(column.numeric) { element.addClass('md-numeric'); } else { element.removeClass('md-numeric'); } }); } return { controller: Controller, compile: compile, require: ['mdCell', '^^mdTable'], restrict: 'A' }; } angular.module('md.data.table').directive('mdColumn', mdColumn); function mdColumn($compile, $mdUtil) { function compile(tElement) { tElement.addClass('md-column'); return postLink; } function postLink(scope, element, attrs, ctrls) { var headCtrl = ctrls.shift(); var tableCtrl = ctrls.shift(); function attachSortIcon() { var sortIcon = angular.element(''); $compile(sortIcon.addClass('md-sort-icon').attr('ng-class', 'getDirection()'))(scope); if(element.hasClass('md-numeric')) { element.prepend(sortIcon); } else { element.append(sortIcon); } } function detachSortIcon() { Array.prototype.some.call(element.find('md-icon'), function (icon) { return icon.classList.contains('md-sort-icon') && element[0].removeChild(icon); }); } function disableSorting() { detachSortIcon(); element.removeClass('md-sort').off('click', setOrder); } function enableSorting() { attachSortIcon(); element.addClass('md-sort').on('click', setOrder); } function getIndex() { return Array.prototype.indexOf.call(element.parent().children(), element[0]); } function isActive() { return scope.orderBy && (headCtrl.order === scope.orderBy || headCtrl.order === '-' + scope.orderBy); } function isNumeric() { return attrs.mdNumeric === '' || scope.numeric; } function setOrder() { scope.$applyAsync(function () { if(isActive()) { headCtrl.order = scope.getDirection() === 'md-asc' ? '-' + scope.orderBy : scope.orderBy; } else { headCtrl.order = scope.getDirection() === 'md-asc' ? scope.orderBy : '-' + scope.orderBy; } if(angular.isFunction(headCtrl.onReorder)) { $mdUtil.nextTick(function () { headCtrl.onReorder(headCtrl.order); }); } }); } function updateColumn(index, column) { tableCtrl.$$columns[index] = column; if(column.numeric) { element.addClass('md-numeric'); } else { element.removeClass('md-numeric'); } } scope.getDirection = function () { if(isActive()) { return headCtrl.order.charAt(0) === '-' ? 'md-desc' : 'md-asc'; } return attrs.mdDesc === '' || scope.$eval(attrs.mdDesc) ? 'md-desc' : 'md-asc'; }; scope.$watch(isActive, function (active) { if(active) { element.addClass('md-active'); } else { element.removeClass('md-active'); } }); scope.$watch(getIndex, function (index) { updateColumn(index, {'numeric': isNumeric()}); }); scope.$watch(isNumeric, function (numeric) { updateColumn(getIndex(), {'numeric': numeric}); }); scope.$watch('orderBy', function (orderBy) { if(orderBy) { if(!element.hasClass('md-sort')) { enableSorting(); } } else if(element.hasClass('md-sort')) { disableSorting(); } }); } return { compile: compile, require: ['^^mdHead', '^^mdTable'], restrict: 'A', scope: { numeric: '=?mdNumeric', orderBy: '@?mdOrderBy' } }; } mdColumn.$inject = ['$compile', '$mdUtil']; angular.module('md.data.table') .decorator('$controller', controllerDecorator) .factory('$mdEditDialog', mdEditDialog); /* * A decorator for ng.$controller to optionally bind properties to the * controller before invoking the constructor. Stolen from the ngMock. * * https://docs.angularjs.org/api/ngMock/service/$controller */ function controllerDecorator($delegate) { return function(expression, locals, later, ident) { if(later && typeof later === 'object') { var create = $delegate(expression, locals, true, ident); angular.extend(create.instance, later); return create(); } return $delegate(expression, locals, later, ident); }; } controllerDecorator.$inject = ['$delegate']; function mdEditDialog($compile, $controller, $document, $mdUtil, $q, $rootScope, $templateCache, $templateRequest, $window) { /* jshint validthis: true */ var ESCAPE = 27; var busy = false; var body = angular.element($document.prop('body')); /* * bindToController * controller * controllerAs * locals * resolve * scope * targetEvent * template * templateUrl */ var defaultOptions = { clickOutsideToClose: true, disableScroll: true, escToClose: true, focusOnOpen: true }; function build(template, options) { var scope = $rootScope.$new(); var element = $compile(template)(scope); var backdrop = $mdUtil.createBackdrop(scope, 'md-edit-dialog-backdrop'); var controller; if(options.controller) { controller = getController(options, scope, {$element: element, $scope: scope}); } else { angular.extend(scope, options.scope); } if(options.disableScroll) { disableScroll(element); } body.prepend(backdrop).append(element.addClass('md-whiteframe-1dp')); positionDialog(element, options.target); if(options.focusOnOpen) { focusOnOpen(element); } if(options.clickOutsideToClose) { backdrop.on('click', function () { element.remove(); }); } if(options.escToClose) { escToClose(element); } element.on('$destroy', function () { busy = false; backdrop.remove(); }); return controller; } function disableScroll(element) { var restoreScroll = $mdUtil.disableScrollAround(element, body); element.on('$destroy', function () { restoreScroll(); }); } function getController(options, scope, inject) { if(!options.controller) { return; } if(options.resolve) { angular.extend(inject, options.resolve); } if(options.locals) { angular.extend(inject, options.locals); } if(options.controllerAs) { scope[options.controllerAs] = {}; if(options.bindToController) { angular.extend(scope[options.controllerAs], options.scope); } else { angular.extend(scope, options.scope); } } else { angular.extend(scope, options.scope); } if(options.bindToController) { return $controller(options.controller, inject, scope[options.controllerAs]); } else { return $controller(options.controller, inject); } } function getTemplate(options) { return $q(function (resolve, reject) { var template = options.template; function illegalType(type) { reject('Unexpected template value. Expected a string; received a ' + type + '.'); } if(template) { return angular.isString(template) ? resolve(template) : illegalType(typeof template); } if(options.templateUrl) { template = $templateCache.get(options.templateUrl); if(template) { return resolve(template); } var success = function (template) { return resolve(template); }; var error = function () { return reject('Error retrieving template from URL.'); }; return $templateRequest(options.templateUrl).then(success, error); } reject('Template not provided.'); }); } function logError(error) { busy = false; console.error(error); } function escToClose(element) { var keyup = function (event) { if(event.keyCode === ESCAPE) { element.remove(); } }; body.on('keyup', keyup); element.on('$destroy', function () { body.off('keyup', keyup); }); } function focusOnOpen(element) { $mdUtil.nextTick(function () { var autofocus = $mdUtil.findFocusTarget(element); if(autofocus) { autofocus.focus(); } }, false); } function positionDialog(element, target) { var table = angular.element(target).controller('mdCell').getTable(); var getHeight = function () { return element.prop('clientHeight'); }; var getSize = function () { return { width: getWidth(), height: getHeight() }; }; var getTableBounds = function () { var parent = table.parent(); if(parent.prop('tagName') === 'MD-TABLE-CONTAINER') { return parent[0].getBoundingClientRect(); } else { return table[0].getBoundingClientRect(); } }; var getWidth = function () { return element.prop('clientWidth'); }; var reposition = function () { var size = getSize(); var cellBounds = target.getBoundingClientRect(); var tableBounds = getTableBounds(); if(size.width > tableBounds.right - cellBounds.left) { element.css('left', tableBounds.right - size.width + 'px'); } else { element.css('left', cellBounds.left + 'px'); } if(size.height > tableBounds.bottom - cellBounds.top) { element.css('top', tableBounds.bottom - size.height + 'px'); } else { element.css('top', cellBounds.top + 1 + 'px'); } element.css('minWidth', cellBounds.width + 'px'); }; var watchWidth = $rootScope.$watch(getWidth, reposition); var watchHeight = $rootScope.$watch(getHeight, reposition); $window.addEventListener('resize', reposition); element.on('$destroy', function () { watchWidth(); watchHeight(); $window.removeEventListener('resize', reposition); }); } function preset(size, options) { function getAttrs() { var attrs = 'type="' + (options.type || 'text') + '"'; for(var attr in options.validators) { attrs += ' ' + attr + '="' + options.validators[attr] + '"'; } return attrs; } return { controller: ['$element', '$q', 'save', '$scope', function ($element, $q, save, $scope) { function update() { if($scope.editDialog.$invalid) { return $q.reject(); } if(angular.isFunction(save)) { return $q.when(save($scope.editDialog.input)); } return $q.resolve(); } this.dismiss = function () { $element.remove(); }; this.getInput = function () { return $scope.editDialog.input; }; $scope.dismiss = this.dismiss; $scope.submit = function () { update().then(function () { $scope.dismiss(); }); }; }], locals: { save: options.save }, scope: { cancel: options.cancel || 'Cancel', messages: options.messages, model: options.modelValue, ok: options.ok || 'Save', placeholder: options.placeholder, title: options.title, size: size }, template: '' + '
' + '
{{title || \'Edit\'}}
' + '
' + '' + '' + '
' + '
{{message}}
' + '
' + '
' + '
' + '
' + '
' + '{{cancel}}' + '{{ok}}' + '
' + '
' }; } this.show = function (options) { if(busy) { return $q.reject(); } busy = true; options = angular.extend({}, defaultOptions, options); if(!options.targetEvent) { return logError('options.targetEvent is required to align the dialog with the table cell.'); } if(!options.targetEvent.currentTarget.classList.contains('md-cell')) { return logError('The event target must be a table cell.'); } if(options.bindToController && !options.controllerAs) { return logError('You must define options.controllerAs when options.bindToController is true.'); } options.target = options.targetEvent.currentTarget; var promise = getTemplate(options); var promises = [promise]; for(var prop in options.resolve) { promise = options.resolve[prop]; promises.push($q.when(angular.isFunction(promise) ? promise() : promise)); } promise = $q.all(promises); promise['catch'](logError); return promise.then(function (results) { var template = results.shift(); for(var prop in options.resolve) { options.resolve[prop] = results.shift(); } return build(template, options); }); }; this.small = function (options) { return this.show(angular.extend({}, options, preset('small', options))); }.bind(this); this.large = function (options) { return this.show(angular.extend({}, options, preset('large', options))); }.bind(this); return this; } mdEditDialog.$inject = ['$compile', '$controller', '$document', '$mdUtil', '$q', '$rootScope', '$templateCache', '$templateRequest', '$window']; angular.module('md.data.table').directive('mdFoot', mdFoot); function mdFoot() { function compile(tElement) { tElement.addClass('md-foot'); } return { compile: compile, restrict: 'A' }; } angular.module('md.data.table').directive('mdHead', mdHead); function mdHead($compile) { function compile(tElement) { tElement.addClass('md-head'); return postLink; } // empty controller to be bind scope properties to function Controller() { } function postLink(scope, element, attrs, tableCtrl) { // because scope.$watch is unpredictable var oldValue = new Array(2); function addCheckboxColumn() { element.children().prepend(''); } function attatchCheckbox() { element.prop('lastElementChild').firstElementChild.appendChild($compile(createCheckBox())(scope)[0]); } function createCheckBox() { return angular.element('').attr({ 'aria-label': 'Select All', 'ng-click': 'toggleAll()', 'ng-checked': 'allSelected()', 'ng-disabled': '!getSelectableRows().length' }); } function detachCheckbox() { var cell = element.prop('lastElementChild').firstElementChild; if(cell.classList.contains('md-checkbox-column')) { angular.element(cell).empty(); } } function enableRowSelection() { return tableCtrl.$$rowSelect; } function mdSelectCtrl(row) { return angular.element(row).controller('mdSelect'); } function removeCheckboxColumn() { Array.prototype.some.call(element.find('th'), function (cell) { return cell.classList.contains('md-checkbox-column') && cell.remove(); }); } scope.allSelected = function () { var rows = scope.getSelectableRows(); return rows.length && rows.every(function (row) { return row.isSelected(); }); }; scope.getSelectableRows = function () { return tableCtrl.getBodyRows().map(mdSelectCtrl).filter(function (ctrl) { return ctrl && !ctrl.disabled; }); }; scope.selectAll = function () { tableCtrl.getBodyRows().map(mdSelectCtrl).forEach(function (ctrl) { if(ctrl && !ctrl.isSelected()) { ctrl.select(); } }); }; scope.toggleAll = function () { return scope.allSelected() ? scope.unSelectAll() : scope.selectAll(); }; scope.unSelectAll = function () { tableCtrl.getBodyRows().map(mdSelectCtrl).forEach(function (ctrl) { if(ctrl && ctrl.isSelected()) { ctrl.deselect(); } }); }; scope.$watchGroup([enableRowSelection, tableCtrl.enableMultiSelect], function (newValue) { if(newValue[0] !== oldValue[0]) { if(newValue[0]) { addCheckboxColumn(); if(newValue[1]) { attatchCheckbox(); } } else { removeCheckboxColumn(); } } else if(newValue[0] && newValue[1] !== oldValue[1]) { if(newValue[1]) { attatchCheckbox(); } else { detachCheckbox(); } } angular.copy(newValue, oldValue); }); } return { bindToController: true, compile: compile, controller: Controller, controllerAs: '$mdHead', require: '^^mdTable', restrict: 'A', scope: { order: '=?mdOrder', onReorder: '=?mdOnReorder' } }; } mdHead.$inject = ['$compile']; angular.module('md.data.table').directive('mdRow', mdRow); function mdRow() { function compile(tElement) { tElement.addClass('md-row'); return postLink; } function postLink(scope, element, attrs, tableCtrl) { function enableRowSelection() { return tableCtrl.$$rowSelect; } function isBodyRow() { return tableCtrl.getBodyRows().indexOf(element[0]) !== -1; } function isChild(node) { return element[0].contains(node[0]); } if(isBodyRow()) { var cell = angular.element(''); scope.$watch(enableRowSelection, function (enable) { // if a row is not selectable, prepend an empty cell to it if(enable && !attrs.mdSelect) { if(!isChild(cell)) { element.prepend(cell); } return; } if(isChild(cell)) { cell.remove(); } }); } } return { compile: compile, require: '^^mdTable', restrict: 'A' }; } angular.module('md.data.table').directive('mdSelect', mdSelect); function mdSelect($compile, $parse) { // empty controller to bind scope properties to function Controller() { } function postLink(scope, element, attrs, ctrls) { var self = ctrls.shift(); var tableCtrl = ctrls.shift(); var getId = $parse(attrs.mdSelectId); self.id = getId(self.model); if(tableCtrl.$$rowSelect && self.id) { if(tableCtrl.$$hash.has(self.id)) { var index = tableCtrl.selected.indexOf(tableCtrl.$$hash.get(self.id)); // if the item is no longer selected remove it if(index === -1) { tableCtrl.$$hash.purge(self.id); } // if the item is not a reference to the current model update the reference else if(!tableCtrl.$$hash.equals(self.id, self.model)) { tableCtrl.$$hash.update(self.id, self.model); tableCtrl.selected.splice(index, 1, self.model); } } else { // check if the item has been selected tableCtrl.selected.some(function (item, index) { if(getId(item) === self.id) { tableCtrl.$$hash.update(self.id, self.model); tableCtrl.selected.splice(index, 1, self.model); return true; } }); } } self.isSelected = function () { if(!tableCtrl.$$rowSelect) { return false; } if(self.id) { return tableCtrl.$$hash.has(self.id); } return tableCtrl.selected.indexOf(self.model) !== -1; }; self.select = function () { if(self.disabled) { return; } if(tableCtrl.enableMultiSelect()) { tableCtrl.selected.push(self.model); } else { tableCtrl.selected.splice(0, tableCtrl.selected.length, self.model); } if(angular.isFunction(self.onSelect)) { self.onSelect(self.model); } }; self.deselect = function () { if(self.disabled) { return; } tableCtrl.selected.splice(tableCtrl.selected.indexOf(self.model), 1); if(angular.isFunction(self.onDeselect)) { self.onDeselect(self.model); } }; self.toggle = function (event) { if(event && event.stopPropagation) { event.stopPropagation(); } return self.isSelected() ? self.deselect() : self.select(); }; function autoSelect() { return attrs.mdAutoSelect === '' || self.autoSelect; } function createCheckbox() { var checkbox = angular.element('').attr({ 'aria-label': 'Select Row', 'ng-click': '$mdSelect.toggle($event)', 'ng-checked': '$mdSelect.isSelected()', 'ng-disabled': '$mdSelect.disabled' }); return angular.element('').append($compile(checkbox)(scope)); } function disableSelection() { Array.prototype.some.call(element.children(), function (child) { return child.classList.contains('md-checkbox-cell') && element[0].removeChild(child); }); if(autoSelect()) { element.off('click', toggle); } } function enableSelection() { element.prepend(createCheckbox()); if(autoSelect()) { element.on('click', toggle); } } function enableRowSelection() { return tableCtrl.$$rowSelect; } function onSelectChange(selected) { if(!self.id) { return; } if(tableCtrl.$$hash.has(self.id)) { // check if the item has been deselected if(selected.indexOf(tableCtrl.$$hash.get(self.id)) === -1) { tableCtrl.$$hash.purge(self.id); } return; } // check if the item has been selected if(selected.indexOf(self.model) !== -1) { tableCtrl.$$hash.update(self.id, self.model); } } function toggle(event) { scope.$applyAsync(function () { self.toggle(event); }); } scope.$watch(enableRowSelection, function (enable) { if(enable) { enableSelection(); } else { disableSelection(); } }); scope.$watch(autoSelect, function (newValue, oldValue) { if(newValue === oldValue) { return; } if(tableCtrl.$$rowSelect && newValue) { element.on('click', toggle); } else { element.off('click', toggle); } }); scope.$watch(self.isSelected, function (isSelected) { return isSelected ? element.addClass('md-selected') : element.removeClass('md-selected'); }); scope.$watch(tableCtrl.enableMultiSelect, function (multiple) { if(tableCtrl.$$rowSelect && !multiple) { // remove all but the first selected item tableCtrl.selected.splice(1); } }); tableCtrl.registerModelChangeListener(onSelectChange); element.on('$destroy', function () { tableCtrl.removeModelChangeListener(onSelectChange); }); } return { bindToController: true, controller: Controller, controllerAs: '$mdSelect', link: postLink, require: ['mdSelect', '^^mdTable'], restrict: 'A', scope: { model: '=mdSelect', disabled: '=ngDisabled', onSelect: '=?mdOnSelect', onDeselect: '=?mdOnDeselect', autoSelect: '=mdAutoSelect' } }; } mdSelect.$inject = ['$compile', '$parse']; angular.module('md.data.table').directive('mdTable', mdTable); function Hash() { var keys = {}; this.equals = function (key, item) { return keys[key] === item; }; this.get = function (key) { return keys[key]; }; this.has = function (key) { return keys.hasOwnProperty(key); }; this.purge = function (key) { delete keys[key]; }; this.update = function (key, item) { keys[key] = item; }; } function mdTable() { function compile(tElement, tAttrs) { tElement.addClass('md-table'); if(tAttrs.hasOwnProperty('mdProgress')) { var body = tElement.find('tbody')[0]; var progress = angular.element(''); if(body) { tElement[0].insertBefore(progress[0], body); } } } function Controller($attrs, $element, $q, $scope) { var self = this; var queue = []; var watchListener; var modelChangeListeners = []; self.$$hash = new Hash(); self.$$columns = {}; function enableRowSelection() { self.$$rowSelect = true; watchListener = $scope.$watchCollection('$mdTable.selected', function (selected) { modelChangeListeners.forEach(function (listener) { listener(selected); }); }); $element.addClass('md-row-select'); } function disableRowSelection() { self.$$rowSelect = false; if(angular.isFunction(watchListener)) { watchListener(); } $element.removeClass('md-row-select'); } function resolvePromises() { if(!queue.length) { return $scope.$applyAsync(); } queue[0]['finally'](function () { queue.shift(); resolvePromises(); }); } function rowSelect() { return $attrs.mdRowSelect === '' || self.rowSelect; } function validateModel() { if(!self.selected) { return console.error('Row selection: ngModel is not defined.'); } if(!angular.isArray(self.selected)) { return console.error('Row selection: Expected an array. Recived ' + typeof self.selected + '.'); } return true; } self.columnCount = function () { return self.getRows($element[0]).reduce(function (count, row) { return row.cells.length > count ? row.cells.length : count; }, 0); }; self.getRows = function (element) { return Array.prototype.filter.call(element.rows, function (row) { return !row.classList.contains('ng-leave'); }); }; self.getBodyRows = function () { return Array.prototype.reduce.call($element.prop('tBodies'), function (result, tbody) { return result.concat(self.getRows(tbody)); }, []); }; self.getElement = function () { return $element; }; self.getHeaderRows = function () { return self.getRows($element.prop('tHead')); }; self.enableMultiSelect = function () { return $attrs.multiple === '' || $scope.$eval($attrs.multiple); }; self.waitingOnPromise = function () { return !!queue.length; }; self.queuePromise = function (promise) { if(!promise) { return; } if(queue.push(angular.isArray(promise) ? $q.all(promise) : $q.when(promise)) === 1) { resolvePromises(); } }; self.registerModelChangeListener = function (listener) { modelChangeListeners.push(listener); }; self.removeModelChangeListener = function (listener) { var index = modelChangeListeners.indexOf(listener); if(index !== -1) { modelChangeListeners.splice(index, 1); } }; if($attrs.hasOwnProperty('mdProgress')) { $scope.$watch('$mdTable.progress', self.queuePromise); } $scope.$watch(rowSelect, function (enable) { if(enable && !!validateModel()) { enableRowSelection(); } else { disableRowSelection(); } }); } Controller.$inject = ['$attrs', '$element', '$q', '$scope']; return { bindToController: true, compile: compile, controller: Controller, controllerAs: '$mdTable', restrict: 'A', scope: { progress: '=?mdProgress', selected: '=ngModel', rowSelect: '=mdRowSelect' } }; } angular.module('md.data.table').directive('mdTablePagination', mdTablePagination); function mdTablePagination() { function compile(tElement) { tElement.addClass('md-table-pagination'); } function Controller($attrs, $mdUtil, $scope) { var self = this; var defaultLabel = { page: 'Page:', rowsPerPage: 'Rows per page:', of: 'of' }; self.label = angular.copy(defaultLabel); function isPositive(number) { return parseInt(number, 10) > 0; } self.eval = function (expression) { return $scope.$eval(expression); }; self.first = function () { self.page = 1; self.onPaginationChange(); }; self.hasNext = function () { return self.page * self.limit < self.total; }; self.hasPrevious = function () { return self.page > 1; }; self.last = function () { self.page = self.pages(); self.onPaginationChange(); }; self.max = function () { return self.hasNext() ? self.page * self.limit : self.total; }; self.min = function () { return isPositive(self.total) ? self.page * self.limit - self.limit + 1 : 0; }; self.next = function () { self.page++; self.onPaginationChange(); }; self.onPaginationChange = function () { if(angular.isFunction(self.onPaginate)) { $mdUtil.nextTick(function () { self.onPaginate(self.page, self.limit); }); } }; self.pages = function () { return isPositive(self.total) ? Math.ceil(self.total / (isPositive(self.limit) ? self.limit : 1)) : 1; }; self.previous = function () { self.page--; self.onPaginationChange(); }; self.showBoundaryLinks = function () { return $attrs.mdBoundaryLinks === '' || self.boundaryLinks; }; self.showPageSelect = function () { return $attrs.mdPageSelect === '' || self.pageSelect; }; $scope.$watch('$pagination.limit', function (newValue, oldValue) { if(isNaN(newValue) || isNaN(oldValue) || newValue === oldValue) { return; } // find closest page from previous min self.page = Math.floor(((self.page * oldValue - oldValue) + newValue) / (isPositive(newValue) ? newValue : 1)); self.onPaginationChange(); }); $attrs.$observe('mdLabel', function (label) { angular.extend(self.label, defaultLabel, $scope.$eval(label)); }); $scope.$watch('$pagination.total', function (newValue, oldValue) { if(isNaN(newValue) || newValue === oldValue) { return; } if(self.page > self.pages()) { self.last(); } }); } Controller.$inject = ['$attrs', '$mdUtil', '$scope']; return { bindToController: { boundaryLinks: '=?mdBoundaryLinks', disabled: '=ngDisabled', limit: '=mdLimit', page: '=mdPage', pageSelect: '=?mdPageSelect', onPaginate: '=?mdOnPaginate', limitOptions: '=?mdLimitOptions', total: '@mdTotal' }, compile: compile, controller: Controller, controllerAs: '$pagination', restrict: 'E', scope: {}, templateUrl: 'md-table-pagination.html' }; } angular.module('md.data.table').directive('mdTableProgress', mdTableProgress); function mdTableProgress() { function postLink(scope, element, attrs, tableCtrl) { scope.columnCount = tableCtrl.columnCount; scope.deferred = tableCtrl.waitingOnPromise; } return { link: postLink, require: '^^mdTable', restrict: 'A', scope: {}, templateUrl: 'md-table-progress.html' }; } angular.module('md.data.table').directive('virtualPageSelect', virtualPageSelect); function virtualPageSelect() { function Controller($element, $scope) { var self = this; var content = $element.find('md-content'); self.pages = []; function getMin(pages, total) { return Math.min(pages, isFinite(total) && isPositive(total) ? total : 1); } function isPositive(number) { return number > 0; } function setPages(max) { if(self.pages.length > max) { return self.pages.splice(max); } for(var i = self.pages.length; i < max; i++) { self.pages.push(i + 1); } } content.on('scroll', function () { if((content.prop('clientHeight') + content.prop('scrollTop')) >= content.prop('scrollHeight')) { $scope.$applyAsync(function () { setPages(getMin(self.pages.length + 10, self.total)); }); } }); $scope.$watch('$pageSelect.total', function (total) { setPages(getMin(Math.max(self.pages.length, 10), total)); }); $scope.$watch('$pagination.page', function (page) { for(var i = self.pages.length; i < page; i++) { self.pages.push(i + 1); } }); } Controller.$inject = ['$element', '$scope']; return { bindToController: { total: '@' }, controller: Controller, controllerAs: '$pageSelect' }; } })(window, angular);