/*********************************************** * 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: 12/21/2012 15:55:15 ***********************************************/ (function(window) { 'use strict'; /*********************************************** * FILE: ..\src\namespace.js ***********************************************/ window.ng = {}; window.ng.$http = undefined; var ngGridServices = angular.module('ngGrid.services', []); var ngGridDirectives = angular.module('ngGrid.directives', []); var ngGridFilters = angular.module('ngGrid.filters', []); // Declare app level module which depends on filters, and services /*********************************************** * FILE: ..\src\constants.js ***********************************************/ var SELECTED_PROP = '__ng_selected__'; var GRID_KEY = '__koGrid__'; // the # of rows we want to add to the top and bottom of the rendered grid rows var EXCESS_ROWS = 8; var SCROLL_THRESHOLD = 6; 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 TEMPLATE_REGEXP = /<.+>/; /*********************************************** * FILE: ..\src\navigation.js ***********************************************/ //set event binding on the grid so we can select using the up/down keys ng.moveSelectionHandler = function($scope, grid, evt) { // null checks if (grid === null || grid === undefined) { return true; } if (grid.config.selectedItems === undefined) { return true; } var charCode = evt.which || evt.keyCode; // detect which direction for arrow keys to navigate the grid var offset = (charCode == 38 ? -1 : (charCode == 40 ? 1 : null)); if (!offset) { return true; } var items = $scope.renderedRows; var index = items.indexOf(grid.selectionService.lastClickedRow) + offset; if (index < 0 || index >= items.length) { return true; } grid.selectionService.ChangeSelection(items[index], evt); if (index > items.length - EXCESS_ROWS) { grid.$viewport.scrollTop(grid.$viewport.scrollTop() + (grid.config.rowHeight * 2)); } else if (index < EXCESS_ROWS) { grid.$viewport.scrollTop(grid.$viewport.scrollTop() - (grid.config.rowHeight * 2)); } if (!$scope.$$phase) { $scope.$parent.$digest(); } return false; }; /*********************************************** * FILE: ..\src\utils.js ***********************************************/ if (!String.prototype.trim) { String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ''); }; } if (!Array.prototype.indexOf) { Array.prototype.indexOf = function(elt /*, from*/) { var len = this.length >>> 0; var from = Number(arguments[1]) || 0; from = (from < 0) ? Math.ceil(from) : Math.floor(from); if (from < 0) { from += len; } for (; from < len; from++) { if (from in this && this[from] === elt) { return from; } } return -1; }; } if (!Array.prototype.filter) { Array.prototype.filter = function(fun /*, thisp */) { "use strict"; var t = Object(this); var len = t.length >>> 0; if (typeof fun !== "function") { throw new TypeError(); } var res = []; var thisp = arguments[1]; for (var i = 0; i < len; i++) { if (i in t) { var val = t[i]; // in case fun mutates this if (fun.call(thisp, val, i, t)) { res.push(val); } } } return res; }; } ng.utils = { visualLength: function(node) { var elem = document.getElementById('testDataLength'); if (!elem) { elem = document.createElement('SPAN'); elem.id = "testDataLength"; elem.style.visibility = "hidden"; document.body.appendChild(elem); } $(elem).css('font', $(node).css('font')); elem.innerHTML = $(node).text(); return elem.offsetWidth; }, forIn: function(obj, action) { for (var prop in obj) { if (obj.hasOwnProperty(prop)) { action(obj[prop], prop); } } }, evalProperty: function(entity, path) { var propPath = path.split('.'), i = 0; var tempProp = entity[propPath[i]], links = propPath.length; i++; while (tempProp && i < links) { tempProp = tempProp[propPath[i]]; i++; } return tempProp; }, endsWith: function(str, suffix) { if (!str || !suffix || typeof str != "string") { return false; } return str.indexOf(suffix, str.length - suffix.length) !== -1; }, isNullOrUndefined: function(obj) { if (obj === undefined || obj === null) { return true; } return false; }, getElementsByClassName: function(cl) { var retnode = []; var myclass = new RegExp('\\b' + cl + '\\b'); var elem = document.getElementsByTagName('*'); for (var i = 0; i < elem.length; i++) { var classes = elem[i].className; if (myclass.test(classes)) { retnode.push(elem[i]); } } return retnode; }, newId: (function() { var seedId = new Date().getTime(); return function() { return seedId += 1; }; })(), // 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(ng.utils, { isIe6: (function() { return ng.utils.ieVersion === 6; })(), isIe7: (function() { return ng.utils.ieVersion === 7; })(), isIe: (function() { return ng.utils.ieVersion !== undefined; })() }); /*********************************************** * FILE: ..\src\filters\ngColumns.js ***********************************************/ ngGridFilters.filter('ngColumns', function() { return function(input) { return input.filter(function(col) { return !col.isAggCol; }); }; }); /*********************************************** * FILE: ..\src\filters\checkmark.js ***********************************************/ ngGridFilters.filter('checkmark', function() { return function(input) { return input ? '\u2714' : '\u2718'; }; }); /*********************************************** * FILE: ..\src\services\SortService.js ***********************************************/ ngGridServices.factory('SortService', function() { var sortService = {}; sortService.colSortFnCache = {}; // cache of sorting functions. Once we create them, we don't want to keep re-doing it sortService.dateRE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/; // nasty regex for date parsing // 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 sortFn, // sorting function that is guessed itemType, // the typeof item dateParts, // for date parsing month, // for date parsing day; // for date parsing if (item === undefined || item === null || item === '') { return null; } itemType = typeof(item); //check for numbers and booleans switch (itemType) { case "number": sortFn = sortService.sortNumber; break; case "boolean": sortFn = sortService.sortBool; break; default: sortFn = undefined; break; } //if we found one, return it if (sortFn) { return sortFn; } //check if the item is a valid Date if (Object.prototype.toString.call(item) === '[object Date]') { return sortService.sortDate; } // if we aren't left with a string, return a basic sorting function... if (itemType !== "string") { return sortService.basicSort; } // now lets string check.. //check if the item data is a valid number if (item.match(/^-?[�$�]?[\d,.]+%?$/)) { return sortService.sortNumberStr; } // check for a date: dd/mm/yyyy or dd/mm/yy // can have / or . or - as separator // can be mm/dd as well dateParts = item.match(sortService.dateRE); if (dateParts) { // looks like a date month = parseInt(dateParts[1], 10); day = parseInt(dateParts[2], 10); if (month > 12) { // definitely dd/mm return sortService.sortDDMMStr; } else if (day > 12) { return sortService.sortMMDDStr; } else { // looks like a date, but we can't tell which, so assume that it's MM/DD return sortService.sortMMDDStr; } } //finally just sort the normal string... return sortService.sortAlpha; }; //#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; } }; sortService.sortDDMMStr = function(a, b) { var dateA, dateB, mtch, m, d, y; mtch = a.match(sortService.dateRE); y = mtch[3]; m = mtch[2]; d = mtch[1]; if (m.length == 1) { m = '0' + m; } if (d.length == 1) { d = '0' + d; } dateA = y + m + d; mtch = b.match(sortService.dateRE); y = mtch[3]; m = mtch[2]; d = mtch[1]; if (m.length == 1) { m = '0' + m; } if (d.length == 1) { d = '0' + d; } dateB = y + m + d; if (dateA == dateB) { return 0; } if (dateA < dateB) { return -1; } return 1; }; sortService.sortMMDDStr = function(a, b) { var dateA, dateB, mtch, m, d, y; mtch = a.match(sortService.dateRE); y = mtch[3]; d = mtch[2]; m = mtch[1]; if (m.length == 1) { m = '0' + m; } if (d.length == 1) { d = '0' + d; } dateA = y + m + d; mtch = b.match(sortService.dateRE); y = mtch[3]; d = mtch[2]; m = mtch[1]; if (m.length == 1) { m = '0' + m; } if (d.length == 1) { d = '0' + d; } dateB = y + m + d; if (dateA == dateB) { return 0; } if (dateA < dateB) { return -1; } return 1; }; //#endregion // the core sorting logic trigger sortService.sortData = function(data /*datasource*/, sortInfo) { // first make sure we are even supposed to do work if (!data || !sortInfo) { return; } // grab the metadata for the rest of the logic var col = sortInfo.column, direction = sortInfo.direction, 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; } else { // try and guess what sort function to use item = data[0]; if (!item) { return; } sortFn = sortService.guessSortFn(item[col.field]); //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; } } //now actually sort the data data.sort(function(itemA, itemB) { var propA = ng.utils.evalProperty(itemA, col.field); var propB = ng.utils.evalProperty(itemB, col.field); // we want to force nulls and such to the bottom when we sort... which effectively is "greater than" if (!propB && !propA) { return 0; } else if (!propA) { return 1; } else if (!propB) { return -1; } //made it this far, we don't have to worry about null & undefined if (direction === ASC) { return sortFn(propA, propB); } else { return 0 - sortFn(propA, propB); } }); return; }; sortService.Sort = function(sortInfo, data) { if (sortService.isSorting) { return; } sortService.isSorting = true; sortService.sortData(data, sortInfo); sortService.isSorting = false; }; return sortService; }); /*********************************************** * FILE: ..\src\services\DomUtilityService.js ***********************************************/ ngGridServices.factory('DomUtilityService', function() { var domUtilityService = {}; 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(rootEl, grid) { grid.$root = $(rootEl); //Headers grid.$topPanel = grid.$root.find(".ngTopPanel"); grid.$groupPanel = grid.$root.find(".ngGroupPanel"); grid.$headerContainer = grid.$topPanel.find(".ngHeaderContainer"); 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"); domUtilityService.UpdateGridLayout(grid); }; domUtilityService.UpdateGridLayout = function(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(); grid.elementDims.rootMaxH = grid.$root.height(); //check to see if anything has changed grid.refreshDomSizes(); grid.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.visibleColumns(), sumWidth = 0; if (!$style) { $style = $('#' + gridId); if (!$style[0]) { $style = $("