You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sirona.apache.org by ol...@apache.org on 2014/08/22 09:06:30 UTC

svn commit: r1619686 [2/3] - in /incubator/sirona/trunk/server/reporting: reporting-api/src/main/java/org/apache/sirona/reporting/web/counters/ reporting-ui/src/main/webapp/css/ reporting-ui/src/main/webapp/js/ reporting-ui/src/main/webapp/js/app/contr...

Added: incubator/sirona/trunk/server/reporting/reporting-ui/src/main/webapp/js/ng-grid-2.0.12.debug.js
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/server/reporting/reporting-ui/src/main/webapp/js/ng-grid-2.0.12.debug.js?rev=1619686&view=auto
==============================================================================
--- incubator/sirona/trunk/server/reporting/reporting-ui/src/main/webapp/js/ng-grid-2.0.12.debug.js (added)
+++ incubator/sirona/trunk/server/reporting/reporting-ui/src/main/webapp/js/ng-grid-2.0.12.debug.js Fri Aug 22 07:06:30 2014
@@ -0,0 +1,4031 @@
+/***********************************************
+* 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: 08/04/2014 16:09
+***********************************************/
+(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 CELL_EDITABLE_CONDITION = /CELL_EDITABLE_CONDITION/g;
+var TEMPLATE_REGEXP = /<.+>/;
+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
+var ngMoveSelectionHandler = function($scope, elm, evt, grid) {
+    if ($scope.selectionProvider.selectedItems === undefined) {
+        return true;
+    }
+
+    var charCode = evt.which || evt.keyCode,
+        newColumnIndex,
+        lastInRow = false,
+        firstInRow = false,
+        rowIndex = $scope.selectionProvider.lastClickedRow === undefined ? 1 : $scope.selectionProvider.lastClickedRow.rowIndex,
+        visibleCols = $scope.columns.filter(function(c) {
+             return c.visible && c.width > 0;
+        }),
+        pinnedCols = $scope.columns.filter(function(c) { return c.pinned; });
+
+    if ($scope.col) {
+        newColumnIndex = visibleCols.indexOf($scope.col);
+    }
+
+    if (charCode !== 37 && charCode !== 38 && charCode !== 39 && charCode !== 40 && (grid.config.noTabInterference || charCode !== 9) && charCode !== 13) {
+        return true;
+    }
+    
+    if ($scope.enableCellSelection) {
+        if (charCode === 9) { //tab key
+            evt.preventDefault();
+        }
+
+        var focusedOnFirstColumn = $scope.showSelectionCheckbox ? newColumnIndex === 1 : newColumnIndex === 0;
+        var focusedOnFirstVisibleColumns = newColumnIndex === 1 || newColumnIndex === 0;
+        var focusedOnLastVisibleColumns = newColumnIndex === (visibleCols.length - 1) || newColumnIndex === (visibleCols.length - 2);
+        var focusedOnLastColumn = visibleCols.indexOf($scope.col) === (visibleCols.length - 1);
+        var focusedOnLastPinnedColumn = pinnedCols.indexOf($scope.col) === (pinnedCols.length - 1);
+        
+        if (charCode === 37 || charCode === 9 && evt.shiftKey) {
+            var scrollTo = 0;
+
+            if (!focusedOnFirstColumn) {
+                newColumnIndex -= 1;
+            }
+
+            if (focusedOnFirstVisibleColumns) {
+                if (focusedOnFirstColumn && charCode === 9 && evt.shiftKey){
+                    scrollTo = grid.$canvas.width();
+                    newColumnIndex = visibleCols.length - 1;
+                    firstInRow = true;
+                }
+                else {
+                    scrollTo = grid.$viewport.scrollLeft() - $scope.col.width;
+                }
+            }
+            else if (pinnedCols.length > 0) {
+                scrollTo = grid.$viewport.scrollLeft() - visibleCols[newColumnIndex].width;
+            }
+
+            grid.$viewport.scrollLeft(scrollTo);
+        
+        }
+        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);
+                }
+            }
+            else if (focusedOnLastPinnedColumn) {
+                grid.$viewport.scrollLeft(0);
+            }
+
+            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.selectionProvider.lastClickedRow.renderedRowIndex >= $scope.renderedRows.length - EXCESS_ROWS - 2) {
+                grid.$viewport.scrollTop(grid.$viewport.scrollTop() + $scope.rowHeight);
+            }
+            else if ($scope.selectionProvider.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(visibleCols[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;
+        });
+    };
+});
+angular.module('ngGrid.services').factory('$domUtilityService',['$utilityService', '$window', function($utils, $window) {
+    var domUtilityService = {};
+    var regexCache = {};
+    var getWidths = function() {
+        var $testContainer = $('<div></div>');
+        $testContainer.appendTo('body');
+        // 1. Run all the following measurements on startup!
+        //measure Scroll Bars
+        $testContainer.height(100).width(100).css("position", "absolute").css("overflow", "scroll");
+        $testContainer.append('<div style="height: 400px; width: 400px;"></div>');
+        domUtilityService.ScrollH = ($testContainer.height() - $testContainer[0].clientHeight);
+        domUtilityService.ScrollW = ($testContainer.width() - $testContainer[0].clientWidth);
+        $testContainer.empty();
+        //clear styles
+        $testContainer.attr('style', '');
+        //measure letter sizes using a pretty typical font size and fat font-family
+        $testContainer.append('<span style="font-family: Verdana, Helvetica, Sans-Serif; font-size: 14px;"><strong>M</strong></span>');
+        domUtilityService.LetterW = $testContainer.children().first().width();
+        $testContainer.remove();
+    };
+    domUtilityService.eventStorage = {};
+    domUtilityService.AssignGridContainers = function($scope, rootEl, grid) {
+        grid.$root = $(rootEl);
+        //Headers
+        grid.$topPanel = grid.$root.find(".ngTopPanel");
+        grid.$groupPanel = grid.$root.find(".ngGroupPanel");
+        grid.$headerContainer = grid.$topPanel.find(".ngHeaderContainer");
+        $scope.$headerContainer = grid.$headerContainer;
+
+        grid.$headerScroller = grid.$topPanel.find(".ngHeaderScroller");
+        grid.$headers = grid.$headerScroller.children();
+        //Viewport
+        grid.$viewport = grid.$root.find(".ngViewport");
+        //Canvas
+        grid.$canvas = grid.$viewport.find(".ngCanvas");
+        //Footers
+        grid.$footerPanel = grid.$root.find(".ngFooterPanel");
+
+        var scopeDereg = $scope.$watch(function () {
+            return grid.$viewport.scrollLeft();
+        }, function (newLeft) {
+            return grid.$headerContainer.scrollLeft(newLeft);
+        });
+
+        $scope.$on('$destroy', function() {
+            // Remove all references to DOM elements, otherwise we get memory leaks
+            if(grid.$root) {
+                $(grid.$root.parent()).off('resize.nggrid');
+
+                grid.$root = null;
+                grid.$topPanel = null;
+                // grid.$groupPanel = null;
+                grid.$headerContainer = null;
+                // grid.$headerScroller = null;
+                grid.$headers = null;
+                grid.$canvas = null;
+                grid.$footerPanel = null;
+            }
+
+            scopeDereg();
+        });
+
+        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) {
+        if (!grid.$root){
+            return;
+        }
+        //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.setStyleText = function(grid, css) {
+        var style = grid.styleSheet,
+            gridId = grid.gridId,
+            doc = $window.document;
+
+        if (!style) {
+            style = doc.getElementById(gridId);
+        }
+        if (!style) {
+            style = doc.createElement('style');
+            style.type = 'text/css';
+            style.id = gridId;
+            (doc.head || doc.getElementsByTagName('head')[0]).appendChild(style);
+        }
+
+        if (style.styleSheet && !style.sheet) {
+            style.styleSheet.cssText = css;
+        } else {
+            style.innerHTML = css;
+        }
+        grid.styleSheet = style;
+        grid.styleText = css;
+    };
+    domUtilityService.BuildStyles = function($scope, grid, digest) {
+        var rowHeight = grid.config.rowHeight,
+            gridId = grid.gridId,
+            css,
+            cols = $scope.columns,
+            sumWidth = 0;
+
+        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) + "px}";
+
+        for (var i = 0; i < cols.length; i++) {
+            var col = cols[i];
+            if (col.visible !== false) {
+                css += "." + gridId + " .col" + i + " { width: " + col.width + "px; left: " + sumWidth + "px; height: " + rowHeight + "px }" +
+                    "." + gridId + " .colt" + i + " { width: " + col.width + "px; }";
+                sumWidth += col.width;
+            }
+        }
+        domUtilityService.setStyleText(grid, css);
+
+        $scope.adjustScrollLeft(grid.$viewport.scrollLeft());
+        if (digest) {
+            domUtilityService.digest($scope);
+        }
+    };
+    domUtilityService.setColLeft = function(col, colLeft, grid) {
+        if (grid.styleText) {
+            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 css = grid.styleText.replace(regex, ".col" + col.index + " { width: " + col.width + "px; left: " + colLeft + "px");
+            domUtilityService.setStyleText(grid, css);
+        }
+    };
+    domUtilityService.setColLeft.immediate = 1;
+    domUtilityService.RebuildGrid = function($scope, grid){
+        domUtilityService.UpdateGridLayout($scope, grid);
+        if (grid.config.maintainColumnRatios == null || 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;
+}]);
+angular.module('ngGrid.services').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
+    sortService.isCustomSort = false; // track if we're using an internal sort or a user provided sort
+    // 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,
+                res,
+                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);
+                // if user provides custom sort, we want them to have full control of the sort
+                if (sortService.isCustomSort) {
+                    res = sortFn(propA, propB);
+                    tem = direction === ASC ? res : 0 - res;
+                } else {
+                    // we want to allow zero values to be evaluated in the sort function
+                    if (propA == null || propB == null) {
+                        // we want to force nulls and such to the bottom when we sort... which effectively is "greater than"
+                        if (propB == null && propA == null) {
+                            tem = 0;
+                        }
+                        else if (propA == null) {
+                            tem = 1;
+                        }
+                        else if (propB == null) {
+                            tem = -1;
+                        }
+                    }
+                    else {
+                        // this will keep nulls at the bottom regardless of ordering
+                        res = sortFn(propA, propB);
+                        tem = direction === ASC ? res : 0 - res;
+                    }
+                }
+                indx++;
+            }
+            return 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, 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;
+            sortService.isCustomSort = true;
+        }
+        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;
+}]);
+
+angular.module('ngGrid.services').factory('$utilityService', ['$parse', function ($parse) {
+    var funcNameRegex = /function (.{1,})\(/;
+    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);
+            }
+            var $node = $(node);
+            $(elem).css({'font': $node.css('font'),
+                        'font-size': $node.css('font-size'),
+                        'font-family': $node.css('font-family')});
+            elem.innerHTML = $node.text();
+            var width = elem.offsetWidth;
+            document.body.removeChild(elem);
+            return width;
+        },
+        forIn: function(obj, action) {
+            for (var prop in obj) {
+                if (obj.hasOwnProperty(prop)) {
+                    action(obj[prop], prop);
+                }
+            }
+        },
+        evalProperty: function (entity, path) {
+            return $parse("entity." + path)({ entity: 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) {
+            if (document.getElementsByClassName) {
+                return document.getElementsByClassName(cl);
+            }
+            else {
+                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];
+            }
+        },
+        getInstanceType: function (o) {
+            var results = (funcNameRegex).exec(o.constructor.toString());
+            if (results && results.length > 1) {
+                var instanceType = results[1].replace(/^\s+|\s+$/g, ""); // Trim surrounding whitespace; IE appears to add a space at the end
+                return instanceType;
+            }
+            else {
+                return "";
+            }
+        }
+    };
+
+    return utils;
+}]);
+
+var ngAggregate = function (aggEntity, rowFactory, rowHeight, groupInitState) {
+    this.rowIndex = 0;
+    this.offsetTop = this.rowIndex * rowHeight;
+    this.entity = aggEntity;
+    this.label = aggEntity.gLabel;
+    this.field = aggEntity.gField;
+    this.depth = aggEntity.gDepth;
+    this.parent = aggEntity.parent;
+    this.children = aggEntity.children;
+    this.aggChildren = aggEntity.aggChildren;
+    this.aggIndex = aggEntity.aggIndex;
+    this.collapsed = groupInitState;
+    this.groupInitState = groupInitState;
+    this.rowFactory = rowFactory;
+    this.rowHeight = rowHeight;
+    this.isAggRow = true;
+    this.offsetLeft = aggEntity.gDepth * 25;
+    this.aggLabelFilter = aggEntity.aggLabelFilter;
+};
+
+ngAggregate.prototype.toggleExpand = function () {
+    this.collapsed = this.collapsed ? false : true;
+    if (this.orig) {
+        this.orig.collapsed = this.collapsed;
+    }
+    this.notifyChildren();
+};
+ngAggregate.prototype.setExpand = function (state) {
+    this.collapsed = state;
+    if (this.orig) {
+        this.orig.collapsed = state;
+    }
+    this.notifyChildren();
+};
+ngAggregate.prototype.notifyChildren = function () {
+    var longest = Math.max(this.rowFactory.aggCache.length, this.children.length);
+    for (var i = 0; i < longest; i++) {
+        if (this.aggChildren[i]) {
+            this.aggChildren[i].entity[NG_HIDDEN] = this.collapsed;
+            if (this.collapsed) {
+                this.aggChildren[i].setExpand(this.collapsed);
+            }
+        }
+        if (this.children[i]) {
+            this.children[i][NG_HIDDEN] = this.collapsed;
+        }
+        if (i > this.aggIndex && this.rowFactory.aggCache[i]) {
+            var agg = this.rowFactory.aggCache[i];
+            var offset = (30 * this.children.length);
+            agg.offsetTop = this.collapsed ? agg.offsetTop - offset : agg.offsetTop + offset;
+        }
+    }
+    this.rowFactory.renderedChange();
+};
+ngAggregate.prototype.aggClass = function () {
+    return this.collapsed ? "ngAggArrowCollapsed" : "ngAggArrowExpanded";
+};
+ngAggregate.prototype.totalChildren = function () {
+    if (this.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(this);
+        return i;
+    } else {
+        return this.children.length;
+    }
+};
+ngAggregate.prototype.copy = function () {
+    var ret = new ngAggregate(this.entity, this.rowFactory, this.rowHeight, this.groupInitState);
+    ret.orig = this;
+    return ret;
+};
+
+var ngColumn = function (config, $scope, grid, domUtilityService, $templateCache, $utils) {
+    var self = this,
+        colDef = config.colDef,
+        delay = 500,
+        clicks = 0,
+        timer = null;
+    self.colDef = config.colDef;
+    self.width = colDef.width;
+    self.groupIndex = 0;
+    self.isGroupedBy = false;
+    self.minWidth = !colDef.minWidth ? 50 : colDef.minWidth;
+    self.maxWidth = !colDef.maxWidth ? 9000 : colDef.maxWidth;
+
+    // TODO: Use the column's definition for enabling cell editing
+    // self.enableCellEdit = config.enableCellEdit || colDef.enableCellEdit;
+    self.enableCellEdit = colDef.enableCellEdit !== undefined ? colDef.enableCellEdit : (config.enableCellEdit || config.enableCellEditOnFocus);
+    
+    self.cellEditableCondition = colDef.cellEditableCondition || config.cellEditableCondition || 'true';
+
+    self.headerRowHeight = config.headerRowHeight;
+
+    // Use colDef.displayName as long as it's not undefined, otherwise default to the field name
+    self.displayName = (colDef.displayName === undefined) ? colDef.field : colDef.displayName;
+
+    self.index = config.index;
+    self.isAggCol = config.isAggCol;
+    self.cellClass = colDef.cellClass;
+    self.sortPriority = undefined;
+    self.cellFilter = colDef.cellFilter ? colDef.cellFilter : "";
+    self.field = colDef.field;
+    self.aggLabelFilter = colDef.aggLabelFilter || colDef.cellFilter;
+    self.visible = $utils.isNullOrUndefined(colDef.visible) || colDef.visible;
+    self.sortable = false;
+    self.resizable = false;
+    self.pinnable = false;
+    self.pinned = (config.enablePinning && colDef.pinned);
+    self.originalIndex = config.originalIndex == null ? self.index : config.originalIndex;
+    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 = colDef.cellEditTemplate || $templateCache.get('cellEditTemplate.html');
+        self.editableCellTemplate = colDef.editableCellTemplate || $templateCache.get('editableCellTemplate.html');
+    }
+    if (colDef.cellTemplate && !TEMPLATE_REGEXP.test(colDef.cellTemplate)) {
+        self.cellTemplate = $templateCache.get(colDef.cellTemplate) || $.ajax({
+            type: "GET",
+            url: colDef.cellTemplate,
+            async: false
+        }).responseText;
+    }
+    if (self.enableCellEdit && colDef.editableCellTemplate && !TEMPLATE_REGEXP.test(colDef.editableCellTemplate)) {
+        self.editableCellTemplate = $templateCache.get(colDef.editableCellTemplate) || $.ajax({
+            type: "GET",
+            url: colDef.editableCellTemplate,
+            async: false
+        }).responseText;
+    }
+    if (colDef.headerCellTemplate && !TEMPLATE_REGEXP.test(colDef.headerCellTemplate)) {
+        self.headerCellTemplate = $templateCache.get(colDef.headerCellTemplate) || $.ajax({
+            type: "GET",
+            url: colDef.headerCellTemplate,
+            async: false
+        }).responseText;
+    }
+    self.colIndex = function () {
+        var classes = self.pinned ? "pinned " : "";
+        classes += "col" + self.index + " colt" + self.index;
+        if (self.cellClass) {
+            classes += " " + self.cellClass;
+        }
+        return classes;
+    };
+    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) {
+        $scope.isColumnResizing = true;
+        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));
+        $scope.hasUserChangedGridColumnWidths = true;
+        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';
+        domUtilityService.digest($scope);
+        $scope.isColumnResizing = false;
+        return false;
+    };
+    self.copy = function() {
+        var ret = new ngColumn(config, $scope, grid, domUtilityService, $templateCache, $utils);
+        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;
+    };
+};
+
+var ngDimension = function (options) {
+    this.outerHeight = null;
+    this.outerWidth = null;
+    $.extend(this, options);
+};
+var ngDomAccessProvider = function (grid) {
+    this.previousColumn = null;
+    this.grid = grid;
+
+};
+
+ngDomAccessProvider.prototype.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
+    });
+};
+ngDomAccessProvider.prototype.focusCellElement = function ($scope, index) { 
+    if ($scope.selectionProvider.lastClickedRow) {
+        var columnIndex = index !== undefined ? index : this.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 (this.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[1].children[0].focus();
+            }
+            this.previousColumn = columnIndex;
+        }
+    }
+};
+ngDomAccessProvider.prototype.selectionHandlers = function ($scope, elm) {
+    var doingKeyDown = false;
+    var self = this;
+
+    function keydown (evt) {
+        if (evt.keyCode === 16) { //shift key
+            self.changeUserSelect(elm, 'none', evt);
+            return true;
+        } else if (!doingKeyDown) {
+            doingKeyDown = true;
+            var ret = ngMoveSelectionHandler($scope, elm, evt, self.grid);
+            doingKeyDown = false;
+            return ret;
+        }
+        return true;
+    }
+
+    elm.bind('keydown', keydown);
+
+    function keyup (evt) {
+        if (evt.keyCode === 16) { //shift key
+            self.changeUserSelect(elm, 'text', evt);
+        }
+        return true;
+    }
+
+    elm.bind('keyup', keyup);
+
+    elm.on('$destroy', function() {
+        elm.off('keydown', keydown);
+        elm.off('keyup', keyup);
+    });
+};
+var ngEventProvider = function (grid, $scope, domUtilityService, $timeout) {
+    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);
+                }
+            });
+
+            grid.$groupPanel.on('$destroy', function() {
+                grid.$groupPanel = null;
+            });
+        } else {
+            grid.$groupPanel.on('mousedown', self.onGroupMouseDown).on('dragover', self.dragOver).on('drop', self.onGroupDrop);
+            grid.$topPanel.on('mousedown', '.ngHeaderScroller', self.onHeaderMouseDown).on('dragover', '.ngHeaderScroller', self.dragOver);
+
+            grid.$groupPanel.on('$destroy', function() {
+                if (grid.$groupPanel){
+                    grid.$groupPanel.off('mousedown');
+                }
+
+                grid.$groupPanel = null;
+            });
+
+            if (grid.config.enableColumnReordering) {
+                grid.$topPanel.on('drop', '.ngHeaderScroller', self.onHeaderDrop);
+            }
+
+            grid.$topPanel.on('$destroy', function() {
+                if (grid.$topPanel){
+                    grid.$topPanel.off('mousedown');
+                }
+
+                if (grid.config.enableColumnReordering && grid.$topPanel) {
+                    grid.$topPanel.off('drop');
+                }
+
+                grid.$topPanel = null;
+            });
+        }
+
+        $scope.$on('$destroy', $scope.$watch('renderedColumns', function() {
+            $timeout(self.setDraggables);
+        }));
+    };
+    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){
+                if(col.className && col.className.indexOf("ngHeaderSortColumn") !== -1){
+                    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);
+
+                        angular.element(col).on('$destroy', function() {
+                            angular.element(col).off('dragstart', self.dragStart);
+                            col.removeEventListener('dragstart', self.dragStart);
+                        });
+                    }
+                }
+            });
+            if (navigator.userAgent.indexOf("MSIE") !== -1){
+                //call native IE dragDrop() to start dragging
+                var sortColumn = grid.$root.find('.ngHeaderSortColumn');
+                sortColumn.bind('selectstart', function () { 
+                    this.dragDrop(); 
+                    return false; 
+                });
+                angular.element(sortColumn).on('$destroy', function() {
+                    sortColumn.off('selectstart');
+                });
+            }
+        } else {
+            if (grid.$root) {
+                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); 
+
+                        angular.element(this).on('$destroy', function() {
+                            this.removeEventListener('dragstart', self.dragStart); 
+                        });
+                    }
+                    if (navigator.userAgent.indexOf("MSIE") !== -1){
+                        //call native IE dragDrop() to start dragging
+                        groupItem.bind('selectstart', function () { 
+                            this.dragDrop(); 
+                            return false; 
+                        });
+
+                        groupItem.on('$destroy', function() {
+                            groupItem.off('selectstart');
+                        });
+                    }
+                }
+                // 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 || self.colToMove.col.pinned) {
+            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 or the target column is pinned, do nothing.
+            if (self.colToMove.col === headerScope.col || headerScope.col.pinned) {
+                return;
+            }
+            // Splice the columns
+            $scope.columns.splice(self.colToMove.col.index, 1);
+            $scope.columns.splice(headerScope.col.index, 0, self.colToMove.col);
+            grid.fixColumnIndexes();
+            // clear out the colToMove object
+            self.colToMove = undefined;
+            domUtilityService.digest($scope);
+        }
+    };
+
+    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);
+        }
+        // resize on window resize
+        var windowThrottle;
+        var windowResize = function(){
+            clearTimeout(windowThrottle);
+            windowThrottle = setTimeout(function() {
+                //in function for IE8 compatibility
+                domUtilityService.RebuildGrid($scope,grid);
+            }, 100);
+        };
+        $(window).on('resize.nggrid', windowResize);
+        // resize on parent resize as well.
+        var parentThrottle;
+        var parentResize = function() {
+            clearTimeout(parentThrottle);
+            parentThrottle = setTimeout(function() {
+                //in function for IE8 compatibility
+                domUtilityService.RebuildGrid($scope,grid);
+            }, 100);
+        };
+        $(grid.$root.parent()).on('resize.nggrid', parentResize);
+
+        $scope.$on('$destroy', function(){
+            $(window).off('resize.nggrid', windowResize);
+            // $(grid.$root.parent()).off('resize.nggrid', parentResize);
+        });
+    };
+    // In this example we want to assign grid events.
+    self.assignGridEventHandlers();
+    self.assignEvents();
+};
+
+var ngFooter = function ($scope, grid) {
+    $scope.maxRows = function () {
+        var ret = Math.max($scope.totalServerItems, grid.data.length);
+        return ret;
+    };
+    
+     $scope.$on('$destroy', $scope.$watch('totalServerItems',function(n,o){
+        $scope.currentMaxPages = $scope.maxPages();
+    }));
+
+    $scope.multiSelect = (grid.config.enableRowSelection && grid.config.multiSelect);
+    $scope.selectedItemCount = grid.selectedItemCount;
+    
+    $scope.maxPages = function () {
+        if($scope.maxRows() === 0) {
+            return 1;
+        }
+        return Math.ceil($scope.maxRows() / $scope.pagingOptions.pageSize);
+    };
+
+    $scope.pageForward = function() {
+        var page = $scope.pagingOptions.currentPage;
+        if ($scope.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.totalServerItems > 0) {
+            return curPage >= maxPages;
+        } else {
+            return grid.data.length < 1;
+        }
+
+    };
+    $scope.cantPageToLast = function() {
+        if ($scope.totalServerItems > 0) {
+            return $scope.cantPageForward();
+        } else {
+            return true;
+        }
+    };
+    
+    $scope.cantPageBackward = function() {
+        var curPage = $scope.pagingOptions.currentPage;
+        return curPage <= 1;
+    };
+};
+/// <reference path="footer.js" />
+/// <reference path="../services/SortService.js" />
+/// <reference path="../../lib/jquery-1.8.2.min" />
+var ngGrid = function ($scope, options, sortService, domUtilityService, $filter, $templateCache, $utils, $timeout, $parse, $http, $q) {
+    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 editing on focus
+        enableCellEditOnFocus: 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,
+        
+        //To be able to have selectable rows in grid.
+        enableRowSelection: true,
+
+        //Enables or disables sorting in grid.
+        enableSorting: true,
+
+        //Enables or disables text highlighting in grid by adding the "unselectable" class (See CSS file)
+        enableHighlighting: false,
+        
+        // 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,
+        
+        // the template for the column menu and filter, including the button.
+        footerTemplate: undefined,
+
+        // Enables a trade off between refreshing the contents of the grid continuously while scrolling (behaviour when true) 
+        // and keeping the scroll bar button responsive at the expense of refreshing grid contents (behaviour when false)
+        forceSyncScrolling: true,
+
+        //Initial fields to group data by. Array of field names, not displayName.
+        groups: [],
+        
+        // set the initial state of aggreagate grouping. "true" means they will be collapsed when grouping changes, "false" means they will be expanded by default.
+        groupsCollapsedByDefault: true,
+        
+        //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,
+
+        // the template for the column menu and filter, including the button.
+        menuTemplate: 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,
+            //currentPage: the uhm... current page.
+            currentPage: 1
+        },
+        
+        //the selection checkbox is pinned to the left side of the viewport or not.
+        pinSelectionCheckbox: false,
+
+        //Array of plugin functions to register in ng-grid
+        plugins: [],
+
+        //User defined unique ID field that allows for better handling of selections and for server-side paging
+        primaryKey: undefined,
+
+        //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: [],
+        
+        //Width of row selection checkbox column
+        selectionCheckboxColumnWidth: 25,
+
+        //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,
+        
+        //totalServerItems: Total items are on the server. 
+        totalServerItems: 0,
+            
+        /*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,
+
+	// Don't handle tabs, so they can be used to navigate between controls.
+	noTabInterference: false
+    },
+        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 = [];
+
+    self.initTemplates = function() {
+        var templates = ['rowTemplate', 'aggregateTemplate', 'headerRowTemplate', 'checkboxCellTemplate', 'checkboxHeaderTemplate', 'menuTemplate', 'footerTemplate'];
+
+        var promises = [];
+        angular.forEach(templates, function(template) {
+            promises.push( self.getTemplate(template) );
+        });
+
+        return $q.all(promises);
+    };
+    
+    //Templates
+    // test templates for urls and get the tempaltes via synchronous ajax calls
+    self.getTemplate = function (key) {
+        var t = self.config[key];
+        var uKey = self.gridId + key + ".html";
+        var p = $q.defer();
+        if (t && !TEMPLATE_REGEXP.test(t)) {
+            $http.get(t, {
+                cache: $templateCache
+            })
+            .success(function(data){
+                $templateCache.put(uKey, data);
+                p.resolve();
+            })
+            .error(function(err){
+                p.reject("Could not load template: " + t);
+            });
+        } else if (t) {
+            $templateCache.put(uKey, t);
+            p.resolve();
+        } else {
+            var dKey = key + ".html";
+            $templateCache.put(uKey, $templateCache.get(dKey));
+            p.resolve();
+        }
+
+        return p.promise;
+    };
+
+    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() {
+        var calculatedHeight;
+        if(self.config.groups.length > 0){
+            calculatedHeight = self.rowFactory.parsedData.filter(function(e) {
+                return !e[NG_HIDDEN];
+            }).length * self.config.rowHeight;
+        } else {
+            calculatedHeight = self.filteredRows.length * self.config.rowHeight;
+        }
+        return calculatedHeight;
+    };
+    self.elementDims = {
+        scrollW: 0,
+        scrollH: 0,
+        rowIndexCellW: self.config.selectionCheckboxColumnWidth,
+        rowSelectedCellW: self.config.selectionCheckboxColumnWidth,
+        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;
+            $scope.renderedRows[i].selected = newRows[i].selected;
+            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 ngDimension();
+        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 ngColumn({
+                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,
+                enablePinning: self.config.enablePinning
+            }, $scope, self, domUtilityService, $templateCache, $utils));
+        }
+        if (columnDefs.length > 0) {
+            var checkboxOffset = self.config.showSelectionCheckbox ? 1 : 0;
+            var groupOffset = $scope.configGroups.length;
+            $scope.configGroups.length = 0;
+            angular.forEach(columnDefs, function(colDef, i) {
+                i += checkboxOffset;
+                var column = new ngColumn({
+                    colDef: colDef,
+                    index: i + groupOffset,
+                    originalIndex: 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 || self.config.enableCellEditOnFocus,
+                    cellEditableCondition: self.config.cellEditableCondition
+                }, $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;
+            if (self.config.groups.length > 0) {
+                self.rowFactory.getGrouping(self.config.groups);
+            }
+        }
+    };
+    self.configureColumnWidths = function() {
+        var asterisksArray = [],
+            percentArray = [],
+            asteriskNum = 0,
+            totalWidth = 0;
+
+        // When rearranging columns, their index in $scope.columns will no longer match the original column order from columnDefs causing
+        // their width config to be out of sync. We can use "originalIndex" on the ngColumns to get hold of the correct setup from columnDefs, but to
+        // avoid O(n) lookups in $scope.columns per column we setup a map.
+        var indexMap = {};
+        // Build a map of columnDefs column indices -> ngColumn indices (via the "originalIndex" property on ngColumns).
+        angular.forEach($scope.columns, function(ngCol, i) {
+            // Disregard columns created by grouping (the grouping columns don't match a column from columnDefs)
+            if (!$utils.isNullOrUndefined(ngCol.originalIndex)) {
+                var origIndex = ngCol.originalIndex;
+                if (self.config.showSelectionCheckbox) {
+                    //if visible, takes up 25 pixels
+                    if(ngCol.originalIndex === 0 && ngCol.visible){
+                        totalWidth += self.config.selectionCheckboxColumnWidth;
+                    }
+                    // The originalIndex will be offset 1 when including the selection column
+                    origIndex--;
+                }
+                indexMap[origIndex] = i;
+            } else if (ngCol.isAggCol && ngCol.visible){ // aggregate columns are 25px in length. 
+                totalWidth+=25;
+            }
+        });
+
+        angular.forEach(self.config.columnDefs, function(colDef, i) {
+                // Get the ngColumn that matches the current column from columnDefs
+            var ngColumn = $scope.columns[indexMap[i]];
+
+            colDef.index = i;
+
+            var isPercent = false, t;
+            //if width is not defined, set it to a single star
+            if ($utils.isNullOrUndefined(colDef.width)) {
+                colDef.width = "*";
+            } else { // get column width
+                isPercent = isNaN(colDef.width) ? $utils.endsWith(colDef.width, "%") : false;
+                t = isPercent ? colDef.width : parseInt(colDef.width, 10);
+            }
+
+             // has bug for resize event causing NaN for all column width after another http.get
+             // if (isNaN(t) && !$scope.hasUserChangedGridColumnWidths) {
+             // check if it is a number
+            if (isNaN(t)) {
+                t = colDef.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.
+                    ngColumn.width = ngColumn.minWidth;
+                    totalWidth += ngColumn.width;
+                    var temp = ngColumn;
+
+                    $scope.$on('$destroy', $scope.$on("ngGridEventData", function () {
+                        self.resizeOnData(temp);
+                    }));
+
+                    return;
+                } else if (t.indexOf("*") !== -1) { //  we need to save it until the end to do the calulations on the remaining width.
+                    if (ngColumn.visible !== false) {
+                        asteriskNum += t.length;
+                    }
+                    asterisksArray.push(colDef);
+                    return;
+                } else if (isPercent) { // If the width is a percentage, save it until the very last.
+                    percentArray.push(colDef);
+                    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 (ngColumn.visible !== false) {
+                totalWidth += ngColumn.width = parseInt(ngColumn.width, 10);
+            }
+        });
+        
+        // Now we check if we saved any percentage columns for calculating last
+        if (percentArray.length > 0) {
+            //If they specificy for maintain column ratios to be false in grid config, then it will remain false. If not specifiied or true, will be true.
+            self.config.maintainColumnRatios = self.config.maintainColumnRatios !== false; 
+            // If any columns with % widths have been hidden, then let other % based columns use their width
+            var percentWidth = 0; // The total % value for all columns setting their width using % (will e.g. be 40 for 2 columns with 20% each)
+            var hiddenPercent = 0; // The total % value for all columns setting their width using %, but which have been hidden
+            angular.forEach(percentArray, function(colDef) {
+                // Get the ngColumn that matches the current column from columnDefs
+                var ngColumn = $scope.columns[indexMap[colDef.index]];
+                var percent = parseFloat(colDef.width) / 100;
+                percentWidth += percent;
+
+                if (!ngColumn.visible) {
+                    hiddenPercent += percent;
+                }
+            });
+            var percentWidthUsed = percentWidth - hiddenPercent;
+
+            // do the math
+            angular.forEach(percentArray, function(colDef) {
+                // Get the ngColumn that matches the current column from columnDefs
+                var ngColumn = $scope.columns[indexMap[colDef.index]];
+                
+                // Calc the % relative to the amount of % reserved for the visible columns (that use % based widths)
+                var percent = parseFloat(colDef.width) / 100;
+                if (hiddenPercent > 0) {
+                    percent = percent / percentWidthUsed;
+                }
+                else {
+                    percent = percent / percentWidth;
+                }
+
+                var pixelsForPercentBasedWidth = self.rootDim.outerWidth * percentWidth;
+                ngColumn.width = pixelsForPercentBasedWidth * percent;
+                totalWidth += ngColumn.width;
+            });
+        }
+
+        // check if we saved any asterisk columns for calculating later
+        if (asterisksArray.length > 0) {
+            //If they specificy for maintain column ratios to be false in grid config, then it will remain false. If not specifiied or true, will be true.
+            self.config.maintainColumnRatios = self.config.maintainColumnRatios !== false; 
+            // get the remaining width
+            var remainingWidth = self.rootDim.outerWidth - totalWidth;
+            // are we overflowing vertically?
+            if (self.maxCanvasHt > $scope.viewportDimHeight()) {
+                //compensate for scrollbar
+                remainingWidth -= domUtilityService.ScrollW;
+            }
+            // calculate the weight of each asterisk rounded down
+            var asteriskVal = Math.floor(remainingWidth / asteriskNum);
+
+            // set the width of each column based on the number of stars
+            angular.forEach(asterisksArray, function(colDef, i) {
+                // Get the ngColumn that matches the current column from columnDefs
+                var ngColumn = $scope.columns[indexMap[colDef.index]];                
+                ngColumn.width = asteriskVal * colDef.width.length;
+                if (ngColumn.width < ngColumn.minWidth) {
+                    ngColumn.width = ngColumn.minWidth;
+                }
+                if (ngColumn.visible !== false) {
+                    totalWidth += ngColumn.width;
+                }
+
+                var isLast = (i === (asterisksArray.length - 1));
+                //if last asterisk and doesn't fill width of grid, add the difference
+                if(isLast && totalWidth < self.rootDim.outerWidth){
+                    var gridWidthDifference = self.rootDim.outerWidth - totalWidth;
+                    if(self.maxCanvasHt > $scope.viewportDimHeight()){
+                        gridWidthDifference -= domUtilityService.ScrollW;
+                    }
+                    ngColumn.width += gridWidthDifference;
+                }
+            });
+        }
+    };
+    self.init = function() {
+        return self.initTemplates().then(function(){
+            //factories and services
+            $scope.selectionProvider = new ngSelectionProvider(self, $scope, $parse);
+            $scope.domAccessProvider = new ngDomAccessProvider(self);
+            self.rowFactory = new ngRowFactory(self, $scope, domUtilityService, $templateCache, $utils);
+            self.searchProvider = new ngSearchProvider($scope, self, $filter, $utils);
+            self.styleProvider = new ngStyleProvider($scope, self);
+            $scope.$on('$destroy', $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.$on('$destroy', $scope.$watch('columns', function (a) {
+                if(!$scope.isColumnResizing){
+                    domUtilityService.RebuildGrid($scope, self);
+                }
+                $scope.$emit('ngGridEventColumns', a);
+            }, true));
+             $scope.$on('$destroy', $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) {
+                $scope.$on('$destroy', $scope.$watch(function() {
+                    return self.config.sortInfo;
+                }, function(sortInfo){
+                    if (!sortService.isSorting) {
+                        self.sortColumnsInit();
+                        $scope.$emit('ngGridEventSorted', self.config.sortInfo);
+                    }
+                }, true));
+            }
+        });
+
+        // var p = $q.defer();
+        // p.resolve();
+        // return p.promise;
+    };
+   
+    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.sortData = function(col, evt) {
+        if (evt && 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;
+            }
+            $scope.$emit('ngGridEventSorted', self.config.sortInfo);
+        } else if (!self.config.useExternalSorting || (self.config.useExternalSorting && self.config.sortInfo )) {
+            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) {
+                angular.forEach(col, function (c, i) {
+                    c.sortPriority = i + 1;
+                    push(c);
+                });
+            } else {
+                self.clearSortingData(col);
+                col.sortPriority = undefined;
+                push(col);
+            }
+
+            self.sortActual();
+            self.searchProvider.evalFilter();
+            $scope.$emit('ngGridEventSorted', self.config.sortInfo);
+        }
+    };
+    self.sortColumnsInit = function() {
+        if (self.config.sortInfo.columns) {
+            self.config.sortInfo.columns.length = 0;
+        } else {
+            self.config.sortInfo.columns = [];
+        }
+
+        var cols = [];
+        angular.forEach($scope.columns, function(c) {
+            var i = self.config.sortInfo.fields.indexOf(c.field);
+            if (i !== -1) {
+                c.sortDirection = self.config.sortInfo.directions[i] || 'asc';
+                cols[i] = c;
+            }
+        });
+
+        if(cols.length === 1){
+            self.sortData(cols[0]);
+        }else{
+            self.sortData(cols);
+        }
+    };
+    self.sortActual = function() {
+        if (!self.config.useExternalSorting) {
+            var tempData = self.data.slice(0);
+            angular.forEach(tempData, function(item, i) {
+                var e = self.rowMap[i];
+                if (e !== undefined) {
+                    var v = self.rowCache[e];
+                    if (v !== undefined) {
+                        item.preSortSelected = v.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.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++) {
+            $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.enableCellEditOnFocus = self.config.enableCellEditOnFocus;
+    $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.forceSyncScrolling = self.config.forceSyncScrolling;
+    $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 ngRange(Math.max(0, rowIndex - EXCESS_ROWS), rowIndex + self.minRowsToRender() + EXCESS_ROWS);
+        } else {
+            var maxLen = $scope.configGroups.length > 0 ? self.rowFactory.parsedData.length : self.filteredRows.length;
+            newRange = new ngRange(0, Math.max(maxLen, self.minRowsToRender() + EXCESS_ROWS));
+        }
+        self.prevScrollTop = scrollTop;
+        self.rowFactory.UpdateViewableRange(newRange);

[... 1885 lines stripped ...]