UNPKG

ng-grid

Version:

__Contributors:__

1,260 lines (1,210 loc) 133 kB
/*********************************************** * ng-grid JavaScript Library * Authors: https://github.com/angular-ui/ng-grid/blob/master/README.md * License: MIT (http://www.opensource.org/licenses/mit-license.php) * Compiled At: 03/06/2013 17:55 ***********************************************/ (function(window) { 'use strict'; // the # of rows we want to add to the top and bottom of the rendered grid rows var EXCESS_ROWS = 6; var SCROLL_THRESHOLD = 4; var ASC = "asc"; // constant for sorting direction var DESC = "desc"; // constant for sorting direction var NG_FIELD = '_ng_field_'; var NG_DEPTH = '_ng_depth_'; var NG_HIDDEN = '_ng_hidden_'; var NG_COLUMN = '_ng_column_'; var CUSTOM_FILTERS = /CUSTOM_FILTERS/g; var COL_FIELD = /COL_FIELD/g; var DISPLAY_CELL_TEMPLATE = /DISPLAY_CELL_TEMPLATE/g; var EDITABLE_CELL_TEMPLATE = /EDITABLE_CELL_TEMPLATE/g; var TEMPLATE_REGEXP = /<.+>/; if (!window.ng) { window.ng = {}; } window.ngGrid = {}; window.ngGrid.i18n = {}; // Declare app level module which depends on filters, and services var ngGridServices = angular.module('ngGrid.services', []); var ngGridDirectives = angular.module('ngGrid.directives', []); var ngGridFilters = angular.module('ngGrid.filters', []); // initialization of services into the main module angular.module('ngGrid', ['ngGrid.services', 'ngGrid.directives', 'ngGrid.filters']); //set event binding on the grid so we can select using the up/down keys ng.moveSelectionHandler = function($scope, elm, evt, grid) { if ($scope.selectionService.selectedItems === undefined) { return true; } var charCode = evt.which || evt.keyCode, newColumnIndex, lastInRow = false, firstInRow = false, rowIndex = $scope.selectionService.lastClickedRow.rowIndex; if ($scope.col) { newColumnIndex = $scope.col.index; } if(charCode != 37 && charCode != 38 && charCode != 39 && charCode != 40 && charCode != 9 && charCode != 13){ return true; } if($scope.enableCellSelection){ if(charCode == 9){ //tab key evt.preventDefault(); } var focusedOnFirstColumn = $scope.showSelectionCheckbox ? $scope.col.index == 1 : $scope.col.index == 0; var focusedOnFirstVisibleColumns = $scope.$index == 1 || $scope.$index == 0; var focusedOnLastVisibleColumns = $scope.$index == ($scope.renderedColumns.length - 1) || $scope.$index == ($scope.renderedColumns.length - 2); var focusedOnLastColumn = $scope.col.index == ($scope.columns.length - 1); if(charCode == 37 || charCode == 9 && evt.shiftKey){ if (focusedOnFirstVisibleColumns) { if(focusedOnFirstColumn && charCode == 9 && evt.shiftKey){ grid.$viewport.scrollLeft(grid.$canvas.width()); newColumnIndex = $scope.columns.length - 1; firstInRow = true; } else { grid.$viewport.scrollLeft(grid.$viewport.scrollLeft() - $scope.col.width); } } if(!focusedOnFirstColumn){ newColumnIndex -= 1; } } else if(charCode == 39 || charCode == 9 && !evt.shiftKey){ if (focusedOnLastVisibleColumns) { if(focusedOnLastColumn && charCode == 9 && !evt.shiftKey){ grid.$viewport.scrollLeft(0); newColumnIndex = $scope.showSelectionCheckbox ? 1 : 0; lastInRow = true; } else { grid.$viewport.scrollLeft(grid.$viewport.scrollLeft() + $scope.col.width); } } if(!focusedOnLastColumn){ newColumnIndex += 1; } } } var items; if ($scope.configGroups.length > 0) { items = grid.rowFactory.parsedData.filter(function (row) { return !row.isAggRow; }); } else { items = grid.filteredRows; } var offset = 0; if(rowIndex != 0 && (charCode == 38 || charCode == 13 && evt.shiftKey || charCode == 9 && evt.shiftKey && firstInRow)){ //arrow key up or shift enter or tab key and first item in row offset = -1; } else if(rowIndex != items.length - 1 && (charCode == 40 || charCode == 13 && !evt.shiftKey || charCode == 9 && lastInRow)){//arrow key down, enter, or tab key and last item in row? offset = 1; } if (offset) { var r = items[rowIndex + offset]; if (r.beforeSelectionChange(r, evt)) { r.continueSelection(evt); $scope.$emit('ngGridEventDigestGridParent'); if ($scope.selectionService.lastClickedRow.renderedRowIndex >= $scope.renderedRows.length - EXCESS_ROWS - 2) { grid.$viewport.scrollTop(grid.$viewport.scrollTop() + $scope.rowHeight); } else if ($scope.selectionService.lastClickedRow.renderedRowIndex <= EXCESS_ROWS + 2) { grid.$viewport.scrollTop(grid.$viewport.scrollTop() - $scope.rowHeight); } } } if($scope.enableCellSelection){ setTimeout(function(){ $scope.domAccessProvider.focusCellElement($scope, $scope.renderedColumns.indexOf($scope.columns[newColumnIndex])); },3); } return false; }; if (!String.prototype.trim) { String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ''); }; } if (!Array.prototype.indexOf) { Array.prototype.indexOf = function(elt /*, from*/) { var len = this.length >>> 0; var from = Number(arguments[1]) || 0; from = (from < 0) ? Math.ceil(from) : Math.floor(from); if (from < 0) { from += len; } for (; from < len; from++) { if (from in this && this[from] === elt) { return from; } } return -1; }; } if (!Array.prototype.filter) { Array.prototype.filter = function(fun /*, thisp */) { "use strict"; var t = Object(this); var len = t.length >>> 0; if (typeof fun !== "function") { throw new TypeError(); } var res = []; var thisp = arguments[1]; for (var i = 0; i < len; i++) { if (i in t) { var val = t[i]; // in case fun mutates this if (fun.call(thisp, val, i, t)) { res.push(val); } } } return res; }; } ng.utils = { visualLength: function(node) { var elem = document.getElementById('testDataLength'); if (!elem) { elem = document.createElement('SPAN'); elem.id = "testDataLength"; elem.style.visibility = "hidden"; document.body.appendChild(elem); } $(elem).css('font', $(node).css('font')); elem.innerHTML = $(node).text(); return elem.offsetWidth; }, forIn: function(obj, action) { for (var prop in obj) { if (obj.hasOwnProperty(prop)) { action(obj[prop], prop); } } }, evalProperty: function(entity, path) { var propPath = path.split('.'), i = 0; var tempProp = entity[propPath[i]], links = propPath.length; i++; while (tempProp && i < links) { tempProp = tempProp[propPath[i]]; i++; } return tempProp; }, endsWith: function(str, suffix) { if (!str || !suffix || typeof str != "string") { return false; } return str.indexOf(suffix, str.length - suffix.length) !== -1; }, isNullOrUndefined: function(obj) { if (obj === undefined || obj === null) { return true; } return false; }, getElementsByClassName: function(cl) { var retnode = []; var myclass = new RegExp('\\b' + cl + '\\b'); var elem = document.getElementsByTagName('*'); for (var i = 0; i < elem.length; i++) { var classes = elem[i].className; if (myclass.test(classes)) { retnode.push(elem[i]); } } return retnode; }, newId: (function() { var seedId = new Date().getTime(); return function() { return seedId += 1; }; })(), seti18n: function($scope, language) { var $langPack = window.ngGrid.i18n[language]; for (var label in $langPack) { $scope.i18n[label] = $langPack[label]; } }, // we copy KO's ie detection here bc it isn't exported in the min versions of KO // Detect IE versions for workarounds (uses IE conditionals, not UA string, for robustness) ieVersion: (function() { var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i'); // Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment while (div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->', iElems[0]); return version > 4 ? version : undefined; })() }; $.extend(ng.utils, { isIe6: (function() { return ng.utils.ieVersion === 6; })(), isIe7: (function() { return ng.utils.ieVersion === 7; })(), isIe: (function() { return ng.utils.ieVersion !== undefined; })() }); ngGridFilters.filter('checkmark', function() { return function(input) { return input ? '\u2714' : '\u2718'; }; }); ngGridFilters.filter('ngColumns', function() { return function(input) { return input.filter(function(col) { return !col.isAggCol; }); }; }); ngGridServices.factory('DomUtilityService', function() { var domUtilityService = {}; var regexCache = {}; var getWidths = function() { var $testContainer = $('<div></div>'); $testContainer.appendTo('body'); // 1. Run all the following measurements on startup! //measure Scroll Bars $testContainer.height(100).width(100).css("position", "absolute").css("overflow", "scroll"); $testContainer.append('<div style="height: 400px; width: 400px;"></div>'); domUtilityService.ScrollH = ($testContainer.height() - $testContainer[0].clientHeight); domUtilityService.ScrollW = ($testContainer.width() - $testContainer[0].clientWidth); $testContainer.empty(); //clear styles $testContainer.attr('style', ''); //measure letter sizes using a pretty typical font size and fat font-family $testContainer.append('<span style="font-family: Verdana, Helvetica, Sans-Serif; font-size: 14px;"><strong>M</strong></span>'); domUtilityService.LetterW = $testContainer.children().first().width(); $testContainer.remove(); }; domUtilityService.eventStorage = {}; domUtilityService.AssignGridContainers = function($scope, rootEl, grid) { grid.$root = $(rootEl); //Headers grid.$topPanel = grid.$root.find(".ngTopPanel"); grid.$groupPanel = grid.$root.find(".ngGroupPanel"); grid.$headerContainer = grid.$topPanel.find(".ngHeaderContainer"); $scope.$headerContainer = grid.$headerContainer; grid.$headerScroller = grid.$topPanel.find(".ngHeaderScroller"); grid.$headers = grid.$headerScroller.children(); //Viewport grid.$viewport = grid.$root.find(".ngViewport"); //Canvas grid.$canvas = grid.$viewport.find(".ngCanvas"); //Footers grid.$footerPanel = grid.$root.find(".ngFooterPanel"); $scope.$watch(function () { return grid.$viewport.scrollLeft(); }, function (newLeft) { return grid.$headerContainer.scrollLeft(newLeft); }); domUtilityService.UpdateGridLayout($scope, grid); }; domUtilityService.getRealWidth = function (obj) { var width = 0; var props = { visibility: "hidden", display: "block" }; var hiddenParents = obj.parents().andSelf().not(':visible'); $.swap(hiddenParents[0], props, function () { width = obj.outerWidth(); }); return width; }; domUtilityService.UpdateGridLayout = function($scope, grid) { //catch this so we can return the viewer to their original scroll after the resize! var scrollTop = grid.$viewport.scrollTop(); grid.elementDims.rootMaxW = grid.$root.width(); if (grid.$root.is(':hidden')) { grid.elementDims.rootMaxW = domUtilityService.getRealWidth(grid.$root); } grid.elementDims.rootMaxH = grid.$root.height(); //check to see if anything has changed grid.refreshDomSizes(); $scope.adjustScrollTop(scrollTop, true); //ensure that the user stays scrolled where they were }; domUtilityService.numberOfGrids = 0; domUtilityService.BuildStyles = function($scope, grid, digest) { var rowHeight = grid.config.rowHeight, $style = grid.$styleSheet, gridId = grid.gridId, css, cols = $scope.columns, sumWidth = 0; if (!$style) { $style = $('#' + gridId); if (!$style[0]) { $style = $("<style id='" + gridId + "' type='text/css' rel='stylesheet' />").appendTo(grid.$root); } } $style.empty(); var trw = $scope.totalRowWidth(); css = "." + gridId + " .ngCanvas { width: " + trw + "px; }" + "." + gridId + " .ngRow { width: " + trw + "px; }" + "." + gridId + " .ngCanvas { width: " + trw + "px; }" + "." + gridId + " .ngHeaderScroller { width: " + (trw + domUtilityService.ScrollH + 2) + "px}"; for (var i = 0; i < cols.length; i++) { var col = cols[i]; if (col.visible) { var colLeft = col.pinned ? grid.$viewport.scrollLeft() + sumWidth : sumWidth; css += "." + gridId + " .col" + i + " { width: " + col.width + "px; left: " + colLeft + "px; height: " + rowHeight + "px }" + "." + gridId + " .colt" + i + " { width: " + col.width + "px; }"; sumWidth += col.width; } }; if (ng.utils.isIe) { // IE $style[0].styleSheet.cssText = css; } else { $style[0].appendChild(document.createTextNode(css)); } grid.$styleSheet = $style; if (digest) { $scope.adjustScrollLeft(grid.$viewport.scrollLeft()); domUtilityService.digest($scope); } }; domUtilityService.setColLeft = function(col, colLeft, grid) { if (grid.$styleSheet) { var regex = regexCache[col.index]; if (!regex) { regex = regexCache[col.index] = new RegExp("\.col" + col.index + " \{ width: [0-9]+px; left: [0-9]+px"); } var str = grid.$styleSheet.html(); var newStr = str.replace(regex, "\.col" + col.index + " \{ width: " + col.width + "px; left: " + colLeft + "px"); if (ng.utils.isIe) { // IE setTimeout(function() { grid.$styleSheet.html(newStr); }); } else { grid.$styleSheet.html(newStr); } } }; domUtilityService.setColLeft.immediate = 1; domUtilityService.RebuildGrid = function($scope, grid){ domUtilityService.UpdateGridLayout($scope, grid); if (grid.config.maintainColumnRatios) { grid.configureColumnWidths(); } $scope.adjustScrollLeft(grid.$viewport.scrollLeft()); domUtilityService.BuildStyles($scope, grid, true); }; domUtilityService.digest = function($scope) { if (!$scope.$root.$$phase) { $scope.$digest(); } }; domUtilityService.ScrollH = 17; // default in IE, Chrome, & most browsers domUtilityService.ScrollW = 17; // default in IE, Chrome, & most browsers domUtilityService.LetterW = 10; getWidths(); return domUtilityService; }); ngGridServices.factory('SortService', ['$parse', function($parse) { var sortService = {}; sortService.colSortFnCache = {}; // cache of sorting functions. Once we create them, we don't want to keep re-doing it // this takes an piece of data from the cell and tries to determine its type and what sorting // function to use for it // @item - the cell data sortService.guessSortFn = function(item) { var itemType = typeof(item); //check for numbers and booleans switch (itemType) { case "number": return sortService.sortNumber; case "boolean": return sortService.sortBool; case "string": // if number string return number string sort fn. else return the str return item.match(/^-?[£$¤]?[\d,.]+%?$/) ? sortService.sortNumberStr : sortService.sortAlpha; default: //check if the item is a valid Date if (Object.prototype.toString.call(item) === '[object Date]') { return sortService.sortDate; } else { //finally just sort the basic sort... return sortService.basicSort; } } }; //#region Sorting Functions sortService.basicSort = function(a, b) { if (a == b) { return 0; } if (a < b) { return -1; } return 1; }; sortService.sortNumber = function(a, b) { return a - b; }; sortService.sortNumberStr = function(a, b) { var numA, numB, badA = false, badB = false; numA = parseFloat(a.replace(/[^0-9.-]/g, '')); if (isNaN(numA)) { badA = true; } numB = parseFloat(b.replace(/[^0-9.-]/g, '')); if (isNaN(numB)) { badB = true; } // we want bad ones to get pushed to the bottom... which effectively is "greater than" if (badA && badB) { return 0; } if (badA) { return 1; } if (badB) { return -1; } return numA - numB; }; sortService.sortAlpha = function(a, b) { var strA = a.toLowerCase(), strB = b.toLowerCase(); return strA == strB ? 0 : (strA < strB ? -1 : 1); }; sortService.sortDate = function(a, b) { var timeA = a.getTime(), timeB = b.getTime(); return timeA == timeB ? 0 : (timeA < timeB ? -1 : 1); }; sortService.sortBool = function(a, b) { if (a && b) { return 0; } if (!a && !b) { return 0; } else { return a ? 1 : -1; } }; //#endregion // the core sorting logic trigger sortService.sortData = function(sortInfo, data /*datasource*/) { // first make sure we are even supposed to do work if (!data || !sortInfo) { return; } var l = sortInfo.fields.length, order = sortInfo.fields, col, direction, // IE9 HACK.... omg, I can't reference data array within the sort fn below. has to be a separate reference....!!!! d = data.slice(0); //now actually sort the data data.sort(function (itemA, itemB) { var tem = 0, indx = 0, sortFn; while (tem == 0 && indx < l) { // grab the metadata for the rest of the logic col = sortInfo.columns[indx]; direction = sortInfo.directions[indx], sortFn = sortService.getSortFn(col, d); var propA = $parse(order[indx])(itemA); var propB = $parse(order[indx])(itemB); // we want to allow zero values to be evaluated in the sort function if ((!propA && propA != 0) || (!propB && propB != 0)) { // we want to force nulls and such to the bottom when we sort... which effectively is "greater than" if (!propB && !propA) { tem = 0; } else if (!propA) { tem = 1; } else if (!propB) { tem = -1; } } else { tem = sortFn(propA, propB); } indx++; } //made it this far, we don't have to worry about null & undefined if (direction === ASC) { return tem; } else { return 0 - tem; } }); }; sortService.Sort = function(sortInfo, data) { if (sortService.isSorting) { return; } sortService.isSorting = true; sortService.sortData(sortInfo, data); sortService.isSorting = false; }; sortService.getSortFn = function(col, data) { var sortFn = undefined, item; //see if we already figured out what to use to sort the column if (sortService.colSortFnCache[col.field]) { sortFn = sortService.colSortFnCache[col.field]; } else if (col.sortingAlgorithm != undefined) { sortFn = col.sortingAlgorithm; sortService.colSortFnCache[col.field] = col.sortingAlgorithm; } else { // try and guess what sort function to use item = data[0]; if (!item) { return sortFn; } sortFn = sortService.guessSortFn($parse(col.field)(item)); //cache it if (sortFn) { sortService.colSortFnCache[col.field] = sortFn; } else { // we assign the alpha sort because anything that is null/undefined will never get passed to // the actual sorting function. It will get caught in our null check and returned to be sorted // down to the bottom sortFn = sortService.sortAlpha; } } return sortFn; }; return sortService; }]); ng.Aggregate = function (aggEntity, rowFactory, rowHeight) { var self = this; self.rowIndex = 0; self.offsetTop = self.rowIndex * rowHeight; self.entity = aggEntity; self.label = aggEntity.gLabel; self.field = aggEntity.gField; self.depth = aggEntity.gDepth; self.parent = aggEntity.parent; self.children = aggEntity.children; self.aggChildren = aggEntity.aggChildren; self.aggIndex = aggEntity.aggIndex; self.collapsed = true; self.isAggRow = true; self.offsetleft = aggEntity.gDepth * 25; self.aggLabelFilter = aggEntity.aggLabelFilter; self.toggleExpand = function() { self.collapsed = self.collapsed ? false : true; if (self.orig) { self.orig.collapsed = self.collapsed; } self.notifyChildren(); }; self.setExpand = function(state) { self.collapsed = state; self.notifyChildren(); }; self.notifyChildren = function () { var longest = Math.max(rowFactory.aggCache.length, self.children.length); for (var i = 0; i < longest; i++) { if (self.aggChildren[i]) { self.aggChildren[i].entity[NG_HIDDEN] = self.collapsed; if (self.collapsed) { self.aggChildren[i].setExpand(self.collapsed); } } if (self.children[i]) { self.children[i][NG_HIDDEN] = self.collapsed; } if (i > self.aggIndex && rowFactory.aggCache[i]) { var agg = rowFactory.aggCache[i]; var offset = (30 * self.children.length); agg.offsetTop = self.collapsed ? agg.offsetTop - offset : agg.offsetTop + offset; } }; rowFactory.renderedChange(); }; self.aggClass = function() { return self.collapsed ? "ngAggArrowCollapsed" : "ngAggArrowExpanded"; }; self.totalChildren = function() { if (self.aggChildren.length > 0) { var i = 0; var recurse = function(cur) { if (cur.aggChildren.length > 0) { angular.forEach(cur.aggChildren, function(a) { recurse(a); }); } else { i += cur.children.length; } }; recurse(self); return i; } else { return self.children.length; } }; self.copy = function () { var ret = new ng.Aggregate(self.entity, rowFactory, rowHeight); ret.orig = self; return ret; }; }; ng.Column = function(config, $scope, grid, domUtilityService, $templateCache) { var self = this, colDef = config.colDef, delay = 500, clicks = 0, timer = null; self.width = colDef.width; self.groupIndex = 0; self.isGroupedBy = false; self.minWidth = !colDef.minWidth ? 50 : colDef.minWidth; self.maxWidth = !colDef.maxWidth ? 9000 : colDef.maxWidth; self.enableCellEdit = config.enableCellEdit || colDef.enableCellEdit; self.headerRowHeight = config.headerRowHeight; self.displayName = colDef.displayName || colDef.field; self.index = config.index; self.isAggCol = config.isAggCol; self.cellClass = colDef.cellClass; self.sortPriority = undefined; self.zIndex = function() { return self.pinned ? 5 : 0; }; self.cellFilter = colDef.cellFilter ? colDef.cellFilter : ""; self.field = colDef.field; self.aggLabelFilter = colDef.cellFilter || colDef.aggLabelFilter; self.visible = ng.utils.isNullOrUndefined(colDef.visible) || colDef.visible; self.sortable = false; self.resizable = false; self.pinnable = false; self.pinned = colDef.pinned; self.originalIndex = self.index; self.groupable = ng.utils.isNullOrUndefined(colDef.groupable) || colDef.groupable; if (config.enableSort) { self.sortable = ng.utils.isNullOrUndefined(colDef.sortable) || colDef.sortable; } if (config.enableResize) { self.resizable = ng.utils.isNullOrUndefined(colDef.resizable) || colDef.resizable; } if (config.enablePinning) { self.pinnable = ng.utils.isNullOrUndefined(colDef.pinnable) || colDef.pinnable; } self.sortDirection = undefined; self.sortingAlgorithm = colDef.sortFn; self.headerClass = colDef.headerClass; self.cursor = self.sortable ? 'pointer' : 'default'; self.headerCellTemplate = colDef.headerCellTemplate || $templateCache.get('headerCellTemplate.html'); self.cellTemplate = colDef.cellTemplate || $templateCache.get('cellTemplate.html').replace(CUSTOM_FILTERS, self.cellFilter ? "|" + self.cellFilter : ""); if(self.enableCellEdit) { self.cellEditTemplate = $templateCache.get('cellEditTemplate.html'); self.editableCellTemplate = colDef.editableCellTemplate || $templateCache.get('editableCellTemplate.html'); } if (colDef.cellTemplate && !TEMPLATE_REGEXP.test(colDef.cellTemplate)) { self.cellTemplate = $.ajax({ type: "GET", url: colDef.cellTemplate, async: false }).responseText; } if (self.enableCellEdit && colDef.editableCellTemplate && !TEMPLATE_REGEXP.test(colDef.editableCellTemplate)) { self.editableCellTemplate = $.ajax({ type: "GET", url: colDef.editableCellTemplate, async: false }).responseText; } if (colDef.headerCellTemplate && !TEMPLATE_REGEXP.test(colDef.headerCellTemplate)) { self.headerCellTemplate = $.ajax({ type: "GET", url: colDef.headerCellTemplate, async: false }).responseText; } self.colIndex = function() { return "col" + self.index + " colt" + self.index; }; self.groupedByClass = function() { return self.isGroupedBy ? "ngGroupedByIcon" : "ngGroupIcon"; }; self.toggleVisible = function() { self.visible = !self.visible; }; self.showSortButtonUp = function() { return self.sortable ? self.sortDirection === DESC : self.sortable; }; self.showSortButtonDown = function() { return self.sortable ? self.sortDirection === ASC : self.sortable; }; self.noSortVisible = function() { return !self.sortDirection; }; self.sort = function(evt) { if (!self.sortable) { return true; // column sorting is disabled, do nothing } var dir = self.sortDirection === ASC ? DESC : ASC; self.sortDirection = dir; config.sortCallback(self, evt); return false; }; self.gripClick = function() { clicks++; //count clicks if (clicks === 1) { timer = setTimeout(function() { //Here you can add a single click action. clicks = 0; //after action performed, reset counter }, delay); } else { clearTimeout(timer); //prevent single-click action config.resizeOnDataCallback(self); //perform double-click action clicks = 0; //after action performed, reset counter } }; self.gripOnMouseDown = function(event) { if (event.ctrlKey && !self.pinned) { self.toggleVisible(); domUtilityService.BuildStyles($scope, grid); return true; } event.target.parentElement.style.cursor = 'col-resize'; self.startMousePosition = event.clientX; self.origWidth = self.width; $(document).mousemove(self.onMouseMove); $(document).mouseup(self.gripOnMouseUp); return false; }; self.onMouseMove = function(event) { var diff = event.clientX - self.startMousePosition; var newWidth = diff + self.origWidth; self.width = (newWidth < self.minWidth ? self.minWidth : (newWidth > self.maxWidth ? self.maxWidth : newWidth)); domUtilityService.BuildStyles($scope, grid); return false; }; self.gripOnMouseUp = function (event) { $(document).off('mousemove', self.onMouseMove); $(document).off('mouseup', self.gripOnMouseUp); event.target.parentElement.style.cursor = 'default'; $scope.adjustScrollLeft(0); domUtilityService.digest($scope); return false; }; self.copy = function() { var ret = new ng.Column(config, $scope, grid, domUtilityService, $templateCache); ret.isClone = true; ret.orig = self; return ret; }; self.setVars = function (fromCol) { self.orig = fromCol; self.width = fromCol.width; self.groupIndex = fromCol.groupIndex; self.isGroupedBy = fromCol.isGroupedBy; self.displayName = fromCol.displayName; self.index = fromCol.index; self.isAggCol = fromCol.isAggCol; self.cellClass = fromCol.cellClass; self.cellFilter = fromCol.cellFilter; self.field = fromCol.field; self.aggLabelFilter = fromCol.aggLabelFilter; self.visible = fromCol.visible; self.sortable = fromCol.sortable; self.resizable = fromCol.resizable; self.pinnable = fromCol.pinnable; self.pinned = fromCol.pinned; self.originalIndex = fromCol.originalIndex; self.sortDirection = fromCol.sortDirection; self.sortingAlgorithm = fromCol.sortingAlgorithm; self.headerClass = fromCol.headerClass; self.headerCellTemplate = fromCol.headerCellTemplate; self.cellTemplate = fromCol.cellTemplate; self.cellEditTemplate = fromCol.cellEditTemplate; }; }; ng.Dimension = function(options) { this.outerHeight = null; this.outerWidth = null; $.extend(this, options); }; ng.DomAccessProvider = function(grid) { var self = this, previousColumn; self.selectInputElement = function(elm){ var node = elm.nodeName.toLowerCase(); if(node == 'input' || node == 'textarea'){ elm.select(); } }; self.focusCellElement = function($scope, index){ if($scope.selectionService.lastClickedRow){ var columnIndex = index != undefined ? index : previousColumn; var elm = $scope.selectionService.lastClickedRow.clone ? $scope.selectionService.lastClickedRow.clone.elm : $scope.selectionService.lastClickedRow.elm; if (columnIndex != undefined && elm) { var columns = angular.element(elm[0].children).filter(function () { return this.nodeType != 8;}); //Remove html comments for IE8 var i = Math.max(Math.min($scope.renderedColumns.length - 1, columnIndex), 0); if(grid.config.showSelectionCheckbox && angular.element(columns[i]).scope() && angular.element(columns[i]).scope().col.index == 0){ i = 1; //don't want to focus on checkbox } if (columns[i]) { columns[i].children[0].focus(); } previousColumn = columnIndex; } } }; var changeUserSelect = function(elm, value) { elm.css({ '-webkit-touch-callout': value, '-webkit-user-select': value, '-khtml-user-select': value, '-moz-user-select': value == 'none' ? '-moz-none' : value, '-ms-user-select': value, 'user-select': value }); }; self.selectionHandlers = function($scope, elm){ var doingKeyDown = false; elm.bind('keydown', function(evt) { if (evt.keyCode == 16) { //shift key changeUserSelect(elm, 'none', evt); return true; } else if (!doingKeyDown) { doingKeyDown = true; var ret = ng.moveSelectionHandler($scope, elm, evt, grid); doingKeyDown = false; return ret; } return true; }); elm.bind('keyup', function(evt) { if (evt.keyCode == 16) { //shift key changeUserSelect(elm, 'text', evt); } return true; }); }; }; ng.EventProvider = function(grid, $scope, domUtilityService) { var self = this; // The init method gets called during the ng-grid directive execution. self.colToMove = undefined; self.groupToMove = undefined; self.assignEvents = function() { // Here we set the onmousedown event handler to the header container. if (grid.config.jqueryUIDraggable && !grid.config.enablePinning) { grid.$groupPanel.droppable({ addClasses: false, drop: function(event) { self.onGroupDrop(event); } }); $scope.$evalAsync(self.setDraggables); } else { grid.$groupPanel.on('mousedown', self.onGroupMouseDown).on('dragover', self.dragOver).on('drop', self.onGroupDrop); grid.$headerScroller.on('mousedown', self.onHeaderMouseDown).on('dragover', self.dragOver); if (grid.config.enableColumnReordering && !grid.config.enablePinning) { grid.$headerScroller.on('drop', self.onHeaderDrop); } if (grid.config.enableRowReordering) { grid.$viewport.on('mousedown', self.onRowMouseDown).on('dragover', self.dragOver).on('drop', self.onRowDrop); } } $scope.$watch('columns', self.setDraggables, true); }; self.dragStart = function(evt){ //FireFox requires there to be dataTransfer if you want to drag and drop. evt.dataTransfer.setData('text', ''); //cannot be empty string }; self.dragOver = function(evt) { evt.preventDefault(); }; //For JQueryUI self.setDraggables = function() { if (!grid.config.jqueryUIDraggable) { //Fix for FireFox. Instead of using jQuery on('dragstart', function) on find, we have to use addEventListeners for each column. var columns = grid.$root.find('.ngHeaderSortColumn'); //have to iterate if using addEventListener angular.forEach(columns, function(col){ col.setAttribute('draggable', 'true'); //jQuery 'on' function doesn't have dataTransfer as part of event in handler unless added to event props, which is not recommended //See more here: http://api.jquery.com/category/events/event-object/ if (col.addEventListener) { //IE8 doesn't have drag drop or event listeners col.addEventListener('dragstart', self.dragStart); } }); if (navigator.userAgent.indexOf("MSIE") != -1){ //call native IE dragDrop() to start dragging grid.$root.find('.ngHeaderSortColumn').bind('selectstart', function () { this.dragDrop(); return false; }); } } else { grid.$root.find('.ngHeaderSortColumn').draggable({ helper: 'clone', appendTo: 'body', stack: 'div', addClasses: false, start: function(event) { self.onHeaderMouseDown(event); } }).droppable({ drop: function(event) { self.onHeaderDrop(event); } }); } }; self.onGroupMouseDown = function(event) { var groupItem = $(event.target); // Get the scope from the header container if (groupItem[0].className != 'ngRemoveGroup') { var groupItemScope = angular.element(groupItem).scope(); if (groupItemScope) { // set draggable events if (!grid.config.jqueryUIDraggable) { groupItem.attr('draggable', 'true'); if(this.addEventListener){//IE8 doesn't have drag drop or event listeners this.addEventListener('dragstart', self.dragStart); } if (navigator.userAgent.indexOf("MSIE") != -1){ //call native IE dragDrop() to start dragging groupItem.bind('selectstart', function () { this.dragDrop(); return false; }); } } // Save the column for later. self.groupToMove = { header: groupItem, groupName: groupItemScope.group, index: groupItemScope.$index }; } } else { self.groupToMove = undefined; } }; self.onGroupDrop = function(event) { event.stopPropagation(); // clear out the colToMove object var groupContainer; var groupScope; if (self.groupToMove) { // Get the closest header to where we dropped groupContainer = $(event.target).closest('.ngGroupElement'); // Get the scope from the header. if (groupContainer.context.className == 'ngGroupPanel') { $scope.configGroups.splice(self.groupToMove.index, 1); $scope.configGroups.push(self.groupToMove.groupName); } else { groupScope = angular.element(groupContainer).scope(); if (groupScope) { // If we have the same column, do nothing. if (self.groupToMove.index != groupScope.$index) { // Splice the columns $scope.configGroups.splice(self.groupToMove.index, 1); $scope.configGroups.splice(groupScope.$index, 0, self.groupToMove.groupName); } } } self.groupToMove = undefined; grid.fixGroupIndexes(); } else if (self.colToMove) { if ($scope.configGroups.indexOf(self.colToMove.col) == -1) { groupContainer = $(event.target).closest('.ngGroupElement'); // Get the scope from the header. if (groupContainer.context.className == 'ngGroupPanel' || groupContainer.context.className == 'ngGroupPanelDescription ng-binding') { $scope.groupBy(self.colToMove.col); } else { groupScope = angular.element(groupContainer).scope(); if (groupScope) { // Splice the columns $scope.removeGroup(groupScope.$index); } } } self.colToMove = undefined; } if (!$scope.$$phase) { $scope.$apply(); } }; //Header functions self.onHeaderMouseDown = function(event) { // Get the closest header container from where we clicked. var headerContainer = $(event.target).closest('.ngHeaderSortColumn'); // Get the scope from the header container var headerScope = angular.element(headerContainer).scope(); if (headerScope) { // Save the column for later. self.colToMove = { header: headerContainer, col: headerScope.col }; } }; self.onHeaderDrop = function(event) { if (!self.colToMove) { return; } // Get the closest header to where we dropped var headerContainer = $(event.target).closest('.ngHeaderSortColumn'); // Get the scope from the header. var headerScope = angular.element(headerContainer).scope(); if (headerScope) { // If we have the same column, do nothing. if (self.colToMove.col == headerScope.col) { return; } // Splice the columns $scope.columns.splice(self.colToMove.col.index, 1); $scope.columns.splice(headerScope.col.index, 0, self.colToMove.col); grid.fixColumnIndexes(); // Finally, rebuild the CSS styles. domUtilityService.BuildStyles($scope, grid, true); // clear out the colToMove object self.colToMove = undefined; } }; // Row functions self.onRowMouseDown = function(event) { // Get the closest row element from where we clicked. var targetRow = $(event.target).closest('.ngRow'); // Get the scope from the row element var rowScope = angular.element(targetRow).scope(); if (rowScope) { // set draggable events targetRow.attr('draggable', 'true'); // Save the row for later. domUtilityService.eventStorage.rowToMove = { targetRow: targetRow, scope: rowScope }; } }; self.onRowDrop = function(event) { // Get the closest row to where we dropped var targetRow = $(event.target).closest('.ngRow'); // Get the scope from the row element. var rowScope = angular.element(targetRow).scope(); if (rowScope) { // If we have the same Row, do nothing. var prevRow = domUtilityService.eventStorage.rowToMove; if (prevRow.scope.row == rowScope.row) { return; } grid.changeRowOrder(prevRow.scope.row, rowScope.row); grid.searchProvider.evalFilter(); // clear out the rowToMove object domUtilityService.eventStorage.rowToMove = undefined; // if there isn't an apply already in progress lets start one domUtilityService.digest(rowScope.$root); } }; self.assignGridEventHandlers = function() { //Chrome and firefox both need a tab index so the grid can recieve focus. //need to give the grid a tabindex if it doesn't already have one so //we'll just give it a tab index of the corresponding gridcache index //that way we'll get the same result every time it is run. //configurable within the options. if (grid.config.tabIndex === -1) { grid.$viewport.attr('tabIndex', domUtilityService.numberOfGrids); domUtilityService.numberOfGrids++; } else { grid.$viewport.attr('tabIndex', grid.config.tabIndex); } $(window).resize(function() { domUtilityService.RebuildGrid($scope,grid); }); }; // In this example we want to assign grid events. self.assignGridEventHandlers(); self.assignEvents(); }; ng.Footer = function($scope, grid) { $scope.maxRows = function () { var ret = Math.max($scope.pagingOptions.totalServerItems, grid.data.length); return ret; }; $scope.multiSelect = (grid.config.enableRowSelection && grid.config.multiSelect); $scope.selectedItemCount = grid.selectedItemCount; $scope.maxPages = function () { return Math.ceil($scope.maxRows() / $scope.pagingOptions.pageSize); }; $scope.pageForward = function() { var page = $scope.pagingOptions.currentPage; if ($scope.pagingOptions.totalServerItems > 0) { $scope.pagingOptions.currentPage = Math.min(page + 1, $scope.maxPages()); } else { $scope.pagingOptions.currentPage++; } }; $scope.pageBackward = function() { var page = $scope.pagingOptions.currentPage; $scope.pagingOptions.currentPage = Math.max(page - 1, 1); }; $scope.pageToFirst = function() { $scope.pagingOptions.currentPage = 1; }; $scope.pageToLast = function() { var maxPages = $scope.maxPages(); $scope.pagingOptions.currentPage = maxPages; }; $scope.cantPageForward = function() { var curPage = $scope.pagingOptions.currentPage; var maxPages = $scope.maxPages(); if ($scope.pagingOptions.totalServerItems > 0) { return !(curPage < maxPages); } else { return grid.data.length < 1; } }; $scope.cantPageToLast = function() { if ($scope.pagingOptions.totalServerItems > 0) { return $scope.cantPageForward(); } else { return true; } }; $scope.cantPageBackward = function() { var curPage = $scope.pagingOptions.currentPage; return !(curPage > 1); }; }; /// <reference path="footer.js" /> /// <reference path="../services/SortService.js" /> /// <reference path="../../lib/jquery-1.8.2.min" /> ng.Grid = function ($scope, options, sortService, domUtilityService, $filter, $templateCache) { var defaults = { //Define an aggregate template to customize the rows when grouped. See github wiki for more details. aggregateTemplate: undefined, //Callback for when you want to validate something after selection. afterSelectionChange: function() { }, /* Callback if you want to inspect something before selection, return false if you want to cancel the selection. return true otherwise. If you need to wait for an async call to proceed with selection you can use rowItem.changeSelection(event) method after returning false initially. Note: when shift+ Selecting multiple items in the grid this will only get called once and the rowItem will be an array of items that are queued to be selected. */ beforeSelectionChange: function() { return true; }, //checkbox templates. checkboxCellTemplate: undefined, checkboxHeaderTemplate: undefined, //definitions of columns as an array [], if not defines columns are auto-generated. See github wiki for more details. columnDefs: undefined, //*Data being displayed in the grid. Each item in the array is mapped to a row being displayed. data: [], //Data updated callback, fires every time the data is modified from outside the grid. dataUpdated: function() { }, //Enables cell editing. enableCellEdit: false, //Enables cell selection. enableCellSelection: false, //Enable or disable resizing of columns enableColumnResize: false, //Enable or disable reordering of columns enableColumnReordering: false, //Enable or disable HEAVY column virtualization. This turns off selection checkboxes and column pinning and is designed for spreadsheet-like data. enableColumnHeavyVirt: false, //Enables the server-side paging feature enablePaging: false, //Enable column pinning enablePinning: false, //Enable drag and drop row reordering. Only works in HTML5 compliant browsers. enableRowReordering: false, //To be able to have selectable rows in grid. enableRowSelection: true, //Enables or disables sorting in grid. enableSorting: true, // string list of properties to exclude when auto-generating columns. excludeProperties: [], /* filterOptions - filterText: The text bound to the built-in search box. useExternalFilter: Bypass internal filtering if you want to roll your own filtering mechanism but want to use builtin search box. */ filterOptions: { filterText