ng-grid
Version:
__Contributors:__
1,260 lines (1,210 loc) • 133 kB
JavaScript
/***********************************************
* 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