/** * @ngdoc fbGrid directive * @name fasit.directive.fbGrid * @fbGrid directive * @param {options=} * * @description * Directive för att visa en tabell med sorteringsfunktionalitet o.s.v. * */ angular.module('fasit').directive('fbGrid', [ '$filter', 'exportService', 'textWidthService', 'utskriftService', function ($filter: ng.IFilterService, exportService: fb.IExportService, textWidthService: fb.ITextWidthService, utskriftService: fb.IUtskriftService) { 'use strict'; var filter = $filter('filter'); var orderBy = $filter('orderBy'); var translate = $filter('translate'); var filteredRows = []; var rowsCopyArray = []; var lastStartPos = 0; var selectedHandle; var oldWidth = 0; var gap = 3; function getPropValue(row: any, propPath: string): any { var paths = propPath.split('.'); var current = row; for (var i = 0; i < paths.length; ++i && i < 10) { if (typeof current[paths[i]] === 'undefined') { return undefined; } else { current = current[paths[i]]; } } // Om changetracking, returnera dess value return current != null && current.value !== undefined ? current.value : current; } function link(scope: fb.IFbGridScope, element: ng.IRootElementService, attrs) { function isRowVisibleOnColumnFiltering(row: any) { return !scope.options.inColumnSearch || _.every(scope.orderedColumns, function (col: fb.IGridColumn) { var columnFilterText: string = scope.columnFilterTextDict[col.Key]; if (!_.isString(columnFilterText)) { return true; } columnFilterText = columnFilterText.trim(); if (columnFilterText === '') { return true; } var colDisplayValue = scope.deepFind(row, col); return colDisplayValue.toLowerCase().indexOf(columnFilterText.toLowerCase()) !== -1; }); } function toggleSelectAllInPage() { if (!scope.isAllSelectedInPage()) { var selectObjects = _.filter(scope.currentSlice, function (obj) { return !_.contains(scope.options.selectedItems, obj); }); scope.options.selectedItems = scope.options.selectedItems.concat(selectObjects); if (scope.options.selectedItems.length > 0 && scope.options.selectedItems.length < scope.options.rows.length) { scope.options.warningIsAllSelectedInPage = true; } } else { scope.options.selectedItems = _.filter(scope.options.selectedItems, function (obj) { return !_.contains(scope.currentSlice, obj); }); scope.options.warningIsAllSelectedInPage = false; } } function selectRow(object: any) { var item = _.findWhere(scope.options.selectedItems, { $$hashKey: object.$$hashKey }); if (_.isUndefined(item)) { scope.options.selectedItems.push(object); } else { var selectedItems = []; for (var i = 0; i < scope.options.selectedItems.length; i++) { var obj = scope.options.selectedItems[i]; if (object.$$hashKey !== obj.$$hashKey) { selectedItems.push(obj); } } scope.options.selectedItems = selectedItems; } } // En förenklad version av "natural sort". Prioriterar siffror över strängar. Sorterar först siffrorna, sedan strängarna, och sist null/tom sträng. function getSortFunction(currentSort) { var key = currentSort.Key; return function (a, b) { if (a[key] === null || a[key].trim() === "") { return 1; } else if (b[key] === null || b[key].trim() === "") { return -1; } else if (!isNaN(Number(a[key].trim())) && !isNaN(Number(b[key].trim()))) { return Number(a[key].trim()) - Number(b[key].trim()); } else if (!isNaN(Number(a[key].trim())) && isNaN(Number(b[key].trim()))) { return -1; } else if (isNaN(Number(a[key].trim())) && !isNaN(Number(b[key].trim()))) { return 1; } else if (isNaN(Number(a[key].trim())) && isNaN(Number(b[key].trim()))) { if (a[key].trim() > b[key].trim()) { return 1; } else { return -1; } } else { return -1; } } } function getRowsInOrder(list, updateListIndex: boolean) { var i = 0; if (scope.options.currentSort !== undefined) { var ordered: any[]; if (scope.options.currentSort.Sort.naturalSort) { // natural sort ordered = list.sort(getSortFunction(scope.options.currentSort)); if (scope.options.currentSort.Sort.Reverse) { ordered.reverse(); } } else if (scope.options.currentSort.SortByDisplayText === true) { // Sortera efter displayText ordered = _.sortBy(list, function (row: any) { return scope.deepFind(row, scope.options.currentSort); }); if (scope.options.currentSort.Sort.Reverse) { ordered.reverse(); } } else if (typeof scope.options.currentSort.Sort.SortFn !== 'undefined') { ordered = _.sortBy(list, scope.options.currentSort.Sort.SortFn); if (scope.options.currentSort.Sort.Reverse) { ordered.reverse(); } } else { // Sortera efter modellvärde var key; if (scope.options.currentSort.ChangeTrackColumn === true) { key = scope.options.currentSort.Key + ".value"; } else { key = scope.options.currentSort.Key; } ordered = $filter('orderByObject')(list, key, !scope.options.currentSort.Sort.Reverse); } if (updateListIndex === true) { for (i = 0; i < ordered.length; i++) { //Update list order to enable correct exports ordered[i].listIndex = i; } } return ordered; } else { if (updateListIndex === true) { for (i = 0; i < list.length; i++) { //Update list order to enable correct exports list[i].listIndex = i; } } } return list; } function updateSlice() { filteredRows = filter(rowsCopyArray, isRowVisibleOnColumnFiltering); filteredRows = getRowsInOrder(filteredRows, true); scope.currentSlice = filteredRows.slice(scope.options.currentPage * scope.options.itemsPerPage, (scope.options.currentPage * scope.options.itemsPerPage) + scope.options.itemsPerPage); } function syncHandles() { var offset = scope.options.checkboxColumnWidth + $('#statusIcon').outerWidth(); var prevHandle = null; scope.handles = []; angular.forEach(scope.orderedColumns, function (column) { var handle = { Leftcolumn: column, Rightcolumn: null, style: { left: column.Width + (offset) + 'px' } }; if (prevHandle !== null) { prevHandle.Rightcolumn = column; } prevHandle = handle; scope.handles.push(handle); offset += column.Width; }); } function mouseMove(event) { var difference; difference = (event.pageX - lastStartPos); selectedHandle.Leftcolumn.Width = Math.max(10, oldWidth + difference); syncHandles(); scope.$apply(); } function mouseUp() { $(window).off('mousemove', mouseMove); } function correctPage() { var lastPageIndex = Math.max(0, scope.lastPage() - 1); if (scope.options.currentPage > lastPageIndex) { scope.setPage(lastPageIndex); } } scope.$on('$destroy', function () { rowsCopyArray = null; scope.currentSlice = null; scope.columnFilterTextDict = null; scope.handles = null; filteredRows = null; $(window).off('mousemove', mouseMove); $(window).off('mouseup', mouseUp); }); scope.options = angular.extend({}, { rows: [], itemsPerPage: 10, currentPage: 0, checkboxColumnWidth: 30, iconColumnWidth: 60, inColumnSearch: false }, scope.options); scope.handles = []; scope.columnFilterTextDict = {}; scope.filterRowsByColumnSearch = function () { updateSlice(); } scope.toggleInColumnSearch = function() { scope.options.inColumnSearch = !scope.options.inColumnSearch; }; scope.$watchCollection('options.rows', function (newVal, oldVal) { // Upptäcker inte ändringar inom en rad rowsCopyArray = scope.options.rows.slice(0); if (newVal !== oldVal) { // Rensa endast om ny ref. av array, antar inga rader har tagits bort/lagts till ur samma array. scope.options.selectedItems.length = 0; // Nya rader, töm alla ev. selekterade } updateSlice(); correctPage(); }); // När antal synliga kolumner ändras, synca om handles för resize scope.$watch(function () { return _.where(scope.options.columns, { Visible: true }).length; }, syncHandles); scope.$watch('options.currentPage', function (newVal, oldVal) { if (newVal !== oldVal) { updateSlice(); } }); scope.$watch('options.inColumnSearch', function (newVal, oldVal) { if (newVal !== oldVal) { updateSlice(); } }); scope.$watch('options.itemsPerPage', function (newVal, oldVal) { if (newVal !== oldVal && newVal !== undefined) { updateSlice(); correctPage(); } }); scope.getDataRows = (visibleColumns: fb.IGridColumn[], exportAllRows?: boolean, dontTranslateStatus?: boolean) => { var statusColumn = scope.options.getSortColumnForStatus(); var gridRows = getRowsInOrder(exportAllRows ? rowsCopyArray : scope.options.selectedItems || [], false); if (!exportAllRows) { gridRows = orderBy(gridRows, 'listIndex', false); } return _.map(gridRows, function (gridRow) { return _.map(visibleColumns, function (col: fb.IGridColumn) { if (dontTranslateStatus && col === statusColumn) { return gridRow["UppdragStatusSpecifikation"]; } var renderedValue = scope.deepFind(gridRow, col); if (col === statusColumn && scope.options.showIcon(gridRow)) { return scope.options.getIconTooltip(gridRow); } if (renderedValue !== '') { return renderedValue; } var dataValue = getPropValue(gridRow, col.Key); if (_.isBoolean(dataValue)) { return dataValue ? 'SANT' : 'FALSKT'; } if (_.isNumber(dataValue)) { return '' + dataValue; } return renderedValue; }); }) }; scope.getHeaders = statusColumn => { return _.sortBy(_.filter(scope.options.columns, function (col: fb.IGridColumn) { return col.Visible !== false || (col === statusColumn); }), function (col) { return col.Order; }); } scope.headersToText = (headers, statusColumn) => { return _.map(headers, function (col) { return col === statusColumn ? translate('GLOBALS.STATUS') : col.Text; }); } scope.exportToExcel = function (exportAllRows?: boolean) { const statusColumn = scope.options.getSortColumnForStatus() var headers = scope.getHeaders(statusColumn); const dataRows = scope.getDataRows(headers); var dataHeaderRow = scope.headersToText(headers, statusColumn); var exportMatrix = [dataHeaderRow].concat(dataRows); exportService.exportExcel(exportMatrix, 'Datauttag'); }; scope.resizeColumn = function (e, handle) { e.preventDefault(); lastStartPos = e.pageX; selectedHandle = handle; oldWidth = handle.Leftcolumn.Width; $(window).on('mousemove', mouseMove); $(window).one('mouseup', mouseUp); }; scope.syncHandles = syncHandles; scope.lastPage = function () { return Math.ceil((filteredRows || []).length / scope.options.itemsPerPage); }; scope.getRange = function () { var ret = []; var size = scope.lastPage(); var start = scope.options.currentPage; var end = start + gap; var begin; var count; if (size <= end) { end = size - 1; start = size - 1 - gap; count = 0; while (start > 0 && Math.abs(start - end) < 2 * gap) { count++; start--; } begin = start; } else { begin = start - gap; } if (begin <= 0) { count = 0; while (end < size - 1 && count < Math.abs(begin)) { end++; count++; } begin = 0; } for (var i = begin; i <= end; i++) { ret.push(i); } return ret; }; scope.isBooleanAndTrue = function (row: any, col: fb.IGridColumn): boolean { if (!_.isObject(row)) { return _.isBoolean(row) && row === true; } var value = getPropValue(row, col.Key); return _.isBoolean(value) && value === true; }; scope.deepFind = function (row: any, col: fb.IGridColumn) { if (!_.isObject(row)) { return (row || '').toString(); } //var cacheKey = 'CACHE-' + col.Key; //if (!_.isUndefined(row[cacheKey])) { // return row[cacheKey]; //} var value = getPropValue(row, col.Key); if (col.Converter !== undefined && typeof col.Converter === 'function') { value = col.Converter(row); } if (_.isNumber(value)) { value = value.toString(); } else if (_.isBoolean(value)) { value = ''; // Renderas som ikon om satt } else { value = (value || '').toString(); } //row[cacheKey] = value; return value; }; var latestSelect; scope.checkToSelect = function ($event: JQueryEventObject, object: any) { if ($event.shiftKey && latestSelect) { scope.options.selectedItems = []; var index1 = scope.currentSlice.indexOf(object); var index2 = scope.currentSlice.indexOf(latestSelect); var minIndex = index1 < index2 ? index1 : index2; var maxIndex = index1 > index2 ? index1 : index2; for (var k = minIndex; k <= maxIndex; k++) { selectRow(scope.currentSlice[k]); } latestSelect = object; } else { selectRow(object); latestSelect = object; } }; scope.toggleSelectAllInPage = function () { toggleSelectAllInPage(); } scope.$on('SelectAll', () => { scope.options.selectedItems = scope.options.rows; scope.options.warningIsAllSelectedInPage = false; scope.options.warningIsAllSelected = true; }); scope.$on('SelectNone', () => { scope.options.selectedItems = []; scope.options.warningIsAllSelected = false; }); scope.isAllSelectedInPage = function () { if (_.isEmpty(scope.currentSlice)) { return false; } return _.every(scope.currentSlice, function (obj) { return _.contains(scope.options.selectedItems, obj) }); } scope.onRowClicked = function ($event: JQueryEventObject, row: any) { if (_.isFunction(scope.options.onRowClicked)) { var target = $($event.target); if (!target.hasClass('fb-form-checkbox-wrapper') && !target.parent().hasClass('fb-form-checkbox-wrapper') && target.find('.fb-form-checkbox').length === 0) { scope.options.onRowClicked(row); } } }; scope.isSelected = function (object: any) { return _.findWhere(scope.options.selectedItems, { $$hashKey: object.$$hashKey }) !== undefined; }; scope.gotoFirstPage = function () { scope.setPage(0); }; scope.prevPage = function () { if (scope.options.currentPage > 0) { --scope.options.currentPage; } }; scope.nextPage = function () { if (scope.options.currentPage < (scope.options.rows.length / scope.options.itemsPerPage) - 1) { ++scope.options.currentPage; } }; scope.gotoLastPage = function () { scope.setPage(Math.ceil(scope.options.rows.length / scope.options.itemsPerPage) - 1); }; scope.setPage = function (page) { scope.options.currentPage = page; }; scope.sort = function (column: fb.IGridColumn) { column.Sort.Reverse = !column.Sort.Reverse; scope.options.currentSort = column; updateSlice(); }; scope.fitContent = function (handle : any) { var col : fb.IGridColumn = handle.Leftcolumn; var cssStyle = { fontSize: '14px' }; var marginWidth = 20; var maxContentWidth = _.max(_.map(scope.currentSlice || [], function (row) { var text = scope.deepFind(row, col) || ''; return textWidthService.getWidth(text, cssStyle); })) + marginWidth; var minWidth = Math.max(80, textWidthService.getWidth(col.Text, cssStyle) + marginWidth); var maxWidth = 500; col.Width = Math.max(minWidth, Math.min(maxContentWidth, maxWidth)); syncHandles(); }; scope.nbrResultInCurrentPage = function () { if (scope.options.rows.length > 0) { var showingRowsFrom = scope.options.currentPage * scope.options.itemsPerPage; var showingRowsTo = Math.min(showingRowsFrom + scope.options.itemsPerPage, scope.options.rows.length); return translate('OBJEKTLISTA.VISAR_TRAEFFAR') + ' ' + (showingRowsFrom + 1) + '-' + showingRowsTo + '.'; } return ''; }; scope.printGrid = function () { utskriftService.skrivUtTabellHtml(angular.element(element).find('#gridTable')); }; } return { restrict: 'E', templateUrl: 'app/Directives/fbGrid/fbGrid.html', replace: true, scope: { options: "=", showNavIcon: '@' }, link: link }; }]);