/** * ngTable: Table + Angular JS * * @author Vitalii Savchuk * @url https://github.com/esvit/ng-table/ * @license New BSD License */ (function(){ /** * @ngdoc object * @name ngTableController * * @description * Each {@link ngTable ngTable} directive creates an instance of `ngTableController` */ angular.module('ngTable').controller('ngTableController', ['$scope', 'NgTableParams', '$timeout', '$parse', '$compile', '$attrs', '$element', 'ngTableColumn', 'ngTableEventsChannel', function($scope, NgTableParams, $timeout, $parse, $compile, $attrs, $element, ngTableColumn, ngTableEventsChannel) { var isFirstTimeLoad = true; $scope.$filterRow = {}; $scope.$loading = false; // until such times as the directive uses an isolated scope, we need to ensure that the check for // the params field only consults the "own properties" of the $scope. This is to avoid seeing the params // field on a $scope higher up in the prototype chain if (!$scope.hasOwnProperty("params")) { $scope.params = new NgTableParams(true); } $scope.params.settings().$scope = $scope; var delayFilter = (function() { var timer = 0; return function(callback, ms) { $timeout.cancel(timer); timer = $timeout(callback, ms); }; })(); function onDataReloadStatusChange (newStatus/*, oldStatus*/) { if (!newStatus || $scope.params.hasErrorState()) { return; } $scope.params.settings().$scope = $scope; var currentParams = $scope.params; var filterOptions = currentParams.settings().filterOptions; if (currentParams.hasFilterChanges()) { var applyFilter = function () { currentParams.page(1); currentParams.reload(); }; if (filterOptions.filterDelay) { delayFilter(applyFilter, filterOptions.filterDelay); } else { applyFilter(); } } else { currentParams.reload(); } } // watch for when a new NgTableParams is bound to the scope // CRITICAL: the watch must be for reference and NOT value equality; this is because NgTableParams maintains // the current data page as a field. Checking this for value equality would be terrible for performance // and potentially cause an error if the items in that array has circular references $scope.$watch('params', function(newParams, oldParams){ if (newParams === oldParams || !newParams) { return; } newParams.reload(); }, false); $scope.$watch('params.isDataReloadRequired()', onDataReloadStatusChange); this.compileDirectiveTemplates = function () { if (!$element.hasClass('ng-table')) { $scope.templates = { header: ($attrs.templateHeader ? $attrs.templateHeader : 'ng-table/header.html'), pagination: ($attrs.templatePagination ? $attrs.templatePagination : 'ng-table/pager.html') }; $element.addClass('ng-table'); var headerTemplate = null; // $element.find('> thead').length === 0 doesn't work on jqlite var theadFound = false; angular.forEach($element.children(), function(e) { if (e.tagName === 'THEAD') { theadFound = true; } }); if (!theadFound) { headerTemplate = angular.element(document.createElement('thead')).attr('ng-include', 'templates.header'); $element.prepend(headerTemplate); } var paginationTemplate = angular.element(document.createElement('div')).attr({ 'ng-table-pagination': 'params', 'template-url': 'templates.pagination' }); $element.after(paginationTemplate); if (headerTemplate) { $compile(headerTemplate)($scope); } $compile(paginationTemplate)($scope); } }; this.loadFilterData = function ($columns) { angular.forEach($columns, function ($column) { var result; result = $column.filterData($scope); if (!result) { delete $column.filterData; return; } // if we're working with a deferred object or a promise, let's wait for the promise /* WARNING: support for returning a $defer is depreciated */ if ((angular.isObject(result) && (angular.isObject(result.promise) || angular.isFunction(result.then)))) { var pData = angular.isFunction(result.then) ? result : result.promise; delete $column.filterData; return pData.then(function(data) { // our deferred can eventually return arrays, functions and objects if (!angular.isArray(data) && !angular.isFunction(data) && !angular.isObject(data)) { // if none of the above was found - we just want an empty array data = []; } $column.data = data; }); } // otherwise, we just return what the user gave us. It could be a function, array, object, whatever else { return $column.data = result; } }); }; this.buildColumns = function (columns) { var result = []; columns.forEach(function(col){ result.push(ngTableColumn.buildColumn(col, $scope, result)); }); return result }; this.parseNgTableDynamicExpr = function (attr) { if (!attr || attr.indexOf(" with ") > -1) { var parts = attr.split(/\s+with\s+/); return { tableParams: parts[0], columns: parts[1] }; } else { throw new Error('Parse error (expected example: ng-table-dynamic=\'tableParams with cols\')'); } }; this.setupBindingsToInternalScope = function(tableParamsExpr){ // note: this we're setting up watches to simulate angular's isolated scope bindings // note: is REALLY important to watch for a change to the ngTableParams *reference* rather than // $watch for value equivalence. This is because ngTableParams references the current page of data as // a field and it's important not to watch this var tableParamsGetter = $parse(tableParamsExpr); $scope.$watch(tableParamsGetter, (function (params) { if (angular.isUndefined(params)) { return; } $scope.paramsModel = tableParamsGetter; $scope.params = params; }), false); setupFilterRowBindingsToInternalScope(); setupGroupRowBindingsToInternalScope(); }; function setupFilterRowBindingsToInternalScope(){ if ($attrs.showFilter) { $scope.$parent.$watch($attrs.showFilter, function(value) { $scope.show_filter = value; }); } else { $scope.$watch(hasVisibleFilterColumn, function(value){ $scope.show_filter = value; }) } if ($attrs.disableFilter) { $scope.$parent.$watch($attrs.disableFilter, function(value) { $scope.$filterRow.disabled = value; }); } } function setupGroupRowBindingsToInternalScope(){ $scope.$groupRow = {}; if ($attrs.showGroup) { var showGroupGetter = $parse($attrs.showGroup); $scope.$parent.$watch(showGroupGetter, function(value) { $scope.$groupRow.show = value; }); if (showGroupGetter.assign){ // setup two-way databinding thus allowing ngTableGrowRow to assign to the showGroup expression $scope.$watch('$groupRow.show', function(value) { showGroupGetter.assign($scope.$parent, value); }); } } else{ $scope.$watch('params.hasGroup()', function(newValue) { $scope.$groupRow.show = newValue; }); } } function getVisibleColumns(){ return ($scope.$columns || []).filter(function(c){ return c.show($scope); }); } function hasVisibleFilterColumn(){ if (!$scope.$columns) return false; return some($scope.$columns, function($column){ return $column.show($scope) && $column.filter($scope); }); } function some(array, predicate){ var found = false; for (var i = 0; i < array.length; i++) { var obj = array[i]; if (predicate(obj)){ found = true; break; } } return found; } function commonInit(){ ngTableEventsChannel.onAfterReloadData(bindDataToScope, $scope, isMyPublisher); ngTableEventsChannel.onPagesChanged(bindPagesToScope, $scope, isMyPublisher); function bindDataToScope(params, newDatapage){ if (params.hasGroup()) { $scope.$groups = newDatapage || []; $scope.$groups.visibleColumnCount = getVisibleColumns().length; } else { $scope.$data = newDatapage; } } function bindPagesToScope(params, newPages){ $scope.pages = newPages } function isMyPublisher(publisher){ return $scope.params === publisher; } } commonInit(); }]); })();