2020-05-19 11:43:42 +03:00

1486 lines
39 KiB
JavaScript

/*
* 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',
'<div class="page-select" ng-if="$pagination.showPageSelect()">\n' +
' <div class="label">{{$pagination.label.page}}</div>\n' +
'\n' +
' <md-select virtual-page-select total="{{$pagination.pages()}}" class="md-table-select" ng-model="$pagination.page" md-container-class="md-pagination-select" ng-change="$pagination.onPaginationChange()" ng-disabled="$pagination.disabled" aria-label="Page">\n' +
' <md-content>\n' +
' <md-option ng-repeat="page in $pageSelect.pages" ng-value="page">{{page}}</md-option>\n' +
' </md-content>\n' +
' </md-select>\n' +
'</div>\n' +
'\n' +
'<div class="limit-select" ng-if="$pagination.limitOptions">\n' +
' <div class="label">{{$pagination.label.rowsPerPage}}</div>\n' +
'\n' +
' <md-select class="md-table-select" ng-model="$pagination.limit" md-container-class="md-pagination-select" ng-disabled="$pagination.disabled" aria-label="Rows" placeholder="{{ $pagination.limitOptions[0] }}">\n' +
' <md-option ng-repeat="option in $pagination.limitOptions" ng-value="option.value ? $pagination.eval(option.value) : option">{{::option.label ? option.label : option}}</md-option>\n' +
' </md-select>\n' +
'</div>\n' +
'\n' +
'<div class="buttons">\n' +
' <div class="label">{{$pagination.min()}} - {{$pagination.max()}} {{$pagination.label.of}} {{$pagination.total}}</div>\n' +
'\n' +
' <md-button class="md-icon-button" type="button" ng-if="$pagination.showBoundaryLinks()" ng-click="$pagination.first()" ng-disabled="$pagination.disabled || !$pagination.hasPrevious()" aria-label="First">\n' +
' <md-icon md-svg-icon="navigate-first.svg"></md-icon>\n' +
' </md-button>\n' +
'\n' +
' <md-button class="md-icon-button" type="button" ng-click="$pagination.previous()" ng-disabled="$pagination.disabled || !$pagination.hasPrevious()" aria-label="Previous">\n' +
' <md-icon md-svg-icon="navigate-before.svg"></md-icon>\n' +
' </md-button>\n' +
'\n' +
' <md-button class="md-icon-button" type="button" ng-click="$pagination.next()" ng-disabled="$pagination.disabled || !$pagination.hasNext()" aria-label="Next">\n' +
' <md-icon md-svg-icon="navigate-next.svg"></md-icon>\n' +
' </md-button>\n' +
'\n' +
' <md-button class="md-icon-button" type="button" ng-if="$pagination.showBoundaryLinks()" ng-click="$pagination.last()" ng-disabled="$pagination.disabled || !$pagination.hasNext()" aria-label="Last">\n' +
' <md-icon md-svg-icon="navigate-last.svg"></md-icon>\n' +
' </md-button>\n' +
'</div>');
}]);
angular.module('md-table-progress.html', []).run(['$templateCache', function($templateCache) {
$templateCache.put('md-table-progress.html',
'<tr>\n' +
' <th colspan="{{columnCount()}}">\n' +
' <md-progress-linear ng-show="deferred()" md-mode="indeterminate"></md-progress-linear>\n' +
' </th>\n' +
'</tr>');
}]);
angular.module('arrow-up.svg', []).run(['$templateCache', function($templateCache) {
$templateCache.put('arrow-up.svg',
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>');
}]);
angular.module('navigate-before.svg', []).run(['$templateCache', function($templateCache) {
$templateCache.put('navigate-before.svg',
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>');
}]);
angular.module('navigate-first.svg', []).run(['$templateCache', function($templateCache) {
$templateCache.put('navigate-first.svg',
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M7 6 v12 h2 v-12 h-2z M17.41 7.41L16 6l-6 6 6 6 1.41-1.41L12.83 12z"/></svg>');
}]);
angular.module('navigate-last.svg', []).run(['$templateCache', function($templateCache) {
$templateCache.put('navigate-last.svg',
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M15 6 v12 h2 v-12 h-2z M8 6L6.59 7.41 11.17 12l-4.58 4.59L8 18l6-6z"/></svg>');
}]);
angular.module('navigate-next.svg', []).run(['$templateCache', function($templateCache) {
$templateCache.put('navigate-next.svg',
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></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('<md-icon md-svg-icon="arrow-up.svg">');
$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:
'<md-edit-dialog>' +
'<div layout="column" class="md-content">' +
'<div ng-if="size === \'large\'" class="md-title">{{title || \'Edit\'}}</div>' +
'<form name="editDialog" layout="column" ng-submit="submit(model)">' +
'<md-input-container md-no-float>' +
'<input name="input" ng-model="model" md-autofocus placeholder="{{placeholder}} "' + getAttrs() + '>' +
'<div ng-messages="editDialog.input.$error">' +
'<div ng-repeat="(key, message) in messages" ng-message="{{key}}">{{message}}</div>' +
'</div>' +
'</md-input-container>' +
'</form>' +
'</div>' +
'<div ng-if="size === \'large\'" layout="row" layout-align="end" class="md-actions">' +
'<md-button class="md-primary" ng-click="dismiss()">{{cancel}}</md-button>' +
'<md-button class="md-primary" ng-click="submit()">{{ok}}</md-button>' +
'</div>' +
'</md-edit-dialog>'
};
}
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('<th class="md-column md-checkbox-column">');
}
function attatchCheckbox() {
element.prop('lastElementChild').firstElementChild.appendChild($compile(createCheckBox())(scope)[0]);
}
function createCheckBox() {
return angular.element('<md-checkbox>').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('<td class="md-cell">');
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('<md-checkbox>').attr({
'aria-label': 'Select Row',
'ng-click': '$mdSelect.toggle($event)',
'ng-checked': '$mdSelect.isSelected()',
'ng-disabled': '$mdSelect.disabled'
});
return angular.element('<td class="md-cell md-checkbox-cell">').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('<thead class="md-table-progress" md-table-progress>');
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);