diff --git a/src/main/java/com/commafeed/frontend/pages/HomePage.java b/src/main/java/com/commafeed/frontend/pages/HomePage.java
index 05366746..20b7c8cd 100644
--- a/src/main/java/com/commafeed/frontend/pages/HomePage.java
+++ b/src/main/java/com/commafeed/frontend/pages/HomePage.java
@@ -14,6 +14,7 @@ import com.commafeed.frontend.references.angularuibootstrap.AngularUIBootstrapRe
import com.commafeed.frontend.references.angularuistate.AngularUIStateReference;
import com.commafeed.frontend.references.csstreeview.CssTreeViewReference;
import com.commafeed.frontend.references.mousetrap.MouseTrapReference;
+import com.commafeed.frontend.references.nggrid.NGGridReference;
import com.commafeed.frontend.references.nginfinitescroll.NGInfiniteScrollReference;
import com.commafeed.frontend.references.ngupload.NGUploadReference;
import com.commafeed.frontend.references.select2.Select2Reference;
@@ -37,6 +38,7 @@ public class HomePage extends BasePage {
Select2Reference.renderHead(response);
SpinJSReference.renderHead(response);
MouseTrapReference.renderHead(response);
+ NGGridReference.renderHead(response);
CssTreeViewReference.renderHead(response);
diff --git a/src/main/java/com/commafeed/frontend/references/nggrid/NGGridReference.java b/src/main/java/com/commafeed/frontend/references/nggrid/NGGridReference.java
new file mode 100644
index 00000000..a49a14af
--- /dev/null
+++ b/src/main/java/com/commafeed/frontend/references/nggrid/NGGridReference.java
@@ -0,0 +1,35 @@
+package com.commafeed.frontend.references.nggrid;
+
+import java.util.Arrays;
+
+import org.apache.wicket.markup.head.CssHeaderItem;
+import org.apache.wicket.markup.head.HeaderItem;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptHeaderItem;
+import org.apache.wicket.request.resource.CssResourceReference;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
+
+import com.commafeed.frontend.references.angular.AngularReference;
+
+public class NGGridReference extends JavaScriptResourceReference {
+ private static final long serialVersionUID = 1L;
+
+ public static final NGGridReference INSTANCE = new NGGridReference();
+
+ private NGGridReference() {
+ super(NGGridReference.class, "ng-grid-2.0.2.js");
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Iterable extends HeaderItem> getDependencies() {
+ return Arrays.asList(JavaScriptHeaderItem
+ .forReference(AngularReference.INSTANCE), CssHeaderItem
+ .forReference(new CssResourceReference(NGGridReference.class,
+ "ng-grid.css")));
+ }
+
+ public static void renderHead(final IHeaderResponse response) {
+ response.render(JavaScriptHeaderItem.forReference(INSTANCE));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/commafeed/frontend/references/nggrid/ng-grid-2.0.2.js b/src/main/java/com/commafeed/frontend/references/nggrid/ng-grid-2.0.2.js
new file mode 100644
index 00000000..7c95af76
--- /dev/null
+++ b/src/main/java/com/commafeed/frontend/references/nggrid/ng-grid-2.0.2.js
@@ -0,0 +1,3185 @@
+/***********************************************
+* 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/08/2013 17:03
+***********************************************/
+(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;
+ };
+}
+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',['$utilityService', function($utils) {
+ var domUtilityService = {};
+ var regexCache = {};
+ var getWidths = function() {
+ var $testContainer = $('
');
+ $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('');
+ 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('M');
+ 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 = $("").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 !== false) {
+ 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 ($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 ($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;
+}]);
+
+ngGridServices.factory('$utilityService', ['$parse', function($parse) {
+ var 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) {
+ return $parse(path)(entity);
+ },
+ 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 = '',
+ iElems[0]) ;
+ return version > 4 ? version : undefined;
+ })()
+ };
+
+ $.extend(utils, {
+ isIe: (function() {
+ return utils.ieVersion !== undefined;
+ })()
+ });
+ return utils;
+}]);
+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, $utils) {
+ 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 = $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 = $utils.isNullOrUndefined(colDef.groupable) || colDef.groupable;
+ if (config.enableSort) {
+ self.sortable = $utils.isNullOrUndefined(colDef.sortable) || colDef.sortable;
+ }
+ if (config.enableResize) {
+ self.resizable = $utils.isNullOrUndefined(colDef.resizable) || colDef.resizable;
+ }
+ if (config.enablePinning) {
+ self.pinnable = $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.selectionProvider.lastClickedRow){
+ var columnIndex = index != undefined ? index : previousColumn;
+ var elm = $scope.selectionProvider.lastClickedRow.clone ? $scope.selectionProvider.lastClickedRow.clone.elm : $scope.selectionProvider.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);
+ };
+};
+///
+///
+///
+ng.Grid = function ($scope, options, sortService, domUtilityService, $filter, $templateCache, $utils, $timeout) {
+ 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: "",
+ useExternalFilter: false
+ },
+
+ //Defining the height of the footer in pixels.
+ footerRowHeight: 55,
+
+ //Initial fields to group data by. Array of field names, not displayName.
+ groups: [],
+
+ //The height of the header row in pixels.
+ headerRowHeight: 30,
+
+ //Define a header row template for further customization. See github wiki for more details.
+ headerRowTemplate: undefined,
+
+ /*Enables the use of jquery UI reaggable/droppable plugin. requires jqueryUI to work if enabled.
+ Useful if you want drag + drop but your users insist on crappy browsers. */
+ jqueryUIDraggable: false,
+
+ //Enable the use jqueryUIThemes
+ jqueryUITheme: false,
+
+ //Prevent unselections when in single selection mode.
+ keepLastSelected: true,
+
+ /*Maintains the column widths while resizing.
+ Defaults to true when using *'s or undefined widths. Can be ovverriden by setting to false.*/
+ maintainColumnRatios: undefined,
+
+ //Set this to false if you only want one item selected at a time
+ multiSelect: true,
+
+ // pagingOptions -
+
+ pagingOptions: {
+ // pageSizes: list of available page sizes.
+ pageSizes: [250, 500, 1000],
+ //pageSize: currently selected page size.
+ pageSize: 250,
+ //totalServerItems: Total items are on the server.
+ totalServerItems: 0,
+ //currentPage: the uhm... current page.
+ currentPage: 1
+ },
+
+ //Array of plugin functions to register in ng-grid
+ pinSelectionCheckbox: false,
+
+ //Array of plugin functions to register in ng-grid
+ plugins: [],
+
+ //Row height of rows in grid.
+ rowHeight: 30,
+
+ //Define a row template to customize output. See github wiki for more details.
+ rowTemplate: undefined,
+
+ //all of the items selected in the grid. In single select mode there will only be one item in the array.
+ selectedItems: [],
+
+ //Disable row selections by clicking on the row and only when the checkbox is clicked.
+ selectWithCheckboxOnly: false,
+
+ /*Enables menu to choose which columns to display and group by.
+ If both showColumnMenu and showFilter are false the menu button will not display.*/
+ showColumnMenu: false,
+
+ /*Enables display of the filterbox in the column menu.
+ If both showColumnMenu and showFilter are false the menu button will not display.*/
+ showFilter: false,
+
+ //Show or hide the footer alltogether the footer is enabled by default
+ showFooter: false,
+
+ //Show the dropzone for drag and drop grouping
+ showGroupPanel: false,
+
+ //Row selection check boxes appear as the first column.
+ showSelectionCheckbox: false,
+
+ /*Define a sortInfo object to specify a default sorting state.
+ You can also observe this variable to utilize server-side sorting (see useExternalSorting).
+ Syntax is sortinfo: { fields: ['fieldName1',' fieldName2'], direction: 'ASC'/'asc' || 'desc'/'DESC'}*/
+ sortInfo: {fields: [], columns: [], directions: [] },
+
+ //Set the tab index of the Vieport.
+ tabIndex: -1,
+ /*Prevents the internal sorting from executing.
+ The sortInfo object will be updated with the sorting information so you can handle sorting (see sortInfo)*/
+ useExternalSorting: false,
+
+ /*i18n language support. choose from the installed or included languages, en, fr, sp, etc...*/
+ i18n: 'en',
+
+ //the threshold in rows to force virtualization on
+ virtualizationThreshold: 50
+ },
+ self = this;
+ self.maxCanvasHt = 0;
+ //self vars
+ self.config = $.extend(defaults, window.ngGrid.config, options);
+
+ // override conflicting settings
+ self.config.showSelectionCheckbox = (self.config.showSelectionCheckbox && self.config.enableColumnHeavyVirt === false);
+ self.config.enablePinning = (self.config.enablePinning && self.config.enableColumnHeavyVirt === false);
+ self.config.selectWithCheckboxOnly = (self.config.selectWithCheckboxOnly && self.config.showSelectionCheckbox !== false);
+ self.config.pinSelectionCheckbox = self.config.enablePinning;
+
+ if (typeof options.columnDefs == "string") {
+ self.config.columnDefs = $scope.$eval(options.columnDefs);
+ }
+ self.rowCache = [];
+ self.rowMap = [];
+ self.gridId = "ng" + $utils.newId();
+ self.$root = null; //this is the root element that is passed in with the binding handler
+ self.$groupPanel = null;
+ self.$topPanel = null;
+ self.$headerContainer = null;
+ self.$headerScroller = null;
+ self.$headers = null;
+ self.$viewport = null;
+ self.$canvas = null;
+ self.rootDim = self.config.gridDim;
+ self.data = [];
+ self.lateBindColumns = false;
+ self.filteredRows = [];
+
+ //Templates
+ // test templates for urls and get the tempaltes via synchronous ajax calls
+ var getTemplate = function (key) {
+ var t = self.config[key];
+ var uKey = self.gridId + key + ".html";
+ if (t && !TEMPLATE_REGEXP.test(t)) {
+ $templateCache.put(uKey, $.ajax({
+ type: "GET",
+ url: t,
+ async: false
+ }).responseText);
+ } else if (t) {
+ $templateCache.put(uKey, t);
+ } else {
+ var dKey = key + ".html";
+ $templateCache.put(uKey, $templateCache.get(dKey));
+ }
+ };
+ getTemplate('rowTemplate');
+ getTemplate('aggregateTemplate');
+ getTemplate('headerRowTemplate');
+ getTemplate('checkboxCellTemplate');
+ getTemplate('checkboxHeaderTemplate');
+
+ if (typeof self.config.data == "object") {
+ self.data = self.config.data; // we cannot watch for updates if you don't pass the string name
+ }
+ self.calcMaxCanvasHeight = function() {
+ return (self.config.groups.length > 0) ? (self.rowFactory.parsedData.filter(function(e) {
+ return !e[NG_HIDDEN];
+ }).length * self.config.rowHeight) : (self.filteredRows.length * self.config.rowHeight);
+ };
+ self.elementDims = {
+ scrollW: 0,
+ scrollH: 0,
+ rowIndexCellW: 25,
+ rowSelectedCellW: 25,
+ rootMaxW: 0,
+ rootMaxH: 0
+ };
+ //self funcs
+ self.setRenderedRows = function (newRows) {
+ $scope.renderedRows.length = newRows.length;
+ for (var i = 0; i < newRows.length; i++) {
+ if (!$scope.renderedRows[i] || (newRows[i].isAggRow || $scope.renderedRows[i].isAggRow)) {
+ $scope.renderedRows[i] = newRows[i].copy();
+ $scope.renderedRows[i].collapsed = newRows[i].collapsed;
+ if (!newRows[i].isAggRow) {
+ $scope.renderedRows[i].setVars(newRows[i]);
+ }
+ } else {
+ $scope.renderedRows[i].setVars(newRows[i]);
+ }
+ $scope.renderedRows[i].rowIndex = newRows[i].rowIndex;
+ $scope.renderedRows[i].offsetTop = newRows[i].offsetTop;
+ newRows[i].renderedRowIndex = i;
+ }
+ self.refreshDomSizes();
+ $scope.$emit('ngGridEventRows', newRows);
+ };
+ self.minRowsToRender = function() {
+ var viewportH = $scope.viewportDimHeight() || 1;
+ return Math.floor(viewportH / self.config.rowHeight);
+ };
+ self.refreshDomSizes = function() {
+ var dim = new ng.Dimension();
+ dim.outerWidth = self.elementDims.rootMaxW;
+ dim.outerHeight = self.elementDims.rootMaxH;
+ self.rootDim = dim;
+ self.maxCanvasHt = self.calcMaxCanvasHeight();
+ };
+ self.buildColumnDefsFromData = function () {
+ self.config.columnDefs = [];
+ var item = self.data[0];
+ if (!item) {
+ self.lateBoundColumns = true;
+ return;
+ }
+ $utils.forIn(item, function (prop, propName) {
+ if (self.config.excludeProperties.indexOf(propName) == -1) {
+ self.config.columnDefs.push({
+ field: propName
+ });
+ }
+ });
+ };
+ self.buildColumns = function() {
+ var columnDefs = self.config.columnDefs,
+ cols = [];
+ if (!columnDefs) {
+ self.buildColumnDefsFromData();
+ columnDefs = self.config.columnDefs;
+ }
+ if (self.config.showSelectionCheckbox) {
+ cols.push(new ng.Column({
+ colDef: {
+ field: '\u2714',
+ width: self.elementDims.rowSelectedCellW,
+ sortable: false,
+ resizable: false,
+ groupable: false,
+ headerCellTemplate: $templateCache.get($scope.gridId + 'checkboxHeaderTemplate.html'),
+ cellTemplate: $templateCache.get($scope.gridId + 'checkboxCellTemplate.html'),
+ pinned: self.config.pinSelectionCheckbox
+ },
+ index: 0,
+ headerRowHeight: self.config.headerRowHeight,
+ sortCallback: self.sortData,
+ resizeOnDataCallback: self.resizeOnData,
+ enableResize: self.config.enableColumnResize,
+ enableSort: self.config.enableSorting
+ }, $scope, self, domUtilityService, $templateCache, $utils));
+ }
+ if (columnDefs.length > 0) {
+ var indexOffset = self.config.showSelectionCheckbox ? self.config.groups.length + 1 : self.config.groups.length;
+ $scope.configGroups.length = 0;
+ angular.forEach(columnDefs, function(colDef, i) {
+ i += indexOffset;
+ var column = new ng.Column({
+ colDef: colDef,
+ index: i,
+ headerRowHeight: self.config.headerRowHeight,
+ sortCallback: self.sortData,
+ resizeOnDataCallback: self.resizeOnData,
+ enableResize: self.config.enableColumnResize,
+ enableSort: self.config.enableSorting,
+ enablePinning: self.config.enablePinning,
+ enableCellEdit: self.config.enableCellEdit
+ }, $scope, self, domUtilityService, $templateCache, $utils);
+ var indx = self.config.groups.indexOf(colDef.field);
+ if (indx != -1) {
+ column.isGroupedBy = true;
+ $scope.configGroups.splice(indx, 0, column);
+ column.groupIndex = $scope.configGroups.length;
+ }
+ cols.push(column);
+ });
+ $scope.columns = cols;
+ }
+ };
+ self.configureColumnWidths = function() {
+ var cols = self.config.columnDefs;
+ var indexOffset = self.config.showSelectionCheckbox ? $scope.configGroups.length + 1 : $scope.configGroups.length;
+ var numOfCols = cols.length + indexOffset,
+ asterisksArray = [],
+ percentArray = [],
+ asteriskNum = 0,
+ totalWidth = 0;
+ totalWidth += self.config.showSelectionCheckbox ? 25 : 0;
+ angular.forEach(cols, function(col, i) {
+ i += indexOffset;
+ var isPercent = false, t = undefined;
+ //if width is not defined, set it to a single star
+ if ($utils.isNullOrUndefined(col.width)) {
+ col.width = "*";
+ } else { // get column width
+ isPercent = isNaN(col.width) ? $utils.endsWith(col.width, "%") : false;
+ t = isPercent ? col.width : parseInt(col.width, 10);
+ }
+ // check if it is a number
+ if (isNaN(t)) {
+ t = col.width;
+ // figure out if the width is defined or if we need to calculate it
+ if (t == 'auto') { // set it for now until we have data and subscribe when it changes so we can set the width.
+ $scope.columns[i].width = col.minWidth;
+ totalWidth += $scope.columns[i].width;
+ var temp = $scope.columns[i];
+ $timeout(function () {
+ self.resizeOnData(temp, true);
+ });
+ return;
+ } else if (t.indexOf("*") != -1) { // we need to save it until the end to do the calulations on the remaining width.
+ if (col.visible !== false) {
+ asteriskNum += t.length;
+ }
+ col.index = i;
+ asterisksArray.push(col);
+ return;
+ } else if (isPercent) { // If the width is a percentage, save it until the very last.
+ col.index = i;
+ percentArray.push(col);
+ return;
+ } else { // we can't parse the width so lets throw an error.
+ throw "unable to parse column width, use percentage (\"10%\",\"20%\", etc...) or \"*\" to use remaining width of grid";
+ }
+ } else if (col.visible !== false) {
+ totalWidth += $scope.columns[i].width = parseInt(col.width, 10);
+ }
+ });
+ // check if we saved any asterisk columns for calculating later
+ if (asterisksArray.length > 0) {
+ self.config.maintainColumnRatios === false ? angular.noop() : self.config.maintainColumnRatios = true;
+ // get the remaining width
+ var remainigWidth = self.rootDim.outerWidth - totalWidth;
+ // calculate the weight of each asterisk rounded down
+ var asteriskVal = Math.floor(remainigWidth / asteriskNum);
+ // set the width of each column based on the number of stars
+ angular.forEach(asterisksArray, function(col) {
+ var t = col.width.length;
+ $scope.columns[col.index].width = asteriskVal * t;
+ //check if we are on the last column
+ if (col.index + 1 == numOfCols) {
+ var offset = 2; //We're going to remove 2 px so we won't overlflow the viwport by default
+ // are we overflowing?
+ if (self.maxCanvasHt > $scope.viewportDimHeight()) {
+ //compensate for scrollbar
+ offset += domUtilityService.ScrollW;
+ }
+ $scope.columns[col.index].width -= offset;
+ }
+ if (col.visible !== false) {
+ totalWidth += $scope.columns[col.index].width;
+ }
+ });
+ }
+ // Now we check if we saved any percentage columns for calculating last
+ if (percentArray.length > 0) {
+ // do the math
+ angular.forEach(percentArray, function(col) {
+ var t = col.width;
+ $scope.columns[col.index].width = Math.floor(self.rootDim.outerWidth * (parseInt(t.slice(0, -1), 10) / 100));
+ });
+ }
+ };
+ self.init = function() {
+ //factories and services
+ $scope.selectionProvider = new ng.selectionProvider(self, $scope);
+ $scope.domAccessProvider = new ng.DomAccessProvider(self);
+ self.rowFactory = new ng.RowFactory(self, $scope, domUtilityService, $templateCache, $utils);
+ self.searchProvider = new ng.SearchProvider($scope, self, $filter);
+ self.styleProvider = new ng.StyleProvider($scope, self, domUtilityService);
+ $scope.$watch('configGroups', function(a) {
+ var tempArr = [];
+ angular.forEach(a, function(item) {
+ tempArr.push(item.field || item);
+ });
+ self.config.groups = tempArr;
+ self.rowFactory.filteredRowsChanged();
+ $scope.$emit('ngGridEventGroups', a);
+ }, true);
+ $scope.$watch('columns', function (a) {
+ domUtilityService.BuildStyles($scope, self, true);
+ $scope.$emit('ngGridEventColumns', a);
+ }, true);
+ $scope.$watch(function() {
+ return options.i18n;
+ }, function(newLang) {
+ $utils.seti18n($scope, newLang);
+ });
+ self.maxCanvasHt = self.calcMaxCanvasHeight();
+ if (self.config.sortInfo.fields && self.config.sortInfo.fields.length > 0) {
+ if (self.config.sortInfo.columns) {
+ self.config.sortInfo.columns.length = 0;
+ } else {
+ self.config.sortInfo.columns = [];
+ }
+ angular.forEach($scope.columns, function (c) {
+ if (self.config.sortInfo.fields.indexOf(c.field) != -1) {
+ self.config.sortInfo.columns.push(c);
+ }
+ return false;
+ });
+ self.sortData(self.config.sortInfo.columns, {});
+ }
+ };
+
+ self.resizeOnData = function(col) {
+ // we calculate the longest data.
+ var longest = col.minWidth;
+ var arr = $utils.getElementsByClassName('col' + col.index);
+ angular.forEach(arr, function(elem, index) {
+ var i;
+ if (index === 0) {
+ var kgHeaderText = $(elem).find('.ngHeaderText');
+ i = $utils.visualLength(kgHeaderText) + 10; // +10 some margin
+ } else {
+ var ngCellText = $(elem).find('.ngCellText');
+ i = $utils.visualLength(ngCellText) + 10; // +10 some margin
+ }
+ if (i > longest) {
+ longest = i;
+ }
+ });
+ col.width = col.longest = Math.min(col.maxWidth, longest + 7); // + 7 px to make it look decent.
+ domUtilityService.BuildStyles($scope, self, true);
+ };
+ self.lastSortedColumns = [];
+ self.changeRowOrder = function(prevRow, targetRow) {
+ // Splice the Rows via the actual datasource
+ var i = self.rowCache.indexOf(prevRow);
+ var j = self.rowCache.indexOf(targetRow);
+ self.rowCache.splice(i, 1);
+ self.rowCache.splice(j, 0, prevRow);
+ $scope.$emit('ngGridEventChangeOrder', self.rowCache);
+ };
+ self.sortData = function(col, evt) {
+ if (evt.shiftKey && self.config.sortInfo) {
+ var indx = self.config.sortInfo.columns.indexOf(col);
+ if (indx === -1) {
+ if (self.config.sortInfo.columns.length == 1) {
+ self.config.sortInfo.columns[0].sortPriority = 1;
+ }
+ self.config.sortInfo.columns.push(col);
+ col.sortPriority = self.config.sortInfo.columns.length;
+ self.config.sortInfo.fields.push(col.field);
+ self.config.sortInfo.directions.push(col.sortDirection);
+ self.lastSortedColumns.push(col);
+ } else {
+ self.config.sortInfo.directions[indx] = col.sortDirection;
+ }
+ } else {
+ var isArr = $.isArray(col);
+ self.config.sortInfo.columns.length = 0;
+ self.config.sortInfo.fields.length = 0;
+ self.config.sortInfo.directions.length = 0;
+ var push = function (c) {
+ self.config.sortInfo.columns.push(c);
+ self.config.sortInfo.fields.push(c.field);
+ self.config.sortInfo.directions.push(c.sortDirection);
+ self.lastSortedColumns.push(c);
+ };
+ if (isArr) {
+ self.clearSortingData();
+ angular.forEach(col, function (c, i) {
+ c.sortPriority = i + 1;
+ push(c);
+ });
+ } else {
+ self.clearSortingData(col);
+ col.sortPriority = undefined;
+ push(col);
+ }
+ }
+ if (!self.config.useExternalSorting) {
+ var tempData = self.data.slice(0);
+ angular.forEach(tempData, function (item, i) {
+ item.preSortSelected = self.rowCache[self.rowMap[i]].selected;
+ item.preSortIndex = i;
+ });
+ sortService.Sort(self.config.sortInfo, tempData);
+ angular.forEach(tempData, function(item, i) {
+ self.rowCache[i].entity = item;
+ self.rowCache[i].selected = item.preSortSelected;
+ self.rowMap[item.preSortIndex] = i;
+ delete item.preSortSelected;
+ delete item.preSortIndex;
+ });
+ }
+ self.searchProvider.evalFilter();
+ $scope.$emit('ngGridEventSorted', self.config.sortInfo);
+ };
+ self.clearSortingData = function (col) {
+ if (!col) {
+ angular.forEach(self.lastSortedColumns, function (c) {
+ c.sortDirection = "";
+ c.sortPriority = null;
+ });
+ self.lastSortedColumns = [];
+ } else {
+ angular.forEach(self.lastSortedColumns, function (c) {
+ if (col.index != c.index) {
+ c.sortDirection = "";
+ c.sortPriority = null;
+ }
+ });
+ self.lastSortedColumns[0] = col;
+ self.lastSortedColumns.length = 1;
+ };
+ };
+ self.fixColumnIndexes = function() {
+ //fix column indexes
+ for (var i = 0; i < $scope.columns.length; i++) {
+ if ($scope.columns[i].visible !== false) {
+ $scope.columns[i].index = i;
+ }
+ }
+ };
+ self.fixGroupIndexes = function() {
+ angular.forEach($scope.configGroups, function(item, i) {
+ item.groupIndex = i + 1;
+ });
+ };
+ //$scope vars
+ $scope.elementsNeedMeasuring = true;
+ $scope.columns = [];
+ $scope.renderedRows = [];
+ $scope.renderedColumns = [];
+ $scope.headerRow = null;
+ $scope.rowHeight = self.config.rowHeight;
+ $scope.jqueryUITheme = self.config.jqueryUITheme;
+ $scope.showSelectionCheckbox = self.config.showSelectionCheckbox;
+ $scope.enableCellSelection = self.config.enableCellSelection;
+ $scope.footer = null;
+ $scope.selectedItems = self.config.selectedItems;
+ $scope.multiSelect = self.config.multiSelect;
+ $scope.showFooter = self.config.showFooter;
+ $scope.footerRowHeight = $scope.showFooter ? self.config.footerRowHeight : 0;
+ $scope.showColumnMenu = self.config.showColumnMenu;
+ $scope.showMenu = false;
+ $scope.configGroups = [];
+ $scope.gridId = self.gridId;
+ //Paging
+ $scope.enablePaging = self.config.enablePaging;
+ $scope.pagingOptions = self.config.pagingOptions;
+
+ //i18n support
+ $scope.i18n = {};
+ $utils.seti18n($scope, self.config.i18n);
+ $scope.adjustScrollLeft = function (scrollLeft) {
+ var colwidths = 0,
+ totalLeft = 0,
+ x = $scope.columns.length,
+ newCols = [],
+ dcv = !self.config.enableColumnHeavyVirt;
+ var r = 0;
+ var addCol = function (c) {
+ if (dcv) {
+ newCols.push(c);
+ } else {
+ if (!$scope.renderedColumns[r]) {
+ $scope.renderedColumns[r] = c.copy();
+ } else {
+ $scope.renderedColumns[r].setVars(c);
+ }
+ }
+ r++;
+ };
+ for (var i = 0; i < x; i++) {
+ var col = $scope.columns[i];
+ if (col.visible !== false) {
+ var w = col.width + colwidths;
+ if (col.pinned) {
+ addCol(col);
+ var newLeft = i > 0 ? (scrollLeft + totalLeft) : scrollLeft;
+ domUtilityService.setColLeft(col, newLeft, self);
+ totalLeft += col.width;
+ } else {
+ if (w >= scrollLeft) {
+ if (colwidths <= scrollLeft + self.rootDim.outerWidth) {
+ addCol(col);
+ }
+ }
+ }
+ colwidths += col.width;
+ }
+ }
+ if (dcv) {
+ $scope.renderedColumns = newCols;
+ }
+ };
+ self.prevScrollTop = 0;
+ self.prevScrollIndex = 0;
+ $scope.adjustScrollTop = function(scrollTop, force) {
+ if (self.prevScrollTop === scrollTop && !force) {
+ return;
+ }
+ if (scrollTop > 0 && self.$viewport[0].scrollHeight - scrollTop <= self.$viewport.outerHeight()) {
+ $scope.$emit('ngGridEventScroll');
+ }
+ var rowIndex = Math.floor(scrollTop / self.config.rowHeight);
+ var newRange;
+ if (self.filteredRows.length > self.config.virtualizationThreshold) {
+ // Have we hit the threshold going down?
+ if (self.prevScrollTop < scrollTop && rowIndex < self.prevScrollIndex + SCROLL_THRESHOLD) {
+ return;
+ }
+ //Have we hit the threshold going up?
+ if (self.prevScrollTop > scrollTop && rowIndex > self.prevScrollIndex - SCROLL_THRESHOLD) {
+ return;
+ }
+ newRange = new ng.Range(Math.max(0, rowIndex - EXCESS_ROWS), rowIndex + self.minRowsToRender() + EXCESS_ROWS);
+ } else {
+ var maxLen = $scope.configGroups.length > 0 ? self.rowFactory.parsedData.length : self.data.length;
+ newRange = new ng.Range(0, Math.max(maxLen, self.minRowsToRender() + EXCESS_ROWS));
+ }
+ self.prevScrollTop = scrollTop;
+ self.rowFactory.UpdateViewableRange(newRange);
+ self.prevScrollIndex = rowIndex;
+ };
+
+ //scope funcs
+ $scope.toggleShowMenu = function() {
+ $scope.showMenu = !$scope.showMenu;
+ };
+ $scope.toggleSelectAll = function(a) {
+ $scope.selectionProvider.toggleSelectAll(a);
+ };
+ $scope.totalFilteredItemsLength = function() {
+ return self.filteredRows.length;
+ };
+ $scope.showGroupPanel = function() {
+ return self.config.showGroupPanel;
+ };
+ $scope.topPanelHeight = function() {
+ return self.config.showGroupPanel === true ? self.config.headerRowHeight + 32 : self.config.headerRowHeight;
+ };
+
+ $scope.viewportDimHeight = function() {
+ return Math.max(0, self.rootDim.outerHeight - $scope.topPanelHeight() - $scope.footerRowHeight - 2);
+ };
+ $scope.groupBy = function (col) {
+ //first sort the column
+ if (!col.sortDirection) col.sort({shiftKey: false});
+ if (self.data.length < 1 || !col.groupable || !col.field) {
+ return;
+ }
+ var indx = $scope.configGroups.indexOf(col);
+ if (indx == -1) {
+ col.isGroupedBy = true;
+ $scope.configGroups.push(col);
+ col.groupIndex = $scope.configGroups.length;
+ } else {
+ $scope.removeGroup(indx);
+ }
+ self.$viewport.scrollTop(0);
+ domUtilityService.digest($scope);
+ };
+ $scope.removeGroup = function(index) {
+ var col = $scope.columns.filter(function(item) {
+ return item.groupIndex == (index + 1);
+ })[0];
+ col.isGroupedBy = false;
+ col.groupIndex = 0;
+ if ($scope.columns[index].isAggCol) {
+ $scope.columns.splice(index, 1);
+ $scope.configGroups.splice(index, 1);
+ self.fixGroupIndexes();
+ }
+ if ($scope.configGroups.length === 0) {
+ self.fixColumnIndexes();
+ domUtilityService.digest($scope);
+ }
+ $scope.adjustScrollLeft(0);
+ };
+ $scope.togglePin = function (col) {
+ var indexFrom = col.index;
+ var indexTo = 0;
+ for (var i = 0; i < $scope.columns.length; i++) {
+ if (!$scope.columns[i].pinned) {
+ break;
+ }
+ indexTo++;
+ }
+ if (col.pinned) {
+ indexTo = Math.max(col.originalIndex, indexTo - 1);
+ }
+ col.pinned = !col.pinned;
+ // Splice the columns
+ $scope.columns.splice(indexFrom, 1);
+ $scope.columns.splice(indexTo, 0, col);
+ self.fixColumnIndexes();
+ // Finally, rebuild the CSS styles.
+ domUtilityService.BuildStyles($scope, self, true);
+ self.$viewport.scrollLeft(self.$viewport.scrollLeft() - col.width);
+ };
+ $scope.totalRowWidth = function() {
+ var totalWidth = 0,
+ cols = $scope.columns;
+ for (var i = 0; i < cols.length; i++) {
+ if (cols[i].visible !== false) {
+ totalWidth += cols[i].width;
+ }
+ }
+ return totalWidth;
+ };
+ $scope.headerScrollerDim = function() {
+ var viewportH = $scope.viewportDimHeight(),
+ maxHeight = self.maxCanvasHt,
+ vScrollBarIsOpen = (maxHeight > viewportH),
+ newDim = new ng.Dimension();
+
+ newDim.autoFitHeight = true;
+ newDim.outerWidth = $scope.totalRowWidth();
+ if (vScrollBarIsOpen) {
+ newDim.outerWidth += self.elementDims.scrollW;
+ } else if ((maxHeight - viewportH) <= self.elementDims.scrollH) { //if the horizontal scroll is open it forces the viewport to be smaller
+ newDim.outerWidth += self.elementDims.scrollW;
+ }
+ return newDim;
+ };
+ //call init
+ self.init();
+};
+
+ng.Range = function(top, bottom) {
+ this.topRow = top;
+ this.bottomRow = bottom;
+};
+ng.Row = function (entity, config, selectionProvider, rowIndex, $utils) {
+ var self = this, // constant for the selection property that we add to each data item
+ enableRowSelection = config.enableRowSelection;
+
+ self.jqueryUITheme = config.jqueryUITheme;
+ self.rowClasses = config.rowClasses;
+ self.entity = entity;
+ self.selectionProvider = selectionProvider;
+ self.selected = selectionProvider.getSelection(entity);
+ self.cursor = enableRowSelection ? 'pointer' : 'default';
+ self.setSelection = function(isSelected) {
+ self.selectionProvider.setSelection(self, isSelected);
+ self.selectionProvider.lastClickedRow = self;
+ };
+ self.continueSelection = function(event) {
+ self.selectionProvider.ChangeSelection(self, event);
+ };
+ self.ensureEntity = function(expected) {
+ if (self.entity != expected) {
+ // Update the entity and determine our selected property
+ self.entity = expected;
+ self.selected = self.selectionProvider.getSelection(self.entity);
+ }
+ };
+ self.toggleSelected = function(event) {
+ if (!enableRowSelection && !config.enableCellSelection) {
+ return true;
+ }
+ var element = event.target || event;
+ //check and make sure its not the bubbling up of our checked 'click' event
+ if (element.type == "checkbox" && element.parentElement.className != "ngSelectionCell ng-scope") {
+ return true;
+ }
+ if (config.selectWithCheckboxOnly && element.type != "checkbox") {
+ self.selectionProvider.lastClickedRow = self;
+ return true;
+ } else {
+ if (self.beforeSelectionChange(self, event)) {
+ self.continueSelection(event);
+ }
+ }
+ return false;
+ };
+ self.rowIndex = rowIndex;
+ self.offsetTop = self.rowIndex * config.rowHeight;
+ self.rowDisplayIndex = 0;
+ self.alternatingRowClass = function () {
+ var isEven = (self.rowIndex % 2) === 0;
+ var classes = {
+ 'selected': self.selected,
+ 'ui-state-default': self.jqueryUITheme && isEven,
+ 'ui-state-active': self.jqueryUITheme && !isEven,
+ 'even': isEven,
+ 'odd': !isEven
+ };
+ return classes;
+ };
+ self.beforeSelectionChange = config.beforeSelectionChangeCallback;
+ self.afterSelectionChange = config.afterSelectionChangeCallback;
+
+ self.getProperty = function(path) {
+ return $utils.evalProperty(self.entity, path);
+ };
+ self.copy = function () {
+ self.clone = new ng.Row(entity, config, selectionProvider, rowIndex, $utils);
+ self.clone.isClone = true;
+ self.clone.elm = self.elm;
+ return self.clone;
+ };
+ self.setVars = function (fromRow) {
+ fromRow.clone = self;
+ self.entity = fromRow.entity;
+ self.selected = fromRow.selected;
+ };
+};
+ng.RowFactory = function (grid, $scope, domUtilityService, $templateCache, $utils) {
+ var self = this;
+ // we cache rows when they are built, and then blow the cache away when sorting
+ self.aggCache = {};
+ self.parentCache = []; // Used for grouping and is cleared each time groups are calulated.
+ self.dataChanged = true;
+ self.parsedData = [];
+ self.rowConfig = {};
+ self.selectionProvider = $scope.selectionProvider;
+ self.rowHeight = 30;
+ self.numberOfAggregates = 0;
+ self.groupedData = undefined;
+ self.rowHeight = grid.config.rowHeight;
+ self.rowConfig = {
+ enableRowSelection: grid.config.enableRowSelection,
+ rowClasses: grid.config.rowClasses,
+ selectedItems: $scope.selectedItems,
+ selectWithCheckboxOnly: grid.config.selectWithCheckboxOnly,
+ beforeSelectionChangeCallback: grid.config.beforeSelectionChange,
+ afterSelectionChangeCallback: grid.config.afterSelectionChange,
+ jqueryUITheme: grid.config.jqueryUITheme,
+ enableCellSelection: grid.config.enableCellSelection,
+ rowHeight: grid.config.rowHeight
+ };
+
+ self.renderedRange = new ng.Range(0, grid.minRowsToRender() + EXCESS_ROWS);
+
+ // @entity - the data item
+ // @rowIndex - the index of the row
+ self.buildEntityRow = function(entity, rowIndex) {
+ // build the row
+ return new ng.Row(entity, self.rowConfig, self.selectionProvider, rowIndex, $utils);
+ };
+
+ self.buildAggregateRow = function(aggEntity, rowIndex) {
+ var agg = self.aggCache[aggEntity.aggIndex]; // first check to see if we've already built it
+ if (!agg) {
+ // build the row
+ agg = new ng.Aggregate(aggEntity, self, self.rowConfig.rowHeight);
+ self.aggCache[aggEntity.aggIndex] = agg;
+ }
+ agg.rowIndex = rowIndex;
+ agg.offsetTop = rowIndex * self.rowConfig.rowHeight;
+ return agg;
+ };
+ self.UpdateViewableRange = function(newRange) {
+ self.renderedRange = newRange;
+ self.renderedChange();
+ };
+ self.filteredRowsChanged = function() {
+ // check for latebound autogenerated columns
+ if (grid.lateBoundColumns && grid.filteredRows.length > 0) {
+ grid.config.columnDefs = undefined;
+ grid.buildColumns();
+ grid.lateBoundColumns = false;
+ $scope.$evalAsync(function() {
+ $scope.adjustScrollLeft(0);
+ });
+ }
+ self.dataChanged = true;
+ if (grid.config.groups.length > 0) {
+ self.getGrouping(grid.config.groups);
+ }
+ self.UpdateViewableRange(self.renderedRange);
+ };
+
+ self.renderedChange = function() {
+ if (!self.groupedData || grid.config.groups.length < 1) {
+ self.renderedChangeNoGroups();
+ grid.refreshDomSizes();
+ return;
+ }
+ self.wasGrouped = true;
+ self.parentCache = [];
+ var x = 0;
+ var temp = self.parsedData.filter(function (e) {
+ if (e.isAggRow) {
+ if (e.parent && e.parent.collapsed) {
+ return false;
+ }
+ return true;
+ }
+ if (!e[NG_HIDDEN]) {
+ e.rowIndex = x++;
+ }
+ return !e[NG_HIDDEN];
+ });
+ self.totalRows = temp.length;
+ var rowArr = [];
+ for (var i = self.renderedRange.topRow; i < self.renderedRange.bottomRow; i++) {
+ if (temp[i]) {
+ temp[i].offsetTop = i * grid.config.rowHeight;
+ rowArr.push(temp[i]);
+ }
+ }
+ grid.setRenderedRows(rowArr);
+ };
+
+ self.renderedChangeNoGroups = function () {
+ var rowArr = [];
+ for (var i = self.renderedRange.topRow; i < self.renderedRange.bottomRow; i++) {
+ if (grid.filteredRows[i]) {
+ grid.filteredRows[i].rowIndex = i;
+ grid.filteredRows[i].offsetTop = i * grid.config.rowHeight;
+ rowArr.push(grid.filteredRows[i]);
+ }
+ }
+ grid.setRenderedRows(rowArr);
+ };
+
+ self.fixRowCache = function () {
+ var newLen = grid.data.length;
+ var diff = newLen - grid.rowCache.length;
+ if (diff < 0) {
+ grid.rowCache.length = grid.rowMap.length = newLen;
+ } else {
+ for (var i = grid.rowCache.length; i < newLen; i++) {
+ grid.rowCache[i] = grid.rowFactory.buildEntityRow(grid.data[i], i);
+ }
+ }
+ };
+
+ //magical recursion. it works. I swear it. I figured it out in the shower one day.
+ self.parseGroupData = function(g) {
+ if (g.values) {
+ for (var x = 0; x < g.values.length; x++){
+ // get the last parent in the array because that's where our children want to be
+ self.parentCache[self.parentCache.length - 1].children.push(g.values[x]);
+ //add the row to our return array
+ self.parsedData.push(g.values[x]);
+ }
+ } else {
+ for (var prop in g) {
+ // exclude the meta properties.
+ if (prop == NG_FIELD || prop == NG_DEPTH || prop == NG_COLUMN) {
+ continue;
+ } else if (g.hasOwnProperty(prop)) {
+ //build the aggregate row
+ var agg = self.buildAggregateRow({
+ gField: g[NG_FIELD],
+ gLabel: prop,
+ gDepth: g[NG_DEPTH],
+ isAggRow: true,
+ '_ng_hidden_': false,
+ children: [],
+ aggChildren: [],
+ aggIndex: self.numberOfAggregates,
+ aggLabelFilter: g[NG_COLUMN].aggLabelFilter
+ }, 0);
+ self.numberOfAggregates++;
+ //set the aggregate parent to the parent in the array that is one less deep.
+ agg.parent = self.parentCache[agg.depth - 1];
+ // if we have a parent, set the parent to not be collapsed and append the current agg to its children
+ if (agg.parent) {
+ agg.parent.collapsed = false;
+ agg.parent.aggChildren.push(agg);
+ }
+ // add the aggregate row to the parsed data.
+ self.parsedData.push(agg);
+ // the current aggregate now the parent of the current depth
+ self.parentCache[agg.depth] = agg;
+ // dig deeper for more aggregates or children.
+ self.parseGroupData(g[prop]);
+ }
+ }
+ }
+ };
+ //Shuffle the data into their respective groupings.
+ self.getGrouping = function(groups) {
+ self.aggCache = [];
+ self.numberOfAggregates = 0;
+ self.groupedData = {};
+ // Here we set the onmousedown event handler to the header container.
+ var rows = grid.filteredRows,
+ maxDepth = groups.length,
+ cols = $scope.columns;
+
+ for (var x = 0; x < rows.length; x++){
+ var model = rows[x].entity;
+ if (!model) return;
+ rows[x][NG_HIDDEN] = true;
+ var ptr = self.groupedData;
+ for (var y = 0; y < groups.length; y++) {
+ var group = groups[y];
+ var col = cols.filter(function(c) {
+ return c.field == group;
+ })[0];
+ var val = $utils.evalProperty(model, group);
+ val = val ? val.toString() : 'null';
+ if (!ptr[val]) {
+ ptr[val] = {};
+ }
+ if (!ptr[NG_FIELD]) {
+ ptr[NG_FIELD] = group;
+ }
+ if (!ptr[NG_DEPTH]) {
+ ptr[NG_DEPTH] = y;
+ }
+ if (!ptr[NG_COLUMN]) {
+ ptr[NG_COLUMN] = col;
+ }
+ ptr = ptr[val];
+ }
+ if (!ptr.values) {
+ ptr.values = [];
+ }
+ ptr.values.push(rows[x]);
+ };
+ //moved out of above loops due to if no data initially, but has initial grouping, columns won't be added
+ for (var z = 0; z < groups.length; z++) {
+ if (!cols[z].isAggCol && z <= maxDepth) {
+ cols.splice(0, 0, new ng.Column({
+ colDef: {
+ field: '',
+ width: 25,
+ sortable: false,
+ resizable: false,
+ headerCellTemplate: '',
+ pinned: grid.config.pinSelectionCheckbox
+ },
+ isAggCol: true,
+ headerRowHeight: grid.config.headerRowHeight
+ }, $scope, grid, domUtilityService, $templateCache, $utils));
+ }
+ }
+ domUtilityService.BuildStyles($scope, grid, true);
+ grid.fixColumnIndexes();
+ $scope.adjustScrollLeft(0);
+ self.parsedData.length = 0;
+ self.parseGroupData(self.groupedData);
+ self.fixRowCache();
+ };
+
+ if (grid.config.groups.length > 0 && grid.filteredRows.length > 0) {
+ self.getGrouping(grid.config.groups);
+ }
+};
+ng.SearchProvider = function ($scope, grid, $filter) {
+ var self = this,
+ searchConditions = [];
+ self.extFilter = grid.config.filterOptions.useExternalFilter;
+ $scope.showFilter = grid.config.showFilter;
+ $scope.filterText = '';
+
+ self.fieldMap = {};
+
+ self.evalFilter = function () {
+ var filterFunc = function(item) {
+ for (var x = 0, len = searchConditions.length; x < len; x++) {
+ var condition = searchConditions[x];
+ //Search entire row
+ var result;
+ if (!condition.column) {
+ for (var prop in item) {
+ if (item.hasOwnProperty(prop)) {
+ var c = self.fieldMap[prop];
+ if (!c)
+ continue;
+ var f = null,
+ s = null;
+ if (c && c.cellFilter) {
+ s = c.cellFilter.split(':');
+ f = $filter(s[0]);
+ }
+ var pVal = item[prop];
+ if (pVal != null) {
+ if (typeof f == 'function') {
+ var filterRes = f(typeof pVal === 'object' ? evalObject(pVal, c.field) : pVal, s[1]).toString();
+ result = condition.regex.test(filterRes);
+ } else {
+ result = condition.regex.test(typeof pVal === 'object' ? evalObject(pVal, c.field).toString() : pVal.toString());
+ }
+ if (pVal && result) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+ //Search by column.
+ var col = self.fieldMap[condition.columnDisplay];
+ if (!col) {
+ return false;
+ }
+ var sp = col.cellFilter.split(':');
+ var filter = col.cellFilter ? $filter(sp[0]) : null;
+ var value = item[condition.column] || item[col.field.split('.')[0]];
+ if (value == null)
+ return false;
+ if (typeof filter == 'function') {
+ var filterResults = filter(typeof value === 'object' ? evalObject(value, col.field) : value, sp[1]).toString();
+ result = condition.regex.test(filterResults);
+ } else {
+ result = condition.regex.test(typeof value === 'object' ? evalObject(value, col.field).toString() : value.toString());
+ }
+ if (!value || !result) {
+ return false;
+ }
+ }
+ return true;
+ };
+ if (searchConditions.length === 0) {
+ grid.filteredRows = grid.rowCache;
+ } else {
+ grid.filteredRows = grid.rowCache.filter(function(row) {
+ return filterFunc(row.entity);
+ });
+ }
+ for (var i = 0; i < grid.filteredRows.length; i++)
+ {
+ grid.filteredRows[i].rowIndex = i;
+
+ }
+ grid.rowFactory.filteredRowsChanged();
+ };
+
+ //Traversing through the object to find the value that we want. If fail, then return the original object.
+ var evalObject = function (obj, columnName) {
+ if (typeof obj != 'object' || typeof columnName != 'string')
+ return obj;
+ var args = columnName.split('.');
+ var cObj = obj;
+ if (args.length > 1) {
+ for (var i = 1, len = args.length; i < len; i++) {
+ cObj = cObj[args[i]];
+ if (!cObj)
+ return obj;
+ }
+ return cObj;
+ }
+ return obj;
+ };
+ var getRegExp = function (str, modifiers) {
+ try {
+ return new RegExp(str, modifiers);
+ } catch (err) {
+ //Escape all RegExp metacharacters.
+ return new RegExp(str.replace(/(\^|\$|\(|\)|\<|\>|\[|\]|\{|\}|\\|\||\.|\*|\+|\?)/g, '\\$1'));
+ }
+ };
+ var buildSearchConditions = function (a) {
+ //reset.
+ searchConditions = [];
+ var qStr;
+ if (!(qStr = $.trim(a))) {
+ return;
+ }
+ var columnFilters = qStr.split(";");
+ for (var i = 0; i < columnFilters.length; i++) {
+ var args = columnFilters[i].split(':');
+ if (args.length > 1) {
+ var columnName = $.trim(args[0]);
+ var columnValue = $.trim(args[1]);
+ if (columnName && columnValue) {
+ searchConditions.push({
+ column: columnName,
+ columnDisplay: columnName.replace(/\s+/g, '').toLowerCase(),
+ regex: getRegExp(columnValue, 'i')
+ });
+ }
+ } else {
+ var val = $.trim(args[0]);
+ if (val) {
+ searchConditions.push({
+ column: '',
+ regex: getRegExp(val, 'i')
+ });
+ }
+ }
+ };
+ };
+ $scope.$watch(grid.config.filterOptions.filterText, function(a){
+ $scope.filterText = a;
+ });
+ $scope.$watch('filterText', function(a){
+ if(!self.extFilter){
+ $scope.$emit('ngGridEventFilter', a);
+ buildSearchConditions(a);
+ self.evalFilter();
+ }
+ });
+ if (!self.extFilter) {
+ $scope.$watch('columns', function (cs) {
+ for (var i = 0; i < cs.length; i++) {
+ var col = cs[i];
+ if(col.field)
+ self.fieldMap[col.field.split('.')[0]] = col;
+ if(col.displayName)
+ self.fieldMap[col.displayName.toLowerCase().replace(/\s+/g, '')] = col;
+ };
+ });
+ }
+};
+ng.selectionProvider = function (grid, $scope) {
+ var self = this;
+ self.multi = grid.config.multiSelect;
+ self.selectedItems = grid.config.selectedItems;
+ self.selectedIndex = grid.config.selectedIndex;
+ self.lastClickedRow = undefined;
+ self.ignoreSelectedItemChanges = false; // flag to prevent circular event loops keeping single-select var in sync
+
+ // function to manage the selection action of a data item (entity)
+ self.ChangeSelection = function (r, evt) {
+ var rowItem = r.isClone ? grid.filteredRows[r.rowIndex] : r;
+ if (evt && evt.shiftKey && !evt.keyCode && self.multi && grid.config.enableRowSelection) {
+ if (self.lastClickedRow) {
+ var rowsArr;
+ if ($scope.configGroups.length > 0) {
+ rowsArr = grid.rowFactory.parsedData.filter(function(row) {
+ return !row.isAggRow;
+ });
+ } else {
+ rowsArr = grid.filteredRows;
+ }
+ var thisIndx = rowItem.rowIndex;
+ var prevIndx = self.lastClickedRow.rowIndex;
+ self.lastClickedRow = rowItem;
+ if (thisIndx == prevIndx) {
+ return false;
+ }
+ if (thisIndx < prevIndx) {
+ thisIndx = thisIndx ^ prevIndx;
+ prevIndx = thisIndx ^ prevIndx;
+ thisIndx = thisIndx ^ prevIndx;
+ thisIndx--;
+ } else {
+ prevIndx++;
+ }
+ var rows = [];
+ for (; prevIndx <= thisIndx; prevIndx++) {
+ rows.push(rowsArr[prevIndx]);
+ }
+ if (rows[rows.length - 1].beforeSelectionChange(rows, evt)) {
+ for (var i = 0; i < rows.length; i++) {
+ var ri = rows[i];
+ var selectionState = ri.selected;
+ ri.selected = !selectionState;
+ if (ri.clone) {
+ ri.clone.selected = ri.selected;
+ }
+ var index = self.selectedItems.indexOf(ri.entity);
+ if (index === -1) {
+ self.selectedItems.push(ri.entity);
+ } else {
+ self.selectedItems.splice(index, 1);
+ }
+ }
+ rows[rows.length - 1].afterSelectionChange(rows, evt);
+ }
+ return true;
+ }
+ } else if (!self.multi) {
+ if (self.lastClickedRow == rowItem) {
+ self.setSelection(self.lastClickedRow, grid.config.keepLastSelected ? true : !rowItem.selected);
+ } else {
+ if (self.lastClickedRow) {
+ self.setSelection(self.lastClickedRow, false);
+ }
+ self.setSelection(rowItem, !rowItem.selected);
+ }
+ } else if (!evt.keyCode) {
+ self.setSelection(rowItem, !rowItem.selected);
+ }
+ self.lastClickedRow = rowItem;
+ return true;
+ };
+
+ self.getSelection = function(entity) {
+ return self.selectedItems.indexOf(entity) !== -1;
+ };
+
+ // just call this func and hand it the rowItem you want to select (or de-select)
+ self.setSelection = function (r, isSelected) {
+ var rowItem = r.isClone ? grid.filteredRows[r.rowIndex] : r;
+ if(grid.config.enableRowSelection){
+ rowItem.selected = isSelected;
+ if (rowItem.clone) {
+ rowItem.clone.selected = isSelected;
+ }
+ if (!isSelected) {
+ var indx = self.selectedItems.indexOf(rowItem.entity);
+ if(indx != -1){
+ self.selectedItems.splice(indx, 1);
+ }
+ } else {
+ if (self.selectedItems.indexOf(rowItem.entity) === -1) {
+ if(!self.multi && self.selectedItems.length > 0){
+ self.toggleSelectAll(false, true);
+ rowItem.selected = isSelected;
+ if (rowItem.clone) {
+ rowItem.clone.selected = isSelected;
+ }
+ }
+ self.selectedItems.push(rowItem.entity);
+ }
+ }
+ rowItem.afterSelectionChange(rowItem);
+ }
+ };
+ // @return - boolean indicating if all items are selected or not
+ // @val - boolean indicating whether to select all/de-select all
+ self.toggleSelectAll = function (checkAll, bypass) {
+ if (bypass || grid.config.beforeSelectionChange(grid.filteredRows)) {
+ var selectedlength = self.selectedItems.length;
+ if (selectedlength > 0) {
+ self.selectedItems.length = 0;
+ }
+ for (var i = 0; i < grid.filteredRows.length; i++) {
+ grid.filteredRows[i].selected = checkAll;
+ if (grid.filteredRows[i].clone) {
+ grid.filteredRows[i].clone.selected = checkAll;
+ }
+ if (checkAll) {
+ self.selectedItems.push(grid.filteredRows[i].entity);
+ }
+ }
+ if (!bypass) {
+ grid.config.afterSelectionChange(grid.filteredRows);
+ }
+ }
+ };
+};
+ng.StyleProvider = function($scope, grid, domUtilityService) {
+ $scope.headerCellStyle = function(col) {
+ return { "height": col.headerRowHeight + "px" };
+ };
+ $scope.rowStyle = function(row) {
+ return { "top": row.offsetTop + "px", "height": $scope.rowHeight + "px" };
+ };
+ $scope.canvasStyle = function() {
+ return { "height": grid.maxCanvasHt.toString() + "px" };
+ };
+ $scope.headerScrollerStyle = function() {
+ return { "height": grid.config.headerRowHeight + "px" };
+ };
+ $scope.topPanelStyle = function() {
+ return { "width": grid.rootDim.outerWidth + "px", "height": $scope.topPanelHeight() + "px" };
+ };
+ $scope.headerStyle = function() {
+ return { "width": (grid.rootDim.outerWidth - domUtilityService.ScrollW) + "px", "height": grid.config.headerRowHeight + "px" };
+ };
+ $scope.groupPanelStyle = function () {
+ return { "width": (grid.rootDim.outerWidth - domUtilityService.ScrollW) + "px", "height": "32px" };
+ };
+ $scope.viewportStyle = function() {
+ return { "width": grid.rootDim.outerWidth + "px", "height": $scope.viewportDimHeight() + "px" };
+ };
+ $scope.footerStyle = function() {
+ return { "width": grid.rootDim.outerWidth + "px", "height": $scope.footerRowHeight + "px" };
+ };
+};
+ngGridDirectives.directive('ngCellHasFocus', ['$domUtilityService',
+ function (domUtilityService) {
+ var focusOnInputElement = function($scope, elm){
+ $scope.isFocused = true;
+ domUtilityService.digest($scope);
+ var elementWithoutComments = angular.element(elm[0].children).filter(function () { return this.nodeType != 8; });//Remove html comments for IE8
+ var inputElement = angular.element(elementWithoutComments[0].children[0]);
+ if(inputElement.length > 0){
+ angular.element(inputElement).focus();
+ $scope.domAccessProvider.selectInputElement(inputElement[0]);
+ angular.element(inputElement).bind('blur', function(){
+ $scope.isFocused = false;
+ domUtilityService.digest($scope);
+ return true;
+ });
+ }
+ };
+ return function($scope, elm) {
+ var isFocused = false;
+ $scope.editCell = function(){
+ setTimeout(function() {
+ focusOnInputElement($scope,elm);
+ }, 0);
+ };
+ elm.bind('mousedown', function(){
+ elm.focus();
+ return true;
+ });
+ elm.bind('focus', function(){
+ isFocused = true;
+ return true;
+ });
+ elm.bind('blur', function(){
+ isFocused = false;
+ return true;
+ });
+ elm.bind('keydown', function(evt){
+ if(isFocused && evt.keyCode != 37 && evt.keyCode != 38 && evt.keyCode != 39 && evt.keyCode != 40 && evt.keyCode != 9 && !evt.shiftKey && evt.keyCode != 13){
+ focusOnInputElement($scope,elm);
+ }
+ if(evt.keyCode == 27){
+ elm.focus();
+ }
+ return true;
+ });
+ };
+ }]);
+ngGridDirectives.directive('ngCellText',
+ function () {
+ return function(scope, elm) {
+ elm.bind('mouseover', function(evt) {
+ evt.preventDefault();
+ elm.css({
+ 'cursor': 'text'
+ });
+ });
+ elm.bind('mouseleave', function(evt) {
+ evt.preventDefault();
+ elm.css({
+ 'cursor': 'default'
+ });
+ });
+ };
+ });
+ngGridDirectives.directive('ngCell', ['$compile', '$domUtilityService', function ($compile, domUtilityService) {
+ var ngCell = {
+ scope: false,
+ compile: function() {
+ return {
+ pre: function($scope, iElement) {
+ var html;
+ var cellTemplate = $scope.col.cellTemplate.replace(COL_FIELD, '$eval(\'row.entity.\' + col.field)');
+ if($scope.col.enableCellEdit){
+ html = $scope.col.cellEditTemplate;
+ html = html.replace(DISPLAY_CELL_TEMPLATE, cellTemplate);
+ html = html.replace(EDITABLE_CELL_TEMPLATE, $scope.col.editableCellTemplate.replace(COL_FIELD, "col.field"));
+ } else {
+ html = cellTemplate;
+ }
+ var cellElement = $compile(html)($scope);
+ if($scope.enableCellSelection && cellElement[0].className.indexOf('ngSelectionCell') == -1){
+ cellElement[0].setAttribute('tabindex', 0);
+ cellElement.addClass('ngCellElement');
+ }
+ iElement.append(cellElement);
+ },
+ post: function($scope, iElement){
+ if($scope.enableCellSelection){
+ $scope.domAccessProvider.selectionHandlers($scope, iElement);
+ }
+
+ $scope.$on('ngGridEventDigestCell', function(){
+ domUtilityService.digest($scope);
+ });
+ }
+ };
+ }
+ };
+ return ngCell;
+}]);
+ngGridDirectives.directive('ngGrid', ['$compile', '$filter', '$templateCache', '$sortService', '$domUtilityService', '$utilityService', '$timeout', function ($compile, $filter, $templateCache, sortService, domUtilityService, $utils, $timeout) {
+ var ngGrid = {
+ scope: true,
+ compile: function() {
+ return {
+ pre: function($scope, iElement, iAttrs) {
+ var $element = $(iElement);
+ var options = $scope.$eval(iAttrs.ngGrid);
+ options.gridDim = new ng.Dimension({ outerHeight: $($element).height(), outerWidth: $($element).width() });
+ var grid = new ng.Grid($scope, options, sortService, domUtilityService, $filter, $templateCache, $utils, $timeout);
+
+ // if columndefs are a string of a property ont he scope watch for changes and rebuild columns.
+ if (typeof options.columnDefs == "string") {
+ $scope.$parent.$watch(options.columnDefs, function (a) {
+ if (!a) {
+ grid.refreshDomSizes();
+ grid.buildColumns();
+ return;
+ }
+ $scope.columns = [];
+ grid.config.columnDefs = a;
+ grid.buildColumns();
+ grid.configureColumnWidths();
+ grid.eventProvider.assignEvents();
+ domUtilityService.RebuildGrid($scope, grid);
+ });
+ } else {
+ grid.buildColumns();
+ }
+
+ // if it is a string we can watch for data changes. otherwise you won't be able to update the grid data
+ if (typeof options.data == "string") {
+ var dataWatcher = function (a) {
+ // make a temporary copy of the data
+ grid.data = $.extend([], a);
+ grid.rowFactory.fixRowCache();
+ angular.forEach(grid.data, function (item, j) {
+ var indx = grid.rowMap[j] || j;
+ if (grid.rowCache[indx]) {
+ grid.rowCache[indx].ensureEntity(item);
+ }
+ grid.rowMap[indx] = j;
+ });
+ grid.searchProvider.evalFilter();
+ grid.configureColumnWidths();
+ grid.refreshDomSizes();
+ if (grid.config.sortInfo.fields.length > 0) {
+ sortService.sortData(grid.config.sortInfo, grid.data.slice(0));
+ }
+ $scope.$emit("ngGridEventData", grid.gridId);
+ };
+ $scope.$parent.$watch(options.data, dataWatcher);
+ $scope.$parent.$watch(options.data + '.length', function() {
+ dataWatcher($scope.$eval(options.data));
+ });
+ }
+
+ grid.footerController = new ng.Footer($scope, grid);
+ //set the right styling on the container
+ iElement.addClass("ngGrid").addClass(grid.gridId.toString());
+ if (options.jqueryUITheme) {
+ iElement.addClass('ui-widget');
+ }
+ iElement.append($compile($templateCache.get('gridTemplate.html'))($scope)); // make sure that if any of these change, we re-fire the calc logic
+ //walk the element's graph and the correct properties on the grid
+ domUtilityService.AssignGridContainers($scope, iElement, grid);
+ //now use the manager to assign the event handlers
+ grid.eventProvider = new ng.EventProvider(grid, $scope, domUtilityService);
+ //initialize plugins.
+ angular.forEach(options.plugins, function (p) {
+ if (typeof p === 'function') {
+ p.call(this, []).init($scope.$new(), grid, { SortService: sortService, DomUtilityService: domUtilityService });
+ } else {
+ p.init($scope.$new(), grid, { SortService: sortService, DomUtilityService: domUtilityService });
+ }
+ });
+ // method for user to select a specific row programatically
+ options.selectRow = function (rowIndex, state) {
+ if (grid.rowCache[rowIndex]) {
+ grid.rowCache[rowIndex].setSelection(state ? true : false);
+ }
+ };
+ // method for user to select the row by data item programatically
+ options.selectItem = function (itemIndex, state) {
+ options.selectRow(grid.rowMap[itemIndex], state);
+ };
+ // method for user to set the select all state.
+ options.selectAll = function (state) {
+ $scope.toggleSelectAll(state);
+ };
+ // method for user to set the groups programatically
+ options.groupBy = function (field) {
+ if (field) {
+ $scope.groupBy($scope.columns.filter(function(c) {
+ return c.field == field;
+ })[0]);
+ } else {
+ var arr = $.extend(true, [], $scope.configGroups);
+ angular.forEach(arr, $scope.groupBy);
+ }
+ };
+ // method for user to set the sort field programatically
+ options.sortBy = function (field) {
+ var col = $scope.columns.filter(function (c) {
+ return c.field == field;
+ })[0];
+ if (col) col.sort();
+ };
+ // the grid Id, entity, scope for convenience
+ options.gridId = grid.gridId;
+ options.ngGrid = grid;
+ options.$gridScope = $scope;
+ $scope.$on('ngGridEventDigestGrid', function(){
+ domUtilityService.digest($scope.$parent);
+ });
+
+ $scope.$on('ngGridEventDigestGridParent', function(){
+ domUtilityService.digest($scope.$parent);
+ });
+ // set up the columns
+ $scope.$evalAsync(function() {
+ $scope.adjustScrollLeft(0);
+ });
+ return null;
+ }
+ };
+ }
+ };
+ return ngGrid;
+}]);
+ngGridDirectives.directive('ngHeaderCell', ['$compile', function($compile) {
+ var ngHeaderCell = {
+ scope: false,
+ compile: function() {
+ return {
+ pre: function($scope, iElement) {
+ iElement.append($compile($scope.col.headerCellTemplate)($scope));
+ }
+ };
+ }
+ };
+ return ngHeaderCell;
+}]);
+ngGridDirectives.directive('ngHeaderRow', ['$compile', '$templateCache', function ($compile, $templateCache) {
+ var ngHeaderRow = {
+ scope: false,
+ compile: function() {
+ return {
+ pre: function($scope, iElement) {
+ if (iElement.children().length === 0) {
+ iElement.append($compile($templateCache.get($scope.gridId + 'headerRowTemplate.html'))($scope));
+ }
+ }
+ };
+ }
+ };
+ return ngHeaderRow;
+}]);
+/*
+ * Defines the ui-if tag. This removes/adds an element from the dom depending on a condition
+ * Originally created by @tigbro, for the @jquery-mobile-angular-adapter
+ * https://github.com/tigbro/jquery-mobile-angular-adapter
+ */
+ngGridDirectives.directive('ngIf', [function () {
+ return {
+ transclude: 'element',
+ priority: 1000,
+ terminal: true,
+ restrict: 'A',
+ compile: function (e, a, transclude) {
+ return function (scope, element, attr) {
+
+ var childElement;
+ var childScope;
+
+ scope.$watch(attr['ngIf'], function (newValue) {
+ if (childElement) {
+ childElement.remove();
+ childElement = undefined;
+ }
+ if (childScope) {
+ childScope.$destroy();
+ childScope = undefined;
+ }
+
+ if (newValue) {
+ childScope = scope.$new();
+ transclude(childScope, function (clone) {
+ childElement = clone;
+ element.after(clone);
+ });
+ }
+ });
+ };
+ }
+ };
+}]);
+ngGridDirectives.directive('ngInput',['$parse', function($parse) {
+ return function ($scope, elm, attrs) {
+ var getter = $parse($scope.$eval(attrs.ngInput));
+ var setter = getter.assign;
+ var oldCellValue = getter($scope.row.entity);
+ elm.val(oldCellValue);
+ elm.bind('keyup', function() {
+ var newVal = elm.val();
+ if (!$scope.$root.$$phase) {
+ $scope.$apply(function(){setter($scope.row.entity,newVal); });
+ }
+ });
+ elm.bind('keydown', function(evt){
+ switch(evt.keyCode){
+ case 37:
+ case 38:
+ case 39:
+ case 40:
+ evt.stopPropagation();
+ break;
+ case 27:
+ if (!$scope.$root.$$phase) {
+ $scope.$apply(function(){
+ setter($scope.row.entity,oldCellValue);
+ elm.val(oldCellValue);
+ elm.blur();
+ });
+ }
+ default:
+ break;
+ }
+ return true;
+ });
+ };
+}]);
+ngGridDirectives.directive('ngRow', ['$compile', '$domUtilityService', '$templateCache', function ($compile, domUtilityService, $templateCache) {
+ var ngRow = {
+ scope: false,
+ compile: function() {
+ return {
+ pre: function($scope, iElement) {
+ $scope.row.elm = iElement;
+ if ($scope.row.clone) {
+ $scope.row.clone.elm = iElement;
+ }
+ if ($scope.row.isAggRow) {
+ var html = $templateCache.get($scope.gridId + 'aggregateTemplate.html');
+ if ($scope.row.aggLabelFilter) {
+ html = html.replace(CUSTOM_FILTERS, '| ' + $scope.row.aggLabelFilter);
+ } else {
+ html = html.replace(CUSTOM_FILTERS, "");
+ }
+ iElement.append($compile(html)($scope));
+ } else {
+ iElement.append($compile($templateCache.get($scope.gridId + 'rowTemplate.html'))($scope));
+ }
+ $scope.$on('ngGridEventDigestRow', function(){
+ domUtilityService.digest($scope);
+ });
+ }
+ };
+ }
+ };
+ return ngRow;
+}]);
+ngGridDirectives.directive('ngViewport', [function() {
+ return function($scope, elm) {
+ var isMouseWheelActive;
+ var prevScollLeft;
+ var prevScollTop = 0;
+ elm.bind('scroll', function(evt) {
+ var scrollLeft = evt.target.scrollLeft,
+ scrollTop = evt.target.scrollTop;
+ if ($scope.$headerContainer) {
+ $scope.$headerContainer.scrollLeft(scrollLeft);
+ }
+ $scope.adjustScrollLeft(scrollLeft);
+ $scope.adjustScrollTop(scrollTop);
+ if (!$scope.$root.$$phase) {
+ $scope.$digest();
+ }
+ prevScollLeft = scrollLeft;
+ prevScollTop = prevScollTop;
+ isMouseWheelActive = false;
+ return true;
+ });
+ elm.bind("mousewheel DOMMouseScroll", function() {
+ isMouseWheelActive = true;
+ elm.focus();
+ return true;
+ });
+ if (!$scope.enableCellSelection) {
+ $scope.domAccessProvider.selectionHandlers($scope, elm);
+ }
+ };
+}]);
+window.ngGrid.i18n['en'] = {
+ ngAggregateLabel: 'items',
+ ngGroupPanelDescription: 'Drag a column header here and drop it to group by that column.',
+ ngSearchPlaceHolder: 'Search...',
+ ngMenuText: 'Choose Columns:',
+ ngShowingItemsLabel: 'Showing Items:',
+ ngTotalItemsLabel: 'Total Items:',
+ ngSelectedItemsLabel: 'Selected Items:',
+ ngPageSizeLabel: 'Page Size:',
+ ngPagerFirstTitle: 'First Page',
+ ngPagerNextTitle: 'Next Page',
+ ngPagerPrevTitle: 'Previous Page',
+ ngPagerLastTitle: 'Last Page'
+};
+window.ngGrid.i18n['fr'] = {
+ ngAggregateLabel: 'articles',
+ ngGroupPanelDescription: 'Faites glisser un en-tête de colonne ici et déposez-le vers un groupe par cette colonne.',
+ ngSearchPlaceHolder: 'Recherche...',
+ ngMenuText: 'Choisir des colonnes:',
+ ngShowingItemsLabel: 'Articles Affichage des:',
+ ngTotalItemsLabel: 'Nombre total d\'articles:',
+ ngSelectedItemsLabel: 'Éléments Articles:',
+ ngPageSizeLabel: 'Taille de page:',
+ ngPagerFirstTitle: 'Première page',
+ ngPagerNextTitle: 'Page Suivante',
+ ngPagerPrevTitle: 'Page précédente',
+ ngPagerLastTitle: 'Dernière page'
+};
+window.ngGrid.i18n['ge'] = {
+ ngAggregateLabel: 'artikel',
+ ngGroupPanelDescription: 'Ziehen Sie eine Spaltenüberschrift hier und legen Sie es der Gruppe nach dieser Spalte.',
+ ngSearchPlaceHolder: 'Suche...',
+ ngMenuText: 'Spalten auswählen:',
+ ngShowingItemsLabel: 'Zeige Artikel:',
+ ngTotalItemsLabel: 'Meiste Artikel:',
+ ngSelectedItemsLabel: 'Ausgewählte Artikel:',
+ ngPageSizeLabel: 'Größe Seite:',
+ ngPagerFirstTitle: 'Erste Page',
+ ngPagerNextTitle: 'Nächste Page',
+ ngPagerPrevTitle: 'Vorherige Page',
+ ngPagerLastTitle: 'Letzte Page'
+};
+window.ngGrid.i18n['sp'] = {
+ ngAggregateLabel: 'Artículos',
+ ngGroupPanelDescription: 'Arrastre un encabezado de columna aquí y soltarlo para agrupar por esa columna.',
+ ngSearchPlaceHolder: 'Buscar...',
+ ngMenuText: 'Elegir columnas:',
+ ngShowingItemsLabel: 'Artículos Mostrando:',
+ ngTotalItemsLabel: 'Artículos Totales:',
+ ngSelectedItemsLabel: 'Artículos Seleccionados:',
+ ngPageSizeLabel: 'Tamaño de Página:',
+ ngPagerFirstTitle: 'Primera Página',
+ ngPagerNextTitle: 'Página Siguiente',
+ ngPagerPrevTitle: 'Página Anterior',
+ ngPagerLastTitle: 'Última Página'
+};
+window.ngGrid.i18n['zh-cn'] = {
+ ngAggregateLabel: '条目',
+ ngGroupPanelDescription: '拖曳表头到此处以进行分组',
+ ngSearchPlaceHolder: '搜索...',
+ ngMenuText: '数据分组与选择列:',
+ ngShowingItemsLabel: '当前显示条目:',
+ ngTotalItemsLabel: '条目总数:',
+ ngSelectedItemsLabel: '选中条目:',
+ ngPageSizeLabel: '每页显示数:',
+ ngPagerFirstTitle: '回到首页',
+ ngPagerNextTitle: '下一页',
+ ngPagerPrevTitle: '上一页',
+ ngPagerLastTitle: '前往尾页'
+};
+
+angular.module("ngGrid").run(["$templateCache", function($templateCache) {
+
+ $templateCache.put("aggregateTemplate.html",
+ "" +
+ "
{{row.label CUSTOM_FILTERS}} ({{row.totalChildren()}} {{AggItemsLabel}})" +
+ "
" +
+ "
" +
+ ""
+ );
+
+ $templateCache.put("cellEditTemplate.html",
+ "" +
+ "
" +
+ " DISPLAY_CELL_TEMPLATE" +
+ "
" +
+ "
" +
+ " EDITABLE_CELL_TEMPLATE" +
+ "
" +
+ "
"
+ );
+
+ $templateCache.put("cellTemplate.html",
+ "{{COL_FIELD CUSTOM_FILTERS}}
"
+ );
+
+ $templateCache.put("checkboxCellTemplate.html",
+ ""
+ );
+
+ $templateCache.put("checkboxHeaderTemplate.html",
+ ""
+ );
+
+ $templateCache.put("editableCellTemplate.html",
+ ""
+ );
+
+ $templateCache.put("gridTemplate.html",
+ "" +
+ "
" +
+ "
{{i18n.ngGroupPanelDescription}}
" +
+ "
0\" class=\"ngGroupList\">" +
+ " - " +
+ " " +
+ " {{group.displayName}}" +
+ " x" +
+ " " +
+ " " +
+ " " +
+ "
" +
+ "
" +
+ "
" +
+ " " +
+ " " +
+ " " +
+ "
" +
+ "" +
+ "" +
+ ""
+ );
+
+ $templateCache.put("headerCellTemplate.html",
+ "" +
+ " " +
+ "
" +
+ "
" +
+ "
{{col.sortPriority}}
" +
+ "
" +
+ "
" +
+ ""
+ );
+
+ $templateCache.put("headerRowTemplate.html",
+ ""
+ );
+
+ $templateCache.put("rowTemplate.html",
+ ""
+ );
+
+}]);
+
+}(window));
\ No newline at end of file
diff --git a/src/main/java/com/commafeed/frontend/references/nggrid/ng-grid-2.0.2.min.js b/src/main/java/com/commafeed/frontend/references/nggrid/ng-grid-2.0.2.min.js
new file mode 100644
index 00000000..e1054002
--- /dev/null
+++ b/src/main/java/com/commafeed/frontend/references/nggrid/ng-grid-2.0.2.min.js
@@ -0,0 +1,2 @@
+(function(e){"use strict";var n=6,t=4,o="asc",r="desc",i="_ng_field_",l="_ng_depth_",a="_ng_hidden_",s="_ng_column_",c=/CUSTOM_FILTERS/g,g=/COL_FIELD/g,d=/DISPLAY_CELL_TEMPLATE/g,u=/EDITABLE_CELL_TEMPLATE/g,f=/<.+>/;e.ng||(e.ng={}),e.ngGrid={},e.ngGrid.i18n={};var p=angular.module("ngGrid.services",[]),h=angular.module("ngGrid.directives",[]),m=angular.module("ngGrid.filters",[]);angular.module("ngGrid",["ngGrid.services","ngGrid.directives","ngGrid.filters"]),ng.moveSelectionHandler=function(e,t,o,r){if(void 0===e.selectionService.selectedItems)return!0;var i,l=o.which||o.keyCode,a=!1,s=!1,c=e.selectionService.lastClickedRow.rowIndex;if(e.col&&(i=e.col.index),37!=l&&38!=l&&39!=l&&40!=l&&9!=l&&13!=l)return!0;if(e.enableCellSelection){9==l&&o.preventDefault();var g=e.showSelectionCheckbox?1==e.col.index:0==e.col.index,d=1==e.$index||0==e.$index,u=e.$index==e.renderedColumns.length-1||e.$index==e.renderedColumns.length-2,f=e.col.index==e.columns.length-1;37==l||9==l&&o.shiftKey?(d&&(g&&9==l&&o.shiftKey?(r.$viewport.scrollLeft(r.$canvas.width()),i=e.columns.length-1,s=!0):r.$viewport.scrollLeft(r.$viewport.scrollLeft()-e.col.width)),g||(i-=1)):(39==l||9==l&&!o.shiftKey)&&(u&&(f&&9==l&&!o.shiftKey?(r.$viewport.scrollLeft(0),i=e.showSelectionCheckbox?1:0,a=!0):r.$viewport.scrollLeft(r.$viewport.scrollLeft()+e.col.width)),f||(i+=1))}var p;p=e.configGroups.length>0?r.rowFactory.parsedData.filter(function(e){return!e.isAggRow}):r.filteredRows;var h=0;if(0!=c&&(38==l||13==l&&o.shiftKey||9==l&&o.shiftKey&&s)?h=-1:c!=p.length-1&&(40==l||13==l&&!o.shiftKey||9==l&&a)&&(h=1),h){var m=p[c+h];m.beforeSelectionChange(m,o)&&(m.continueSelection(o),e.$emit("ngGridEventDigestGridParent"),e.selectionService.lastClickedRow.renderedRowIndex>=e.renderedRows.length-n-2?r.$viewport.scrollTop(r.$viewport.scrollTop()+e.rowHeight):n+2>=e.selectionService.lastClickedRow.renderedRowIndex&&r.$viewport.scrollTop(r.$viewport.scrollTop()-e.rowHeight))}return e.enableCellSelection&&setTimeout(function(){e.domAccessProvider.focusCellElement(e,e.renderedColumns.indexOf(e.columns[i]))},3),!1},String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")}),Array.prototype.indexOf||(Array.prototype.indexOf=function(e){var n=this.length>>>0,t=Number(arguments[1])||0;for(t=0>t?Math.ceil(t):Math.floor(t),0>t&&(t+=n);n>t;t++)if(t in this&&this[t]===e)return t;return-1}),Array.prototype.filter||(Array.prototype.filter=function(e){var n=Object(this),t=n.length>>>0;if("function"!=typeof e)throw new TypeError;for(var o=[],r=arguments[1],i=0;t>i;i++)if(i in n){var l=n[i];e.call(r,l,i,n)&&o.push(l)}return o}),m.filter("checkmark",function(){return function(e){return e?"✔":"✘"}}),m.filter("ngColumns",function(){return function(e){return e.filter(function(e){return!e.isAggCol})}}),p.factory("$domUtilityService",["$utilityService",function(e){var n={},t={},o=function(){var e=$("");e.appendTo("body"),e.height(100).width(100).css("position","absolute").css("overflow","scroll"),e.append(''),n.ScrollH=e.height()-e[0].clientHeight,n.ScrollW=e.width()-e[0].clientWidth,e.empty(),e.attr("style",""),e.append('M'),n.LetterW=e.children().first().width(),e.remove()};return n.eventStorage={},n.AssignGridContainers=function(e,t,o){o.$root=$(t),o.$topPanel=o.$root.find(".ngTopPanel"),o.$groupPanel=o.$root.find(".ngGroupPanel"),o.$headerContainer=o.$topPanel.find(".ngHeaderContainer"),e.$headerContainer=o.$headerContainer,o.$headerScroller=o.$topPanel.find(".ngHeaderScroller"),o.$headers=o.$headerScroller.children(),o.$viewport=o.$root.find(".ngViewport"),o.$canvas=o.$viewport.find(".ngCanvas"),o.$footerPanel=o.$root.find(".ngFooterPanel"),e.$watch(function(){return o.$viewport.scrollLeft()},function(e){return o.$headerContainer.scrollLeft(e)}),n.UpdateGridLayout(e,o)},n.getRealWidth=function(e){var n=0,t={visibility:"hidden",display:"block"},o=e.parents().andSelf().not(":visible");return $.swap(o[0],t,function(){n=e.outerWidth()}),n},n.UpdateGridLayout=function(e,t){var o=t.$viewport.scrollTop();t.elementDims.rootMaxW=t.$root.width(),t.$root.is(":hidden")&&(t.elementDims.rootMaxW=n.getRealWidth(t.$root)),t.elementDims.rootMaxH=t.$root.height(),t.refreshDomSizes(),e.adjustScrollTop(o,!0)},n.numberOfGrids=0,n.BuildStyles=function(t,o,r){var i,l=o.config.rowHeight,a=o.$styleSheet,s=o.gridId,c=t.columns,g=0;a||(a=$("#"+s),a[0]||(a=$("").appendTo(o.$root))),a.empty();var d=t.totalRowWidth();i="."+s+" .ngCanvas { width: "+d+"px; }"+"."+s+" .ngRow { width: "+d+"px; }"+"."+s+" .ngCanvas { width: "+d+"px; }"+"."+s+" .ngHeaderScroller { width: "+(d+n.ScrollH+2)+"px}";for(var u=0;c.length>u;u++){var f=c[u];if(f.visible!==!1){var p=f.pinned?o.$viewport.scrollLeft()+g:g;i+="."+s+" .col"+u+" { width: "+f.width+"px; left: "+p+"px; height: "+l+"px }"+"."+s+" .colt"+u+" { width: "+f.width+"px; }",g+=f.width}}e.isIe?a[0].styleSheet.cssText=i:a[0].appendChild(document.createTextNode(i)),o.$styleSheet=a,r&&(t.adjustScrollLeft(o.$viewport.scrollLeft()),n.digest(t))},n.setColLeft=function(n,o,r){if(r.$styleSheet){var i=t[n.index];i||(i=t[n.index]=RegExp(".col"+n.index+" { width: [0-9]+px; left: [0-9]+px"));var l=r.$styleSheet.html(),a=l.replace(i,".col"+n.index+" { width: "+n.width+"px; left: "+o+"px");e.isIe?setTimeout(function(){r.$styleSheet.html(a)}):r.$styleSheet.html(a)}},n.setColLeft.immediate=1,n.RebuildGrid=function(e,t){n.UpdateGridLayout(e,t),t.config.maintainColumnRatios&&t.configureColumnWidths(),e.adjustScrollLeft(t.$viewport.scrollLeft()),n.BuildStyles(e,t,!0)},n.digest=function(e){e.$root.$$phase||e.$digest()},n.ScrollH=17,n.ScrollW=17,n.LetterW=10,o(),n}]),p.factory("$sortService",["$parse",function(e){var n={};return n.colSortFnCache={},n.guessSortFn=function(e){var t=typeof e;switch(t){case"number":return n.sortNumber;case"boolean":return n.sortBool;case"string":return e.match(/^-?[£$¤]?[\d,.]+%?$/)?n.sortNumberStr:n.sortAlpha;default:return"[object Date]"===Object.prototype.toString.call(e)?n.sortDate:n.basicSort}},n.basicSort=function(e,n){return e==n?0:n>e?-1:1},n.sortNumber=function(e,n){return e-n},n.sortNumberStr=function(e,n){var t,o,r=!1,i=!1;return t=parseFloat(e.replace(/[^0-9.-]/g,"")),isNaN(t)&&(r=!0),o=parseFloat(n.replace(/[^0-9.-]/g,"")),isNaN(o)&&(i=!0),r&&i?0:r?1:i?-1:t-o},n.sortAlpha=function(e,n){var t=e.toLowerCase(),o=n.toLowerCase();return t==o?0:o>t?-1:1},n.sortDate=function(e,n){var t=e.getTime(),o=n.getTime();return t==o?0:o>t?-1:1},n.sortBool=function(e,n){return e&&n?0:e||n?e?1:-1:0},n.sortData=function(t,r){if(r&&t){var i,l,a=t.fields.length,s=t.fields,c=r.slice(0);r.sort(function(r,g){for(var d,u=0,f=0;0==u&&a>f;){i=t.columns[f],l=t.directions[f],d=n.getSortFn(i,c);var p=e(s[f])(r),h=e(s[f])(g);!p&&0!=p||!h&&0!=h?h||p?p?h||(u=-1):u=1:u=0:u=d(p,h),f++}return l===o?u:0-u})}},n.Sort=function(e,t){n.isSorting||(n.isSorting=!0,n.sortData(e,t),n.isSorting=!1)},n.getSortFn=function(t,o){var r,i=void 0;if(n.colSortFnCache[t.field])i=n.colSortFnCache[t.field];else if(void 0!=t.sortingAlgorithm)i=t.sortingAlgorithm,n.colSortFnCache[t.field]=t.sortingAlgorithm;else{if(r=o[0],!r)return i;i=n.guessSortFn(e(t.field)(r)),i?n.colSortFnCache[t.field]=i:i=n.sortAlpha}return i},n}]),p.factory("$utilityService",["$parse",function(n){var t={visualLength:function(e){var n=document.getElementById("testDataLength");return n||(n=document.createElement("SPAN"),n.id="testDataLength",n.style.visibility="hidden",document.body.appendChild(n)),$(n).css("font",$(e).css("font")),n.innerHTML=$(e).text(),n.offsetWidth},forIn:function(e,n){for(var t in e)e.hasOwnProperty(t)&&n(e[t],t)},evalProperty:function(e,t){return n(t)(e)},endsWith:function(e,n){return e&&n&&"string"==typeof e?-1!==e.indexOf(n,e.length-n.length):!1},isNullOrUndefined:function(e){return void 0===e||null===e?!0:!1},getElementsByClassName:function(e){for(var n=[],t=RegExp("\\b"+e+"\\b"),o=document.getElementsByTagName("*"),r=0;o.length>r;r++){var i=o[r].className;t.test(i)&&n.push(o[r])}return n},newId:function(){var e=(new Date).getTime();return function(){return e+=1}}(),seti18n:function(n,t){var o=e.ngGrid.i18n[t];for(var r in o)n.i18n[r]=o[r]},ieVersion:function(){for(var e=3,n=document.createElement("div"),t=n.getElementsByTagName("i");n.innerHTML="",t[0];);return e>4?e:void 0}()};return $.extend(t,{isIe:function(){return void 0!==t.ieVersion}()}),t}]),ng.Aggregate=function(e,n,t){var o=this;o.rowIndex=0,o.offsetTop=o.rowIndex*t,o.entity=e,o.label=e.gLabel,o.field=e.gField,o.depth=e.gDepth,o.parent=e.parent,o.children=e.children,o.aggChildren=e.aggChildren,o.aggIndex=e.aggIndex,o.collapsed=!0,o.isAggRow=!0,o.offsetleft=25*e.gDepth,o.aggLabelFilter=e.aggLabelFilter,o.toggleExpand=function(){o.collapsed=o.collapsed?!1:!0,o.orig&&(o.orig.collapsed=o.collapsed),o.notifyChildren()},o.setExpand=function(e){o.collapsed=e,o.notifyChildren()},o.notifyChildren=function(){for(var e=Math.max(n.aggCache.length,o.children.length),t=0;e>t;t++)if(o.aggChildren[t]&&(o.aggChildren[t].entity[a]=o.collapsed,o.collapsed&&o.aggChildren[t].setExpand(o.collapsed)),o.children[t]&&(o.children[t][a]=o.collapsed),t>o.aggIndex&&n.aggCache[t]){var r=n.aggCache[t],i=30*o.children.length;r.offsetTop=o.collapsed?r.offsetTop-i:r.offsetTop+i}n.renderedChange()},o.aggClass=function(){return o.collapsed?"ngAggArrowCollapsed":"ngAggArrowExpanded"},o.totalChildren=function(){if(o.aggChildren.length>0){var e=0,n=function(t){t.aggChildren.length>0?angular.forEach(t.aggChildren,function(e){n(e)}):e+=t.children.length};return n(o),e}return o.children.length},o.copy=function(){var e=new ng.Aggregate(o.entity,n,t);return e.orig=o,e}},ng.Column=function(e,n,t,i,l,a){var s=this,g=e.colDef,d=500,u=0,p=null;s.width=g.width,s.groupIndex=0,s.isGroupedBy=!1,s.minWidth=g.minWidth?g.minWidth:50,s.maxWidth=g.maxWidth?g.maxWidth:9e3,s.enableCellEdit=e.enableCellEdit||g.enableCellEdit,s.headerRowHeight=e.headerRowHeight,s.displayName=g.displayName||g.field,s.index=e.index,s.isAggCol=e.isAggCol,s.cellClass=g.cellClass,s.sortPriority=void 0,s.zIndex=function(){return s.pinned?5:0},s.cellFilter=g.cellFilter?g.cellFilter:"",s.field=g.field,s.aggLabelFilter=g.cellFilter||g.aggLabelFilter,s.visible=a.isNullOrUndefined(g.visible)||g.visible,s.sortable=!1,s.resizable=!1,s.pinnable=!1,s.pinned=g.pinned,s.originalIndex=s.index,s.groupable=a.isNullOrUndefined(g.groupable)||g.groupable,e.enableSort&&(s.sortable=a.isNullOrUndefined(g.sortable)||g.sortable),e.enableResize&&(s.resizable=a.isNullOrUndefined(g.resizable)||g.resizable),e.enablePinning&&(s.pinnable=a.isNullOrUndefined(g.pinnable)||g.pinnable),s.sortDirection=void 0,s.sortingAlgorithm=g.sortFn,s.headerClass=g.headerClass,s.cursor=s.sortable?"pointer":"default",s.headerCellTemplate=g.headerCellTemplate||l.get("headerCellTemplate.html"),s.cellTemplate=g.cellTemplate||l.get("cellTemplate.html").replace(c,s.cellFilter?"|"+s.cellFilter:""),s.enableCellEdit&&(s.cellEditTemplate=l.get("cellEditTemplate.html"),s.editableCellTemplate=g.editableCellTemplate||l.get("editableCellTemplate.html")),g.cellTemplate&&!f.test(g.cellTemplate)&&(s.cellTemplate=$.ajax({type:"GET",url:g.cellTemplate,async:!1}).responseText),s.enableCellEdit&&g.editableCellTemplate&&!f.test(g.editableCellTemplate)&&(s.editableCellTemplate=$.ajax({type:"GET",url:g.editableCellTemplate,async:!1}).responseText),g.headerCellTemplate&&!f.test(g.headerCellTemplate)&&(s.headerCellTemplate=$.ajax({type:"GET",url:g.headerCellTemplate,async:!1}).responseText),s.colIndex=function(){return"col"+s.index+" colt"+s.index},s.groupedByClass=function(){return s.isGroupedBy?"ngGroupedByIcon":"ngGroupIcon"},s.toggleVisible=function(){s.visible=!s.visible},s.showSortButtonUp=function(){return s.sortable?s.sortDirection===r:s.sortable},s.showSortButtonDown=function(){return s.sortable?s.sortDirection===o:s.sortable},s.noSortVisible=function(){return!s.sortDirection},s.sort=function(n){if(!s.sortable)return!0;var t=s.sortDirection===o?r:o;return s.sortDirection=t,e.sortCallback(s,n),!1},s.gripClick=function(){u++,1===u?p=setTimeout(function(){u=0},d):(clearTimeout(p),e.resizeOnDataCallback(s),u=0)},s.gripOnMouseDown=function(e){return e.ctrlKey&&!s.pinned?(s.toggleVisible(),i.BuildStyles(n,t),!0):(e.target.parentElement.style.cursor="col-resize",s.startMousePosition=e.clientX,s.origWidth=s.width,$(document).mousemove(s.onMouseMove),$(document).mouseup(s.gripOnMouseUp),!1)},s.onMouseMove=function(e){var o=e.clientX-s.startMousePosition,r=o+s.origWidth;return s.width=s.minWidth>r?s.minWidth:r>s.maxWidth?s.maxWidth:r,i.BuildStyles(n,t),!1},s.gripOnMouseUp=function(e){return $(document).off("mousemove",s.onMouseMove),$(document).off("mouseup",s.gripOnMouseUp),e.target.parentElement.style.cursor="default",n.adjustScrollLeft(0),i.digest(n),!1},s.copy=function(){var o=new ng.Column(e,n,t,i,l);return o.isClone=!0,o.orig=s,o},s.setVars=function(e){s.orig=e,s.width=e.width,s.groupIndex=e.groupIndex,s.isGroupedBy=e.isGroupedBy,s.displayName=e.displayName,s.index=e.index,s.isAggCol=e.isAggCol,s.cellClass=e.cellClass,s.cellFilter=e.cellFilter,s.field=e.field,s.aggLabelFilter=e.aggLabelFilter,s.visible=e.visible,s.sortable=e.sortable,s.resizable=e.resizable,s.pinnable=e.pinnable,s.pinned=e.pinned,s.originalIndex=e.originalIndex,s.sortDirection=e.sortDirection,s.sortingAlgorithm=e.sortingAlgorithm,s.headerClass=e.headerClass,s.headerCellTemplate=e.headerCellTemplate,s.cellTemplate=e.cellTemplate,s.cellEditTemplate=e.cellEditTemplate}},ng.Dimension=function(e){this.outerHeight=null,this.outerWidth=null,$.extend(this,e)},ng.DomAccessProvider=function(e){var n,t=this;t.selectInputElement=function(e){var n=e.nodeName.toLowerCase();("input"==n||"textarea"==n)&&e.select()},t.focusCellElement=function(t,o){if(t.selectionProvider.lastClickedRow){var r=void 0!=o?o:n,i=t.selectionProvider.lastClickedRow.clone?t.selectionProvider.lastClickedRow.clone.elm:t.selectionProvider.lastClickedRow.elm;if(void 0!=r&&i){var l=angular.element(i[0].children).filter(function(){return 8!=this.nodeType}),a=Math.max(Math.min(t.renderedColumns.length-1,r),0);e.config.showSelectionCheckbox&&angular.element(l[a]).scope()&&0==angular.element(l[a]).scope().col.index&&(a=1),l[a]&&l[a].children[0].focus(),n=r}}};var o=function(e,n){e.css({"-webkit-touch-callout":n,"-webkit-user-select":n,"-khtml-user-select":n,"-moz-user-select":"none"==n?"-moz-none":n,"-ms-user-select":n,"user-select":n})};t.selectionHandlers=function(n,t){var r=!1;t.bind("keydown",function(i){if(16==i.keyCode)return o(t,"none",i),!0;if(!r){r=!0;var l=ng.moveSelectionHandler(n,t,i,e);return r=!1,l}return!0}),t.bind("keyup",function(e){return 16==e.keyCode&&o(t,"text",e),!0})}},ng.EventProvider=function(n,t,o){var r=this;r.colToMove=void 0,r.groupToMove=void 0,r.assignEvents=function(){n.config.jqueryUIDraggable&&!n.config.enablePinning?(n.$groupPanel.droppable({addClasses:!1,drop:function(e){r.onGroupDrop(e)}}),t.$evalAsync(r.setDraggables)):(n.$groupPanel.on("mousedown",r.onGroupMouseDown).on("dragover",r.dragOver).on("drop",r.onGroupDrop),n.$headerScroller.on("mousedown",r.onHeaderMouseDown).on("dragover",r.dragOver),n.config.enableColumnReordering&&!n.config.enablePinning&&n.$headerScroller.on("drop",r.onHeaderDrop),n.config.enableRowReordering&&n.$viewport.on("mousedown",r.onRowMouseDown).on("dragover",r.dragOver).on("drop",r.onRowDrop)),t.$watch("columns",r.setDraggables,!0)},r.dragStart=function(e){e.dataTransfer.setData("text","")},r.dragOver=function(e){e.preventDefault()},r.setDraggables=function(){if(n.config.jqueryUIDraggable)n.$root.find(".ngHeaderSortColumn").draggable({helper:"clone",appendTo:"body",stack:"div",addClasses:!1,start:function(e){r.onHeaderMouseDown(e)}}).droppable({drop:function(e){r.onHeaderDrop(e)}});else{var e=n.$root.find(".ngHeaderSortColumn");angular.forEach(e,function(e){e.setAttribute("draggable","true"),e.addEventListener&&e.addEventListener("dragstart",r.dragStart)}),-1!=navigator.userAgent.indexOf("MSIE")&&n.$root.find(".ngHeaderSortColumn").bind("selectstart",function(){return this.dragDrop(),!1})}},r.onGroupMouseDown=function(e){var t=$(e.target);if("ngRemoveGroup"!=t[0].className){var o=angular.element(t).scope();o&&(n.config.jqueryUIDraggable||(t.attr("draggable","true"),this.addEventListener&&this.addEventListener("dragstart",r.dragStart),-1!=navigator.userAgent.indexOf("MSIE")&&t.bind("selectstart",function(){return this.dragDrop(),!1})),r.groupToMove={header:t,groupName:o.group,index:o.$index})}else r.groupToMove=void 0},r.onGroupDrop=function(e){e.stopPropagation();var o,i;r.groupToMove?(o=$(e.target).closest(".ngGroupElement"),"ngGroupPanel"==o.context.className?(t.configGroups.splice(r.groupToMove.index,1),t.configGroups.push(r.groupToMove.groupName)):(i=angular.element(o).scope(),i&&r.groupToMove.index!=i.$index&&(t.configGroups.splice(r.groupToMove.index,1),t.configGroups.splice(i.$index,0,r.groupToMove.groupName))),r.groupToMove=void 0,n.fixGroupIndexes()):r.colToMove&&(-1==t.configGroups.indexOf(r.colToMove.col)&&(o=$(e.target).closest(".ngGroupElement"),"ngGroupPanel"==o.context.className||"ngGroupPanelDescription ng-binding"==o.context.className?t.groupBy(r.colToMove.col):(i=angular.element(o).scope(),i&&t.removeGroup(i.$index))),r.colToMove=void 0),t.$$phase||t.$apply()},r.onHeaderMouseDown=function(e){var n=$(e.target).closest(".ngHeaderSortColumn"),t=angular.element(n).scope();t&&(r.colToMove={header:n,col:t.col})},r.onHeaderDrop=function(e){if(r.colToMove){var i=$(e.target).closest(".ngHeaderSortColumn"),l=angular.element(i).scope();if(l){if(r.colToMove.col==l.col)return;t.columns.splice(r.colToMove.col.index,1),t.columns.splice(l.col.index,0,r.colToMove.col),n.fixColumnIndexes(),o.BuildStyles(t,n,!0),r.colToMove=void 0}}},r.onRowMouseDown=function(e){var n=$(e.target).closest(".ngRow"),t=angular.element(n).scope();t&&(n.attr("draggable","true"),o.eventStorage.rowToMove={targetRow:n,scope:t})},r.onRowDrop=function(e){var t=$(e.target).closest(".ngRow"),r=angular.element(t).scope();if(r){var i=o.eventStorage.rowToMove;if(i.scope.row==r.row)return;n.changeRowOrder(i.scope.row,r.row),n.searchProvider.evalFilter(),o.eventStorage.rowToMove=void 0,o.digest(r.$root)}},r.assignGridEventHandlers=function(){-1===n.config.tabIndex?(n.$viewport.attr("tabIndex",o.numberOfGrids),o.numberOfGrids++):n.$viewport.attr("tabIndex",n.config.tabIndex),$(e).resize(function(){o.RebuildGrid(t,n)})},r.assignGridEventHandlers(),r.assignEvents()},ng.Footer=function(e,n){e.maxRows=function(){var t=Math.max(e.pagingOptions.totalServerItems,n.data.length);return t},e.multiSelect=n.config.enableRowSelection&&n.config.multiSelect,e.selectedItemCount=n.selectedItemCount,e.maxPages=function(){return Math.ceil(e.maxRows()/e.pagingOptions.pageSize)},e.pageForward=function(){var n=e.pagingOptions.currentPage;e.pagingOptions.totalServerItems>0?e.pagingOptions.currentPage=Math.min(n+1,e.maxPages()):e.pagingOptions.currentPage++},e.pageBackward=function(){var n=e.pagingOptions.currentPage;e.pagingOptions.currentPage=Math.max(n-1,1)},e.pageToFirst=function(){e.pagingOptions.currentPage=1},e.pageToLast=function(){var n=e.maxPages();e.pagingOptions.currentPage=n},e.cantPageForward=function(){var t=e.pagingOptions.currentPage,o=e.maxPages();return e.pagingOptions.totalServerItems>0?!(o>t):1>n.data.length},e.cantPageToLast=function(){return e.pagingOptions.totalServerItems>0?e.cantPageForward():!0},e.cantPageBackward=function(){var n=e.pagingOptions.currentPage;return!(n>1)}},ng.Grid=function(o,r,i,l,s,c,g,d){var u={aggregateTemplate:void 0,afterSelectionChange:function(){},beforeSelectionChange:function(){return!0},checkboxCellTemplate:void 0,checkboxHeaderTemplate:void 0,columnDefs:void 0,data:[],dataUpdated:function(){},enableCellEdit:!1,enableCellSelection:!1,enableColumnResize:!1,enableColumnReordering:!1,enableColumnHeavyVirt:!1,enablePaging:!1,enablePinning:!1,enableRowReordering:!1,enableRowSelection:!0,enableSorting:!0,excludeProperties:[],filterOptions:{filterText:"",useExternalFilter:!1},footerRowHeight:55,groups:[],headerRowHeight:30,headerRowTemplate:void 0,jqueryUIDraggable:!1,jqueryUITheme:!1,keepLastSelected:!0,maintainColumnRatios:void 0,multiSelect:!0,pagingOptions:{pageSizes:[250,500,1e3],pageSize:250,totalServerItems:0,currentPage:1},pinSelectionCheckbox:!1,plugins:[],rowHeight:30,rowTemplate:void 0,selectedItems:[],selectWithCheckboxOnly:!1,showColumnMenu:!1,showFilter:!1,showFooter:!1,showGroupPanel:!1,showSelectionCheckbox:!1,sortInfo:{fields:[],columns:[],directions:[]},tabIndex:-1,useExternalSorting:!1,i18n:"en",virtualizationThreshold:50},p=this;p.maxCanvasHt=0,p.config=$.extend(u,e.ngGrid.config,r),p.config.showSelectionCheckbox=p.config.showSelectionCheckbox&&p.config.enableColumnHeavyVirt===!1,p.config.enablePinning=p.config.enablePinning&&p.config.enableColumnHeavyVirt===!1,p.config.selectWithCheckboxOnly=p.config.selectWithCheckboxOnly&&p.config.showSelectionCheckbox!==!1,p.config.pinSelectionCheckbox=p.config.enablePinning,"string"==typeof r.columnDefs&&(p.config.columnDefs=o.$eval(r.columnDefs)),p.rowCache=[],p.rowMap=[],p.gridId="ng"+g.newId(),p.$root=null,p.$groupPanel=null,p.$topPanel=null,p.$headerContainer=null,p.$headerScroller=null,p.$headers=null,p.$viewport=null,p.$canvas=null,p.rootDim=p.config.gridDim,p.data=[],p.lateBindColumns=!1,p.filteredRows=[];var h=function(e){var n=p.config[e],t=p.gridId+e+".html";if(n&&!f.test(n))c.put(t,$.ajax({type:"GET",url:n,async:!1}).responseText);else if(n)c.put(t,n);else{var o=e+".html";c.put(t,c.get(o))}};h("rowTemplate"),h("aggregateTemplate"),h("headerRowTemplate"),h("checkboxCellTemplate"),h("checkboxHeaderTemplate"),"object"==typeof p.config.data&&(p.data=p.config.data),p.calcMaxCanvasHeight=function(){return p.config.groups.length>0?p.rowFactory.parsedData.filter(function(e){return!e[a]}).length*p.config.rowHeight:p.filteredRows.length*p.config.rowHeight},p.elementDims={scrollW:0,scrollH:0,rowIndexCellW:25,rowSelectedCellW:25,rootMaxW:0,rootMaxH:0},p.setRenderedRows=function(e){o.renderedRows.length=e.length;for(var n=0;e.length>n;n++)!o.renderedRows[n]||e[n].isAggRow||o.renderedRows[n].isAggRow?(o.renderedRows[n]=e[n].copy(),o.renderedRows[n].collapsed=e[n].collapsed,e[n].isAggRow||o.renderedRows[n].setVars(e[n])):o.renderedRows[n].setVars(e[n]),o.renderedRows[n].rowIndex=e[n].rowIndex,o.renderedRows[n].offsetTop=e[n].offsetTop,e[n].renderedRowIndex=n;p.refreshDomSizes(),o.$emit("ngGridEventRows",e)},p.minRowsToRender=function(){var e=o.viewportDimHeight()||1;return Math.floor(e/p.config.rowHeight)},p.refreshDomSizes=function(){var e=new ng.Dimension;e.outerWidth=p.elementDims.rootMaxW,e.outerHeight=p.elementDims.rootMaxH,p.rootDim=e,p.maxCanvasHt=p.calcMaxCanvasHeight()},p.buildColumnDefsFromData=function(){p.config.columnDefs=[];var e=p.data[0];return e?(g.forIn(e,function(e,n){-1==p.config.excludeProperties.indexOf(n)&&p.config.columnDefs.push({field:n})}),void 0):(p.lateBoundColumns=!0,void 0)},p.buildColumns=function(){var e=p.config.columnDefs,n=[];if(e||(p.buildColumnDefsFromData(),e=p.config.columnDefs),p.config.showSelectionCheckbox&&n.push(new ng.Column({colDef:{field:"✔",width:p.elementDims.rowSelectedCellW,sortable:!1,resizable:!1,groupable:!1,headerCellTemplate:c.get(o.gridId+"checkboxHeaderTemplate.html"),cellTemplate:c.get(o.gridId+"checkboxCellTemplate.html"),pinned:p.config.pinSelectionCheckbox},index:0,headerRowHeight:p.config.headerRowHeight,sortCallback:p.sortData,resizeOnDataCallback:p.resizeOnData,enableResize:p.config.enableColumnResize,enableSort:p.config.enableSorting},o,p,l,c,g)),e.length>0){var t=p.config.showSelectionCheckbox?p.config.groups.length+1:p.config.groups.length;o.configGroups.length=0,angular.forEach(e,function(e,r){r+=t;var i=new ng.Column({colDef:e,index:r,headerRowHeight:p.config.headerRowHeight,sortCallback:p.sortData,resizeOnDataCallback:p.resizeOnData,enableResize:p.config.enableColumnResize,enableSort:p.config.enableSorting,enablePinning:p.config.enablePinning,enableCellEdit:p.config.enableCellEdit},o,p,l,c,g),a=p.config.groups.indexOf(e.field);-1!=a&&(i.isGroupedBy=!0,o.configGroups.splice(a,0,i),i.groupIndex=o.configGroups.length),n.push(i)}),o.columns=n}},p.configureColumnWidths=function(){var e=p.config.columnDefs,n=p.config.showSelectionCheckbox?o.configGroups.length+1:o.configGroups.length,t=e.length+n,r=[],i=[],a=0,s=0;if(s+=p.config.showSelectionCheckbox?25:0,angular.forEach(e,function(e,t){t+=n;var l=!1,c=void 0;if(g.isNullOrUndefined(e.width)?e.width="*":(l=isNaN(e.width)?g.endsWith(e.width,"%"):!1,c=l?e.width:parseInt(e.width,10)),isNaN(c)){if(c=e.width,"auto"==c){o.columns[t].width=e.minWidth,s+=o.columns[t].width;var u=o.columns[t];return d(function(){p.resizeOnData(u,!0)}),void 0}if(-1!=c.indexOf("*"))return e.visible!==!1&&(a+=c.length),e.index=t,r.push(e),void 0;if(l)return e.index=t,i.push(e),void 0;throw'unable to parse column width, use percentage ("10%","20%", etc...) or "*" to use remaining width of grid'}e.visible!==!1&&(s+=o.columns[t].width=parseInt(e.width,10))}),r.length>0){p.config.maintainColumnRatios===!1?angular.noop():p.config.maintainColumnRatios=!0;var c=p.rootDim.outerWidth-s,u=Math.floor(c/a);angular.forEach(r,function(e){var n=e.width.length;if(o.columns[e.index].width=u*n,e.index+1==t){var r=2;p.maxCanvasHt>o.viewportDimHeight()&&(r+=l.ScrollW),o.columns[e.index].width-=r}e.visible!==!1&&(s+=o.columns[e.index].width)})}i.length>0&&angular.forEach(i,function(e){var n=e.width;o.columns[e.index].width=Math.floor(p.rootDim.outerWidth*(parseInt(n.slice(0,-1),10)/100))})},p.init=function(){o.selectionProvider=new ng.selectionProvider(p,o),o.domAccessProvider=new ng.DomAccessProvider(p),p.rowFactory=new ng.RowFactory(p,o,l,c,g),p.searchProvider=new ng.SearchProvider(o,p,s),p.styleProvider=new ng.StyleProvider(o,p,l),o.$watch("configGroups",function(e){var n=[];angular.forEach(e,function(e){n.push(e.field||e)}),p.config.groups=n,p.rowFactory.filteredRowsChanged(),o.$emit("ngGridEventGroups",e)},!0),o.$watch("columns",function(e){l.BuildStyles(o,p,!0),o.$emit("ngGridEventColumns",e)},!0),o.$watch(function(){return r.i18n},function(e){g.seti18n(o,e)}),p.maxCanvasHt=p.calcMaxCanvasHeight(),p.config.sortInfo.fields&&p.config.sortInfo.fields.length>0&&(p.config.sortInfo.columns?p.config.sortInfo.columns.length=0:p.config.sortInfo.columns=[],angular.forEach(o.columns,function(e){return-1!=p.config.sortInfo.fields.indexOf(e.field)&&p.config.sortInfo.columns.push(e),!1}),p.sortData(p.config.sortInfo.columns,{}))},p.resizeOnData=function(e){var n=e.minWidth,t=g.getElementsByClassName("col"+e.index);angular.forEach(t,function(e,t){var o;if(0===t){var r=$(e).find(".ngHeaderText");o=g.visualLength(r)+10}else{var i=$(e).find(".ngCellText");o=g.visualLength(i)+10}o>n&&(n=o)}),e.width=e.longest=Math.min(e.maxWidth,n+7),l.BuildStyles(o,p,!0)},p.lastSortedColumns=[],p.changeRowOrder=function(e,n){var t=p.rowCache.indexOf(e),r=p.rowCache.indexOf(n);p.rowCache.splice(t,1),p.rowCache.splice(r,0,e),o.$emit("ngGridEventChangeOrder",p.rowCache)},p.sortData=function(e,n){if(n.shiftKey&&p.config.sortInfo){var t=p.config.sortInfo.columns.indexOf(e);-1===t?(1==p.config.sortInfo.columns.length&&(p.config.sortInfo.columns[0].sortPriority=1),p.config.sortInfo.columns.push(e),e.sortPriority=p.config.sortInfo.columns.length,p.config.sortInfo.fields.push(e.field),p.config.sortInfo.directions.push(e.sortDirection),p.lastSortedColumns.push(e)):p.config.sortInfo.directions[t]=e.sortDirection}else{var r=$.isArray(e);p.config.sortInfo.columns.length=0,p.config.sortInfo.fields.length=0,p.config.sortInfo.directions.length=0;var l=function(e){p.config.sortInfo.columns.push(e),p.config.sortInfo.fields.push(e.field),p.config.sortInfo.directions.push(e.sortDirection),p.lastSortedColumns.push(e)};r?(p.clearSortingData(),angular.forEach(e,function(e,n){e.sortPriority=n+1,l(e)})):(p.clearSortingData(e),e.sortPriority=void 0,l(e))}if(!p.config.useExternalSorting){var a=p.data.slice(0);angular.forEach(a,function(e,n){e.preSortSelected=p.rowCache[p.rowMap[n]].selected,e.preSortIndex=n}),i.Sort(p.config.sortInfo,a),angular.forEach(a,function(e,n){p.rowCache[n].entity=e,p.rowCache[n].selected=e.preSortSelected,p.rowMap[e.preSortIndex]=n,delete e.preSortSelected,delete e.preSortIndex})}p.searchProvider.evalFilter(),o.$emit("ngGridEventSorted",p.config.sortInfo)},p.clearSortingData=function(e){e?(angular.forEach(p.lastSortedColumns,function(n){e.index!=n.index&&(n.sortDirection="",n.sortPriority=null)}),p.lastSortedColumns[0]=e,p.lastSortedColumns.length=1):(angular.forEach(p.lastSortedColumns,function(e){e.sortDirection="",e.sortPriority=null}),p.lastSortedColumns=[])},p.fixColumnIndexes=function(){for(var e=0;o.columns.length>e;e++)o.columns[e].visible!==!1&&(o.columns[e].index=e)},p.fixGroupIndexes=function(){angular.forEach(o.configGroups,function(e,n){e.groupIndex=n+1})},o.elementsNeedMeasuring=!0,o.columns=[],o.renderedRows=[],o.renderedColumns=[],o.headerRow=null,o.rowHeight=p.config.rowHeight,o.jqueryUITheme=p.config.jqueryUITheme,o.showSelectionCheckbox=p.config.showSelectionCheckbox,o.enableCellSelection=p.config.enableCellSelection,o.footer=null,o.selectedItems=p.config.selectedItems,o.multiSelect=p.config.multiSelect,o.showFooter=p.config.showFooter,o.footerRowHeight=o.showFooter?p.config.footerRowHeight:0,o.showColumnMenu=p.config.showColumnMenu,o.showMenu=!1,o.configGroups=[],o.gridId=p.gridId,o.enablePaging=p.config.enablePaging,o.pagingOptions=p.config.pagingOptions,o.i18n={},g.seti18n(o,p.config.i18n),o.adjustScrollLeft=function(e){for(var n=0,t=0,r=o.columns.length,i=[],a=!p.config.enableColumnHeavyVirt,s=0,c=function(e){a?i.push(e):o.renderedColumns[s]?o.renderedColumns[s].setVars(e):o.renderedColumns[s]=e.copy(),s++},g=0;r>g;g++){var d=o.columns[g];if(d.visible!==!1){var u=d.width+n;if(d.pinned){c(d);var f=g>0?e+t:e;l.setColLeft(d,f,p),t+=d.width}else u>=e&&e+p.rootDim.outerWidth>=n&&c(d);n+=d.width}}a&&(o.renderedColumns=i)},p.prevScrollTop=0,p.prevScrollIndex=0,o.adjustScrollTop=function(e,r){if(p.prevScrollTop!==e||r){e>0&&p.$viewport[0].scrollHeight-e<=p.$viewport.outerHeight()&&o.$emit("ngGridEventScroll");var i,l=Math.floor(e/p.config.rowHeight);if(p.filteredRows.length>p.config.virtualizationThreshold){if(e>p.prevScrollTop&&p.prevScrollIndex+t>l)return;if(p.prevScrollTop>e&&l>p.prevScrollIndex-t)return;i=new ng.Range(Math.max(0,l-n),l+p.minRowsToRender()+n)}else{var a=o.configGroups.length>0?p.rowFactory.parsedData.length:p.data.length;i=new ng.Range(0,Math.max(a,p.minRowsToRender()+n))}p.prevScrollTop=e,p.rowFactory.UpdateViewableRange(i),p.prevScrollIndex=l}},o.toggleShowMenu=function(){o.showMenu=!o.showMenu},o.toggleSelectAll=function(e){o.selectionProvider.toggleSelectAll(e)},o.totalFilteredItemsLength=function(){return p.filteredRows.length},o.showGroupPanel=function(){return p.config.showGroupPanel},o.topPanelHeight=function(){return p.config.showGroupPanel===!0?p.config.headerRowHeight+32:p.config.headerRowHeight},o.viewportDimHeight=function(){return Math.max(0,p.rootDim.outerHeight-o.topPanelHeight()-o.footerRowHeight-2)},o.groupBy=function(e){if(e.sortDirection||e.sort({shiftKey:!1}),!(1>p.data.length)&&e.groupable&&e.field){var n=o.configGroups.indexOf(e);-1==n?(e.isGroupedBy=!0,o.configGroups.push(e),e.groupIndex=o.configGroups.length):o.removeGroup(n),p.$viewport.scrollTop(0),l.digest(o)}},o.removeGroup=function(e){var n=o.columns.filter(function(n){return n.groupIndex==e+1})[0];n.isGroupedBy=!1,n.groupIndex=0,o.columns[e].isAggCol&&(o.columns.splice(e,1),o.configGroups.splice(e,1),p.fixGroupIndexes()),0===o.configGroups.length&&(p.fixColumnIndexes(),l.digest(o)),o.adjustScrollLeft(0)},o.togglePin=function(e){for(var n=e.index,t=0,r=0;o.columns.length>r&&o.columns[r].pinned;r++)t++;e.pinned&&(t=Math.max(e.originalIndex,t-1)),e.pinned=!e.pinned,o.columns.splice(n,1),o.columns.splice(t,0,e),p.fixColumnIndexes(),l.BuildStyles(o,p,!0),p.$viewport.scrollLeft(p.$viewport.scrollLeft()-e.width)
+},o.totalRowWidth=function(){for(var e=0,n=o.columns,t=0;n.length>t;t++)n[t].visible!==!1&&(e+=n[t].width);return e},o.headerScrollerDim=function(){var e=o.viewportDimHeight(),n=p.maxCanvasHt,t=n>e,r=new ng.Dimension;return r.autoFitHeight=!0,r.outerWidth=o.totalRowWidth(),t?r.outerWidth+=p.elementDims.scrollW:p.elementDims.scrollH>=n-e&&(r.outerWidth+=p.elementDims.scrollW),r},p.init()},ng.Range=function(e,n){this.topRow=e,this.bottomRow=n},ng.Row=function(e,n,t,o,r){var i=this,l=n.enableRowSelection;i.jqueryUITheme=n.jqueryUITheme,i.rowClasses=n.rowClasses,i.entity=e,i.selectionProvider=t,i.selected=t.getSelection(e),i.cursor=l?"pointer":"default",i.setSelection=function(e){i.selectionProvider.setSelection(i,e),i.selectionProvider.lastClickedRow=i},i.continueSelection=function(e){i.selectionProvider.ChangeSelection(i,e)},i.ensureEntity=function(e){i.entity!=e&&(i.entity=e,i.selected=i.selectionProvider.getSelection(i.entity))},i.toggleSelected=function(e){if(!l&&!n.enableCellSelection)return!0;var t=e.target||e;return"checkbox"==t.type&&"ngSelectionCell ng-scope"!=t.parentElement.className?!0:n.selectWithCheckboxOnly&&"checkbox"!=t.type?(i.selectionProvider.lastClickedRow=i,!0):(i.beforeSelectionChange(i,e)&&i.continueSelection(e),!1)},i.rowIndex=o,i.offsetTop=i.rowIndex*n.rowHeight,i.rowDisplayIndex=0,i.alternatingRowClass=function(){var e=0===i.rowIndex%2,n={selected:i.selected,"ui-state-default":i.jqueryUITheme&&e,"ui-state-active":i.jqueryUITheme&&!e,even:e,odd:!e};return n},i.beforeSelectionChange=n.beforeSelectionChangeCallback,i.afterSelectionChange=n.afterSelectionChangeCallback,i.getProperty=function(e){return r.evalProperty(i.entity,e)},i.copy=function(){return i.clone=new ng.Row(e,n,t,o,r),i.clone.isClone=!0,i.clone.elm=i.elm,i.clone},i.setVars=function(e){e.clone=i,i.entity=e.entity,i.selected=e.selected}},ng.RowFactory=function(e,t,o,r,c){var g=this;g.aggCache={},g.parentCache=[],g.dataChanged=!0,g.parsedData=[],g.rowConfig={},g.selectionProvider=t.selectionProvider,g.rowHeight=30,g.numberOfAggregates=0,g.groupedData=void 0,g.rowHeight=e.config.rowHeight,g.rowConfig={enableRowSelection:e.config.enableRowSelection,rowClasses:e.config.rowClasses,selectedItems:t.selectedItems,selectWithCheckboxOnly:e.config.selectWithCheckboxOnly,beforeSelectionChangeCallback:e.config.beforeSelectionChange,afterSelectionChangeCallback:e.config.afterSelectionChange,jqueryUITheme:e.config.jqueryUITheme,enableCellSelection:e.config.enableCellSelection,rowHeight:e.config.rowHeight},g.renderedRange=new ng.Range(0,e.minRowsToRender()+n),g.buildEntityRow=function(e,n){return new ng.Row(e,g.rowConfig,g.selectionProvider,n,c)},g.buildAggregateRow=function(e,n){var t=g.aggCache[e.aggIndex];return t||(t=new ng.Aggregate(e,g,g.rowConfig.rowHeight),g.aggCache[e.aggIndex]=t),t.rowIndex=n,t.offsetTop=n*g.rowConfig.rowHeight,t},g.UpdateViewableRange=function(e){g.renderedRange=e,g.renderedChange()},g.filteredRowsChanged=function(){e.lateBoundColumns&&e.filteredRows.length>0&&(e.config.columnDefs=void 0,e.buildColumns(),e.lateBoundColumns=!1,t.$evalAsync(function(){t.adjustScrollLeft(0)})),g.dataChanged=!0,e.config.groups.length>0&&g.getGrouping(e.config.groups),g.UpdateViewableRange(g.renderedRange)},g.renderedChange=function(){if(!g.groupedData||1>e.config.groups.length)return g.renderedChangeNoGroups(),e.refreshDomSizes(),void 0;g.wasGrouped=!0,g.parentCache=[];var n=0,t=g.parsedData.filter(function(e){return e.isAggRow?e.parent&&e.parent.collapsed?!1:!0:(e[a]||(e.rowIndex=n++),!e[a])});g.totalRows=t.length;for(var o=[],r=g.renderedRange.topRow;g.renderedRange.bottomRow>r;r++)t[r]&&(t[r].offsetTop=r*e.config.rowHeight,o.push(t[r]));e.setRenderedRows(o)},g.renderedChangeNoGroups=function(){for(var n=[],t=g.renderedRange.topRow;g.renderedRange.bottomRow>t;t++)e.filteredRows[t]&&(e.filteredRows[t].rowIndex=t,e.filteredRows[t].offsetTop=t*e.config.rowHeight,n.push(e.filteredRows[t]));e.setRenderedRows(n)},g.fixRowCache=function(){var n=e.data.length,t=n-e.rowCache.length;if(0>t)e.rowCache.length=e.rowMap.length=n;else for(var o=e.rowCache.length;n>o;o++)e.rowCache[o]=e.rowFactory.buildEntityRow(e.data[o],o)},g.parseGroupData=function(e){if(e.values)for(var n=0;e.values.length>n;n++)g.parentCache[g.parentCache.length-1].children.push(e.values[n]),g.parsedData.push(e.values[n]);else for(var t in e)if(t!=i&&t!=l&&t!=s&&e.hasOwnProperty(t)){var o=g.buildAggregateRow({gField:e[i],gLabel:t,gDepth:e[l],isAggRow:!0,_ng_hidden_:!1,children:[],aggChildren:[],aggIndex:g.numberOfAggregates,aggLabelFilter:e[s].aggLabelFilter},0);g.numberOfAggregates++,o.parent=g.parentCache[o.depth-1],o.parent&&(o.parent.collapsed=!1,o.parent.aggChildren.push(o)),g.parsedData.push(o),g.parentCache[o.depth]=o,g.parseGroupData(e[t])}},g.getGrouping=function(n){g.aggCache=[],g.numberOfAggregates=0,g.groupedData={};for(var d=e.filteredRows,u=n.length,f=t.columns,p=0;d.length>p;p++){var h=d[p].entity;if(!h)return;d[p][a]=!0;for(var m=g.groupedData,v=0;n.length>v;v++){var w=n[v],C=f.filter(function(e){return e.field==w})[0],b=c.evalProperty(h,w);b=b?""+b:"null",m[b]||(m[b]={}),m[i]||(m[i]=w),m[l]||(m[l]=v),m[s]||(m[s]=C),m=m[b]}m.values||(m.values=[]),m.values.push(d[p])}for(var x=0;n.length>x;x++)!f[x].isAggCol&&u>=x&&f.splice(0,0,new ng.Column({colDef:{field:"",width:25,sortable:!1,resizable:!1,headerCellTemplate:'',pinned:e.config.pinSelectionCheckbox},isAggCol:!0,headerRowHeight:e.config.headerRowHeight},t,e,o,r,c));o.BuildStyles(t,e,!0),e.fixColumnIndexes(),t.adjustScrollLeft(0),g.parsedData.length=0,g.parseGroupData(g.groupedData),g.fixRowCache()},e.config.groups.length>0&&e.filteredRows.length>0&&g.getGrouping(e.config.groups)},ng.SearchProvider=function(e,n,t){var o=this,r=[];o.extFilter=n.config.filterOptions.useExternalFilter,e.showFilter=n.config.showFilter,e.filterText="",o.fieldMap={},o.evalFilter=function(){var e=function(e){for(var n=0,l=r.length;l>n;n++){var a,s=r[n];if(!s.column){for(var c in e)if(e.hasOwnProperty(c)){var g=o.fieldMap[c];if(!g)continue;var d=null,u=null;g&&g.cellFilter&&(u=g.cellFilter.split(":"),d=t(u[0]));var f=e[c];if(null!=f){if("function"==typeof d){var p=""+d("object"==typeof f?i(f,g.field):f,u[1]);a=s.regex.test(p)}else a=s.regex.test("object"==typeof f?""+i(f,g.field):""+f);if(f&&a)return!0}}return!1}var h=o.fieldMap[s.columnDisplay];if(!h)return!1;var m=h.cellFilter.split(":"),v=h.cellFilter?t(m[0]):null,w=e[s.column]||e[h.field.split(".")[0]];if(null==w)return!1;if("function"==typeof v){var C=""+v("object"==typeof w?i(w,h.field):w,m[1]);a=s.regex.test(C)}else a=s.regex.test("object"==typeof w?""+i(w,h.field):""+w);if(!w||!a)return!1}return!0};n.filteredRows=0===r.length?n.rowCache:n.rowCache.filter(function(n){return e(n.entity)});for(var l=0;n.filteredRows.length>l;l++)n.filteredRows[l].rowIndex=l;n.rowFactory.filteredRowsChanged()};var i=function(e,n){if("object"!=typeof e||"string"!=typeof n)return e;var t=n.split("."),o=e;if(t.length>1){for(var r=1,i=t.length;i>r;r++)if(o=o[t[r]],!o)return e;return o}return e},l=function(e,n){try{return RegExp(e,n)}catch(t){return RegExp(e.replace(/(\^|\$|\(|\)|\<|\>|\[|\]|\{|\}|\\|\||\.|\*|\+|\?)/g,"\\$1"))}},a=function(e){r=[];var n;if(n=$.trim(e))for(var t=n.split(";"),o=0;t.length>o;o++){var i=t[o].split(":");if(i.length>1){var a=$.trim(i[0]),s=$.trim(i[1]);a&&s&&r.push({column:a,columnDisplay:a.replace(/\s+/g,"").toLowerCase(),regex:l(s,"i")})}else{var c=$.trim(i[0]);c&&r.push({column:"",regex:l(c,"i")})}}};e.$watch(n.config.filterOptions.filterText,function(n){e.filterText=n}),e.$watch("filterText",function(n){o.extFilter||(e.$emit("ngGridEventFilter",n),a(n),o.evalFilter())}),o.extFilter||e.$watch("columns",function(e){for(var n=0;e.length>n;n++){var t=e[n];t.field&&(o.fieldMap[t.field.split(".")[0]]=t),t.displayName&&(o.fieldMap[t.displayName.toLowerCase().replace(/\s+/g,"")]=t)}})},ng.selectionProvider=function(e,n){var t=this;t.multi=e.config.multiSelect,t.selectedItems=e.config.selectedItems,t.selectedIndex=e.config.selectedIndex,t.lastClickedRow=void 0,t.ignoreSelectedItemChanges=!1,t.ChangeSelection=function(o,r){var i=o.isClone?e.filteredRows[o.rowIndex]:o;if(r&&r.shiftKey&&!r.keyCode&&t.multi&&e.config.enableRowSelection){if(t.lastClickedRow){var l;l=n.configGroups.length>0?e.rowFactory.parsedData.filter(function(e){return!e.isAggRow}):e.filteredRows;var a=i.rowIndex,s=t.lastClickedRow.rowIndex;if(t.lastClickedRow=i,a==s)return!1;s>a?(a^=s,s=a^s,a^=s,a--):s++;for(var c=[];a>=s;s++)c.push(l[s]);if(c[c.length-1].beforeSelectionChange(c,r)){for(var g=0;c.length>g;g++){var d=c[g],u=d.selected;d.selected=!u,d.clone&&(d.clone.selected=d.selected);var f=t.selectedItems.indexOf(d.entity);-1===f?t.selectedItems.push(d.entity):t.selectedItems.splice(f,1)}c[c.length-1].afterSelectionChange(c,r)}return!0}}else t.multi?r.keyCode||t.setSelection(i,!i.selected):t.lastClickedRow==i?t.setSelection(t.lastClickedRow,e.config.keepLastSelected?!0:!i.selected):(t.lastClickedRow&&t.setSelection(t.lastClickedRow,!1),t.setSelection(i,!i.selected));return t.lastClickedRow=i,!0},t.getSelection=function(e){return-1!==t.selectedItems.indexOf(e)},t.setSelection=function(n,o){var r=n.isClone?e.filteredRows[n.rowIndex]:n;if(e.config.enableRowSelection){if(r.selected=o,r.clone&&(r.clone.selected=o),o)-1===t.selectedItems.indexOf(r.entity)&&(!t.multi&&t.selectedItems.length>0&&(t.toggleSelectAll(!1,!0),r.selected=o,r.clone&&(r.clone.selected=o)),t.selectedItems.push(r.entity));else{var i=t.selectedItems.indexOf(r.entity);-1!=i&&t.selectedItems.splice(i,1)}r.afterSelectionChange(r)}},t.toggleSelectAll=function(n,o){if(o||e.config.beforeSelectionChange(e.filteredRows)){var r=t.selectedItems.length;r>0&&(t.selectedItems.length=0);for(var i=0;e.filteredRows.length>i;i++)e.filteredRows[i].selected=n,e.filteredRows[i].clone&&(e.filteredRows[i].clone.selected=n),n&&t.selectedItems.push(e.filteredRows[i].entity);o||e.config.afterSelectionChange(e.filteredRows)}}},ng.StyleProvider=function(e,n,t){e.headerCellStyle=function(e){return{height:e.headerRowHeight+"px"}},e.rowStyle=function(n){return{top:n.offsetTop+"px",height:e.rowHeight+"px"}},e.canvasStyle=function(){return{height:""+n.maxCanvasHt+"px"}},e.headerScrollerStyle=function(){return{height:n.config.headerRowHeight+"px"}},e.topPanelStyle=function(){return{width:n.rootDim.outerWidth+"px",height:e.topPanelHeight()+"px"}},e.headerStyle=function(){return{width:n.rootDim.outerWidth-t.ScrollW+"px",height:n.config.headerRowHeight+"px"}},e.groupPanelStyle=function(){return{width:n.rootDim.outerWidth-t.ScrollW+"px",height:"32px"}},e.viewportStyle=function(){return{width:n.rootDim.outerWidth+"px",height:e.viewportDimHeight()+"px"}},e.footerStyle=function(){return{width:n.rootDim.outerWidth+"px",height:e.footerRowHeight+"px"}}},h.directive("ngCellHasFocus",["$domUtilityService",function(e){var n=function(n,t){n.isFocused=!0,e.digest(n);var o=angular.element(t[0].children).filter(function(){return 8!=this.nodeType}),r=angular.element(o[0].children[0]);r.length>0&&(angular.element(r).focus(),n.domAccessProvider.selectInputElement(r[0]),angular.element(r).bind("blur",function(){return n.isFocused=!1,e.digest(n),!0}))};return function(e,t){var o=!1;e.editCell=function(){setTimeout(function(){n(e,t)},0)},t.bind("mousedown",function(){return t.focus(),!0}),t.bind("focus",function(){return o=!0,!0}),t.bind("blur",function(){return o=!1,!0}),t.bind("keydown",function(r){return o&&37!=r.keyCode&&38!=r.keyCode&&39!=r.keyCode&&40!=r.keyCode&&9!=r.keyCode&&!r.shiftKey&&13!=r.keyCode&&n(e,t),27==r.keyCode&&t.focus(),!0})}}]),h.directive("ngCellText",function(){return function(e,n){n.bind("mouseover",function(e){e.preventDefault(),n.css({cursor:"text"})}),n.bind("mouseleave",function(e){e.preventDefault(),n.css({cursor:"default"})})}}),h.directive("ngCell",["$compile","$domUtilityService",function(e,n){var t={scope:!1,compile:function(){return{pre:function(n,t){var o,r=n.col.cellTemplate.replace(g,"$eval('row.entity.' + col.field)");n.col.enableCellEdit?(o=n.col.cellEditTemplate,o=o.replace(d,r),o=o.replace(u,n.col.editableCellTemplate.replace(g,"col.field"))):o=r;var i=e(o)(n);n.enableCellSelection&&-1==i[0].className.indexOf("ngSelectionCell")&&(i[0].setAttribute("tabindex",0),i.addClass("ngCellElement")),t.append(i)},post:function(e,t){e.enableCellSelection&&e.domAccessProvider.selectionHandlers(e,t),e.$on("ngGridEventDigestCell",function(){n.digest(e)})}}}};return t}]),h.directive("ngGrid",["$compile","$filter","$templateCache","$sortService","$domUtilityService","$utilityService","$timeout",function(e,n,t,o,r,i,l){var a={scope:!0,compile:function(){return{pre:function(a,s,c){var g=$(s),d=a.$eval(c.ngGrid);d.gridDim=new ng.Dimension({outerHeight:$(g).height(),outerWidth:$(g).width()});var u=new ng.Grid(a,d,o,r,n,t,i,l);if("string"==typeof d.columnDefs?a.$parent.$watch(d.columnDefs,function(e){return e?(a.columns=[],u.config.columnDefs=e,u.buildColumns(),u.configureColumnWidths(),u.eventProvider.assignEvents(),r.RebuildGrid(a,u),void 0):(u.refreshDomSizes(),u.buildColumns(),void 0)}):u.buildColumns(),"string"==typeof d.data){var f=function(e){u.data=$.extend([],e),u.rowFactory.fixRowCache(),angular.forEach(u.data,function(e,n){var t=u.rowMap[n]||n;u.rowCache[t]&&u.rowCache[t].ensureEntity(e),u.rowMap[t]=n}),u.searchProvider.evalFilter(),u.configureColumnWidths(),u.refreshDomSizes(),u.config.sortInfo.fields.length>0&&o.sortData(u.config.sortInfo,u.data.slice(0)),a.$emit("ngGridEventData",u.gridId)};a.$parent.$watch(d.data,f),a.$parent.$watch(d.data+".length",function(){f(a.$eval(d.data))})}return u.footerController=new ng.Footer(a,u),s.addClass("ngGrid").addClass(""+u.gridId),d.jqueryUITheme&&s.addClass("ui-widget"),s.append(e(t.get("gridTemplate.html"))(a)),r.AssignGridContainers(a,s,u),u.eventProvider=new ng.EventProvider(u,a,r),angular.forEach(d.plugins,function(e){"function"==typeof e?e.call(this,[]).init(a.$new(),u,{SortService:o,DomUtilityService:r}):e.init(a.$new(),u,{SortService:o,DomUtilityService:r})}),d.selectRow=function(e,n){u.rowCache[e]&&u.rowCache[e].setSelection(n?!0:!1)},d.selectItem=function(e,n){d.selectRow(u.rowMap[e],n)},d.selectAll=function(e){a.toggleSelectAll(e)},d.groupBy=function(e){if(e)a.groupBy(a.columns.filter(function(n){return n.field==e})[0]);else{var n=$.extend(!0,[],a.configGroups);angular.forEach(n,a.groupBy)}},d.sortBy=function(e){var n=a.columns.filter(function(n){return n.field==e})[0];n&&n.sort()},d.gridId=u.gridId,d.ngGrid=u,d.$gridScope=a,a.$on("ngGridEventDigestGrid",function(){r.digest(a.$parent)}),a.$on("ngGridEventDigestGridParent",function(){r.digest(a.$parent)}),a.$evalAsync(function(){a.adjustScrollLeft(0)}),null}}}};return a}]),h.directive("ngHeaderCell",["$compile",function(e){var n={scope:!1,compile:function(){return{pre:function(n,t){t.append(e(n.col.headerCellTemplate)(n))}}}};return n}]),h.directive("ngHeaderRow",["$compile","$templateCache",function(e,n){var t={scope:!1,compile:function(){return{pre:function(t,o){0===o.children().length&&o.append(e(n.get(t.gridId+"headerRowTemplate.html"))(t))}}}};return t}]),h.directive("ngIf",[function(){return{transclude:"element",priority:1e3,terminal:!0,restrict:"A",compile:function(e,n,t){return function(e,n,o){var r,i;e.$watch(o.ngIf,function(o){r&&(r.remove(),r=void 0),i&&(i.$destroy(),i=void 0),o&&(i=e.$new(),t(i,function(e){r=e,n.after(e)}))})}}}}]),h.directive("ngInput",["$parse",function(e){return function(n,t,o){var r=e(n.$eval(o.ngInput)),i=r.assign,l=r(n.row.entity);t.val(l),t.bind("keyup",function(){var e=t.val();n.$root.$$phase||n.$apply(function(){i(n.row.entity,e)})}),t.bind("keydown",function(e){switch(e.keyCode){case 37:case 38:case 39:case 40:e.stopPropagation();break;case 27:n.$root.$$phase||n.$apply(function(){i(n.row.entity,l),t.val(l),t.blur()});default:}return!0})}}]),h.directive("ngRow",["$compile","$domUtilityService","$templateCache",function(e,n,t){var o={scope:!1,compile:function(){return{pre:function(o,r){if(o.row.elm=r,o.row.clone&&(o.row.clone.elm=r),o.row.isAggRow){var i=t.get(o.gridId+"aggregateTemplate.html");i=o.row.aggLabelFilter?i.replace(c,"| "+o.row.aggLabelFilter):i.replace(c,""),r.append(e(i)(o))}else r.append(e(t.get(o.gridId+"rowTemplate.html"))(o));o.$on("ngGridEventDigestRow",function(){n.digest(o)})}}}};return o}]),h.directive("ngViewport",[function(){return function(e,n){var t,o,r=0;n.bind("scroll",function(n){var i=n.target.scrollLeft,l=n.target.scrollTop;return e.$headerContainer&&e.$headerContainer.scrollLeft(i),e.adjustScrollLeft(i),e.adjustScrollTop(l),e.$root.$$phase||e.$digest(),o=i,r=r,t=!1,!0}),n.bind("mousewheel DOMMouseScroll",function(){return t=!0,n.focus(),!0}),e.enableCellSelection||e.domAccessProvider.selectionHandlers(e,n)}}]),e.ngGrid.i18n.en={ngAggregateLabel:"items",ngGroupPanelDescription:"Drag a column header here and drop it to group by that column.",ngSearchPlaceHolder:"Search...",ngMenuText:"Choose Columns:",ngShowingItemsLabel:"Showing Items:",ngTotalItemsLabel:"Total Items:",ngSelectedItemsLabel:"Selected Items:",ngPageSizeLabel:"Page Size:",ngPagerFirstTitle:"First Page",ngPagerNextTitle:"Next Page",ngPagerPrevTitle:"Previous Page",ngPagerLastTitle:"Last Page"},e.ngGrid.i18n.fr={ngAggregateLabel:"articles",ngGroupPanelDescription:"Faites glisser un en-tête de colonne ici et déposez-le vers un groupe par cette colonne.",ngSearchPlaceHolder:"Recherche...",ngMenuText:"Choisir des colonnes:",ngShowingItemsLabel:"Articles Affichage des:",ngTotalItemsLabel:"Nombre total d'articles:",ngSelectedItemsLabel:"Éléments Articles:",ngPageSizeLabel:"Taille de page:",ngPagerFirstTitle:"Première page",ngPagerNextTitle:"Page Suivante",ngPagerPrevTitle:"Page précédente",ngPagerLastTitle:"Dernière page"},e.ngGrid.i18n.ge={ngAggregateLabel:"artikel",ngGroupPanelDescription:"Ziehen Sie eine Spaltenüberschrift hier und legen Sie es der Gruppe nach dieser Spalte.",ngSearchPlaceHolder:"Suche...",ngMenuText:"Spalten auswählen:",ngShowingItemsLabel:"Zeige Artikel:",ngTotalItemsLabel:"Meiste Artikel:",ngSelectedItemsLabel:"Ausgewählte Artikel:",ngPageSizeLabel:"Größe Seite:",ngPagerFirstTitle:"Erste Page",ngPagerNextTitle:"Nächste Page",ngPagerPrevTitle:"Vorherige Page",ngPagerLastTitle:"Letzte Page"},e.ngGrid.i18n.sp={ngAggregateLabel:"Artículos",ngGroupPanelDescription:"Arrastre un encabezado de columna aquí y soltarlo para agrupar por esa columna.",ngSearchPlaceHolder:"Buscar...",ngMenuText:"Elegir columnas:",ngShowingItemsLabel:"Artículos Mostrando:",ngTotalItemsLabel:"Artículos Totales:",ngSelectedItemsLabel:"Artículos Seleccionados:",ngPageSizeLabel:"Tamaño de Página:",ngPagerFirstTitle:"Primera Página",ngPagerNextTitle:"Página Siguiente",ngPagerPrevTitle:"Página Anterior",ngPagerLastTitle:"Última Página"},e.ngGrid.i18n["zh-cn"]={ngAggregateLabel:"条目",ngGroupPanelDescription:"拖曳表头到此处以进行分组",ngSearchPlaceHolder:"搜索...",ngMenuText:"数据分组与选择列:",ngShowingItemsLabel:"当前显示条目:",ngTotalItemsLabel:"条目总数:",ngSelectedItemsLabel:"选中条目:",ngPageSizeLabel:"每页显示数:",ngPagerFirstTitle:"回到首页",ngPagerNextTitle:"下一页",ngPagerPrevTitle:"上一页",ngPagerLastTitle:"前往尾页"},angular.module("ngGrid").run(["$templateCache",function(e){e.put("aggregateTemplate.html",' {{row.label CUSTOM_FILTERS}} ({{row.totalChildren()}} {{AggItemsLabel}}) '),e.put("cellEditTemplate.html",' DISPLAY_CELL_TEMPLATE
EDITABLE_CELL_TEMPLATE
'),e.put("cellTemplate.html",'{{COL_FIELD CUSTOM_FILTERS}}
'),e.put("checkboxCellTemplate.html",''),e.put("checkboxHeaderTemplate.html",''),e.put("editableCellTemplate.html",''),e.put("gridTemplate.html",' {{i18n.ngGroupPanelDescription}}
'),e.put("headerCellTemplate.html",''),e.put("headerRowTemplate.html",''),e.put("rowTemplate.html",'')}])})(window);
\ No newline at end of file
diff --git a/src/main/java/com/commafeed/frontend/references/nggrid/ng-grid.css b/src/main/java/com/commafeed/frontend/references/nggrid/ng-grid.css
new file mode 100644
index 00000000..f5d06b21
--- /dev/null
+++ b/src/main/java/com/commafeed/frontend/references/nggrid/ng-grid.css
@@ -0,0 +1,472 @@
+
+/******** Grid Global ********/
+.nglabel {
+ display: block;
+ float: left;
+ font-weight: bold;
+ padding-right: 5px;
+}
+/******** Grid ********/
+
+.ngGrid{
+ background-color: rgb(253, 253, 253);
+}
+
+/******** Header ********/
+
+.ngGroupPanel{
+ background-color: rgb(234, 234, 234);
+ overflow: hidden;
+ border-bottom: 1px solid rgb(212,212,212);
+}
+
+.ngGroupPanelDescription{
+ margin-top: 5px;
+ margin-left: 5px;
+}
+
+.ngGroupList {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+
+.ngGroupItem {
+ float: left;
+}
+
+.ngGroupElement {
+ float: left;
+ height: 100%;
+ width: 100%;
+}
+
+.ngGroupName {
+ background-color: rgb(247,247,247);
+ border: 1px solid rgb(212,212,212);
+ padding: 3px 10px;
+ float: left;
+ margin-left: 0;
+ margin-top: 2px;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ font-weight: bold;
+}
+
+.ngGroupItem:first-child{
+ margin-left: 2px;
+}
+
+.ngRemoveGroup {
+ width: 5px;
+ -moz-opacity: 0.4;
+ opacity: 0.4;
+ margin-top: -1px;
+ margin-left: 5px;
+}
+.ngRemoveGroup:hover {
+ color: black;
+ text-decoration: none;
+ cursor: pointer;
+ -moz-opacity: 0.7;
+ opacity: 0.7;
+}
+.ngGroupArrow {
+ width: 0;
+ height: 0;
+ border-top: 6px solid transparent;
+ border-bottom: 6px solid transparent;
+ border-left: 6px solid black;
+ margin-top: 10px;
+ margin-left: 5px;
+ margin-right: 5px;
+ float: right;
+}
+
+.ngTopPanel {
+ position: relative;
+ z-index:5;
+ background-color: rgb(234, 234, 234);
+ border-bottom: 1px solid rgb(212,212,212);
+}
+.ngHeaderContainer {
+ position: relative;
+ overflow: hidden;
+ font-weight: bold;
+ background-color: inherit;
+}
+
+.ngHeaderScroller {
+ position:absolute;
+ background-color: inherit;
+}
+.ngHeaderSortColumn{
+ position:absolute;
+ overflow: hidden;
+}
+
+.ngHeaderCell{
+ border-right: 1px solid rgb(212,212,212);
+ border-left: 1px solid rgb(212,212,212);
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ background-color: inherit;
+}
+
+.ngHeaderCell:first-child{
+ border-left: 0;
+}
+
+.ngSortButtonUp {
+ position: absolute;
+ top: 3px;
+ left: 0;
+ right: 0;
+ margin-left: auto;
+ margin-right: auto;
+ border-color: gray transparent;
+ border-style: solid;
+ border-width: 0 5px 5px 5px;
+ height: 0;
+ width: 0;
+}
+.ngSortButtonDown {
+ position: absolute;
+ top: 3px;
+ left: 0;
+ right: 0;
+ margin-left: auto;
+ margin-right: auto;
+ border-color: gray transparent;
+ border-style: solid;
+ border-width: 5px 5px 0 5px;
+ height: 0;
+ width: 0;
+}
+.ngSortPriority {
+ position: absolute;
+ top: -5px;
+ left: 1px;
+ font-size: 6pt;
+ font-weight: bold;
+}
+.ngHeaderGrip {
+ cursor: col-resize;
+ width: 10px;
+ right: -5px;
+ top: 0;
+ height: 100%;
+ position: absolute;
+ background-color: transparent;
+}
+.ngHeaderText {
+ padding: 5px;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ white-space: nowrap;
+ -ms-text-overflow: ellipsis;
+ -o-text-overflow: ellipsis;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+/******** Viewport ********/
+.ngViewport{
+ overflow: auto;
+ min-height: 20px;
+}
+
+.ngCanvas{
+ position: relative;
+}
+
+/******** Rows ********/
+.ngRow {
+ position: absolute;
+ border-bottom: 1px solid rgb(229, 229, 229);
+}
+.ngRow.even {
+ background-color: rgb(243, 243, 243);
+}
+.ngRow.odd {
+ background-color: rgb(253, 253, 253);
+}
+.ngRow.selected {
+ background-color: rgb(201, 221, 225);
+}
+.ngRow.canSelect {
+ cursor: pointer;
+}
+
+/******** Cells ********/
+
+.ngCell {
+ overflow: hidden;
+ position: absolute;
+ border-right: 1px solid rgb(212,212,212);
+ border-left: 1px solid rgb(212,212,212);
+ top: 0;
+ bottom: 0;
+ background-color: inherit;
+}
+
+.ngCell:first-child{
+ border-left: 0;
+}
+
+.ngCellElement:focus {
+ outline: 0;
+ background-color: rgb(179, 196, 199);
+}
+
+.ngCellText {
+ padding: 5px;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ white-space: nowrap;
+ -ms-text-overflow: ellipsis;
+ -o-text-overflow: ellipsis;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+.ngSelectionHeader {
+ position: absolute;
+ top: 11px;
+ left: 6px;
+}
+.ngGrid input[type="checkbox"] {
+ margin: 0;
+ padding: 0;
+}
+.ngGrid input {
+ vertical-align:top;
+}
+.ngSelectionCell{
+ margin-top: 9px;
+ margin-left: 6px;
+}
+.ngSelectionCheckbox{
+ margin-top: 9px;
+ margin-left: 6px;
+}
+.ngNoSort {
+ cursor:default;
+}
+
+/******** Footer ********/
+.ngFooterPanel{
+ background-color: rgb(234, 234, 234);
+ padding: 0;
+ border-top: 1px solid rgb(212,212,212);
+ position: relative;
+}
+.ngTotalSelectContainer {
+ float: left;
+ margin: 5px;
+ margin-top: 7px;
+}
+.ngFooterSelectedItems {
+ padding: 2px;
+}
+.ngFooterTotalItems {
+ padding: 2px;
+}
+.ngFooterTotalItems.ngnoMultiSelect {
+ padding: 0 !important;
+}
+
+/* Aggregates */
+.ngAggHeader {
+ position: absolute;
+ border: none;
+}
+.ngAggregate {
+ position: absolute;
+ background-color: rgb(201, 221, 225);
+ border-bottom: 1px solid beige;
+ overflow: hidden;
+ top: 0;
+ bottom: 0;
+ right: -1px;
+ left: 0;
+}
+.ngAggregateText {
+ position: absolute;
+ left: 27px;
+ top: 5px;
+ line-height: 20px;
+ white-space:nowrap;
+}
+.ngAggArrowExpanded {
+ position: absolute;
+ left: 8px;
+ bottom: 10px;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 0 0 9px 9px;
+ border-color: transparent transparent #000000 transparent;
+}
+.ngAggArrowCollapsed {
+ position: absolute;
+ left: 8px;
+ bottom: 10px;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 5px 0 5px 8.7px;
+ border-color: transparent transparent transparent #000000;
+}
+
+.ngHeaderButton {
+ position: absolute;
+ right: 2px;
+ top: 8px;
+ -moz-border-radius: 50%;
+ -webkit-border-radius: 50%;
+ border-radius: 50%;
+ width: 14px;
+ height: 14px;
+ z-index: 5;
+ background-color: rgb(179, 191, 188);
+ cursor: pointer;
+ /* width and height can be anything, as long as they're equal */
+}
+.ngHeaderButtonArrow {
+ position: absolute;
+ top: 4px;
+ left: 3px;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 6.5px 4.5px 0 4.5px;
+ border-color: #000 transparent transparent transparent;
+ /* width and height can be anything, as long as they're equal */
+}
+.ngColMenu {
+ right: 2px;
+ padding: 5px;
+ top: 25px;
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ background-color: #BDD0CB;
+ position: absolute;
+ border: 2px solid rgb(212,212,212);
+ z-index: 5;
+}
+.ngMenuText {
+ position: relative;
+ top: 2px;
+ left: 2px;
+}
+.ngColList {
+ list-style-type: none;
+}
+
+.ngColListItem {
+ position: relative;
+ right: 17px;
+ top: 2px;
+ white-space:nowrap;
+}
+.ngColListCheckbox {
+ position: relative;
+ right: 3px;
+ top: 4px;
+}
+
+/********Paging Styles **********/
+
+.ngPagerButton{
+ height: 25px;
+ min-width: 26px;
+}
+
+.ngPagerFirstTriangle{
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 5px 8.7px 5px 0;
+ border-color: transparent #000000 transparent transparent;
+ margin-left: 2px;
+}
+
+.ngPagerFirstBar{
+ width: 10px;
+ border-left: 2px solid black;
+ margin-top: -6px;
+ height: 12px;
+ margin-left: -3px;
+}
+
+.ngPagerLastTriangle{
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 5px 0 5px 8.7px;
+ border-color: transparent transparent transparent #000000;
+ margin-left: -1px;
+}
+
+.ngPagerLastBar{
+ width: 10px;
+ border-left: 2px solid black;
+ margin-top: -6px;
+ height: 12px;
+ margin-left: 1px;
+}
+
+.ngPagerPrevTriangle{
+ margin-left: 0;
+}
+
+.ngPagerNextTriangle{
+ margin-left: 1px;
+}
+.ngGroupIcon {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAANCAYAAACZ3F9/AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAEFJREFUKFNjoAhISkr+h2J5JDZODNXGwGBsbPwfhIGAA8bGh6HaGBiAGhxAGJmND4M1gQCSM0adCsVQbcPcqQwMALWDGyDvWPefAAAAAElFTkSuQmCC);
+ background-repeat:no-repeat;
+ height: 15px;
+ width: 15px;
+ position: absolute;
+ right: -2px;
+ top: 2px;
+}
+
+.ngGroupedByIcon {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAANCAYAAACZ3F9/AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAElJREFUKFNjoAhISkr+R8LyaHwMDNXGwGBsbPwfhoGAA5mPDUO1oWpE52PDYE0gALTFAYbR+dgwWBMIoPlh1I9ADNU2NPzIwAAAFQYI9E4OLvEAAAAASUVORK5CYII=);
+ background-repeat:no-repeat;
+ height: 15px;
+ width: 15px;
+ position: absolute;
+ right: -2px;
+ top: 2px;
+}
+.ngPinnedIcon {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAAABp0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjUuMTAw9HKhAAAAmElEQVQoU33PQapBURjA8UtkwJuaWYGSgfQWYBMvczPmTCzAAGVuaA228BZhRCkDGSmE31FucuRfvzq3vr5zT/JSjSU7DsypEPXDkDVn2hSIytJhw4kWGaLCxgHh2gt/RBuLzNhz5caWPjnSqqw4EraFfwznf8qklWjwy4IRTerkiQoPGtPl40OehcEJvcfXl8LglLfBJLkDcMgbgHlHhK8AAAAASUVORK5CYII=);
+ background-repeat: no-repeat;
+ position: absolute;
+ right: 5px;
+ top: 5px;
+ height: 10px;
+ width: 10px;
+}
+.ngUnPinnedIcon {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAAABp0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjUuMTAw9HKhAAAAlElEQVQoU33PPQrCQBRF4fFnI2KfZVi5ARvdgo1l6mwmkCJVOgluwd5OwUoDtnoOxAei8cLXTN7cvEl/skCNDCMPfsUPO5zQwOHIDEvYtMURHe6wOVLgigvOePRyeDkyR4ln7wZ//7XfFBu8B23+aDJjrHGAwza7hjtHJvDmHg7b7Bru7AMjK7Rw2ObBVHDY5oGk9AKQNB2zy8MBTgAAAABJRU5ErkJggg==);
+ background-repeat: no-repeat;
+ position: absolute;
+ height: 10px;
+ width: 10px;
+ right: 5px;
+ top: 5px;
+}
+.ngGroupingNumber {
+ position: absolute;
+ right: -10px;
+ top: -2px;
+}
diff --git a/src/main/java/com/commafeed/frontend/rest/RESTApplication.java b/src/main/java/com/commafeed/frontend/rest/RESTApplication.java
index 20c25752..6679cf4b 100644
--- a/src/main/java/com/commafeed/frontend/rest/RESTApplication.java
+++ b/src/main/java/com/commafeed/frontend/rest/RESTApplication.java
@@ -5,7 +5,7 @@ import java.util.Set;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
-import com.commafeed.frontend.rest.resources.AdminREST;
+import com.commafeed.frontend.rest.resources.AdminUsersREST;
import com.commafeed.frontend.rest.resources.EntriesREST;
import com.commafeed.frontend.rest.resources.SettingsREST;
import com.commafeed.frontend.rest.resources.SubscriptionsREST;
@@ -22,7 +22,7 @@ public class RESTApplication extends Application {
set.add(SubscriptionsREST.class);
set.add(EntriesREST.class);
set.add(SettingsREST.class);
- set.add(AdminREST.class);
+ set.add(AdminUsersREST.class);
return set;
}
}
diff --git a/src/main/java/com/commafeed/frontend/rest/resources/AdminREST.java b/src/main/java/com/commafeed/frontend/rest/resources/AdminUsersREST.java
similarity index 88%
rename from src/main/java/com/commafeed/frontend/rest/resources/AdminREST.java
rename to src/main/java/com/commafeed/frontend/rest/resources/AdminUsersREST.java
index 7102c18b..bed0993a 100644
--- a/src/main/java/com/commafeed/frontend/rest/resources/AdminREST.java
+++ b/src/main/java/com/commafeed/frontend/rest/resources/AdminUsersREST.java
@@ -14,10 +14,10 @@ import com.commafeed.frontend.rest.SecurityCheck;
import com.google.common.collect.Maps;
@SecurityCheck(Role.ADMIN)
-@Path("admin")
-public class AdminREST extends AbstractREST {
+@Path("admin/users")
+public class AdminUsersREST extends AbstractREST {
- @Path("users/get")
+ @Path("get")
@GET
public Collection getUsers() {
Map users = Maps.newHashMap();
diff --git a/src/main/webapp/js/controllers.js b/src/main/webapp/js/controllers.js
index 164f2258..6db9a2e4 100644
--- a/src/main/webapp/js/controllers.js
+++ b/src/main/webapp/js/controllers.js
@@ -140,7 +140,7 @@ module.controller('FeedListCtrl', function($scope, $stateParams, $http, $route,
}, function(data) {
for ( var i = 0; i < data.entries.length; i++) {
$scope.entries.push(data.entries[i]);
- }
+ };
$scope.name = data.name;
$scope.busy = false;
$scope.hasMore = data.entries.length == limit
@@ -239,7 +239,7 @@ module.controller('FeedListCtrl', function($scope, $stateParams, $http, $route,
openPreviousEntry(e);
})
});
-
+
$scope.$on('reload', function(event, args) {
$scope.name = null;
$scope.entries = [];
@@ -247,4 +247,11 @@ module.controller('FeedListCtrl', function($scope, $stateParams, $http, $route,
$scope.hasMore = true;
$scope.loadMoreEntries();
});
+});
+
+module.controller('ManageUsersCtrl', function($scope, AdminUsersService) {
+ $scope.users = AdminUsersService.get();
+ $scope.gridOptions = {
+ data : 'users'
+ };
});
\ No newline at end of file
diff --git a/src/main/webapp/js/main.js b/src/main/webapp/js/main.js
index a4dffb8d..833c76d3 100644
--- a/src/main/webapp/js/main.js
+++ b/src/main/webapp/js/main.js
@@ -1,6 +1,6 @@
var app = angular.module('commafeed', [ 'ui', 'ui.bootstrap', 'ui.state',
'commafeed.directives', 'commafeed.controllers', 'commafeed.services',
- 'ngSanitize', 'ngUpload', 'infinite-scroll' ]);
+ 'ngSanitize', 'ngUpload', 'infinite-scroll', 'ngGrid' ]);
app.config(function($routeProvider, $stateProvider, $urlRouterProvider) {
$stateProvider.state('feeds', {
@@ -20,6 +20,7 @@ app.config(function($routeProvider, $stateProvider, $urlRouterProvider) {
});
$stateProvider.state('admin.users', {
url : '/users',
+ controller : 'ManageUsersCtrl',
templateUrl : 'templates/admin.users.html'
});
diff --git a/src/main/webapp/js/services.js b/src/main/webapp/js/services.js
index 3f6faefe..3dc25d5e 100644
--- a/src/main/webapp/js/services.js
+++ b/src/main/webapp/js/services.js
@@ -126,4 +126,18 @@ module.service('SettingsService', function($resource) {
$resource('rest/settings/save').save(s.settings);
};
return s;
-});
\ No newline at end of file
+});
+
+module.service('AdminUsersService', function($resource) {
+ var actions = {
+ get : {
+ method : 'GET',
+ params : {
+ _method : 'get'
+ },
+ isArray : true
+ }
+ };
+ var res = $resource('rest/admin/:_method', {}, actions);
+ return res;
+})
\ No newline at end of file
diff --git a/src/main/webapp/templates/admin.users.html b/src/main/webapp/templates/admin.users.html
index e63103de..bdd1620f 100644
--- a/src/main/webapp/templates/admin.users.html
+++ b/src/main/webapp/templates/admin.users.html
@@ -1 +1,3 @@
-Manage users
\ No newline at end of file
+Manage users
+
+