').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); | |