(function (angular) { var module = angular.module('framework.directives.UI'); module.directive('tableFor', tableForDirective); module.directive('tableTitlebar', tableTitlebarDirective); module.directive('tableHeading', tableHeadingDirective); module.directive('tableDataRows', tableDataRowsDirective); module.directive('tableFooter', tableFooterDirective); module.filter('publish', publishFilter); module.filter('softFilter', softFilter); module.filter('pageGroup', pageGroupFilter); tableForDirective.$inject = ['$parse', 'Model', '$modal', '$q', '$filter', '$timeout']; function tableForDirective($parse, Model, $modal, $q, $filter, $timeout) { var defaults = { wrapperClass: 'table-responsive', tableClass: 'table table-bordered', minWidth: '600px' } function linkFn(scope, element, attrs, ctrls, transcludeFn) { var externalSearch = $parse(attrs.filtering)(scope); var externalSorting = $parse(attrs.sorting)(scope); var externalGrouping = $parse(attrs.grouping)(scope); var externalPaging = $parse(attrs.paging)(scope); scope.$schema = Model[attrs.schema].$schema; scope.$table = { actionColumn: false, selectionColumn: false, cardTemplate: '{{$row | json}}', toggleSelectAll: toggleSelectAll, toggleRowSelection: toggleRowSelection, toggleGroupSelection: toggleGroupSelection, allSelected: false, reset: reset, configureColumns: configureColumns, sort: sort, getSortingClass: getSortingClass }; scope.$columns = generateColumns(scope.$schema); scope.$filtering = externalSearch || { $: '' } scope.$sorting = externalSorting || []; scope.$grouping = externalGrouping || { value: undefined }; scope.$paging = externalPaging || { currentPage: 1, totalItems: 0, maxPerPage: 10 }; scope.$data = {}; //object as it's grouped scope.$datasource = function () { return arguments[4]; //returns existing data by default; }; var datasource = $parse(attrs.datasource)(scope); if (angular.isArray(datasource)) { scope.$datasource = generateLocalDatasource(datasource); } else if (angular.isFunction(datasource)) { scope.$datasource = datasource; } else { scope.$datasource = generateModelDatasource(scope.$schema.query); } scope.$watchCollection(function () { return scope.$filtering; }, debouncedUpdateData); scope.$watchCollection(function () { return scope.$sorting; }, debouncedUpdateData); scope.$watchCollection(function () { return scope.$grouping; }, debouncedUpdateData); scope.$watch(function () { return scope.$paging.currentPage; }, debouncedUpdateData); scope.$watch(function () { return scope.$paging.maxPerPage; }, debouncedUpdateData); var updateDebounce = null; function debouncedUpdateData() { if (updateDebounce) { $timeout.cancel(updateDebounce); updateDebounce = null; } updateDebounce = $timeout(updateData, 500); } function updateData() { for (var key in scope.$data) { delete scope.$data[key]; } scope.$data.$loading = true; scope.$data.$error = null; scope.$datasource(scope.$filtering, scope.$sorting, scope.$grouping, scope.$paging, scope.$data).then(dataReceived, dataError, dataNotified); } function dataReceived(data) { for (var key in scope.$data) { delete scope.$data[key]; } scope.$data.$loading = false; angular.extend(scope.$data, data); } function dataError(error) { for (var key in scope.$data) { delete scope.$data[key]; } scope.$data.$loading = false; scope.$data.$error = error; } function dataNotified(dataLength) { scope.$paging.totalItems = dataLength; } transcludeFn(scope, function (clone, scope) { element.find('table').append(clone); }); function generateLocalDatasource(initialData) { var data = initialData; return function (filtering, sorting, grouping, paging, existing) { var deferred = $q.defer(); setTimeout(function () { var filter = $filter('filter'); var orderBy = $filter('orderBy'); var groupBy = $filter('groupBy'); var pageGroup = $filter('pageGroup'); deferred.notify(filter(data, filtering).length); var amendedSorting = [grouping.value]; [].push.apply(amendedSorting, sorting); var result = pageGroup(groupBy(orderBy(filter(data, filtering), amendedSorting), grouping.value), paging); deferred.resolve(result); }, 0); return deferred.promise; } } function generateModelDatasource(modelQuery) { var queryBase = modelQuery; return function (filtering, sorting, grouping, paging, existing) { var deferred = $q.defer(); query = queryBase(); if (grouping.value) { query.orderBy(grouping.value); } angular.forEach(ordering, function (o) { query.orderBy(o); }); setTimeout(function () { var filter = $filter('filter'); var groupBy = $filter('groupBy'); var pageGroup = $filter('pageGroup'); deferred.notify(filter(data, filtering).length); var amendedSorting = [grouping.value]; [].push.apply(amendedSorting, sorting); var result = pageGroup(groupBy(orderBy(filter(data, filtering), amendedSorting), grouping.value), paging); deferred.resolve(result); }, 0); return deferred.promise; } } function generateColumns(modelSchema) { var columns = []; angular.forEach(modelSchema, function (value, key) { var config = angular.extend({}, value, value.table || {}); if (config.hidden) { return; } if (!angular.isDefined(config.visible)) { config.visible = true; } if (!angular.isDefined(config.binding) && config.type === moment) { config.binding = key + ".toDate() | date:'dd/MM/yyyy'"; } columns.push({ key: key, title: config.display || key, accessor: angular.isFunction(config.binding) ? config.binding : buildAccessor(config.binding || key), binding: config.binding || key, visible: config.visible, filters: config.filters }); }); return columns; } function toggleSelectAll() { scope.$table.allSelected = !scope.$table.allSelected; angular.forEach(scope.$data, function (group) { toggleGroupSelection(group, scope.$table.allSelected); }); } function toggleRowSelection(group, row, force) { row.$selected = angular.isDefined(force) ? force : !row.$selected; var selected = group.filter(function (d) { return d.$selected; }); group.$selected = selected.length === group.length; var groups = 0; var selectedGroups = 0; angular.forEach(scope.$data, function (group, title) { if (title[0] == '$') { return; } groups++; if (group.$selected) selectedGroups++; }); scope.$table.allSelected = groups === selectedGroups; } function toggleGroupSelection(group, force) { if (angular.isDefined(force)) { group.$selected = force; } else { group.$selected = !group.$selected; } angular.forEach(group, function (row) { row.$selected = group.$selected; }); if (!angular.isDefined(force)) { var groups = 0; var selectedGroups = 0; angular.forEach(scope.$data, function (group, title) { if (title[0] == '$') { return; } groups++; if (group.$selected) selectedGroups++; }); debugger; scope.$table.allSelected = groups === selectedGroups; } } function setAreAllSelected() { scope.$table.allSelected = false; } function sort(col) { //multisort var currentSort = scope.$sorting[0]; if (col == currentSort) { scope.$sorting[0] = '-' + col; } else if ('-' + col == currentSort) { scope.$sorting[0] = col; } else { scope.$sorting.length = 0; scope.$sorting.push(col); } } function getSortingClass(col) { if (col == scope.$sorting[0]) { return 'icon-Arrow-Down2 brand-primary'; } else if ('-' + col == scope.$sorting[0]) { return 'icon-Arrow-Up2 brand-primary'; } else { return 'icon-Arrow-Down'; } } function reset() { scope.$filtering = externalSearch || { $: '' } scope.$sorting = []; //TODO: Deselect ALL scope.$columns.length = 0; [].push.apply(scope.$columns, generateColumns(scope.$schema)); } function buildAccessor(fieldName) { console.log('return row.' + fieldName + ';'); window.count = window.count || 0; return new Function('row', 'return row.' + fieldName + ';'); } function configureColumns() { $modal.open({ template: '' + '' + '', controller: function ($scope, columns) { $scope.columns = columns; $scope.moveUp = function (column) { var index = $scope.columns.indexOf(column); move(index, index - 1); } $scope.moveDown = function (column) { var index = $scope.columns.indexOf(column); move(index, index + 1); } function move(old_index, new_index) { $scope.columns.splice(new_index, 0, $scope.columns.splice(old_index, 1)[0]); } }, size: 'sm', resolve: { columns: function () { return scope.$columns } } }); } } function compileFn(element, attrs) { return linkFn; } return { scope: true, transclude: true, template: function (element, attrs) { return '
' }, compile: function (element, attrs) { return linkFn; } }; } function tableTitlebarDirective() { return { restrict: 'E', scope: true, replace: true, template: function (element, attrs) { var template = '' + ''; if (attrs.title) { template += '

' + attrs.title + '

'; } if (attrs.showCount) { template += '

({{$visible.length}} / {{ $data.length }})

'; } if (attrs.columnsOptions) { template += '
' + 'Reset' + 'Arrange' + '
'; } template += '
' + '' + '
'; if (attrs.search) { template += ''; } template += '' return template; } } } function tableHeadingDirective() { var defaults = { } return { restrict: 'E', scope: true, replace: true, template: function (element, attrs) { var tmpl = '' + '' + '' + '
' + '' + '' + '
{{ ::$column.title }}' + '' + '
' + //add filter and search menu '' + '' + 'Actions' + '' + ''; tmpl += ''; return tmpl; } }; } tableDataRowsDirective.$inject = ['$parse', 'Model']; function tableDataRowsDirective($parse, Model) { return { restrict: 'E', scope: true, replace: true, template: function (element, attrs) { var tmp = '' + '' + '' + '' + '
' + '' + '{{ title }}' + '' + '' + '' + '
' + '' + '' + '{{$column.accessor($row)}}' + '' + '' + '' + '' + ''; return tmp; }, compile: function (element, attrs) { var selectionElement = _findSelectionColumn(element); var actionsElement = _findActionColumn(element); return link; function link(scope, element, attrs) { scope.$table.actionColumn = actionsElement; scope.$table.selectionColumn = selectionElement; scope.$table.getCellValue = getCellValue; function getCellValue(row, col) { console.log('GetCellValue is depreciated for performance, please use "$column.accessor($row)" instead, functions are now precompilied;'); if (typeof col.binding === 'string') return $parse(col.binding)(row); else if (angular.isFunction(col.binding)) return col.binding(row, col); else return ''; } } } } function _findActionColumn(element) { var actionsElement = jQuery(element.context).find('table-actions'); if (actionsElement.length) { return { html: actionsElement.html() }; } return false; } function _findSelectionColumn(element) { var selectionElement = jQuery(element.context).find('table-selection'); if (selectionElement.length) { return { html: selectionElement.html() }; } return false; } } function tableFooterDirective() { return { restrict: 'E', scope: true, replace: true, template: function (element, attrs) { var template = '' + ''; if (attrs.title) { template += '

' + attrs.title + '

'; } if (attrs.showCount) { template += '(showing {{$paging.maxPerPage}} from {{ $paging.totalItems }})'; } if (attrs.columnsOptions) { template += '
' + 'Reset' + 'Arrange' + '
'; } template += '
' + '' + '
'; if (attrs.search) { template += ''; } template += '' return template; } } } softFilter.$inject = ['$filter']; function softFilter($filter) { return function (array, filteringConfig) { var filterFn = $filter('filter'); var filtered = filterFn(array, filteringConfig); for (var i = 0; i < array.length; i++) { var target = array[i]; if (filtered.indexOf(target) > -1) { target.$filtered = true; } else { target.$filtered = false; } } return array; } } function pageGroupFilter() { return function (groups, pagingConfig) { var newGroups = {}; var skip = (pagingConfig.currentPage - 1) * pagingConfig.maxPerPage; var take = pagingConfig.maxPerPage; if (angular.isArray(groups)) { newGroups.undefined = groups.slice(skip, skip + take); } else { var groupNames = []; for (var key in groups) { if (groups.hasOwnProperty(key) && key[0] != '$') { groupNames.push(key); } } groupNames.sort(); angular.forEach(groupNames, function (title) { var values = groups[title]; if (take <= 0) { return; } else if (skip > values.length) { skip -= values.length; return; } else if (skip > 0) { values.splice(0, skip); skip = 0; } if (take >= values.length) { newGroups[title] = values; take -= values.length; } else { newGroups[title] = values.slice(0, take); take = 0; } }); } return newGroups; }; } function pagingFilter() { return function (array, pagingConfig) { if (array.length > pagingConfig.minimum) { } else { return array; } } } function publishFilter() { return function (array, target) { if (angular.isArray(target)) { target.length = 0; [].push.apply(target, array); } return array; } } })(angular);