You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2014/12/10 16:17:28 UTC

[1/5] incubator-nifi git commit: NIFI-27: - Latest version of slickgrid.

Repository: incubator-nifi
Updated Branches:
  refs/heads/nifi-27 0ded44245 -> eb880757b


http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.grid.js
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.grid.js b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.grid.js
index b4a2d7d..c12bae9 100755
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.grid.js
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.grid.js
@@ -1,2832 +1,3422 @@
-/**
- * @license
- * (c) 2009-2012 Michael Leibman
- * michael{dot}leibman{at}gmail{dot}com
- * http://github.com/mleibman/slickgrid
- *
- * Distributed under MIT license.
- * All rights reserved.
- *
- * SlickGrid v2.0
- *
- * NOTES:
- *     Cell/row DOM manipulations are done directly bypassing jQuery's DOM manipulation methods.
- *     This increases the speed dramatically, but can only be done safely because there are no event handlers
- *     or data associated with any cell/row DOM nodes.  Cell editors must make sure they implement .destroy()
- *     and do proper cleanup.
- */
-
-// make sure required JavaScript modules are loaded
-if (typeof jQuery === "undefined") {
-    throw "SlickGrid requires jquery module to be loaded";
-}
-if (!jQuery.fn.drag) {
-    throw "SlickGrid requires jquery.event.drag module to be loaded";
-}
-if (typeof Slick === "undefined") {
-    throw "slick.core.js not loaded";
-}
-
-
-(function ($) {
-    // Slick.Grid
-    $.extend(true, window, {
-        Slick: {
-            Grid: SlickGrid
-        }
-    });
-
-    // shared across all grids on the page
-    var scrollbarDimensions;
-    var maxSupportedCssHeight;  // browser's breaking point
-
-    //////////////////////////////////////////////////////////////////////////////////////////////
-    // SlickGrid class implementation (available as Slick.Grid)
-
-    /**
-     * Creates a new instance of the grid.
-     * @class SlickGrid
-     * @constructor
-     * @param {Node}              container   Container node to create the grid in.
-     * @param {Array,Object}      data        An array of objects for databinding.
-     * @param {Array}             columns     An array of column definitions.
-     * @param {Object}            options     Grid options.
-     **/
-    function SlickGrid(container, data, columns, options) {
-        // settings
-        var defaults = {
-            explicitInitialization: false,
-            rowHeight: 25,
-            defaultColumnWidth: 80,
-            enableAddRow: false,
-            leaveSpaceForNewRows: false,
-            editable: false,
-            autoEdit: true,
-            enableCellNavigation: true,
-            enableColumnReorder: true,
-            asyncEditorLoading: false,
-            asyncEditorLoadDelay: 100,
-            forceFitColumns: false,
-            enableAsyncPostRender: false,
-            asyncPostRenderDelay: 60,
-            autoHeight: false,
-            editorLock: Slick.GlobalEditorLock,
-            showHeaderRow: false,
-            headerRowHeight: 25,
-            showTopPanel: false,
-            topPanelHeight: 25,
-            formatterFactory: null,
-            editorFactory: null,
-            cellFlashingCssClass: "flashing",
-            selectedCellCssClass: "selected",
-            multiSelect: true,
-            enableTextSelectionOnCells: false,
-            dataItemColumnValueExtractor: null,
-            fullWidthRows: false,
-            multiColumnSort: false,
-            defaultFormatter: defaultFormatter
-        };
-
-        var columnDefaults = {
-            name: "",
-            resizable: true,
-            sortable: false,
-            minWidth: 30,
-            rerenderOnResize: false,
-            headerCssClass: null
-        };
-
-        // scroller
-        var th;   // virtual height
-        var h;    // real scrollable height
-        var ph;   // page height
-        var n;    // number of pages
-        var cj;   // "jumpiness" coefficient
-
-        var page = 0;       // current page
-        var offset = 0;     // current page offset
-        var scrollDir = 1;
-
-        // private
-        var initialized = false;
-        var $container;
-        var uid = "slickgrid_" + Math.round(1000000 * Math.random());
-        var self = this;
-        var $focusSink;
-        var $headerScroller;
-        var $headers;
-        var $headerRow, $headerRowScroller;
-        var $topPanelScroller;
-        var $topPanel;
-        var $viewport;
-        var $canvas;
-        var $style;
-        var stylesheet, columnCssRulesL, columnCssRulesR;
-        var viewportH, viewportW;
-        var canvasWidth;
-        var viewportHasHScroll, viewportHasVScroll;
-        var headerColumnWidthDiff = 0, headerColumnHeightDiff = 0, // border+padding
-                cellWidthDiff = 0, cellHeightDiff = 0;
-        var absoluteColumnMinWidth;
-        var numberOfRows = 0;
-
-        var activePosX;
-        var activeRow, activeCell;
-        var activeCellNode = null;
-        var currentEditor = null;
-        var serializedEditorValue;
-        var editController;
-
-        var rowsCache = {};
-        var renderedRows = 0;
-        var numVisibleRows;
-        var prevScrollTop = 0;
-        var scrollTop = 0;
-        var lastRenderedScrollTop = 0;
-        var prevScrollLeft = 0;
-        var avgRowRenderTime = 10;
-
-        var selectionModel;
-        var selectedRows = [];
-
-        var plugins = [];
-        var cellCssClasses = {};
-
-        var columnsById = {};
-        var sortColumns = [];
-
-
-        // async call handles
-        var h_editorLoader = null;
-        var h_render = null;
-        var h_postrender = null;
-        var postProcessedRows = {};
-        var postProcessToRow = null;
-        var postProcessFromRow = null;
-
-        // perf counters
-        var counter_rows_rendered = 0;
-        var counter_rows_removed = 0;
-
-
-        //////////////////////////////////////////////////////////////////////////////////////////////
-        // Initialization
-
-        function init() {
-            $container = $(container);
-            if ($container.length < 1) {
-                throw new Error("SlickGrid requires a valid container, " + container + " does not exist in the DOM.");
-            }
-
-            // calculate these only once and share between grid instances
-            maxSupportedCssHeight = maxSupportedCssHeight || getMaxSupportedCssHeight();
-            scrollbarDimensions = scrollbarDimensions || measureScrollbar();
-
-            options = $.extend({}, defaults, options);
-            columnDefaults.width = options.defaultColumnWidth;
-
-            // validate loaded JavaScript modules against requested options
-            if (options.enableColumnReorder && !$.fn.sortable) {
-                throw new Error("SlickGrid's 'enableColumnReorder = true' option requires jquery-ui.sortable module to be loaded");
-            }
-
-            editController = {
-                "commitCurrentEdit": commitCurrentEdit,
-                "cancelCurrentEdit": cancelCurrentEdit
-            };
-
-            $container
-                    .empty()
-                    .css("overflow", "hidden")
-                    .css("outline", 0)
-                    .addClass(uid)
-                    .addClass("ui-widget");
-
-            // set up a positioning container if needed
-            if (!/relative|absolute|fixed/.test($container.css("position"))) {
-                $container.css("position", "relative");
-            }
-
-            $focusSink = $("<div tabIndex='0' hideFocus style='position:fixed;width:0;height:0;top:0;left:0;outline:0;'></div>").appendTo($container);
-
-            $headerScroller = $("<div class='slick-header ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
-            $headers = $("<div class='slick-header-columns' style='width:10000px; left:-1000px' />").appendTo($headerScroller);
-
-            $headerRowScroller = $("<div class='slick-headerrow ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
-            $headerRow = $("<div class='slick-headerrow-columns' />").appendTo($headerRowScroller);
-
-            $topPanelScroller = $("<div class='slick-top-panel-scroller ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
-            $topPanel = $("<div class='slick-top-panel' style='width:10000px' />").appendTo($topPanelScroller);
-
-            if (!options.showTopPanel) {
-                $topPanelScroller.hide();
-            }
-
-            if (!options.showHeaderRow) {
-                $headerRowScroller.hide();
-            }
-
-            $viewport = $("<div class='slick-viewport' style='width:100%;overflow:auto;outline:0;position:relative;;'>").appendTo($container);
-            $viewport.css("overflow-y", options.autoHeight ? "hidden" : "auto");
-
-            $canvas = $("<div class='grid-canvas' />").appendTo($viewport);
-
-            if (!options.explicitInitialization) {
-                finishInitialization();
-            }
-        }
-
-        function finishInitialization() {
-            if (!initialized) {
-                initialized = true;
-
-                viewportW = parseFloat($.css($container[0], "width", true));
-
-                // header columns and cells may have different padding/border skewing width calculations (box-sizing, hello?)
-                // calculate the diff so we can set consistent sizes
-                measureCellPaddingAndBorder();
-
-                // for usability reasons, all text selection in SlickGrid is disabled
-                // with the exception of input and textarea elements (selection must
-                // be enabled there so that editors work as expected); note that
-                // selection in grid cells (grid body) is already unavailable in
-                // all browsers except IE
-                disableSelection($headers); // disable all text selection in header (including input and textarea)
-
-                if (!options.enableTextSelectionOnCells) {
-                    // disable text selection in grid cells except in input and textarea elements
-                    // (this is IE-specific, because selectstart event will only fire in IE)
-                    $viewport.bind("selectstart.ui", function (event) {
-                        return $(event.target).is("input,textarea");
-                    });
-                }
-
-                createColumnHeaders();
-                setupColumnSort();
-                createCssRules();
-                resizeCanvas();
-                bindAncestorScrollEvents();
-
-                $container
-                        .bind("resize.slickgrid", resizeCanvas);
-                $viewport
-                        .bind("scroll.slickgrid", handleScroll);
-                $headerScroller
-                        .bind("contextmenu.slickgrid", handleHeaderContextMenu)
-                        .bind("click.slickgrid", handleHeaderClick);
-                $focusSink
-                        .bind("keydown.slickgrid", handleKeyDown);
-                $canvas
-                        .bind("keydown.slickgrid", handleKeyDown)
-                        .bind("click.slickgrid", handleClick)
-                        .bind("dblclick.slickgrid", handleDblClick)
-                        .bind("contextmenu.slickgrid", handleContextMenu)
-                        .bind("draginit", handleDragInit)
-                        .bind("dragstart", handleDragStart)
-                        .bind("drag", handleDrag)
-                        .bind("dragend", handleDragEnd)
-                        .delegate(".slick-cell", "mouseenter", handleMouseEnter)
-                        .delegate(".slick-cell", "mouseleave", handleMouseLeave);
-            }
-        }
-
-        function registerPlugin(plugin) {
-            plugins.unshift(plugin);
-            plugin.init(self);
-        }
-
-        function unregisterPlugin(plugin) {
-            for (var i = plugins.length; i >= 0; i--) {
-                if (plugins[i] === plugin) {
-                    if (plugins[i].destroy) {
-                        plugins[i].destroy();
-                    }
-                    plugins.splice(i, 1);
-                    break;
-                }
-            }
-        }
-
-        function setSelectionModel(model) {
-            if (selectionModel) {
-                selectionModel.onSelectedRangesChanged.unsubscribe(handleSelectedRangesChanged);
-                if (selectionModel.destroy) {
-                    selectionModel.destroy();
-                }
-            }
-
-            selectionModel = model;
-            if (selectionModel) {
-                selectionModel.init(self);
-                selectionModel.onSelectedRangesChanged.subscribe(handleSelectedRangesChanged);
-            }
-        }
-
-        function getSelectionModel() {
-            return selectionModel;
-        }
-
-        function getCanvasNode() {
-            return $canvas[0];
-        }
-
-        function measureScrollbar() {
-            var $c = $("<div style='position:absolute; top:-10000px; left:-10000px; width:100px; height:100px; overflow:scroll;'></div>").appendTo("body");
-            var dim = {
-                width: $c.width() - $c[0].clientWidth,
-                height: $c.height() - $c[0].clientHeight
-            };
-            $c.remove();
-            return dim;
-        }
-
-        function getCanvasWidth() {
-            var availableWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW;
-            var rowWidth = 0;
-            var i = columns.length;
-            while (i--) {
-                rowWidth += (columns[i].width || columnDefaults.width);
-            }
-            return options.fullWidthRows ? Math.max(rowWidth, availableWidth) : rowWidth;
-        }
-
-        function updateCanvasWidth(forceColumnWidthsUpdate) {
-            var oldCanvasWidth = canvasWidth;
-            canvasWidth = getCanvasWidth();
-
-            if (canvasWidth != oldCanvasWidth) {
-                $canvas.width(canvasWidth);
-                $headerRow.width(canvasWidth);
-                viewportHasHScroll = (canvasWidth > viewportW - scrollbarDimensions.width);
-            }
-
-            if (canvasWidth != oldCanvasWidth || forceColumnWidthsUpdate) {
-                applyColumnWidths();
-            }
-        }
-
-        function disableSelection($target) {
-            if ($target && $target.jquery) {
-                $target
-                        .attr("unselectable", "on")
-                        .css("MozUserSelect", "none")
-                        .bind("selectstart.ui", function () {
-                            return false;
-                        }); // from jquery:ui.core.js 1.7.2
-            }
-        }
-
-        function getMaxSupportedCssHeight() {
-            var increment = 1000000;
-            var supportedHeight = increment;
-            // FF reports the height back but still renders blank after ~6M px
-            var testUpTo = ($.browser.mozilla) ? 5000000 : 1000000000;
-            var div = $("<div style='display:none' />").appendTo(document.body);
-
-            while (supportedHeight <= testUpTo) {
-                div.css("height", supportedHeight + increment);
-                if (div.height() !== supportedHeight + increment) {
-                    break;
-                } else {
-                    supportedHeight += increment;
-                }
-            }
-
-            div.remove();
-            return supportedHeight;
-        }
-
-        // TODO:  this is static.  need to handle page mutation.
-        function bindAncestorScrollEvents() {
-            var elem = $canvas[0];
-            while ((elem = elem.parentNode) != document.body && elem != null) {
-                // bind to scroll containers only
-                if (elem == $viewport[0] || elem.scrollWidth != elem.clientWidth || elem.scrollHeight != elem.clientHeight) {
-                    $(elem).bind("scroll.slickgrid", handleActiveCellPositionChange);
-                }
-            }
-        }
-
-        function unbindAncestorScrollEvents() {
-            $canvas.parents().unbind("scroll.slickgrid");
-        }
-
-        function updateColumnHeader(columnId, title, toolTip) {
-            if (!initialized) {
-                return;
-            }
-            var idx = getColumnIndex(columnId);
-            var $header = $headers.children().eq(idx);
-            if ($header) {
-                columns[idx].name = title;
-                columns[idx].toolTip = toolTip;
-                $header
-                        .attr("title", toolTip || title || "")
-                        .children().eq(0).html(title);
-            }
-        }
-
-        function getHeaderRow() {
-            return $headerRow[0];
-        }
-
-        function getHeaderRowColumn(columnId) {
-            var idx = getColumnIndex(columnId);
-            var $header = $headerRow.children().eq(idx);
-            return $header && $header[0];
-        }
-
-        function createColumnHeaders() {
-            function hoverBegin() {
-                $(this).addClass("ui-state-hover");
-            }
-
-            function hoverEnd() {
-                $(this).removeClass("ui-state-hover");
-            }
-
-            $headers.empty();
-            $headerRow.empty();
-            columnsById = {};
-
-            for (var i = 0; i < columns.length; i++) {
-                var m = columns[i] = $.extend({}, columnDefaults, columns[i]);
-                columnsById[m.id] = i;
-
-                var header = $("<div class='ui-state-default slick-header-column' id='" + uid + m.id + "' />")
-                        .html("<span class='slick-column-name'>" + m.name + "</span>")
-                        .width(m.width - headerColumnWidthDiff)
-                        .attr("title", m.toolTip || m.name || "")
-                        .data("fieldId", m.id)
-                        .addClass(m.headerCssClass || "")
-                        .appendTo($headers);
-
-                if (options.enableColumnReorder || m.sortable) {
-                    header.hover(hoverBegin, hoverEnd);
-                }
-
-                if (m.sortable) {
-                    header.append("<span class='slick-sort-indicator' />");
-                }
-
-                if (options.showHeaderRow) {
-                    $("<div class='ui-state-default slick-headerrow-column l" + i + " r" + i + "'></div>")
-                            .appendTo($headerRow);
-                }
-            }
-
-            if (options.showHeaderRow) {
-                // add a spacer to let the container scroll beyond the header row columns width
-                $("<div style='display:block;height:1px;width:10000px;position:absolute;top:0;left:0;'></div>")
-                        .appendTo($headerRowScroller);
-            }
-
-            setSortColumns(sortColumns);
-            setupColumnResize();
-            if (options.enableColumnReorder) {
-                setupColumnReorder();
-            }
-        }
-
-        function setupColumnSort() {
-            $headers.click(function (e) {
-                // temporary workaround for a bug in jQuery 1.7.1 (http://bugs.jquery.com/ticket/11328)
-                e.metaKey = e.metaKey || e.ctrlKey;
-
-                if ($(e.target).hasClass("slick-resizable-handle")) {
-                    return;
-                }
-
-                var $col = $(e.target).closest(".slick-header-column");
-                if (!$col.length) {
-                    return;
-                }
-
-                var column = columns[getColumnIndex($col.data("fieldId"))];
-                if (column.sortable) {
-                    if (!getEditorLock().commitCurrentEdit()) {
-                        return;
-                    }
-
-                    var sortOpts = null;
-                    var i = 0;
-                    for (; i < sortColumns.length; i++) {
-                        if (sortColumns[i].columnId == column.id) {
-                            sortOpts = sortColumns[i];
-                            sortOpts.sortAsc = !sortOpts.sortAsc;
-                            break;
-                        }
-                    }
-
-                    if (e.metaKey && options.multiColumnSort) {
-                        if (sortOpts) {
-                            sortColumns.splice(i, 1);
-                        }
-                    }
-                    else {
-                        if ((!e.shiftKey && !e.metaKey) || !options.multiColumnSort) {
-                            sortColumns = [];
-                        }
-
-                        if (!sortOpts) {
-                            sortOpts = {columnId: column.id, sortAsc: true};
-                            sortColumns.push(sortOpts);
-                        } else if (sortColumns.length == 0) {
-                            sortColumns.push(sortOpts);
-                        }
-                    }
-
-                    setSortColumns(sortColumns);
-
-                    if (!options.multiColumnSort) {
-                        trigger(self.onSort, {
-                            multiColumnSort: false,
-                            sortCol: column,
-                            sortAsc: sortOpts.sortAsc}, e);
-                    } else {
-                        trigger(self.onSort, {
-                            multiColumnSort: true,
-                            sortCols: $.map(sortColumns, function (col) {
-                                return {sortCol: columns[getColumnIndex(col.columnId)], sortAsc: col.sortAsc};
-                            })}, e);
-                    }
-                }
-            });
-        }
-
-        function setupColumnReorder() {
-            $headers.sortable({
-                containment: "parent",
-                axis: "x",
-                cursor: "default",
-                tolerance: "intersection",
-                helper: "clone",
-                placeholder: "slick-sortable-placeholder ui-state-default slick-header-column",
-                forcePlaceholderSize: true,
-                start: function (e, ui) {
-                    $(ui.helper).addClass("slick-header-column-active");
-                },
-                beforeStop: function (e, ui) {
-                    $(ui.helper).removeClass("slick-header-column-active");
-                },
-                stop: function (e) {
-                    if (!getEditorLock().commitCurrentEdit()) {
-                        $(this).sortable("cancel");
-                        return;
-                    }
-
-                    var reorderedIds = $headers.sortable("toArray");
-                    var reorderedColumns = [];
-                    for (var i = 0; i < reorderedIds.length; i++) {
-                        reorderedColumns.push(columns[getColumnIndex(reorderedIds[i].replace(uid, ""))]);
-                    }
-                    setColumns(reorderedColumns);
-
-                    trigger(self.onColumnsReordered, {});
-                    e.stopPropagation();
-                    setupColumnResize();
-                }
-            });
-        }
-
-        function setupColumnResize() {
-            var $col, j, c, pageX, columnElements, minPageX, maxPageX, firstResizable, lastResizable;
-            columnElements = $headers.children();
-            columnElements.find(".slick-resizable-handle").remove();
-            columnElements.each(function (i, e) {
-                if (columns[i].resizable) {
-                    if (firstResizable === undefined) {
-                        firstResizable = i;
-                    }
-                    lastResizable = i;
-                }
-            });
-            if (firstResizable === undefined) {
-                return;
-            }
-            columnElements.each(function (i, e) {
-                if (i < firstResizable || (options.forceFitColumns && i >= lastResizable)) {
-                    return;
-                }
-                $col = $(e);
-                $("<div class='slick-resizable-handle' />")
-                        .appendTo(e)
-                        .bind("dragstart", function (e, dd) {
-                            if (!getEditorLock().commitCurrentEdit()) {
-                                return false;
-                            }
-                            pageX = e.pageX;
-                            $(this).parent().addClass("slick-header-column-active");
-                            var shrinkLeewayOnRight = null, stretchLeewayOnRight = null;
-                            // lock each column's width option to current width
-                            columnElements.each(function (i, e) {
-                                columns[i].previousWidth = $(e).outerWidth();
-                            });
-                            if (options.forceFitColumns) {
-                                shrinkLeewayOnRight = 0;
-                                stretchLeewayOnRight = 0;
-                                // colums on right affect maxPageX/minPageX
-                                for (j = i + 1; j < columnElements.length; j++) {
-                                    c = columns[j];
-                                    if (c.resizable) {
-                                        if (stretchLeewayOnRight !== null) {
-                                            if (c.maxWidth) {
-                                                stretchLeewayOnRight += c.maxWidth - c.previousWidth;
-                                            } else {
-                                                stretchLeewayOnRight = null;
-                                            }
-                                        }
-                                        shrinkLeewayOnRight += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth);
-                                    }
-                                }
-                            }
-                            var shrinkLeewayOnLeft = 0, stretchLeewayOnLeft = 0;
-                            for (j = 0; j <= i; j++) {
-                                // columns on left only affect minPageX
-                                c = columns[j];
-                                if (c.resizable) {
-                                    if (stretchLeewayOnLeft !== null) {
-                                        if (c.maxWidth) {
-                                            stretchLeewayOnLeft += c.maxWidth - c.previousWidth;
-                                        } else {
-                                            stretchLeewayOnLeft = null;
-                                        }
-                                    }
-                                    shrinkLeewayOnLeft += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth);
-                                }
-                            }
-                            if (shrinkLeewayOnRight === null) {
-                                shrinkLeewayOnRight = 100000;
-                            }
-                            if (shrinkLeewayOnLeft === null) {
-                                shrinkLeewayOnLeft = 100000;
-                            }
-                            if (stretchLeewayOnRight === null) {
-                                stretchLeewayOnRight = 100000;
-                            }
-                            if (stretchLeewayOnLeft === null) {
-                                stretchLeewayOnLeft = 100000;
-                            }
-                            maxPageX = pageX + Math.min(shrinkLeewayOnRight, stretchLeewayOnLeft);
-                            minPageX = pageX - Math.min(shrinkLeewayOnLeft, stretchLeewayOnRight);
-                        })
-                        .bind("drag", function (e, dd) {
-                            var actualMinWidth, d = Math.min(maxPageX, Math.max(minPageX, e.pageX)) - pageX, x;
-                            if (d < 0) { // shrink column
-                                x = d;
-                                for (j = i; j >= 0; j--) {
-                                    c = columns[j];
-                                    if (c.resizable) {
-                                        actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth);
-                                        if (x && c.previousWidth + x < actualMinWidth) {
-                                            x += c.previousWidth - actualMinWidth;
-                                            c.width = actualMinWidth;
-                                        } else {
-                                            c.width = c.previousWidth + x;
-                                            x = 0;
-                                        }
-                                    }
-                                }
-
-                                if (options.forceFitColumns) {
-                                    x = -d;
-                                    for (j = i + 1; j < columnElements.length; j++) {
-                                        c = columns[j];
-                                        if (c.resizable) {
-                                            if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {
-                                                x -= c.maxWidth - c.previousWidth;
-                                                c.width = c.maxWidth;
-                                            } else {
-                                                c.width = c.previousWidth + x;
-                                                x = 0;
-                                            }
-                                        }
-                                    }
-                                }
-                            } else { // stretch column
-                                x = d;
-                                for (j = i; j >= 0; j--) {
-                                    c = columns[j];
-                                    if (c.resizable) {
-                                        if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {
-                                            x -= c.maxWidth - c.previousWidth;
-                                            c.width = c.maxWidth;
-                                        } else {
-                                            c.width = c.previousWidth + x;
-                                            x = 0;
-                                        }
-                                    }
-                                }
-
-                                if (options.forceFitColumns) {
-                                    x = -d;
-                                    for (j = i + 1; j < columnElements.length; j++) {
-                                        c = columns[j];
-                                        if (c.resizable) {
-                                            actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth);
-                                            if (x && c.previousWidth + x < actualMinWidth) {
-                                                x += c.previousWidth - actualMinWidth;
-                                                c.width = actualMinWidth;
-                                            } else {
-                                                c.width = c.previousWidth + x;
-                                                x = 0;
-                                            }
-                                        }
-                                    }
-                                }
-                            }
-                            applyColumnHeaderWidths();
-                            if (options.syncColumnCellResize) {
-                                applyColumnWidths();
-                            }
-                        })
-                        .bind("dragend", function (e, dd) {
-                            var newWidth;
-                            $(this).parent().removeClass("slick-header-column-active");
-                            for (j = 0; j < columnElements.length; j++) {
-                                c = columns[j];
-                                newWidth = $(columnElements[j]).outerWidth();
-
-                                if (c.previousWidth !== newWidth && c.rerenderOnResize) {
-                                    invalidateAllRows();
-                                }
-                            }
-                            updateCanvasWidth(true);
-                            render();
-                            trigger(self.onColumnsResized, {});
-                        });
-            });
-        }
-
-        function getVBoxDelta($el) {
-            var p = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"];
-            var delta = 0;
-            $.each(p, function (n, val) {
-                delta += parseFloat($el.css(val)) || 0;
-            });
-            return delta;
-        }
-
-        function measureCellPaddingAndBorder() {
-            var el;
-            var h = ["borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight"];
-            var v = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"];
-
-            el = $("<div class='ui-state-default slick-header-column' style='visibility:hidden'>-</div>").appendTo($headers);
-            headerColumnWidthDiff = headerColumnHeightDiff = 0;
-            $.each(h, function (n, val) {
-                headerColumnWidthDiff += parseFloat(el.css(val)) || 0;
-            });
-            $.each(v, function (n, val) {
-                headerColumnHeightDiff += parseFloat(el.css(val)) || 0;
-            });
-            el.remove();
-
-            var r = $("<div class='slick-row' />").appendTo($canvas);
-            el = $("<div class='slick-cell' id='' style='visibility:hidden'>-</div>").appendTo(r);
-            cellWidthDiff = cellHeightDiff = 0;
-            $.each(h, function (n, val) {
-                cellWidthDiff += parseFloat(el.css(val)) || 0;
-            });
-            $.each(v, function (n, val) {
-                cellHeightDiff += parseFloat(el.css(val)) || 0;
-            });
-            r.remove();
-
-            absoluteColumnMinWidth = Math.max(headerColumnWidthDiff, cellWidthDiff);
-        }
-
-        function createCssRules() {
-            $style = $("<style type='text/css' rel='stylesheet' />").appendTo($("head"));
-            var rowHeight = (options.rowHeight - cellHeightDiff);
-            var rules = [
-                "." + uid + " .slick-header-column { left: 1000px; }",
-                "." + uid + " .slick-top-panel { height:" + options.topPanelHeight + "px; }",
-                "." + uid + " .slick-headerrow-columns { height:" + options.headerRowHeight + "px; }",
-                "." + uid + " .slick-cell { height:" + rowHeight + "px; }",
-                "." + uid + " .slick-row { height:" + options.rowHeight + "px; }"
-            ];
-
-            for (var i = 0; i < columns.length; i++) {
-                rules.push("." + uid + " .l" + i + " { }");
-                rules.push("." + uid + " .r" + i + " { }");
-            }
-
-            if ($style[0].styleSheet) { // IE
-                $style[0].styleSheet.cssText = rules.join(" ");
-            } else {
-                $style[0].appendChild(document.createTextNode(rules.join(" ")));
-            }
-        }
-
-        function getColumnCssRules(idx) {
-            if (!stylesheet) {
-                var sheets = document.styleSheets;
-                for (var i = 0; i < sheets.length; i++) {
-                    if ((sheets[i].ownerNode || sheets[i].owningElement) == $style[0]) {
-                        stylesheet = sheets[i];
-                        break;
-                    }
-                }
-
-                if (!stylesheet) {
-                    throw new Error("Cannot find stylesheet.");
-                }
-
-                // find and cache column CSS rules
-                columnCssRulesL = [];
-                columnCssRulesR = [];
-                var cssRules = (stylesheet.cssRules || stylesheet.rules);
-                var matches, columnIdx;
-                for (var i = 0; i < cssRules.length; i++) {
-                    var selector = cssRules[i].selectorText;
-                    if (matches = /\.l\d+/.exec(selector)) {
-                        columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10);
-                        columnCssRulesL[columnIdx] = cssRules[i];
-                    } else if (matches = /\.r\d+/.exec(selector)) {
-                        columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10);
-                        columnCssRulesR[columnIdx] = cssRules[i];
-                    }
-                }
-            }
-
-            return {
-                "left": columnCssRulesL[idx],
-                "right": columnCssRulesR[idx]
-            };
-        }
-
-        function removeCssRules() {
-            $style.remove();
-            stylesheet = null;
-        }
-
-        function destroy() {
-            getEditorLock().cancelCurrentEdit();
-
-            trigger(self.onBeforeDestroy, {});
-
-            for (var i = 0; i < plugins.length; i++) {
-                unregisterPlugin(plugins[i]);
-            }
-
-            if (options.enableColumnReorder && $headers.sortable) {
-                $headers.sortable("destroy");
-            }
-
-            unbindAncestorScrollEvents();
-            $container.unbind(".slickgrid");
-            removeCssRules();
-
-            $canvas.unbind("draginit dragstart dragend drag");
-            $container.empty().removeClass(uid);
-        }
-
-
-        //////////////////////////////////////////////////////////////////////////////////////////////
-        // General
-
-        function trigger(evt, args, e) {
-            e = e || new Slick.EventData();
-            args = args || {};
-            args.grid = self;
-            return evt.notify(args, e, self);
-        }
-
-        function getEditorLock() {
-            return options.editorLock;
-        }
-
-        function getEditController() {
-            return editController;
-        }
-
-        function getColumnIndex(id) {
-            return columnsById[id];
-        }
-
-        function autosizeColumns() {
-            var i, c,
-                    widths = [],
-                    shrinkLeeway = 0,
-                    total = 0,
-                    prevTotal,
-                    availWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW;
-
-            for (i = 0; i < columns.length; i++) {
-                c = columns[i];
-                widths.push(c.width);
-                total += c.width;
-                if (c.resizable) {
-                    shrinkLeeway += c.width - Math.max(c.minWidth, absoluteColumnMinWidth);
-                }
-            }
-
-            // shrink
-            prevTotal = total;
-            while (total > availWidth && shrinkLeeway) {
-                var shrinkProportion = (total - availWidth) / shrinkLeeway;
-                for (i = 0; i < columns.length && total > availWidth; i++) {
-                    c = columns[i];
-                    var width = widths[i];
-                    if (!c.resizable || width <= c.minWidth || width <= absoluteColumnMinWidth) {
-                        continue;
-                    }
-                    var absMinWidth = Math.max(c.minWidth, absoluteColumnMinWidth);
-                    var shrinkSize = Math.floor(shrinkProportion * (width - absMinWidth)) || 1;
-                    shrinkSize = Math.min(shrinkSize, width - absMinWidth);
-                    total -= shrinkSize;
-                    shrinkLeeway -= shrinkSize;
-                    widths[i] -= shrinkSize;
-                }
-                if (prevTotal == total) {  // avoid infinite loop
-                    break;
-                }
-                prevTotal = total;
-            }
-
-            // grow
-            prevTotal = total;
-            while (total < availWidth) {
-                var growProportion = availWidth / total;
-                for (i = 0; i < columns.length && total < availWidth; i++) {
-                    c = columns[i];
-                    if (!c.resizable || c.maxWidth <= c.width) {
-                        continue;
-                    }
-                    var growSize = Math.min(Math.floor(growProportion * c.width) - c.width, (c.maxWidth - c.width) || 1000000) || 1;
-                    total += growSize;
-                    widths[i] += growSize;
-                }
-                if (prevTotal == total) {  // avoid infinite loop
-                    break;
-                }
-                prevTotal = total;
-            }
-
-            var reRender = false;
-            for (i = 0; i < columns.length; i++) {
-                if (columns[i].rerenderOnResize && columns[i].width != widths[i]) {
-                    reRender = true;
-                }
-                columns[i].width = widths[i];
-            }
-
-            applyColumnHeaderWidths();
-            updateCanvasWidth(true);
-            if (reRender) {
-                invalidateAllRows();
-                render();
-            }
-        }
-
-        function applyColumnHeaderWidths() {
-            if (!initialized) {
-                return;
-            }
-            var h;
-            for (var i = 0, headers = $headers.children(), ii = headers.length; i < ii; i++) {
-                h = $(headers[i]);
-                if (h.width() !== columns[i].width - headerColumnWidthDiff) {
-                    h.width(columns[i].width - headerColumnWidthDiff);
-                }
-            }
-        }
-
-        function applyColumnWidths() {
-            var x = 0, w, rule;
-            for (var i = 0; i < columns.length; i++) {
-                w = columns[i].width;
-
-                rule = getColumnCssRules(i);
-                rule.left.style.left = x + "px";
-                rule.right.style.right = (canvasWidth - x - w) + "px";
-
-                x += columns[i].width;
-            }
-        }
-
-        function setSortColumn(columnId, ascending) {
-            setSortColumns([{columnId: columnId, sortAsc: ascending}]);
-        }
-
-        function setSortColumns(cols) {
-            sortColumns = cols;
-
-            var headerColumnEls = $headers.children();
-            headerColumnEls
-                    .removeClass("slick-header-column-sorted")
-                    .find(".slick-sort-indicator")
-                    .removeClass("slick-sort-indicator-asc slick-sort-indicator-desc");
-
-            $.each(sortColumns, function (i, col) {
-                if (col.sortAsc == null) {
-                    col.sortAsc = true;
-                }
-                var columnIndex = getColumnIndex(col.columnId);
-                if (columnIndex != null) {
-                    headerColumnEls.eq(columnIndex)
-                            .addClass("slick-header-column-sorted")
-                            .find(".slick-sort-indicator")
-                            .addClass(col.sortAsc ? "slick-sort-indicator-asc" : "slick-sort-indicator-desc");
-                }
-            });
-        }
-
-        function getSortColumns() {
-            return sortColumns;
-        }
-
-        function handleSelectedRangesChanged(e, ranges) {
-            selectedRows = [];
-            var hash = {};
-            for (var i = 0; i < ranges.length; i++) {
-                for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
-                    if (!hash[j]) {  // prevent duplicates
-                        selectedRows.push(j);
-                    }
-                    hash[j] = {};
-                    for (var k = ranges[i].fromCell; k <= ranges[i].toCell; k++) {
-                        if (canCellBeSelected(j, k)) {
-                            hash[j][columns[k].id] = options.selectedCellCssClass;
-                        }
-                    }
-                }
-            }
-
-            setCellCssStyles(options.selectedCellCssClass, hash);
-
-            trigger(self.onSelectedRowsChanged, {rows: getSelectedRows()}, e);
-        }
-
-        function getColumns() {
-            return columns;
-        }
-
-        function setColumns(columnDefinitions) {
-            columns = columnDefinitions;
-            if (initialized) {
-                invalidateAllRows();
-                createColumnHeaders();
-                removeCssRules();
-                createCssRules();
-                resizeCanvas();
-                applyColumnWidths();
-                handleScroll();
-            }
-        }
-
-        function getOptions() {
-            return options;
-        }
-
-        function setOptions(args) {
-            if (!getEditorLock().commitCurrentEdit()) {
-                return;
-            }
-
-            makeActiveCellNormal();
-
-            if (options.enableAddRow !== args.enableAddRow) {
-                invalidateRow(getDataLength());
-            }
-
-            options = $.extend(options, args);
-
-            $viewport.css("overflow-y", options.autoHeight ? "hidden" : "auto");
-            render();
-        }
-
-        function setData(newData, scrollToTop) {
-            data = newData;
-            invalidateAllRows();
-            updateRowCount();
-            if (scrollToTop) {
-                scrollTo(0);
-            }
-        }
-
-        function getData() {
-            return data;
-        }
-
-        function getDataLength() {
-            if (data.getLength) {
-                return data.getLength();
-            } else {
-                return data.length;
-            }
-        }
-
-        function getDataItem(i) {
-            if (data.getItem) {
-                return data.getItem(i);
-            } else {
-                return data[i];
-            }
-        }
-
-        function getTopPanel() {
-            return $topPanel[0];
-        }
-
-        function showTopPanel() {
-            options.showTopPanel = true;
-            $topPanelScroller.slideDown("fast", resizeCanvas);
-        }
-
-        function hideTopPanel() {
-            options.showTopPanel = false;
-            $topPanelScroller.slideUp("fast", resizeCanvas);
-        }
-
-        function showHeaderRowColumns() {
-            options.showHeaderRow = true;
-            $headerRowScroller.slideDown("fast", resizeCanvas);
-        }
-
-        function hideHeaderRowColumns() {
-            options.showHeaderRow = false;
-            $headerRowScroller.slideUp("fast", resizeCanvas);
-        }
-
-        //////////////////////////////////////////////////////////////////////////////////////////////
-        // Rendering / Scrolling
-
-        function scrollTo(y) {
-            var oldOffset = offset;
-
-            page = Math.min(n - 1, Math.floor(y / ph));
-            offset = Math.round(page * cj);
-            var newScrollTop = y - offset;
-
-            if (offset != oldOffset) {
-                var range = getVisibleRange(newScrollTop);
-                cleanupRows(range.top, range.bottom);
-                updateRowPositions();
-            }
-
-            if (prevScrollTop != newScrollTop) {
-                scrollDir = (prevScrollTop + oldOffset < newScrollTop + offset) ? 1 : -1;
-                $viewport[0].scrollTop = (lastRenderedScrollTop = scrollTop = prevScrollTop = newScrollTop);
-
-                trigger(self.onViewportChanged, {});
-            }
-        }
-
-        function defaultFormatter(row, cell, value, columnDef, dataContext) {
-            if (value == null) {
-                return "";
-            } else {
-                return value.toString().replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
-            }
-        }
-
-        function getFormatter(row, column) {
-            var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
-
-            // look up by id, then index
-            var columnOverrides = rowMetadata &&
-                    rowMetadata.columns &&
-                    (rowMetadata.columns[column.id] || rowMetadata.columns[getColumnIndex(column.id)]);
-
-            return (columnOverrides && columnOverrides.formatter) ||
-                    (rowMetadata && rowMetadata.formatter) ||
-                    column.formatter ||
-                    (options.formatterFactory && options.formatterFactory.getFormatter(column)) ||
-                    options.defaultFormatter;
-        }
-
-        function getEditor(row, cell) {
-            var column = columns[cell];
-            var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
-            var columnMetadata = rowMetadata && rowMetadata.columns;
-
-            if (columnMetadata && columnMetadata[column.id] && columnMetadata[column.id].editor !== undefined) {
-                return columnMetadata[column.id].editor;
-            }
-            if (columnMetadata && columnMetadata[cell] && columnMetadata[cell].editor !== undefined) {
-                return columnMetadata[cell].editor;
-            }
-
-            return column.editor || (options.editorFactory && options.editorFactory.getEditor(column));
-        }
-
-        function getDataItemValueForColumn(item, columnDef) {
-            if (options.dataItemColumnValueExtractor) {
-                return options.dataItemColumnValueExtractor(item, columnDef);
-            }
-            return item[columnDef.field];
-        }
-
-        function appendRowHtml(stringArray, row) {
-            var d = getDataItem(row);
-            var dataLoading = row < getDataLength() && !d;
-            var cellCss;
-            var rowCss = "slick-row " +
-                    (dataLoading ? " loading" : "") +
-                    (row % 2 == 1 ? " odd" : " even");
-
-            var metadata = data.getItemMetadata && data.getItemMetadata(row);
-
-            if (metadata && metadata.cssClasses) {
-                rowCss += " " + metadata.cssClasses;
-            }
-
-            stringArray.push("<div class='ui-widget-content " + rowCss + "' row='" + row + "' style='top:" + (options.rowHeight * row - offset) + "px'>");
-
-            var colspan, m;
-            for (var i = 0, cols = columns.length; i < cols; i++) {
-                m = columns[i];
-                colspan = getColspan(row, i);  // TODO:  don't calc unless we have to
-                cellCss = "slick-cell l" + i + " r" + Math.min(columns.length - 1, i + colspan - 1) + (m.cssClass ? " " + m.cssClass : "");
-                if (row === activeRow && i === activeCell) {
-                    cellCss += (" active");
-                }
-
-                // TODO:  merge them together in the setter
-                for (var key in cellCssClasses) {
-                    if (cellCssClasses[key][row] && cellCssClasses[key][row][m.id]) {
-                        cellCss += (" " + cellCssClasses[key][row][m.id]);
-                    }
-                }
-
-                stringArray.push("<div class='" + cellCss + "'>");
-
-                // if there is a corresponding row (if not, this is the Add New row or this data hasn't been loaded yet)
-                if (d) {
-                    stringArray.push(getFormatter(row, m)(row, i, getDataItemValueForColumn(d, m), m, d));
-                }
-
-                stringArray.push("</div>");
-
-                if (colspan) {
-                    i += (colspan - 1);
-                }
-            }
-
-            stringArray.push("</div>");
-        }
-
-        function cleanupRows(rangeToKeep) {
-            for (var i in rowsCache) {
-                if (((i = parseInt(i, 10)) !== activeRow) && (i < rangeToKeep.top || i > rangeToKeep.bottom)) {
-                    removeRowFromCache(i);
-                }
-            }
-        }
-
-        function invalidate() {
-            updateRowCount();
-            invalidateAllRows();
-            render();
-        }
-
-        function invalidateAllRows() {
-            if (currentEditor) {
-                makeActiveCellNormal();
-            }
-            for (var row in rowsCache) {
-                removeRowFromCache(row);
-            }
-        }
-
-        function removeRowFromCache(row) {
-            var node = rowsCache[row];
-            if (!node) {
-                return;
-            }
-            $canvas[0].removeChild(node);
-            delete rowsCache[row];
-            delete postProcessedRows[row];
-            renderedRows--;
-            counter_rows_removed++;
-        }
-
-        function invalidateRows(rows) {
-            var i, rl;
-            if (!rows || !rows.length) {
-                return;
-            }
-            scrollDir = 0;
-            for (i = 0, rl = rows.length; i < rl; i++) {
-                if (currentEditor && activeRow === rows[i]) {
-                    makeActiveCellNormal();
-                }
-                if (rowsCache[rows[i]]) {
-                    removeRowFromCache(rows[i]);
-                }
-            }
-        }
-
-        function invalidateRow(row) {
-            invalidateRows([row]);
-        }
-
-        function updateCell(row, cell) {
-            var cellNode = getCellNode(row, cell);
-            if (!cellNode) {
-                return;
-            }
-
-            var m = columns[cell], d = getDataItem(row);
-            if (currentEditor && activeRow === row && activeCell === cell) {
-                currentEditor.loadValue(d);
-            } else {
-                cellNode.innerHTML = d ? getFormatter(row, m)(row, cell, getDataItemValueForColumn(d, m), m, d) : "";
-                invalidatePostProcessingResults(row);
-            }
-        }
-
-        function updateRow(row) {
-            if (!rowsCache[row]) {
-                return;
-            }
-
-            var columnIndex = 0
-            $(rowsCache[row]).children().each(function (i) {
-                var m = columns[columnIndex], d = getDataItem(row);
-                if (row === activeRow && i === activeCell && currentEditor) {
-                    currentEditor.loadValue(getDataItem(activeRow));
-                } else if (d) {
-                    this.innerHTML = getFormatter(row, m)(row, columnIndex, getDataItemValueForColumn(d, m), m, getDataItem(row));
-                } else {
-                    this.innerHTML = "";
-                }
-
-                columnIndex += getColspan(row, i);
-            });
-
-            invalidatePostProcessingResults(row);
-        }
-
-        function getViewportHeight() {
-            return parseFloat($.css($container[0], "height", true)) -
-                    parseFloat($.css($headerScroller[0], "height")) - getVBoxDelta($headerScroller) -
-                    (options.showTopPanel ? options.topPanelHeight + getVBoxDelta($topPanelScroller) : 0) -
-                    (options.showHeaderRow ? options.headerRowHeight + getVBoxDelta($headerRowScroller) : 0);
-        }
-
-        function resizeCanvas() {
-            if (!initialized) {
-                return;
-            }
-            if (options.autoHeight) {
-                viewportH = options.rowHeight * (getDataLength() + (options.enableAddRow ? 1 : 0) + (options.leaveSpaceForNewRows ? numVisibleRows - 1 : 0));
-            } else {
-                viewportH = getViewportHeight();
-            }
-
-            numVisibleRows = Math.ceil(viewportH / options.rowHeight);
-            viewportW = parseFloat($.css($container[0], "width", true));
-            $viewport.height(viewportH);
-
-            if (options.forceFitColumns) {
-                autosizeColumns();
-            }
-
-            updateRowCount();
-            render();
-        }
-
-        function updateRowCount() {
-            if (!initialized) {
-                return;
-            }
-            numberOfRows = getDataLength() +
-                    (options.enableAddRow ? 1 : 0) +
-                    (options.leaveSpaceForNewRows ? numVisibleRows - 1 : 0);
-
-            var oldViewportHasVScroll = viewportHasVScroll;
-            // with autoHeight, we do not need to accommodate the vertical scroll bar
-            viewportHasVScroll = !options.autoHeight && (numberOfRows * options.rowHeight > viewportH);
-
-            // remove the rows that are now outside of the data range
-            // this helps avoid redundant calls to .removeRow() when the size of the data decreased by thousands of rows
-            var l = options.enableAddRow ? getDataLength() : getDataLength() - 1;
-            for (var i in rowsCache) {
-                if (i >= l) {
-                    removeRowFromCache(i);
-                }
-            }
-
-            var oldH = h;
-            th = Math.max(options.rowHeight * numberOfRows, viewportH - scrollbarDimensions.height);
-            if (th < maxSupportedCssHeight) {
-                // just one page
-                h = ph = th;
-                n = 1;
-                cj = 0;
-            } else {
-                // break into pages
-                h = maxSupportedCssHeight;
-                ph = h / 100;
-                n = Math.floor(th / ph);
-                cj = (th - h) / (n - 1);
-            }
-
-            if (h !== oldH) {
-                $canvas.css("height", h);
-                scrollTop = $viewport[0].scrollTop;
-            }
-
-            var oldScrollTopInRange = (scrollTop + offset <= th - viewportH);
-
-            if (th == 0 || scrollTop == 0) {
-                page = offset = 0;
-            } else if (oldScrollTopInRange) {
-                // maintain virtual position
-                scrollTo(scrollTop + offset);
-            } else {
-                // scroll to bottom
-                scrollTo(th - viewportH);
-            }
-
-            if (h != oldH && options.autoHeight) {
-                resizeCanvas();
-            }
-
-            if (options.forceFitColumns && oldViewportHasVScroll != viewportHasVScroll) {
-                autosizeColumns();
-            }
-            updateCanvasWidth(false);
-        }
-
-        function getVisibleRange(viewportTop) {
-            if (viewportTop == null) {
-                viewportTop = scrollTop;
-            }
-
-            return {
-                top: Math.floor((viewportTop + offset) / options.rowHeight),
-                bottom: Math.ceil((viewportTop + offset + viewportH) / options.rowHeight)
-            };
-        }
-
-        function getRenderedRange(viewportTop) {
-            var range = getVisibleRange(viewportTop);
-            var buffer = Math.round(viewportH / options.rowHeight);
-            var minBuffer = 3;
-
-            if (scrollDir == -1) {
-                range.top -= buffer;
-                range.bottom += minBuffer;
-            } else if (scrollDir == 1) {
-                range.top -= minBuffer;
-                range.bottom += buffer;
-            } else {
-                range.top -= minBuffer;
-                range.bottom += minBuffer;
-            }
-
-            range.top = Math.max(0, range.top);
-            range.bottom = Math.min(options.enableAddRow ? getDataLength() : getDataLength() - 1, range.bottom);
-
-            return range;
-        }
-
-        function renderRows(range) {
-            var i, l,
-                    parentNode = $canvas[0],
-                    rowsBefore = renderedRows,
-                    stringArray = [],
-                    rows = [],
-                    startTimestamp = new Date(),
-                    needToReselectCell = false;
-
-            for (i = range.top; i <= range.bottom; i++) {
-                if (rowsCache[i]) {
-                    continue;
-                }
-                renderedRows++;
-                rows.push(i);
-                appendRowHtml(stringArray, i);
-                if (activeCellNode && activeRow === i) {
-                    needToReselectCell = true;
-                }
-                counter_rows_rendered++;
-            }
-
-            if (!rows.length) {
-                return;
-            }
-
-            var x = document.createElement("div");
-            x.innerHTML = stringArray.join("");
-
-            for (i = 0, l = x.childNodes.length; i < l; i++) {
-                rowsCache[rows[i]] = parentNode.appendChild(x.firstChild);
-            }
-
-            if (needToReselectCell) {
-                activeCellNode = getCellNode(activeRow, activeCell);
-            }
-
-            if (renderedRows - rowsBefore > 5) {
-                avgRowRenderTime = (new Date() - startTimestamp) / (renderedRows - rowsBefore);
-            }
-        }
-
-        function startPostProcessing() {
-            if (!options.enableAsyncPostRender) {
-                return;
-            }
-            clearTimeout(h_postrender);
-            h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay);
-        }
-
-        function invalidatePostProcessingResults(row) {
-            delete postProcessedRows[row];
-            postProcessFromRow = Math.min(postProcessFromRow, row);
-            postProcessToRow = Math.max(postProcessToRow, row);
-            startPostProcessing();
-        }
-
-        function updateRowPositions() {
-            for (var row in rowsCache) {
-                rowsCache[row].style.top = (row * options.rowHeight - offset) + "px";
-            }
-        }
-
-        function render() {
-            if (!initialized) {
-                return;
-            }
-            var visible = getVisibleRange();
-            var rendered = getRenderedRange();
-
-            // remove rows no longer in the viewport
-            cleanupRows(rendered);
-
-            // add new rows
-            renderRows(rendered);
-
-            postProcessFromRow = visible.top;
-            postProcessToRow = Math.min(options.enableAddRow ? getDataLength() : getDataLength() - 1, visible.bottom);
-            startPostProcessing();
-
-            lastRenderedScrollTop = scrollTop;
-            h_render = null;
-        }
-
-        function handleScroll() {
-            scrollTop = $viewport[0].scrollTop;
-            var scrollLeft = $viewport[0].scrollLeft;
-            var scrollDist = Math.abs(scrollTop - prevScrollTop);
-
-            if (scrollLeft !== prevScrollLeft) {
-                prevScrollLeft = scrollLeft;
-                $headerScroller[0].scrollLeft = scrollLeft;
-                $topPanelScroller[0].scrollLeft = scrollLeft;
-                $headerRowScroller[0].scrollLeft = scrollLeft;
-            }
-
-            if (scrollDist) {
-                scrollDir = prevScrollTop < scrollTop ? 1 : -1;
-                prevScrollTop = scrollTop;
-
-                // switch virtual pages if needed
-                if (scrollDist < viewportH) {
-                    scrollTo(scrollTop + offset);
-                } else {
-                    var oldOffset = offset;
-                    page = Math.min(n - 1, Math.floor(scrollTop * ((th - viewportH) / (h - viewportH)) * (1 / ph)));
-                    offset = Math.round(page * cj);
-                    if (oldOffset != offset) {
-                        invalidateAllRows();
-                    }
-                }
-
-                if (h_render) {
-                    clearTimeout(h_render);
-                }
-
-                if (Math.abs(lastRenderedScrollTop - scrollTop) < viewportH) {
-                    render();
-                } else {
-                    h_render = setTimeout(render, 50);
-                }
-
-                trigger(self.onViewportChanged, {});
-            }
-
-            trigger(self.onScroll, {scrollLeft: scrollLeft, scrollTop: scrollTop});
-        }
-
-        function asyncPostProcessRows() {
-            while (postProcessFromRow <= postProcessToRow) {
-                var row = (scrollDir >= 0) ? postProcessFromRow++ : postProcessToRow--;
-                var rowNode = rowsCache[row];
-                if (!rowNode || postProcessedRows[row] || row >= getDataLength()) {
-                    continue;
-                }
-
-                var d = getDataItem(row), cellNodes = rowNode.childNodes;
-                for (var i = 0, j = 0, l = columns.length; i < l; ++i) {
-                    var m = columns[i];
-                    if (m.asyncPostRender) {
-                        m.asyncPostRender(cellNodes[j], postProcessFromRow, d, m);
-                    }
-                    ++j;
-                }
-
-                postProcessedRows[row] = true;
-                h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay);
-                return;
-            }
-        }
-
-        function updateCellCssStylesOnRenderedRows(addedHash, removedHash) {
-            var node, columnId, addedRowHash, removedRowHash;
-            for (var row in rowsCache) {
-                removedRowHash = removedHash && removedHash[row];
-                addedRowHash = addedHash && addedHash[row];
-
-                if (removedRowHash) {
-                    for (columnId in removedRowHash) {
-                        if (!addedRowHash || removedRowHash[columnId] != addedRowHash[columnId]) {
-                            node = getCellNode(row, getColumnIndex(columnId));
-                            if (node) {
-                                $(node).removeClass(removedRowHash[columnId]);
-                            }
-                        }
-                    }
-                }
-
-                if (addedRowHash) {
-                    for (columnId in addedRowHash) {
-                        if (!removedRowHash || removedRowHash[columnId] != addedRowHash[columnId]) {
-                            node = getCellNode(row, getColumnIndex(columnId));
-                            if (node) {
-                                $(node).addClass(addedRowHash[columnId]);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        function addCellCssStyles(key, hash) {
-            if (cellCssClasses[key]) {
-                throw "addCellCssStyles: cell CSS hash with key '" + key + "' already exists.";
-            }
-
-            cellCssClasses[key] = hash;
-            updateCellCssStylesOnRenderedRows(hash, null);
-
-            trigger(self.onCellCssStylesChanged, {"key": key, "hash": hash});
-        }
-
-        function removeCellCssStyles(key) {
-            if (!cellCssClasses[key]) {
-                return;
-            }
-
-            updateCellCssStylesOnRenderedRows(null, cellCssClasses[key]);
-            delete cellCssClasses[key];
-
-            trigger(self.onCellCssStylesChanged, {"key": key, "hash": null});
-        }
-
-        function setCellCssStyles(key, hash) {
-            var prevHash = cellCssClasses[key];
-
-            cellCssClasses[key] = hash;
-            updateCellCssStylesOnRenderedRows(hash, prevHash);
-
-            trigger(self.onCellCssStylesChanged, {"key": key, "hash": hash});
-        }
-
-        function getCellCssStyles(key) {
-            return cellCssClasses[key];
-        }
-
-        function flashCell(row, cell, speed) {
-            speed = speed || 100;
-            if (rowsCache[row]) {
-                var $cell = $(getCellNode(row, cell));
-
-                function toggleCellClass(times) {
-                    if (!times) {
-                        return;
-                    }
-                    setTimeout(function () {
-                        $cell.queue(function () {
-                            $cell.toggleClass(options.cellFlashingCssClass).dequeue();
-                            toggleCellClass(times - 1);
-                        });
-                    },
-                            speed);
-                }
-
-                toggleCellClass(4);
-            }
-        }
-
-        //////////////////////////////////////////////////////////////////////////////////////////////
-        // Interactivity
-
-        function handleDragInit(e, dd) {
-            var cell = getCellFromEvent(e);
-            if (!cell || !cellExists(cell.row, cell.cell)) {
-                return false;
-            }
-
-            retval = trigger(self.onDragInit, dd, e);
-            if (e.isImmediatePropagationStopped()) {
-                return retval;
-            }
-
-            // if nobody claims to be handling drag'n'drop by stopping immediate propagation,
-            // cancel out of it
-            return false;
-        }
-
-        function handleDragStart(e, dd) {
-            var cell = getCellFromEvent(e);
-            if (!cell || !cellExists(cell.row, cell.cell)) {
-                return false;
-            }
-
-            var retval = trigger(self.onDragStart, dd, e);
-            if (e.isImmediatePropagationStopped()) {
-                return retval;
-            }
-
-            return false;
-        }
-
-        function handleDrag(e, dd) {
-            return trigger(self.onDrag, dd, e);
-        }
-
-        function handleDragEnd(e, dd) {
-            trigger(self.onDragEnd, dd, e);
-        }
-
-        function handleKeyDown(e) {
-            trigger(self.onKeyDown, {row: activeRow, cell: activeCell}, e);
-            var handled = e.isImmediatePropagationStopped();
-
-            if (!handled) {
-                if (!e.shiftKey && !e.altKey && !e.ctrlKey) {
-                    if (e.which == 27) {
-                        if (!getEditorLock().isActive()) {
-                            return; // no editing mode to cancel, allow bubbling and default processing (exit without cancelling the event)
-                        }
-                        cancelEditAndSetFocus();
-                    } else if (e.which == 37) {
-                        navigateLeft();
-                    } else if (e.which == 39) {
-                        navigateRight();
-                    } else if (e.which == 38) {
-                        navigateUp();
-                    } else if (e.which == 40) {
-                        navigateDown();
-                    } else if (e.which == 9) {
-                        navigateNext();
-                    } else if (e.which == 13) {
-                        if (options.editable) {
-                            if (currentEditor) {
-                                // adding new row
-                                if (activeRow === getDataLength()) {
-                                    navigateDown();
-                                }
-                                else {
-                                    commitEditAndSetFocus();
-                                }
-                            } else {
-                                if (getEditorLock().commitCurrentEdit()) {
-                                    makeActiveCellEditable();
-                                }
-                            }
-                        }
-                    } else {
-                        return;
-                    }
-                } else if (e.which == 9 && e.shiftKey && !e.ctrlKey && !e.altKey) {
-                    navigatePrev();
-                } else {
-                    return;
-                }
-            }
-
-            // the event has been handled so don't let parent element (bubbling/propagation) or browser (default) handle it
-            e.stopPropagation();
-            e.preventDefault();
-            try {
-                e.originalEvent.keyCode = 0; // prevent default behaviour for special keys in IE browsers (F3, F5, etc.)
-            }
-            // ignore exceptions - setting the original event's keycode throws access denied exception for "Ctrl"
-            // (hitting control key only, nothing else), "Shift" (maybe others)
-            catch (error) {
-            }
-        }
-
-        function handleClick(e) {
-            if (!currentEditor) {
-                setFocus();
-            }
-
-            var cell = getCellFromEvent(e);
-            if (!cell || (currentEditor !== null && activeRow == cell.row && activeCell == cell.cell)) {
-                return;
-            }
-
-            trigger(self.onClick, {row: cell.row, cell: cell.cell}, e);
-            if (e.isImmediatePropagationStopped()) {
-                return;
-            }
-
-            if (canCellBeActive(cell.row, cell.cell)) {
-                if (!getEditorLock().isActive() || getEditorLock().commitCurrentEdit()) {
-                    scrollRowIntoView(cell.row, false);
-                    setActiveCellInternal(getCellNode(cell.row, cell.cell), (cell.row === getDataLength()) || options.autoEdit);
-                }
-            }
-        }
-
-        function handleContextMenu(e) {
-            var $cell = $(e.target).closest(".slick-cell", $canvas);
-            if ($cell.length === 0) {
-                return;
-            }
-
-            // are we editing this cell?
-            if (activeCellNode === $cell[0] && currentEditor !== null) {
-                return;
-            }
-
-            trigger(self.onContextMenu, {}, e);
-        }
-
-        function handleDblClick(e) {
-            var cell = getCellFromEvent(e);
-            if (!cell || (currentEditor !== null && activeRow == cell.row && activeCell == cell.cell)) {
-                return;
-            }
-
-            trigger(self.onDblClick, {row: cell.row, cell: cell.cell}, e);
-            if (e.isImmediatePropagationStopped()) {
-                return;
-            }
-
-            if (options.editable) {
-                gotoCell(cell.row, cell.cell, true);
-            }
-        }
-
-        function handleHeaderContextMenu(e) {
-            var $header = $(e.target).closest(".slick-header-column", ".slick-header-columns");
-            var column = $header && columns[self.getColumnIndex($header.data("fieldId"))];
-            trigger(self.onHeaderContextMenu, {column: column}, e);
-        }
-
-        function handleHeaderClick(e) {
-            var $header = $(e.target).closest(".slick-header-column", ".slick-header-columns");
-            var column = $header && columns[self.getColumnIndex($header.data("fieldId"))];
-            if (column) {
-                trigger(self.onHeaderClick, {column: column}, e);
-            }
-        }
-
-        function handleMouseEnter(e) {
-            trigger(self.onMouseEnter, {}, e);
-        }
-
-        function handleMouseLeave(e) {
-            trigger(self.onMouseLeave, {}, e);
-        }
-
-        function cellExists(row, cell) {
-            return !(row < 0 || row >= getDataLength() || cell < 0 || cell >= columns.length);
-        }
-
-        function getCellFromPoint(x, y) {
-            var row = Math.floor((y + offset) / options.rowHeight);
-            var cell = 0;
-
-            var w = 0;
-            for (var i = 0; i < columns.length && w < x; i++) {
-                w += columns[i].width;
-                cell++;
-            }
-
-            if (cell < 0) {
-                cell = 0;
-            }
-
-            return {row: row, cell: cell - 1};
-        }
-
-        function getCellFromNode(node) {
-            // read column number from .l<columnNumber> CSS class
-            var cls = /l\d+/.exec(node.className);
-            if (!cls) {
-                throw "getCellFromNode: cannot get cell - " + node.className;
-            }
-            return parseInt(cls[0].substr(1, cls[0].length - 1), 10);
-        }
-
-        function getCellFromEvent(e) {
-            var $cell = $(e.target).closest(".slick-cell", $canvas);
-            if (!$cell.length) {
-                return null;
-            }
-
-            return {
-                row: $cell.parent().attr("row") | 0,
-                cell: getCellFromNode($cell[0])
-            };
-        }
-
-        function getCellNodeBox(row, cell) {
-            if (!cellExists(row, cell)) {
-                return null;
-            }
-
-            var y1 = row * options.rowHeight - offset;
-            var y2 = y1 + options.rowHeight - 1;
-            var x1 = 0;
-            for (var i = 0; i < cell; i++) {
-                x1 += columns[i].width;
-            }
-            var x2 = x1 + columns[cell].width;
-
-            return {
-                top: y1,
-                left: x1,
-                bottom: y2,
-                right: x2
-            };
-        }
-
-        //////////////////////////////////////////////////////////////////////////////////////////////
-        // Cell switching
-
-        function resetActiveCell() {
-            setActiveCellInternal(null, false);
-        }
-
-        function setFocus() {
-            $focusSink[0].focus();
-        }
-
-        function scrollActiveCellIntoView() {
-            if (activeCellNode) {
-                var left = $(activeCellNode).position().left,
-                        right = left + $(activeCellNode).outerWidth(),
-                        scrollLeft = $viewport.scrollLeft(),
-                        scrollRight = scrollLeft + $viewport.width();
-
-                if (left < scrollLeft) {
-                    $viewport.scrollLeft(left);
-                } else if (right > scrollRight) {
-                    $viewport.scrollLeft(Math.min(left, right - $viewport[0].clientWidth));
-                }
-            }
-        }
-
-        function setActiveCellInternal(newCell, editMode) {
-            if (activeCellNode !== null) {
-                makeActiveCellNormal();
-                $(activeCellNode).removeClass("active");
-            }
-
-            var activeCellChanged = (activeCellNode !== newCell);
-            activeCellNode = newCell;
-
-            if (activeCellNode != null) {
-                activeRow = parseInt($(activeCellNode).parent().attr("row"));
-                activeCell = activePosX = getCellFromNode(activeCellNode);
-
-                $(activeCellNode).addClass("active");
-
-                if (options.editable && editMode && isCellPotentiallyEditable(activeRow, activeCell)) {
-                    clearTimeout(h_editorLoader);
-
-                    if (options.asyncEditorLoading) {
-                        h_editorLoader = setTimeout(function () {
-                            makeActiveCellEditable();
-                        }, options.asyncEditorLoadDelay);
-                    } else {
-                        makeActiveCellEditable();
-                    }
-                } else {
-                    setFocus();
-                }
-            } else {
-                activeRow = activeCell = null;
-            }
-
-            if (activeCellChanged) {
-                scrollActiveCellIntoView();
-                trigger(self.onActiveCellChanged, getActiveCell());
-            }
-        }
-
-        function clearTextSelection() {
-            if (document.selection && document.selection.empty) {
-                document.selection.empty();
-            } else if (window.getSelection) {
-                var sel = window.getSelection();
-                if (sel && sel.removeAllRanges) {
-                    sel.removeAllRanges();
-                }
-            }
-        }
-
-        function isCellPotentiallyEditable(row, cell) {
-            // is the data for this row loaded?
-            if (row < getDataLength() && !getDataItem(row)) {
-                return false;
-            }
-
-            // are we in the Add New row?  can we create new from this cell?
-            if (columns[cell].cannotTriggerInsert && row >= getDataLength()) {
-                return false;
-            }
-
-            // does this cell have an editor?
-            if (!getEditor(row, cell)) {
-                return false;
-            }
-
-            return true;
-        }
-
-        function makeActiveCellNormal() {
-            if (!currentEditor) {
-                return;
-            }
-            trigger(self.onBeforeCellEditorDestroy, {editor: currentEditor});
-            currentEditor.destroy();
-            currentEditor = null;
-
-            if (activeCellNode) {
-                var d = getDataItem(activeRow);
-                $(activeCellNode).removeClass("editable invalid");
-                if (d) {
-                    var column = columns[activeCell];
-                    var formatter = getFormatter(activeRow, column);
-                    activeCellNode.innerHTML = formatter(activeRow, activeCell, getDataItemValueForColumn(d, column), column, getDataItem(activeRow));
-                    invalidatePostProcessingResults(activeRow);
-                }
-            }
-
-            // if there previously was text selected on a page (such as selected text in the edit cell just removed),
-            // IE can't set focus to anything else correctly
-            if ($.browser.msie) {
-                clearTextSelection();
-            }
-
-            getEditorLock().deactivate(editController);
-        }
-
-        function makeActiveCellEditable(editor) {
-            if (!activeCellNode) {
-                return;
-            }
-            if (!options.editable) {
-                throw "Grid : makeActiveCellEditable : should never get called when options.editable is false";
-            }
-
-            // cancel pending async call if there is one
-            clearTimeout(h_editorLoader);
-
-            if (!isCellPotentiallyEditable(activeRow, activeCell)) {
-                return;
-            }
-
-            var columnDef = columns[activeCell];
-            var item = getDataItem(activeRow);
-
-            if (trigger(self.onBeforeEditCell, {row: activeRow, cell: activeCell, item: item, column: columnDef}) === false) {
-                setFocus();
-                return;
-            }
-
-            getEditorLock().activate(editController);
-            $(activeCellNode).addClass("editable");
-
-            // don't clear the cell if a custom editor is passed through
-            if (!editor) {
-                activeCellNode.innerHTML = "";
-            }
-
-            currentEditor = new (editor || getEditor(activeRow, activeCell))({
-                grid: self,
-                gridPosition: absBox($container[0]),
-                position: absBox(activeCellNode),
-                container: activeCellNode,
-                column: columnDef,
-                item: item || {},
-                commitChanges: commitEditAndSetFocus,
-                cancelChanges: cancelEditAndSetFocus
-            });
-
-            if (item) {
-                currentEditor.loadValue(item);
-            }
-
-            serializedEditorValue = currentEditor.serializeValue();
-
-            if (currentEditor.position) {
-                handleActiveCellPositionChange();
-            }
-        }
-
-        function commitEditAndSetFocus() {
-            // if the commit fails, it would do so due to a validation error
-            // if so, do not steal the focus from the editor
-            if (getEditorLock().commitCurrentEdit()) {
-                setFocus();
-                if (options.autoEdit) {
-                    navigateDown();
-                }
-            }
-        }
-
-        function cancelEditAndSetFocus() {
-            if (getEditorLock().cancelCurrentEdit()) {
-                setFocus();
-            }
-        }
-
-        function absBox(elem) {
-            var box = {
-                top: elem.offsetTop,
-                left: elem.offsetLeft,
-                bottom: 0,
-                right: 0,
-                width: $(elem).outerWidth(),
-                height: $(elem).outerHeight(),
-                visible: true};
-            box.bottom = box.top + box.height;
-            box.right = box.left + box.width;
-
-            // walk up the tree
-            var offsetParent = elem.offsetParent;
-            while ((elem = elem.parentNode) != document.body) {
-                if (box.visible && elem.scrollHeight != elem.offsetHeight && $(elem).css("overflowY") != "visible") {
-                    box.visible = box.bottom > elem.scrollTop && box.top < elem.scrollTop + elem.clientHeight;
-                }
-
-                if (box.visible && elem.scrollWidth != elem.offsetWidth && $(elem).css("overflowX") != "visible") {
-                    box.visible = box.right > elem.scrollLeft && box.left < elem.scrollLeft + elem.clientWidth;
-                }
-
-                box.left -= elem.scrollLeft;
-                box.top -= elem.scrollTop;
-
-                if (elem === offsetParent) {
-                    box.left += elem.offsetLeft;
-                    box.top += elem.offsetTop;
-                    offsetParent = elem.offsetParent;
-                }
-
-                box.bottom = box.top + box.height;
-                box.right = box.left + box.width;
-            }
-
-            return box;
-        }
-
-        function getActiveCellPosition() {
-            return absBox(activeCellNode);
-        }
-
-        function getGridPosition() {
-            return absBox($container[0])
-        }
-
-        function handleActiveCellPositionChange() {
-            if (!activeCellNode) {
-                return;
-            }
-
-            trigger(self.onActiveCellPositionChanged, {});
-
-            if (currentEditor) {
-                var cellBox = getActiveCellPosition();
-                if (currentEditor.show && currentEditor.hide) {
-                    if (!cellBox.visible) {
-                        currentEditor.hide();
-                    } else {
-                        currentEditor.show();
-                    }
-                }
-
-                if (currentEditor.position) {
-                    currentEditor.position(cellBox);
-                }
-            }
-        }
-
-        function getCellEditor() {
-            return currentEditor;
-        }
-
-        function getActiveCell() {
-            if (!activeCellNode) {
-                return null;
-            } else {
-                return {row: activeRow, cell: activeCell};
-            }
-        }
-
-        function getActiveCellNode() {
-            return activeCellNode;
-        }
-
-        function scrollRowIntoView(row, doPaging) {
-            var rowAtTop = row * options.rowHeight;
-            var rowAtBottom = (row + 1) * options.rowHeight - viewportH + (viewportHasHScroll ? scrollbarDimensions.height : 0);
-
-            // need to page down?
-            if ((row + 1) * options.rowHeight > scrollTop + viewportH + offset) {
-                scrollTo(doPaging ? rowAtTop : rowAtBottom);
-                render();
-            }
-            // or page up?
-            else if (row * options.rowHeight < scrollTop + offset) {
-                scrollTo(doPaging ? rowAtBottom : rowAtTop);
-                render();
-            }
-        }
-
-        function getColspan(row, cell) {
-            var metadata = data.getItemMetadata && data.getItemMetadata(row);
-            if (!metadata || !metadata.columns) {
-                return 1;
-            }
-
-            var columnData = metadata.columns[columns[cell].id] || metadata.columns[cell];
-            var colspan = (columnData && columnData.colspan);
-            if (colspan === "*") {
-                colspan = columns.length - cell;
-            }
-            return (colspan || 1);
-        }
-
-        function findFirstFocusableCell(row) {
-            var cell = 0;
-            while (cell < columns.length) {
-                if (canCellBeActive(row, cell)) {
-                    return cell;
-                }
-                cell += getColspan(row, cell);
-            }
-            return null;
-        }
-
-        function findLastFocusableCell(row) {
-            var cell = 0;
-            var lastFocusableCell = null;
-            while (cell < columns.length) {
-                if (canCellBeActive(row, cell)) {
-                    lastFocusableCell = cell;
-                }
-                cell += getColspan(row, cell);
-            }
-            return lastFocusableCell;
-        }
-
-        function gotoRight(row, cell, posX) {
-            if (cell >= columns.length) {
-                return null;
-            }
-
-            do {
-                cell += getColspan(row, cell);
-            }
-            while (cell < columns.length && !canCellBeActive(row, cell));
-
-            if (cell < columns.length) {
-                return {
-                    "row": row,
-                    "cell": cell,
-                    "posX": cell
-                };
-            }
-            return null;
-        }
-
-        function gotoLeft(row, cell, posX) {
-            if (cell <= 0) {
-                return null;
-            }
-
-            var firstFocusableCell

<TRUNCATED>

[3/5] incubator-nifi git commit: NIFI-27: - Latest version of slickgrid.

Posted by mc...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.dataview.js
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.dataview.js b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.dataview.js
index 07f5900..f1c1b5e 100755
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.dataview.js
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.dataview.js
@@ -1,914 +1,1126 @@
 (function ($) {
-    $.extend(true, window, {
-        Slick: {
-            Data: {
-                DataView: DataView,
-                Aggregators: {
-                    Avg: AvgAggregator,
-                    Min: MinAggregator,
-                    Max: MaxAggregator,
-                    Sum: SumAggregator
-                }
-            }
+  $.extend(true, window, {
+    Slick: {
+      Data: {
+        DataView: DataView,
+        Aggregators: {
+          Avg: AvgAggregator,
+          Min: MinAggregator,
+          Max: MaxAggregator,
+          Sum: SumAggregator
         }
-    });
-
+      }
+    }
+  });
+
+
+  /***
+   * A sample Model implementation.
+   * Provides a filtered view of the underlying data.
+   *
+   * Relies on the data item having an "id" property uniquely identifying it.
+   */
+  function DataView(options) {
+    var self = this;
+
+    var defaults = {
+      groupItemMetadataProvider: null,
+      inlineFilters: false
+    };
+
+
+    // private
+    var idProperty = "id";  // property holding a unique row id
+    var items = [];         // data by index
+    var rows = [];          // data by row
+    var idxById = {};       // indexes by id
+    var rowsById = null;    // rows by id; lazy-calculated
+    var filter = null;      // filter function
+    var updated = null;     // updated item ids
+    var suspend = false;    // suspends the recalculation
+    var sortAsc = true;
+    var fastSortField;
+    var sortComparer;
+    var refreshHints = {};
+    var prevRefreshHints = {};
+    var filterArgs;
+    var filteredItems = [];
+    var compiledFilter;
+    var compiledFilterWithCaching;
+    var filterCache = [];
+
+    // grouping
+    var groupingInfoDefaults = {
+      getter: null,
+      formatter: null,
+      comparer: function(a, b) { return a.value - b.value; },
+      predefinedValues: [],
+      aggregators: [],
+      aggregateEmpty: false,
+      aggregateCollapsed: false,
+      aggregateChildGroups: false,
+      collapsed: false,
+      displayTotalsRow: true,
+      lazyTotalsCalculation: false
+    };
+    var groupingInfos = [];
+    var groups = [];
+    var toggledGroupsByLevel = [];
+    var groupingDelimiter = ':|:';
+
+    var pagesize = 0;
+    var pagenum = 0;
+    var totalRows = 0;
+
+    // events
+    var onRowCountChanged = new Slick.Event();
+    var onRowsChanged = new Slick.Event();
+    var onPagingInfoChanged = new Slick.Event();
+
+    options = $.extend(true, {}, defaults, options);
+
+
+    function beginUpdate() {
+      suspend = true;
+    }
+
+    function endUpdate() {
+      suspend = false;
+      refresh();
+    }
+
+    function setRefreshHints(hints) {
+      refreshHints = hints;
+    }
+
+    function setFilterArgs(args) {
+      filterArgs = args;
+    }
+
+    function updateIdxById(startingIndex) {
+      startingIndex = startingIndex || 0;
+      var id;
+      for (var i = startingIndex, l = items.length; i < l; i++) {
+        id = items[i][idProperty];
+        if (id === undefined) {
+          throw "Each data element must implement a unique 'id' property";
+        }
+        idxById[id] = i;
+      }
+    }
+
+    function ensureIdUniqueness() {
+      var id;
+      for (var i = 0, l = items.length; i < l; i++) {
+        id = items[i][idProperty];
+        if (id === undefined || idxById[id] !== i) {
+          throw "Each data element must implement a unique 'id' property";
+        }
+      }
+    }
+
+    function getItems() {
+      return items;
+    }
+
+    function setItems(data, objectIdProperty) {
+      if (objectIdProperty !== undefined) {
+        idProperty = objectIdProperty;
+      }
+      items = filteredItems = data;
+      idxById = {};
+      updateIdxById();
+      ensureIdUniqueness();
+      refresh();
+    }
+
+    function setPagingOptions(args) {
+      if (args.pageSize != undefined) {
+        pagesize = args.pageSize;
+        pagenum = pagesize ? Math.min(pagenum, Math.max(0, Math.ceil(totalRows / pagesize) - 1)) : 0;
+      }
+
+      if (args.pageNum != undefined) {
+        pagenum = Math.min(args.pageNum, Math.max(0, Math.ceil(totalRows / pagesize) - 1));
+      }
+
+      onPagingInfoChanged.notify(getPagingInfo(), null, self);
+
+      refresh();
+    }
+
+    function getPagingInfo() {
+      var totalPages = pagesize ? Math.max(1, Math.ceil(totalRows / pagesize)) : 1;
+      return {pageSize: pagesize, pageNum: pagenum, totalRows: totalRows, totalPages: totalPages};
+    }
+
+    function sort(comparer, ascending) {
+      sortAsc = ascending;
+      sortComparer = comparer;
+      fastSortField = null;
+      if (ascending === false) {
+        items.reverse();
+      }
+      items.sort(comparer);
+      if (ascending === false) {
+        items.reverse();
+      }
+      idxById = {};
+      updateIdxById();
+      refresh();
+    }
 
     /***
-     * A sample Model implementation.
-     * Provides a filtered view of the underlying data.
-     *
-     * Relies on the data item having an "id" property uniquely identifying it.
+     * Provides a workaround for the extremely slow sorting in IE.
+     * Does a [lexicographic] sort on a give column by temporarily overriding Object.prototype.toString
+     * to return the value of that field and then doing a native Array.sort().
      */
-    function DataView(options) {
-        var self = this;
-
-        var defaults = {
-            groupItemMetadataProvider: null,
-            inlineFilters: false
-        };
-
-
-        // private
-        var idProperty = "id";  // property holding a unique row id
-        var items = [];         // data by index
-        var rows = [];          // data by row
-        var idxById = {};       // indexes by id
-        var rowsById = null;    // rows by id; lazy-calculated
-        var filter = null;      // filter function
-        var updated = null;     // updated item ids
-        var suspend = false;    // suspends the recalculation
-        var sortAsc = true;
-        var fastSortField;
-        var sortComparer;
-        var refreshHints = {};
-        var prevRefreshHints = {};
-        var filterArgs;
-        var filteredItems = [];
-        var compiledFilter;
-        var compiledFilterWithCaching;
-        var filterCache = [];
-
-        // grouping
-        var groupingGetter;
-        var groupingGetterIsAFn;
-        var groupingFormatter;
-        var groupingComparer;
-        var groups = [];
-        var collapsedGroups = {};
-        var aggregators;
-        var aggregateCollapsed = false;
-        var compiledAccumulators;
-
-        var pagesize = 0;
-        var pagenum = 0;
-        var totalRows = 0;
-
-        // events
-        var onRowCountChanged = new Slick.Event();
-        var onRowsChanged = new Slick.Event();
-        var onPagingInfoChanged = new Slick.Event();
-
-        options = $.extend(true, {}, defaults, options);
-
-
-        function beginUpdate() {
-            suspend = true;
-        }
-
-        function endUpdate() {
-            suspend = false;
-            refresh();
-        }
-
-        function setRefreshHints(hints) {
-            refreshHints = hints;
-        }
-
-        function setFilterArgs(args) {
-            filterArgs = args;
-        }
-
-        function updateIdxById(startingIndex) {
-            startingIndex = startingIndex || 0;
-            var id;
-            for (var i = startingIndex, l = items.length; i < l; i++) {
-                id = items[i][idProperty];
-                if (id === undefined) {
-                    throw "Each data element must implement a unique 'id' property";
-                }
-                idxById[id] = i;
-            }
+    function fastSort(field, ascending) {
+      sortAsc = ascending;
+      fastSortField = field;
+      sortComparer = null;
+      var oldToString = Object.prototype.toString;
+      Object.prototype.toString = (typeof field == "function") ? field : function () {
+        return this[field]
+      };
+      // an extra reversal for descending sort keeps the sort stable
+      // (assuming a stable native sort implementation, which isn't true in some cases)
+      if (ascending === false) {
+        items.reverse();
+      }
+      items.sort();
+      Object.prototype.toString = oldToString;
+      if (ascending === false) {
+        items.reverse();
+      }
+      idxById = {};
+      updateIdxById();
+      refresh();
+    }
+
+    function reSort() {
+      if (sortComparer) {
+        sort(sortComparer, sortAsc);
+      } else if (fastSortField) {
+        fastSort(fastSortField, sortAsc);
+      }
+    }
+
+    function setFilter(filterFn) {
+      filter = filterFn;
+      if (options.inlineFilters) {
+        compiledFilter = compileFilter();
+        compiledFilterWithCaching = compileFilterWithCaching();
+      }
+      refresh();
+    }
+
+    function getGrouping() {
+      return groupingInfos;
+    }
+
+    function setGrouping(groupingInfo) {
+      if (!options.groupItemMetadataProvider) {
+        options.groupItemMetadataProvider = new Slick.Data.GroupItemMetadataProvider();
+      }
+
+      groups = [];
+      toggledGroupsByLevel = [];
+      groupingInfo = groupingInfo || [];
+      groupingInfos = (groupingInfo instanceof Array) ? groupingInfo : [groupingInfo];
+
+      for (var i = 0; i < groupingInfos.length; i++) {
+        var gi = groupingInfos[i] = $.extend(true, {}, groupingInfoDefaults, groupingInfos[i]);
+        gi.getterIsAFn = typeof gi.getter === "function";
+
+        // pre-compile accumulator loops
+        gi.compiledAccumulators = [];
+        var idx = gi.aggregators.length;
+        while (idx--) {
+          gi.compiledAccumulators[idx] = compileAccumulatorLoop(gi.aggregators[idx]);
         }
 
-        function ensureIdUniqueness() {
-            var id;
-            for (var i = 0, l = items.length; i < l; i++) {
-                id = items[i][idProperty];
-                if (id === undefined || idxById[id] !== i) {
-                    throw "Each data element must implement a unique 'id' property";
-                }
-            }
-        }
+        toggledGroupsByLevel[i] = {};
+      }
 
-        function getItems() {
-            return items;
-        }
+      refresh();
+    }
 
-        function setItems(data, objectIdProperty) {
-            if (objectIdProperty !== undefined) {
-                idProperty = objectIdProperty;
-            }
-            items = filteredItems = data;
-            idxById = {};
-            updateIdxById();
-            ensureIdUniqueness();
-            refresh();
+    /**
+     * @deprecated Please use {@link setGrouping}.
+     */
+    function groupBy(valueGetter, valueFormatter, sortComparer) {
+      if (valueGetter == null) {
+        setGrouping([]);
+        return;
+      }
+
+      setGrouping({
+        getter: valueGetter,
+        formatter: valueFormatter,
+        comparer: sortComparer
+      });
+    }
+
+    /**
+     * @deprecated Please use {@link setGrouping}.
+     */
+    function setAggregators(groupAggregators, includeCollapsed) {
+      if (!groupingInfos.length) {
+        throw new Error("At least one grouping must be specified before calling setAggregators().");
+      }
+
+      groupingInfos[0].aggregators = groupAggregators;
+      groupingInfos[0].aggregateCollapsed = includeCollapsed;
+
+      setGrouping(groupingInfos);
+    }
+
+    function getItemByIdx(i) {
+      return items[i];
+    }
+
+    function getIdxById(id) {
+      return idxById[id];
+    }
+
+    function ensureRowsByIdCache() {
+      if (!rowsById) {
+        rowsById = {};
+        for (var i = 0, l = rows.length; i < l; i++) {
+          rowsById[rows[i][idProperty]] = i;
         }
-
-        function setPagingOptions(args) {
-            if (args.pageSize != undefined) {
-                pagesize = args.pageSize;
-                pagenum = pagesize ? Math.min(pagenum, Math.max(0, Math.ceil(totalRows / pagesize) - 1)) : 0;
-            }
-
-            if (args.pageNum != undefined) {
-                pagenum = Math.min(args.pageNum, Math.max(0, Math.ceil(totalRows / pagesize) - 1));
-            }
-
-            onPagingInfoChanged.notify(getPagingInfo(), null, self);
-
-            refresh();
+      }
+    }
+
+    function getRowById(id) {
+      ensureRowsByIdCache();
+      return rowsById[id];
+    }
+
+    function getItemById(id) {
+      return items[idxById[id]];
+    }
+
+    function mapIdsToRows(idArray) {
+      var rows = [];
+      ensureRowsByIdCache();
+      for (var i = 0, l = idArray.length; i < l; i++) {
+        var row = rowsById[idArray[i]];
+        if (row != null) {
+          rows[rows.length] = row;
         }
-
-        function getPagingInfo() {
-            var totalPages = pagesize ? Math.max(1, Math.ceil(totalRows / pagesize)) : 1;
-            return {pageSize: pagesize, pageNum: pagenum, totalRows: totalRows, totalPages: totalPages};
+      }
+      return rows;
+    }
+
+    function mapRowsToIds(rowArray) {
+      var ids = [];
+      for (var i = 0, l = rowArray.length; i < l; i++) {
+        if (rowArray[i] < rows.length) {
+          ids[ids.length] = rows[rowArray[i]][idProperty];
         }
-
-        function sort(comparer, ascending) {
-            sortAsc = ascending;
-            sortComparer = comparer;
-            fastSortField = null;
-            if (ascending === false) {
-                items.reverse();
-            }
-            items.sort(comparer);
-            if (ascending === false) {
-                items.reverse();
-            }
-            idxById = {};
-            updateIdxById();
-            refresh();
-        }
-
-        /***
-         * Provides a workaround for the extremely slow sorting in IE.
-         * Does a [lexicographic] sort on a give column by temporarily overriding Object.prototype.toString
-         * to return the value of that field and then doing a native Array.sort().
-         */
-        function fastSort(field, ascending) {
-            sortAsc = ascending;
-            fastSortField = field;
-            sortComparer = null;
-            var oldToString = Object.prototype.toString;
-            Object.prototype.toString = (typeof field == "function") ? field : function () {
-                return this[field]
-            };
-            // an extra reversal for descending sort keeps the sort stable
-            // (assuming a stable native sort implementation, which isn't true in some cases)
-            if (ascending === false) {
-                items.reverse();
-            }
-            items.sort();
-            Object.prototype.toString = oldToString;
-            if (ascending === false) {
-                items.reverse();
-            }
-            idxById = {};
-            updateIdxById();
-            refresh();
+      }
+      return ids;
+    }
+
+    function updateItem(id, item) {
+      if (idxById[id] === undefined || id !== item[idProperty]) {
+        throw "Invalid or non-matching id";
+      }
+      items[idxById[id]] = item;
+      if (!updated) {
+        updated = {};
+      }
+      updated[id] = true;
+      refresh();
+    }
+
+    function insertItem(insertBefore, item) {
+      items.splice(insertBefore, 0, item);
+      updateIdxById(insertBefore);
+      refresh();
+    }
+
+    function addItem(item) {
+      items.push(item);
+      updateIdxById(items.length - 1);
+      refresh();
+    }
+
+    function deleteItem(id) {
+      var idx = idxById[id];
+      if (idx === undefined) {
+        throw "Invalid id";
+      }
+      delete idxById[id];
+      items.splice(idx, 1);
+      updateIdxById(idx);
+      refresh();
+    }
+
+    function getLength() {
+      return rows.length;
+    }
+
+    function getItem(i) {
+      var item = rows[i];
+
+      // if this is a group row, make sure totals are calculated and update the title
+      if (item && item.__group && item.totals && !item.totals.initialized) {
+        var gi = groupingInfos[item.level];
+        if (!gi.displayTotalsRow) {
+          calculateTotals(item.totals);
+          item.title = gi.formatter ? gi.formatter(item) : item.value;
         }
-
-        function reSort() {
-            if (sortComparer) {
-                sort(sortComparer, sortAsc);
-            } else if (fastSortField) {
-                fastSort(fastSortField, sortAsc);
-            }
+      }
+      // if this is a totals row, make sure it's calculated
+      else if (item && item.__groupTotals && !item.initialized) {
+        calculateTotals(item);
+      }
+
+      return item;
+    }
+
+    function getItemMetadata(i) {
+      var item = rows[i];
+      if (item === undefined) {
+        return null;
+      }
+
+      // overrides for grouping rows
+      if (item.__group) {
+        return options.groupItemMetadataProvider.getGroupRowMetadata(item);
+      }
+
+      // overrides for totals rows
+      if (item.__groupTotals) {
+        return options.groupItemMetadataProvider.getTotalsRowMetadata(item);
+      }
+
+      return null;
+    }
+
+    function expandCollapseAllGroups(level, collapse) {
+      if (level == null) {
+        for (var i = 0; i < groupingInfos.length; i++) {
+          toggledGroupsByLevel[i] = {};
+          groupingInfos[i].collapsed = collapse;
         }
+      } else {
+        toggledGroupsByLevel[level] = {};
+        groupingInfos[level].collapsed = collapse;
+      }
+      refresh();
+    }
+
+    /**
+     * @param level {Number} Optional level to collapse.  If not specified, applies to all levels.
+     */
+    function collapseAllGroups(level) {
+      expandCollapseAllGroups(level, true);
+    }
 
-        function setFilter(filterFn) {
-            filter = filterFn;
-            if (options.inlineFilters) {
-                compiledFilter = compileFilter();
-                compiledFilterWithCaching = compileFilterWithCaching();
-            }
-            refresh();
+    /**
+     * @param level {Number} Optional level to expand.  If not specified, applies to all levels.
+     */
+    function expandAllGroups(level) {
+      expandCollapseAllGroups(level, false);
+    }
+
+    function expandCollapseGroup(level, groupingKey, collapse) {
+      toggledGroupsByLevel[level][groupingKey] = groupingInfos[level].collapsed ^ collapse;
+      refresh();
+    }
+
+    /**
+     * @param varArgs Either a Slick.Group's "groupingKey" property, or a
+     *     variable argument list of grouping values denoting a unique path to the row.  For
+     *     example, calling collapseGroup('high', '10%') will collapse the '10%' subgroup of
+     *     the 'high' group.
+     */
+    function collapseGroup(varArgs) {
+      var args = Array.prototype.slice.call(arguments);
+      var arg0 = args[0];
+      if (args.length == 1 && arg0.indexOf(groupingDelimiter) != -1) {
+        expandCollapseGroup(arg0.split(groupingDelimiter).length - 1, arg0, true);
+      } else {
+        expandCollapseGroup(args.length - 1, args.join(groupingDelimiter), true);
+      }
+    }
+
+    /**
+     * @param varArgs Either a Slick.Group's "groupingKey" property, or a
+     *     variable argument list of grouping values denoting a unique path to the row.  For
+     *     example, calling expandGroup('high', '10%') will expand the '10%' subgroup of
+     *     the 'high' group.
+     */
+    function expandGroup(varArgs) {
+      var args = Array.prototype.slice.call(arguments);
+      var arg0 = args[0];
+      if (args.length == 1 && arg0.indexOf(groupingDelimiter) != -1) {
+        expandCollapseGroup(arg0.split(groupingDelimiter).length - 1, arg0, false);
+      } else {
+        expandCollapseGroup(args.length - 1, args.join(groupingDelimiter), false);
+      }
+    }
+
+    function getGroups() {
+      return groups;
+    }
+
+    function extractGroups(rows, parentGroup) {
+      var group;
+      var val;
+      var groups = [];
+      var groupsByVal = {};
+      var r;
+      var level = parentGroup ? parentGroup.level + 1 : 0;
+      var gi = groupingInfos[level];
+
+      for (var i = 0, l = gi.predefinedValues.length; i < l; i++) {
+        val = gi.predefinedValues[i];
+        group = groupsByVal[val];
+        if (!group) {
+          group = new Slick.Group();
+          group.value = val;
+          group.level = level;
+          group.groupingKey = (parentGroup ? parentGroup.groupingKey + groupingDelimiter : '') + val;
+          groups[groups.length] = group;
+          groupsByVal[val] = group;
         }
-
-        function groupBy(valueGetter, valueFormatter, sortComparer) {
-            if (!options.groupItemMetadataProvider) {
-                options.groupItemMetadataProvider = new Slick.Data.GroupItemMetadataProvider();
-            }
-
-            groupingGetter = valueGetter;
-            groupingGetterIsAFn = typeof groupingGetter === "function";
-            groupingFormatter = valueFormatter;
-            groupingComparer = sortComparer;
-            collapsedGroups = {};
-            groups = [];
-            refresh();
-        }
-
-        function setAggregators(groupAggregators, includeCollapsed) {
-            aggregators = groupAggregators;
-            aggregateCollapsed = (includeCollapsed !== undefined)
-                    ? includeCollapsed : aggregateCollapsed;
-
-            // pre-compile accumulator loops
-            compiledAccumulators = [];
-            var idx = aggregators.length;
-            while (idx--) {
-                compiledAccumulators[idx] = compileAccumulatorLoop(aggregators[idx]);
-            }
-
-            refresh();
+      }
+
+      for (var i = 0, l = rows.length; i < l; i++) {
+        r = rows[i];
+        val = gi.getterIsAFn ? gi.getter(r) : r[gi.getter];
+        group = groupsByVal[val];
+        if (!group) {
+          group = new Slick.Group();
+          group.value = val;
+          group.level = level;
+          group.groupingKey = (parentGroup ? parentGroup.groupingKey + groupingDelimiter : '') + val;
+          groups[groups.length] = group;
+          groupsByVal[val] = group;
         }
 
-        function getItemByIdx(i) {
-            return items[i];
-        }
+        group.rows[group.count++] = r;
+      }
 
-        function getIdxById(id) {
-            return idxById[id];
+      if (level < groupingInfos.length - 1) {
+        for (var i = 0; i < groups.length; i++) {
+          group = groups[i];
+          group.groups = extractGroups(group.rows, group);
         }
-
-        function ensureRowsByIdCache() {
-            if (!rowsById) {
-                rowsById = {};
-                for (var i = 0, l = rows.length; i < l; i++) {
-                    rowsById[rows[i][idProperty]] = i;
-                }
-            }
+      }      
+
+      groups.sort(groupingInfos[level].comparer);
+
+      return groups;
+    }
+
+    function calculateTotals(totals) {
+      var group = totals.group;
+      var gi = groupingInfos[group.level];
+      var isLeafLevel = (group.level == groupingInfos.length);
+      var agg, idx = gi.aggregators.length;
+
+      if (!isLeafLevel && gi.aggregateChildGroups) {
+        // make sure all the subgroups are calculated
+        var i = group.groups.length;
+        while (i--) {
+          if (!group.groups[i].initialized) {
+            calculateTotals(group.groups[i]);
+          }
         }
-
-        function getRowById(id) {
-            ensureRowsByIdCache();
-            return rowsById[id];
+      }
+
+      while (idx--) {
+        agg = gi.aggregators[idx];
+        agg.init();
+        if (!isLeafLevel && gi.aggregateChildGroups) {
+          gi.compiledAccumulators[idx].call(agg, group.groups);
+        } else {
+          gi.compiledAccumulators[idx].call(agg, group.rows);
         }
-
-        function getItemById(id) {
-            return items[idxById[id]];
+        agg.storeResult(totals);
+      }
+      totals.initialized = true;
+    }
+
+    function addGroupTotals(group) {
+      var gi = groupingInfos[group.level];
+      var totals = new Slick.GroupTotals();
+      totals.group = group;
+      group.totals = totals;
+      if (!gi.lazyTotalsCalculation) {
+        calculateTotals(totals);
+      }
+    }
+
+    function addTotals(groups, level) {
+      level = level || 0;
+      var gi = groupingInfos[level];
+      var groupCollapsed = gi.collapsed;
+      var toggledGroups = toggledGroupsByLevel[level];      
+      var idx = groups.length, g;
+      while (idx--) {
+        g = groups[idx];
+
+        if (g.collapsed && !gi.aggregateCollapsed) {
+          continue;
         }
 
-        function mapIdsToRows(idArray) {
-            var rows = [];
-            ensureRowsByIdCache();
-            for (var i = 0; i < idArray.length; i++) {
-                var row = rowsById[idArray[i]];
-                if (row != null) {
-                    rows[rows.length] = row;
-                }
-            }
-            return rows;
+        // Do a depth-first aggregation so that parent group aggregators can access subgroup totals.
+        if (g.groups) {
+          addTotals(g.groups, level + 1);
         }
 
-        function mapRowsToIds(rowArray) {
-            var ids = [];
-            for (var i = 0; i < rowArray.length; i++) {
-                if (rowArray[i] < rows.length) {
-                    ids[ids.length] = rows[rowArray[i]][idProperty];
-                }
-            }
-            return ids;
+        if (gi.aggregators.length && (
+            gi.aggregateEmpty || g.rows.length || (g.groups && g.groups.length))) {
+          addGroupTotals(g);
         }
 
-        function updateItem(id, item) {
-            if (idxById[id] === undefined || id !== item[idProperty]) {
-                throw "Invalid or non-matching id";
-            }
-            items[idxById[id]] = item;
-            if (!updated) {
-                updated = {};
-            }
-            updated[id] = true;
-            refresh();
+        g.collapsed = groupCollapsed ^ toggledGroups[g.groupingKey];
+        g.title = gi.formatter ? gi.formatter(g) : g.value;
+      }
+    } 
+
+    function flattenGroupedRows(groups, level) {
+      level = level || 0;
+      var gi = groupingInfos[level];
+      var groupedRows = [], rows, gl = 0, g;
+      for (var i = 0, l = groups.length; i < l; i++) {
+        g = groups[i];
+        groupedRows[gl++] = g;
+
+        if (!g.collapsed) {
+          rows = g.groups ? flattenGroupedRows(g.groups, level + 1) : g.rows;
+          for (var j = 0, jj = rows.length; j < jj; j++) {
+            groupedRows[gl++] = rows[j];
+          }
         }
 
-        function insertItem(insertBefore, item) {
-            items.splice(insertBefore, 0, item);
-            updateIdxById(insertBefore);
-            refresh();
+        if (g.totals && gi.displayTotalsRow && (!g.collapsed || gi.aggregateCollapsed)) {
+          groupedRows[gl++] = g.totals;
         }
-
-        function addItem(item) {
-            items.push(item);
-            updateIdxById(items.length - 1);
-            refresh();
+      }
+      return groupedRows;
+    }
+
+    function getFunctionInfo(fn) {
+      var fnRegex = /^function[^(]*\(([^)]*)\)\s*{([\s\S]*)}$/;
+      var matches = fn.toString().match(fnRegex);
+      return {
+        params: matches[1].split(","),
+        body: matches[2]
+      };
+    }
+
+    function compileAccumulatorLoop(aggregator) {
+      var accumulatorInfo = getFunctionInfo(aggregator.accumulate);
+      var fn = new Function(
+          "_items",
+          "for (var " + accumulatorInfo.params[0] + ", _i=0, _il=_items.length; _i<_il; _i++) {" +
+              accumulatorInfo.params[0] + " = _items[_i]; " +
+              accumulatorInfo.body +
+          "}"
+      );
+      fn.displayName = fn.name = "compiledAccumulatorLoop";
+      return fn;
+    }
+
+    function compileFilter() {
+      var filterInfo = getFunctionInfo(filter);
+
+      var filterBody = filterInfo.body
+          .replace(/return false\s*([;}]|$)/gi, "{ continue _coreloop; }$1")
+          .replace(/return true\s*([;}]|$)/gi, "{ _retval[_idx++] = $item$; continue _coreloop; }$1")
+          .replace(/return ([^;}]+?)\s*([;}]|$)/gi,
+          "{ if ($1) { _retval[_idx++] = $item$; }; continue _coreloop; }$2");
+
+      // This preserves the function template code after JS compression,
+      // so that replace() commands still work as expected.
+      var tpl = [
+        //"function(_items, _args) { ",
+        "var _retval = [], _idx = 0; ",
+        "var $item$, $args$ = _args; ",
+        "_coreloop: ",
+        "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ",
+        "$item$ = _items[_i]; ",
+        "$filter$; ",
+        "} ",
+        "return _retval; "
+        //"}"
+      ].join("");
+      tpl = tpl.replace(/\$filter\$/gi, filterBody);
+      tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]);
+      tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]);
+
+      var fn = new Function("_items,_args", tpl);
+      fn.displayName = fn.name = "compiledFilter";
+      return fn;
+    }
+
+    function compileFilterWithCaching() {
+      var filterInfo = getFunctionInfo(filter);
+
+      var filterBody = filterInfo.body
+          .replace(/return false\s*([;}]|$)/gi, "{ continue _coreloop; }$1")
+          .replace(/return true\s*([;}]|$)/gi, "{ _cache[_i] = true;_retval[_idx++] = $item$; continue _coreloop; }$1")
+          .replace(/return ([^;}]+?)\s*([;}]|$)/gi,
+          "{ if ((_cache[_i] = $1)) { _retval[_idx++] = $item$; }; continue _coreloop; }$2");
+
+      // This preserves the function template code after JS compression,
+      // so that replace() commands still work as expected.
+      var tpl = [
+        //"function(_items, _args, _cache) { ",
+        "var _retval = [], _idx = 0; ",
+        "var $item$, $args$ = _args; ",
+        "_coreloop: ",
+        "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ",
+        "$item$ = _items[_i]; ",
+        "if (_cache[_i]) { ",
+        "_retval[_idx++] = $item$; ",
+        "continue _coreloop; ",
+        "} ",
+        "$filter$; ",
+        "} ",
+        "return _retval; "
+        //"}"
+      ].join("");
+      tpl = tpl.replace(/\$filter\$/gi, filterBody);
+      tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]);
+      tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]);
+
+      var fn = new Function("_items,_args,_cache", tpl);
+      fn.displayName = fn.name = "compiledFilterWithCaching";
+      return fn;
+    }
+
+    function uncompiledFilter(items, args) {
+      var retval = [], idx = 0;
+
+      for (var i = 0, ii = items.length; i < ii; i++) {
+        if (filter(items[i], args)) {
+          retval[idx++] = items[i];
         }
+      }
 
-        function deleteItem(id) {
-            var idx = idxById[id];
-            if (idx === undefined) {
-                throw "Invalid id";
-            }
-            delete idxById[id];
-            items.splice(idx, 1);
-            updateIdxById(idx);
-            refresh();
-        }
+      return retval;
+    }
 
-        function getLength() {
-            return rows.length;
-        }
+    function uncompiledFilterWithCaching(items, args, cache) {
+      var retval = [], idx = 0, item;
 
-        function getItem(i) {
-            return rows[i];
+      for (var i = 0, ii = items.length; i < ii; i++) {
+        item = items[i];
+        if (cache[i]) {
+          retval[idx++] = item;
+        } else if (filter(item, args)) {
+          retval[idx++] = item;
+          cache[i] = true;
         }
-
-        function getItemMetadata(i) {
-            var item = rows[i];
-            if (item === undefined) {
-                return null;
-            }
-
-            // overrides for group rows
-            if (item.__group) {
-                return options.groupItemMetadataProvider.getGroupRowMetadata(item);
-            }
-
-            // overrides for totals rows
-            if (item.__groupTotals) {
-                return options.groupItemMetadataProvider.getTotalsRowMetadata(item);
-            }
-
-            return null;
+      }
+
+      return retval;
+    }
+
+    function getFilteredAndPagedItems(items) {
+      if (filter) {
+        var batchFilter = options.inlineFilters ? compiledFilter : uncompiledFilter;
+        var batchFilterWithCaching = options.inlineFilters ? compiledFilterWithCaching : uncompiledFilterWithCaching;
+
+        if (refreshHints.isFilterNarrowing) {
+          filteredItems = batchFilter(filteredItems, filterArgs);
+        } else if (refreshHints.isFilterExpanding) {
+          filteredItems = batchFilterWithCaching(items, filterArgs, filterCache);
+        } else if (!refreshHints.isFilterUnchanged) {
+          filteredItems = batchFilter(items, filterArgs);
         }
-
-        function collapseGroup(groupingValue) {
-            collapsedGroups[groupingValue] = true;
-            refresh();
+      } else {
+        // special case:  if not filtering and not paging, the resulting
+        // rows collection needs to be a copy so that changes due to sort
+        // can be caught
+        filteredItems = pagesize ? items : items.concat();
+      }
+
+      // get the current page
+      var paged;
+      if (pagesize) {
+        if (filteredItems.length < pagenum * pagesize) {
+          pagenum = Math.floor(filteredItems.length / pagesize);
         }
-
-        function expandGroup(groupingValue) {
-            delete collapsedGroups[groupingValue];
-            refresh();
+        paged = filteredItems.slice(pagesize * pagenum, pagesize * pagenum + pagesize);
+      } else {
+        paged = filteredItems;
+      }
+
+      return {totalRows: filteredItems.length, rows: paged};
+    }
+
+    function getRowDiffs(rows, newRows) {
+      var item, r, eitherIsNonData, diff = [];
+      var from = 0, to = newRows.length;
+
+      if (refreshHints && refreshHints.ignoreDiffsBefore) {
+        from = Math.max(0,
+            Math.min(newRows.length, refreshHints.ignoreDiffsBefore));
+      }
+
+      if (refreshHints && refreshHints.ignoreDiffsAfter) {
+        to = Math.min(newRows.length,
+            Math.max(0, refreshHints.ignoreDiffsAfter));
+      }
+
+      for (var i = from, rl = rows.length; i < to; i++) {
+        if (i >= rl) {
+          diff[diff.length] = i;
+        } else {
+          item = newRows[i];
+          r = rows[i];
+
+          if ((groupingInfos.length && (eitherIsNonData = (item.__nonDataRow) || (r.__nonDataRow)) &&
+              item.__group !== r.__group ||
+              item.__group && !item.equals(r))
+              || (eitherIsNonData &&
+              // no good way to compare totals since they are arbitrary DTOs
+              // deep object comparison is pretty expensive
+              // always considering them 'dirty' seems easier for the time being
+              (item.__groupTotals || r.__groupTotals))
+              || item[idProperty] != r[idProperty]
+              || (updated && updated[item[idProperty]])
+              ) {
+            diff[diff.length] = i;
+          }
         }
-
-        function getGroups() {
-            return groups;
+      }
+      return diff;
+    }
+
+    function recalc(_items) {
+      rowsById = null;
+
+      if (refreshHints.isFilterNarrowing != prevRefreshHints.isFilterNarrowing ||
+          refreshHints.isFilterExpanding != prevRefreshHints.isFilterExpanding) {
+        filterCache = [];
+      }
+
+      var filteredItems = getFilteredAndPagedItems(_items);
+      totalRows = filteredItems.totalRows;
+      var newRows = filteredItems.rows;
+
+      groups = [];
+      if (groupingInfos.length) {
+        groups = extractGroups(newRows);
+        if (groups.length) {
+          addTotals(groups);
+          newRows = flattenGroupedRows(groups);
         }
+      }
 
-        function extractGroups(rows) {
-            var group;
-            var val;
-            var groups = [];
-            var groupsByVal = [];
-            var r;
+      var diff = getRowDiffs(rows, newRows);
 
-            for (var i = 0, l = rows.length; i < l; i++) {
-                r = rows[i];
-                val = (groupingGetterIsAFn) ? groupingGetter(r) : r[groupingGetter];
-                val = val || 0;
-                group = groupsByVal[val];
-                if (!group) {
-                    group = new Slick.Group();
-                    group.count = 0;
-                    group.value = val;
-                    group.rows = [];
-                    groups[groups.length] = group;
-                    groupsByVal[val] = group;
-                }
+      rows = newRows;
 
-                group.rows[group.count++] = r;
-            }
+      return diff;
+    }
 
-            return groups;
-        }
+    function refresh() {
+      if (suspend) {
+        return;
+      }
 
-        // TODO:  lazy totals calculation
-        function calculateGroupTotals(group) {
-            if (group.collapsed && !aggregateCollapsed) {
-                return;
-            }
+      var countBefore = rows.length;
+      var totalRowsBefore = totalRows;
 
-            // TODO:  try moving iterating over groups into compiled accumulator
-            var totals = new Slick.GroupTotals();
-            var agg, idx = aggregators.length;
-            while (idx--) {
-                agg = aggregators[idx];
-                agg.init();
-                compiledAccumulators[idx].call(agg, group.rows);
-                agg.storeResult(totals);
-            }
-            totals.group = group;
-            group.totals = totals;
-        }
+      var diff = recalc(items, filter); // pass as direct refs to avoid closure perf hit
 
-        function calculateTotals(groups) {
-            var idx = groups.length;
-            while (idx--) {
-                calculateGroupTotals(groups[idx]);
-            }
-        }
+      // if the current page is no longer valid, go to last page and recalc
+      // we suffer a performance penalty here, but the main loop (recalc) remains highly optimized
+      if (pagesize && totalRows < pagenum * pagesize) {
+        pagenum = Math.max(0, Math.ceil(totalRows / pagesize) - 1);
+        diff = recalc(items, filter);
+      }
 
-        function finalizeGroups(groups) {
-            var idx = groups.length, g;
-            while (idx--) {
-                g = groups[idx];
-                g.collapsed = (g.value in collapsedGroups);
-                g.title = groupingFormatter ? groupingFormatter(g) : g.value;
-            }
-        }
+      updated = null;
+      prevRefreshHints = refreshHints;
+      refreshHints = {};
 
-        function flattenGroupedRows(groups) {
-            var groupedRows = [], gl = 0, g;
-            for (var i = 0, l = groups.length; i < l; i++) {
-                g = groups[i];
-                groupedRows[gl++] = g;
+      if (totalRowsBefore != totalRows) {
+        onPagingInfoChanged.notify(getPagingInfo(), null, self);
+      }
+      if (countBefore != rows.length) {
+        onRowCountChanged.notify({previous: countBefore, current: rows.length}, null, self);
+      }
+      if (diff.length > 0) {
+        onRowsChanged.notify({rows: diff}, null, self);
+      }
+    }
 
-                if (!g.collapsed) {
-                    for (var j = 0, jj = g.rows.length; j < jj; j++) {
-                        groupedRows[gl++] = g.rows[j];
-                    }
-                }
-
-                if (g.totals && (!g.collapsed || aggregateCollapsed)) {
-                    groupedRows[gl++] = g.totals;
-                }
-            }
-            return groupedRows;
-        }
-
-        function getFunctionInfo(fn) {
-            var fnRegex = /^function[^(]*\(([^)]*)\)\s*{([\s\S]*)}$/;
-            var matches = fn.toString().match(fnRegex);
-            return {
-                params: matches[1].split(","),
-                body: matches[2]
-            };
-        }
-
-        function compileAccumulatorLoop(aggregator) {
-            var accumulatorInfo = getFunctionInfo(aggregator.accumulate);
-            var fn = new Function(
-                    "_items",
-                    "for (var " + accumulatorInfo.params[0] + ", _i=0, _il=_items.length; _i<_il; _i++) {" +
-                    accumulatorInfo.params[0] + " = _items[_i]; " +
-                    accumulatorInfo.body +
-                    "}"
-                    );
-            fn.displayName = fn.name = "compiledAccumulatorLoop";
-            return fn;
-        }
-
-        function compileFilter() {
-            var filterInfo = getFunctionInfo(filter);
-
-            var filterBody = filterInfo.body
-                    .replace(/return false[;}]/gi, "{ continue _coreloop; }")
-                    .replace(/return true[;}]/gi, "{ _retval[_idx++] = $item$; continue _coreloop; }")
-                    .replace(/return ([^;}]+?);/gi,
-                            "{ if ($1) { _retval[_idx++] = $item$; }; continue _coreloop; }");
-
-            // This preserves the function template code after JS compression,
-            // so that replace() commands still work as expected.
-            var tpl = [
-                //"function(_items, _args) { ",
-                "var _retval = [], _idx = 0; ",
-                "var $item$, $args$ = _args; ",
-                "_coreloop: ",
-                "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ",
-                "$item$ = _items[_i]; ",
-                "$filter$; ",
-                "} ",
-                "return _retval; "
-                        //"}"
-            ].join("");
-            tpl = tpl.replace(/\$filter\$/gi, filterBody);
-            tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]);
-            tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]);
-
-            var fn = new Function("_items,_args", tpl);
-            fn.displayName = fn.name = "compiledFilter";
-            return fn;
-        }
-
-        function compileFilterWithCaching() {
-            var filterInfo = getFunctionInfo(filter);
-
-            var filterBody = filterInfo.body
-                    .replace(/return false[;}]/gi, "{ continue _coreloop; }")
-                    .replace(/return true[;}]/gi, "{ _cache[_i] = true;_retval[_idx++] = $item$; continue _coreloop; }")
-                    .replace(/return ([^;}]+?);/gi,
-                            "{ if ((_cache[_i] = $1)) { _retval[_idx++] = $item$; }; continue _coreloop; }");
-
-            // This preserves the function template code after JS compression,
-            // so that replace() commands still work as expected.
-            var tpl = [
-                //"function(_items, _args, _cache) { ",
-                "var _retval = [], _idx = 0; ",
-                "var $item$, $args$ = _args; ",
-                "_coreloop: ",
-                "for (var _i = 0, _il = _items.length; _i < _il; _i++) { ",
-                "$item$ = _items[_i]; ",
-                "if (_cache[_i]) { ",
-                "_retval[_idx++] = $item$; ",
-                "continue _coreloop; ",
-                "} ",
-                "$filter$; ",
-                "} ",
-                "return _retval; "
-                        //"}"
-            ].join("");
-            tpl = tpl.replace(/\$filter\$/gi, filterBody);
-            tpl = tpl.replace(/\$item\$/gi, filterInfo.params[0]);
-            tpl = tpl.replace(/\$args\$/gi, filterInfo.params[1]);
-
-            var fn = new Function("_items,_args,_cache", tpl);
-            fn.displayName = fn.name = "compiledFilterWithCaching";
-            return fn;
-        }
-
-        function uncompiledFilter(items, args) {
-            var retval = [], idx = 0;
-
-            for (var i = 0, ii = items.length; i < ii; i++) {
-                if (filter(items[i], args)) {
-                    retval[idx++] = items[i];
-                }
-            }
-
-            return retval;
+    /***
+     * Wires the grid and the DataView together to keep row selection tied to item ids.
+     * This is useful since, without it, the grid only knows about rows, so if the items
+     * move around, the same rows stay selected instead of the selection moving along
+     * with the items.
+     *
+     * NOTE:  This doesn't work with cell selection model.
+     *
+     * @param grid {Slick.Grid} The grid to sync selection with.
+     * @param preserveHidden {Boolean} Whether to keep selected items that go out of the
+     *     view due to them getting filtered out.
+     * @param preserveHiddenOnSelectionChange {Boolean} Whether to keep selected items
+     *     that are currently out of the view (see preserveHidden) as selected when selection
+     *     changes.
+     * @return {Slick.Event} An event that notifies when an internal list of selected row ids
+     *     changes.  This is useful since, in combination with the above two options, it allows
+     *     access to the full list selected row ids, and not just the ones visible to the grid.
+     * @method syncGridSelection
+     */
+    function syncGridSelection(grid, preserveHidden, preserveHiddenOnSelectionChange) {
+      var self = this;
+      var inHandler;
+      var selectedRowIds = self.mapRowsToIds(grid.getSelectedRows());
+      var onSelectedRowIdsChanged = new Slick.Event();
+
+      function setSelectedRowIds(rowIds) {
+        if (selectedRowIds.join(",") == rowIds.join(",")) {
+          return;
         }
 
-        function uncompiledFilterWithCaching(items, args, cache) {
-            var retval = [], idx = 0, item;
-
-            for (var i = 0, ii = items.length; i < ii; i++) {
-                item = items[i];
-                if (cache[i]) {
-                    retval[idx++] = item;
-                } else if (filter(item, args)) {
-                    retval[idx++] = item;
-                    cache[i] = true;
-                }
-            }
+        selectedRowIds = rowIds;
+
+        onSelectedRowIdsChanged.notify({
+          "grid": grid,
+          "ids": selectedRowIds
+        }, new Slick.EventData(), self);
+      }
+
+      function update() {
+        if (selectedRowIds.length > 0) {
+          inHandler = true;
+          var selectedRows = self.mapIdsToRows(selectedRowIds);
+          if (!preserveHidden) {
+            setSelectedRowIds(self.mapRowsToIds(selectedRows));       
+          }
+          grid.setSelectedRows(selectedRows);
+          inHandler = false;
+        }
+      }
+
+      grid.onSelectedRowsChanged.subscribe(function(e, args) {
+        if (inHandler) { return; }
+        var newSelectedRowIds = self.mapRowsToIds(grid.getSelectedRows());
+        if (!preserveHiddenOnSelectionChange || !grid.getOptions().multiSelect) {
+          setSelectedRowIds(newSelectedRowIds);
+        } else {
+          // keep the ones that are hidden
+          var existing = $.grep(selectedRowIds, function(id) { return self.getRowById(id) === undefined; });
+          // add the newly selected ones
+          setSelectedRowIds(existing.concat(newSelectedRowIds));
+        }
+      });
 
-            return retval;
-        }
-
-        function getFilteredAndPagedItems(items) {
-            if (filter) {
-                var batchFilter = options.inlineFilters ? compiledFilter : uncompiledFilter;
-                var batchFilterWithCaching = options.inlineFilters ? compiledFilterWithCaching : uncompiledFilterWithCaching;
-
-                if (refreshHints.isFilterNarrowing) {
-                    filteredItems = batchFilter(filteredItems, filterArgs);
-                } else if (refreshHints.isFilterExpanding) {
-                    filteredItems = batchFilterWithCaching(items, filterArgs, filterCache);
-                } else if (!refreshHints.isFilterUnchanged) {
-                    filteredItems = batchFilter(items, filterArgs);
-                }
-            } else {
-                // special case:  if not filtering and not paging, the resulting
-                // rows collection needs to be a copy so that changes due to sort
-                // can be caught
-                filteredItems = pagesize ? items : items.concat();
-            }
+      this.onRowsChanged.subscribe(update);
 
-            // get the current page
-            var paged;
-            if (pagesize) {
-                if (filteredItems.length < pagenum * pagesize) {
-                    pagenum = Math.floor(filteredItems.length / pagesize);
-                }
-                paged = filteredItems.slice(pagesize * pagenum, pagesize * pagenum + pagesize);
-            } else {
-                paged = filteredItems;
-            }
+      this.onRowCountChanged.subscribe(update);
 
-            return {totalRows: filteredItems.length, rows: paged};
-        }
+      return onSelectedRowIdsChanged;
+    }
 
-        function getRowDiffs(rows, newRows) {
-            var item, r, eitherIsNonData, diff = [];
-            var from = 0, to = newRows.length;
+    function syncGridCellCssStyles(grid, key) {
+      var hashById;
+      var inHandler;
 
-            if (refreshHints && refreshHints.ignoreDiffsBefore) {
-                from = Math.max(0,
-                        Math.min(newRows.length, refreshHints.ignoreDiffsBefore));
-            }
+      // since this method can be called after the cell styles have been set,
+      // get the existing ones right away
+      storeCellCssStyles(grid.getCellCssStyles(key));
 
-            if (refreshHints && refreshHints.ignoreDiffsAfter) {
-                to = Math.min(newRows.length,
-                        Math.max(0, refreshHints.ignoreDiffsAfter));
+      function storeCellCssStyles(hash) {
+        hashById = {};
+        for (var row in hash) {
+          var id = rows[row][idProperty];
+          hashById[id] = hash[row];
+        }
+      }
+
+      function update() {
+        if (hashById) {
+          inHandler = true;
+          ensureRowsByIdCache();
+          var newHash = {};
+          for (var id in hashById) {
+            var row = rowsById[id];
+            if (row != undefined) {
+              newHash[row] = hashById[id];
             }
+          }
+          grid.setCellCssStyles(key, newHash);
+          inHandler = false;
+        }
+      }
 
-            for (var i = from, rl = rows.length; i < to; i++) {
-                if (i >= rl) {
-                    diff[diff.length] = i;
-                } else {
-                    item = newRows[i];
-                    r = rows[i];
-
-                    if ((groupingGetter && (eitherIsNonData = (item.__nonDataRow) || (r.__nonDataRow)) &&
-                            item.__group !== r.__group ||
-                            item.__updated ||
-                            item.__group && !item.equals(r))
-                            || (aggregators && eitherIsNonData &&
-                                    // no good way to compare totals since they are arbitrary DTOs
-                                            // deep object comparison is pretty expensive
-                                                    // always considering them 'dirty' seems easier for the time being
-                                                            (item.__groupTotals || r.__groupTotals))
-                                                    || item[idProperty] != r[idProperty]
-                                                    || (updated && updated[item[idProperty]])
-                                                    ) {
-                                        diff[diff.length] = i;
-                                    }
-                                }
-                            }
-                            return diff;
-                        }
-
-                        function recalc(_items) {
-                            rowsById = null;
-
-                            if (refreshHints.isFilterNarrowing != prevRefreshHints.isFilterNarrowing ||
-                                    refreshHints.isFilterExpanding != prevRefreshHints.isFilterExpanding) {
-                                filterCache = [];
-                            }
-
-                            var filteredItems = getFilteredAndPagedItems(_items);
-                            totalRows = filteredItems.totalRows;
-                            var newRows = filteredItems.rows;
-
-                            groups = [];
-                            if (groupingGetter != null) {
-                                groups = extractGroups(newRows);
-                                if (groups.length) {
-                                    finalizeGroups(groups);
-                                    if (aggregators) {
-                                        calculateTotals(groups);
-                                    }
-                                    groups.sort(groupingComparer);
-                                    newRows = flattenGroupedRows(groups);
-                                }
-                            }
-
-                            var diff = getRowDiffs(rows, newRows);
-
-                            rows = newRows;
-
-                            return diff;
-                        }
-
-                        function refresh() {
-                            if (suspend) {
-                                return;
-                            }
-
-                            var countBefore = rows.length;
-                            var totalRowsBefore = totalRows;
-
-                            var diff = recalc(items, filter); // pass as direct refs to avoid closure perf hit
-
-                            // if the current page is no longer valid, go to last page and recalc
-                            // we suffer a performance penalty here, but the main loop (recalc) remains highly optimized
-                            if (pagesize && totalRows < pagenum * pagesize) {
-                                pagenum = Math.max(0, Math.ceil(totalRows / pagesize) - 1);
-                                diff = recalc(items, filter);
-                            }
-
-                            updated = null;
-                            prevRefreshHints = refreshHints;
-                            refreshHints = {};
-
-                            if (totalRowsBefore != totalRows) {
-                                onPagingInfoChanged.notify(getPagingInfo(), null, self);
-                            }
-                            if (countBefore != rows.length) {
-                                onRowCountChanged.notify({previous: countBefore, current: rows.length}, null, self);
-                            }
-                            if (diff.length > 0) {
-                                onRowsChanged.notify({rows: diff}, null, self);
-                            }
-                        }
-
-                        function syncGridSelection(grid, preserveHidden) {
-                            var self = this;
-                            var selectedRowIds = self.mapRowsToIds(grid.getSelectedRows());
-                            ;
-                            var inHandler;
-
-                            grid.onSelectedRowsChanged.subscribe(function (e, args) {
-                                if (inHandler) {
-                                    return;
-                                }
-                                selectedRowIds = self.mapRowsToIds(grid.getSelectedRows());
-                            });
-
-                            this.onRowsChanged.subscribe(function (e, args) {
-                                if (selectedRowIds.length > 0) {
-                                    inHandler = true;
-                                    var selectedRows = self.mapIdsToRows(selectedRowIds);
-                                    if (!preserveHidden) {
-                                        selectedRowIds = self.mapRowsToIds(selectedRows);
-                                    }
-                                    grid.setSelectedRows(selectedRows);
-                                    inHandler = false;
-                                }
-                            });
-                        }
-
-                        function syncGridCellCssStyles(grid, key) {
-                            var hashById;
-                            var inHandler;
-
-                            // since this method can be called after the cell styles have been set,
-                            // get the existing ones right away
-                            storeCellCssStyles(grid.getCellCssStyles(key));
-
-                            function storeCellCssStyles(hash) {
-                                hashById = {};
-                                for (var row in hash) {
-                                    var id = rows[row][idProperty];
-                                    hashById[id] = hash[row];
-                                }
-                            }
-
-                            grid.onCellCssStylesChanged.subscribe(function (e, args) {
-                                if (inHandler) {
-                                    return;
-                                }
-                                if (key != args.key) {
-                                    return;
-                                }
-                                if (args.hash) {
-                                    storeCellCssStyles(args.hash);
-                                }
-                            });
-
-                            this.onRowsChanged.subscribe(function (e, args) {
-                                if (hashById) {
-                                    inHandler = true;
-                                    ensureRowsByIdCache();
-                                    var newHash = {};
-                                    for (var id in hashById) {
-                                        var row = rowsById[id];
-                                        if (row != undefined) {
-                                            newHash[row] = hashById[id];
-                                        }
-                                    }
-                                    grid.setCellCssStyles(key, newHash);
-                                    inHandler = false;
-                                }
-                            });
-                        }
-
-                        return {
-                            // methods
-                            "beginUpdate": beginUpdate,
-                            "endUpdate": endUpdate,
-                            "setPagingOptions": setPagingOptions,
-                            "getPagingInfo": getPagingInfo,
-                            "getItems": getItems,
-                            "setItems": setItems,
-                            "setFilter": setFilter,
-                            "sort": sort,
-                            "fastSort": fastSort,
-                            "reSort": reSort,
-                            "groupBy": groupBy,
-                            "setAggregators": setAggregators,
-                            "collapseGroup": collapseGroup,
-                            "expandGroup": expandGroup,
-                            "getGroups": getGroups,
-                            "getIdxById": getIdxById,
-                            "getRowById": getRowById,
-                            "getItemById": getItemById,
-                            "getItemByIdx": getItemByIdx,
-                            "mapRowsToIds": mapRowsToIds,
-                            "mapIdsToRows": mapIdsToRows,
-                            "setRefreshHints": setRefreshHints,
-                            "setFilterArgs": setFilterArgs,
-                            "refresh": refresh,
-                            "updateItem": updateItem,
-                            "insertItem": insertItem,
-                            "addItem": addItem,
-                            "deleteItem": deleteItem,
-                            "syncGridSelection": syncGridSelection,
-                            "syncGridCellCssStyles": syncGridCellCssStyles,
-                            // data provider methods
-                            "getLength": getLength,
-                            "getItem": getItem,
-                            "getItemMetadata": getItemMetadata,
-                            // events
-                            "onRowCountChanged": onRowCountChanged,
-                            "onRowsChanged": onRowsChanged,
-                            "onPagingInfoChanged": onPagingInfoChanged
-                        };
-                    }
-
-                    function AvgAggregator(field) {
-                        this.field_ = field;
-
-                        this.init = function () {
-                            this.count_ = 0;
-                            this.nonNullCount_ = 0;
-                            this.sum_ = 0;
-                        };
-
-                        this.accumulate = function (item) {
-                            var val = item[this.field_];
-                            this.count_++;
-                            if (val != null && val != "" && val != NaN) {
-                                this.nonNullCount_++;
-                                this.sum_ += parseFloat(val);
-                            }
-                        };
-
-                        this.storeResult = function (groupTotals) {
-                            if (!groupTotals.avg) {
-                                groupTotals.avg = {};
-                            }
-                            if (this.nonNullCount_ != 0) {
-                                groupTotals.avg[this.field_] = this.sum_ / this.nonNullCount_;
-                            }
-                        };
-                    }
-
-                    function MinAggregator(field) {
-                        this.field_ = field;
-
-                        this.init = function () {
-                            this.min_ = null;
-                        };
-
-                        this.accumulate = function (item) {
-                            var val = item[this.field_];
-                            if (val != null && val != "" && val != NaN) {
-                                if (this.min_ == null || val < this.min_) {
-                                    this.min_ = val;
-                                }
-                            }
-                        };
-
-                        this.storeResult = function (groupTotals) {
-                            if (!groupTotals.min) {
-                                groupTotals.min = {};
-                            }
-                            groupTotals.min[this.field_] = this.min_;
-                        }
-                    }
-
-                    function MaxAggregator(field) {
-                        this.field_ = field;
-
-                        this.init = function () {
-                            this.max_ = null;
-                        };
-
-                        this.accumulate = function (item) {
-                            var val = item[this.field_];
-                            if (val != null && val != "" && val != NaN) {
-                                if (this.max_ == null || val > this.max_) {
-                                    this.max_ = val;
-                                }
-                            }
-                        };
-
-                        this.storeResult = function (groupTotals) {
-                            if (!groupTotals.max) {
-                                groupTotals.max = {};
-                            }
-                            groupTotals.max[this.field_] = this.max_;
-                        }
-                    }
-
-                    function SumAggregator(field) {
-                        this.field_ = field;
-
-                        this.init = function () {
-                            this.sum_ = null;
-                        };
-
-                        this.accumulate = function (item) {
-                            var val = item[this.field_];
-                            if (val != null && val != "" && val != NaN) {
-                                this.sum_ += parseFloat(val);
-                            }
-                        };
-
-                        this.storeResult = function (groupTotals) {
-                            if (!groupTotals.sum) {
-                                groupTotals.sum = {};
-                            }
-                            groupTotals.sum[this.field_] = this.sum_;
-                        }
-                    }
-
-                    // TODO:  add more built-in aggregators
-                    // TODO:  merge common aggregators in one to prevent needles iterating
-
-                })(jQuery);
\ No newline at end of file
+      grid.onCellCssStylesChanged.subscribe(function(e, args) {
+        if (inHandler) { return; }
+        if (key != args.key) { return; }
+        if (args.hash) {
+          storeCellCssStyles(args.hash);
+        }
+      });
+
+      this.onRowsChanged.subscribe(update);
+
+      this.onRowCountChanged.subscribe(update);
+    }
+
+    $.extend(this, {
+      // methods
+      "beginUpdate": beginUpdate,
+      "endUpdate": endUpdate,
+      "setPagingOptions": setPagingOptions,
+      "getPagingInfo": getPagingInfo,
+      "getItems": getItems,
+      "setItems": setItems,
+      "setFilter": setFilter,
+      "sort": sort,
+      "fastSort": fastSort,
+      "reSort": reSort,
+      "setGrouping": setGrouping,
+      "getGrouping": getGrouping,
+      "groupBy": groupBy,
+      "setAggregators": setAggregators,
+      "collapseAllGroups": collapseAllGroups,
+      "expandAllGroups": expandAllGroups,
+      "collapseGroup": collapseGroup,
+      "expandGroup": expandGroup,
+      "getGroups": getGroups,
+      "getIdxById": getIdxById,
+      "getRowById": getRowById,
+      "getItemById": getItemById,
+      "getItemByIdx": getItemByIdx,
+      "mapRowsToIds": mapRowsToIds,
+      "mapIdsToRows": mapIdsToRows,
+      "setRefreshHints": setRefreshHints,
+      "setFilterArgs": setFilterArgs,
+      "refresh": refresh,
+      "updateItem": updateItem,
+      "insertItem": insertItem,
+      "addItem": addItem,
+      "deleteItem": deleteItem,
+      "syncGridSelection": syncGridSelection,
+      "syncGridCellCssStyles": syncGridCellCssStyles,
+
+      // data provider methods
+      "getLength": getLength,
+      "getItem": getItem,
+      "getItemMetadata": getItemMetadata,
+
+      // events
+      "onRowCountChanged": onRowCountChanged,
+      "onRowsChanged": onRowsChanged,
+      "onPagingInfoChanged": onPagingInfoChanged
+    });
+  }
+
+  function AvgAggregator(field) {
+    this.field_ = field;
+
+    this.init = function () {
+      this.count_ = 0;
+      this.nonNullCount_ = 0;
+      this.sum_ = 0;
+    };
+
+    this.accumulate = function (item) {
+      var val = item[this.field_];
+      this.count_++;
+      if (val != null && val !== "" && val !== NaN) {
+        this.nonNullCount_++;
+        this.sum_ += parseFloat(val);
+      }
+    };
+
+    this.storeResult = function (groupTotals) {
+      if (!groupTotals.avg) {
+        groupTotals.avg = {};
+      }
+      if (this.nonNullCount_ != 0) {
+        groupTotals.avg[this.field_] = this.sum_ / this.nonNullCount_;
+      }
+    };
+  }
+
+  function MinAggregator(field) {
+    this.field_ = field;
+
+    this.init = function () {
+      this.min_ = null;
+    };
+
+    this.accumulate = function (item) {
+      var val = item[this.field_];
+      if (val != null && val !== "" && val !== NaN) {
+        if (this.min_ == null || val < this.min_) {
+          this.min_ = val;
+        }
+      }
+    };
+
+    this.storeResult = function (groupTotals) {
+      if (!groupTotals.min) {
+        groupTotals.min = {};
+      }
+      groupTotals.min[this.field_] = this.min_;
+    }
+  }
+
+  function MaxAggregator(field) {
+    this.field_ = field;
+
+    this.init = function () {
+      this.max_ = null;
+    };
+
+    this.accumulate = function (item) {
+      var val = item[this.field_];
+      if (val != null && val !== "" && val !== NaN) {
+        if (this.max_ == null || val > this.max_) {
+          this.max_ = val;
+        }
+      }
+    };
+
+    this.storeResult = function (groupTotals) {
+      if (!groupTotals.max) {
+        groupTotals.max = {};
+      }
+      groupTotals.max[this.field_] = this.max_;
+    }
+  }
+
+  function SumAggregator(field) {
+    this.field_ = field;
+
+    this.init = function () {
+      this.sum_ = null;
+    };
+
+    this.accumulate = function (item) {
+      var val = item[this.field_];
+      if (val != null && val !== "" && val !== NaN) {
+        this.sum_ += parseFloat(val);
+      }
+    };
+
+    this.storeResult = function (groupTotals) {
+      if (!groupTotals.sum) {
+        groupTotals.sum = {};
+      }
+      groupTotals.sum[this.field_] = this.sum_;
+    }
+  }
+
+  // TODO:  add more built-in aggregators
+  // TODO:  merge common aggregators in one to prevent needles iterating
+
+})(jQuery);


[2/5] incubator-nifi git commit: NIFI-27: - Latest version of slickgrid.

Posted by mc...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.editors.js
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.editors.js b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.editors.js
index 2d8a482..04b20d2 100755
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.editors.js
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.editors.js
@@ -5,508 +5,508 @@
  */
 
 (function ($) {
-    // register namespace
-    $.extend(true, window, {
-        "Slick": {
-            "Editors": {
-                "Text": TextEditor,
-                "Integer": IntegerEditor,
-                "Date": DateEditor,
-                "YesNoSelect": YesNoSelectEditor,
-                "Checkbox": CheckboxEditor,
-                "PercentComplete": PercentCompleteEditor,
-                "LongText": LongTextEditor
-            }
-        }
-    });
-
-    function TextEditor(args) {
-        var $input;
-        var defaultValue;
-        var scope = this;
-
-        this.init = function () {
-            $input = $("<INPUT type=text class='editor-text' />")
-                    .appendTo(args.container)
-                    .bind("keydown.nav", function (e) {
-                        if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) {
-                            e.stopImmediatePropagation();
-                        }
-                    })
-                    .focus()
-                    .select();
-        };
-
-        this.destroy = function () {
-            $input.remove();
-        };
-
-        this.focus = function () {
-            $input.focus();
-        };
-
-        this.getValue = function () {
-            return $input.val();
-        };
-
-        this.setValue = function (val) {
-            $input.val(val);
-        };
-
-        this.loadValue = function (item) {
-            defaultValue = item[args.column.field] || "";
-            $input.val(defaultValue);
-            $input[0].defaultValue = defaultValue;
-            $input.select();
-        };
-
-        this.serializeValue = function () {
-            return $input.val();
-        };
-
-        this.applyValue = function (item, state) {
-            item[args.column.field] = state;
-        };
-
-        this.isValueChanged = function () {
-            return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
-        };
-
-        this.validate = function () {
-            if (args.column.validator) {
-                var validationResults = args.column.validator($input.val());
-                if (!validationResults.valid) {
-                    return validationResults;
-                }
-            }
-
-            return {
-                valid: true,
-                msg: null
-            };
-        };
-
-        this.init();
-    }
-
-    function IntegerEditor(args) {
-        var $input;
-        var defaultValue;
-        var scope = this;
-
-        this.init = function () {
-            $input = $("<INPUT type=text class='editor-text' />");
-
-            $input.bind("keydown.nav", function (e) {
-                if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) {
-                    e.stopImmediatePropagation();
-                }
-            });
-
-            $input.appendTo(args.container);
-            $input.focus().select();
-        };
-
-        this.destroy = function () {
-            $input.remove();
-        };
-
-        this.focus = function () {
-            $input.focus();
-        };
-
-        this.loadValue = function (item) {
-            defaultValue = item[args.column.field];
-            $input.val(defaultValue);
-            $input[0].defaultValue = defaultValue;
-            $input.select();
-        };
-
-        this.serializeValue = function () {
-            return parseInt($input.val(), 10) || 0;
-        };
-
-        this.applyValue = function (item, state) {
-            item[args.column.field] = state;
-        };
-
-        this.isValueChanged = function () {
-            return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
-        };
-
-        this.validate = function () {
-            if (isNaN($input.val())) {
-                return {
-                    valid: false,
-                    msg: "Please enter a valid integer"
-                };
-            }
-
-            return {
-                valid: true,
-                msg: null
-            };
-        };
-
-        this.init();
-    }
-
-    function DateEditor(args) {
-        var $input;
-        var defaultValue;
-        var scope = this;
-        var calendarOpen = false;
-
-        this.init = function () {
-            $input = $("<INPUT type=text class='editor-text' />");
-            $input.appendTo(args.container);
-            $input.focus().select();
-            $input.datepicker({
-                showOn: "button",
-                buttonImageOnly: true,
-                buttonImage: "../images/calendar.gif",
-                beforeShow: function () {
-                    calendarOpen = true
-                },
-                onClose: function () {
-                    calendarOpen = false
-                }
-            });
-            $input.width($input.width() - 18);
-        };
-
-        this.destroy = function () {
-            $.datepicker.dpDiv.stop(true, true);
-            $input.datepicker("hide");
-            $input.datepicker("destroy");
-            $input.remove();
-        };
-
-        this.show = function () {
-            if (calendarOpen) {
-                $.datepicker.dpDiv.stop(true, true).show();
-            }
-        };
-
-        this.hide = function () {
-            if (calendarOpen) {
-                $.datepicker.dpDiv.stop(true, true).hide();
-            }
-        };
-
-        this.position = function (position) {
-            if (!calendarOpen) {
-                return;
-            }
-            $.datepicker.dpDiv
-                    .css("top", position.top + 30)
-                    .css("left", position.left);
-        };
-
-        this.focus = function () {
-            $input.focus();
-        };
-
-        this.loadValue = function (item) {
-            defaultValue = item[args.column.field];
-            $input.val(defaultValue);
-            $input[0].defaultValue = defaultValue;
-            $input.select();
-        };
-
-        this.serializeValue = function () {
-            return $input.val();
-        };
-
-        this.applyValue = function (item, state) {
-            item[args.column.field] = state;
-        };
-
-        this.isValueChanged = function () {
-            return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
-        };
-
-        this.validate = function () {
-            return {
-                valid: true,
-                msg: null
-            };
-        };
-
-        this.init();
-    }
-
-    function YesNoSelectEditor(args) {
-        var $select;
-        var defaultValue;
-        var scope = this;
-
-        this.init = function () {
-            $select = $("<SELECT tabIndex='0' class='editor-yesno'><OPTION value='yes'>Yes</OPTION><OPTION value='no'>No</OPTION></SELECT>");
-            $select.appendTo(args.container);
-            $select.focus();
-        };
-
-        this.destroy = function () {
-            $select.remove();
-        };
-
-        this.focus = function () {
-            $select.focus();
-        };
-
-        this.loadValue = function (item) {
-            $select.val((defaultValue = item[args.column.field]) ? "yes" : "no");
-            $select.select();
-        };
-
-        this.serializeValue = function () {
-            return ($select.val() == "yes");
-        };
-
-        this.applyValue = function (item, state) {
-            item[args.column.field] = state;
-        };
-
-        this.isValueChanged = function () {
-            return ($select.val() != defaultValue);
-        };
-
-        this.validate = function () {
-            return {
-                valid: true,
-                msg: null
-            };
-        };
-
-        this.init();
+  // register namespace
+  $.extend(true, window, {
+    "Slick": {
+      "Editors": {
+        "Text": TextEditor,
+        "Integer": IntegerEditor,
+        "Date": DateEditor,
+        "YesNoSelect": YesNoSelectEditor,
+        "Checkbox": CheckboxEditor,
+        "PercentComplete": PercentCompleteEditor,
+        "LongText": LongTextEditor
+      }
     }
-
-    function CheckboxEditor(args) {
-        var $select;
-        var defaultValue;
-        var scope = this;
-
-        this.init = function () {
-            $select = $("<INPUT type=checkbox value='true' class='editor-checkbox' hideFocus>");
-            $select.appendTo(args.container);
-            $select.focus();
-        };
-
-        this.destroy = function () {
-            $select.remove();
-        };
-
-        this.focus = function () {
-            $select.focus();
-        };
-
-        this.loadValue = function (item) {
-            defaultValue = item[args.column.field];
-            if (defaultValue) {
-                $select.attr("checked", "checked");
-            } else {
-                $select.removeAttr("checked");
+  });
+
+  function TextEditor(args) {
+    var $input;
+    var defaultValue;
+    var scope = this;
+
+    this.init = function () {
+      $input = $("<INPUT type=text class='editor-text' />")
+          .appendTo(args.container)
+          .bind("keydown.nav", function (e) {
+            if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) {
+              e.stopImmediatePropagation();
             }
-        };
-
-        this.serializeValue = function () {
-            return $select.attr("checked");
-        };
-
-        this.applyValue = function (item, state) {
-            item[args.column.field] = state;
-        };
-
-        this.isValueChanged = function () {
-            return ($select.attr("checked") != defaultValue);
-        };
-
-        this.validate = function () {
-            return {
-                valid: true,
-                msg: null
-            };
-        };
-
-        this.init();
-    }
-
-    function PercentCompleteEditor(args) {
-        var $input, $picker;
-        var defaultValue;
-        var scope = this;
-
-        this.init = function () {
-            $input = $("<INPUT type=text class='editor-percentcomplete' />");
-            $input.width($(args.container).innerWidth() - 25);
-            $input.appendTo(args.container);
-
-            $picker = $("<div class='editor-percentcomplete-picker' />").appendTo(args.container);
-            $picker.append("<div class='editor-percentcomplete-helper'><div class='editor-percentcomplete-wrapper'><div class='editor-percentcomplete-slider' /><div class='editor-percentcomplete-buttons' /></div></div>");
-
-            $picker.find(".editor-percentcomplete-buttons").append("<button val=0>Not started</button><br/><button val=50>In Progress</button><br/><button val=100>Complete</button>");
-
-            $input.focus().select();
-
-            $picker.find(".editor-percentcomplete-slider").slider({
-                orientation: "vertical",
-                range: "min",
-                value: defaultValue,
-                slide: function (event, ui) {
-                    $input.val(ui.value)
-                }
-            });
-
-            $picker.find(".editor-percentcomplete-buttons button").bind("click", function (e) {
-                $input.val($(this).attr("val"));
-                $picker.find(".editor-percentcomplete-slider").slider("value", $(this).attr("val"));
-            })
-        };
-
-        this.destroy = function () {
-            $input.remove();
-            $picker.remove();
-        };
-
-        this.focus = function () {
-            $input.focus();
-        };
-
-        this.loadValue = function (item) {
-            $input.val(defaultValue = item[args.column.field]);
-            $input.select();
-        };
-
-        this.serializeValue = function () {
-            return parseInt($input.val(), 10) || 0;
-        };
-
-        this.applyValue = function (item, state) {
-            item[args.column.field] = state;
-        };
-
-        this.isValueChanged = function () {
-            return (!($input.val() == "" && defaultValue == null)) && ((parseInt($input.val(), 10) || 0) != defaultValue);
-        };
-
-        this.validate = function () {
-            if (isNaN(parseInt($input.val(), 10))) {
-                return {
-                    valid: false,
-                    msg: "Please enter a valid positive number"
-                };
-            }
-
-            return {
-                valid: true,
-                msg: null
-            };
-        };
-
-        this.init();
-    }
-
-    /*
-     * An example of a "detached" editor.
-     * The UI is added onto document BODY and .position(), .show() and .hide() are implemented.
-     * KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter.
-     */
-    function LongTextEditor(args) {
-        var $input, $wrapper;
-        var defaultValue;
-        var scope = this;
-
-        this.init = function () {
-            var $container = $("body");
-
-            $wrapper = $("<DIV style='z-index:10000;position:absolute;background:white;padding:5px;border:3px solid gray; -moz-border-radius:10px; border-radius:10px;'/>")
-                    .appendTo($container);
-
-            $input = $("<TEXTAREA hidefocus rows=5 style='backround:white;width:250px;height:80px;border:0;outline:0'>")
-                    .appendTo($wrapper);
-
-            $("<DIV style='text-align:right'><BUTTON>Save</BUTTON><BUTTON>Cancel</BUTTON></DIV>")
-                    .appendTo($wrapper);
-
-            $wrapper.find("button:first").bind("click", this.save);
-            $wrapper.find("button:last").bind("click", this.cancel);
-            $input.bind("keydown", this.handleKeyDown);
-
-            scope.position(args.position);
-            $input.focus().select();
-        };
-
-        this.handleKeyDown = function (e) {
-            if (e.which == $.ui.keyCode.ENTER && e.ctrlKey) {
-                scope.save();
-            } else if (e.which == $.ui.keyCode.ESCAPE) {
-                e.preventDefault();
-                scope.cancel();
-            } else if (e.which == $.ui.keyCode.TAB && e.shiftKey) {
-                e.preventDefault();
-                grid.navigatePrev();
-            } else if (e.which == $.ui.keyCode.TAB) {
-                e.preventDefault();
-                grid.navigateNext();
-            }
-        };
-
-        this.save = function () {
-            args.commitChanges();
-        };
-
-        this.cancel = function () {
-            $input.val(defaultValue);
-            args.cancelChanges();
-        };
-
-        this.hide = function () {
-            $wrapper.hide();
-        };
-
-        this.show = function () {
-            $wrapper.show();
-        };
-
-        this.position = function (position) {
-            $wrapper
-                    .css("top", position.top - 5)
-                    .css("left", position.left - 5)
-        };
-
-        this.destroy = function () {
-            $wrapper.remove();
-        };
-
-        this.focus = function () {
-            $input.focus();
-        };
-
-        this.loadValue = function (item) {
-            $input.val(defaultValue = item[args.column.field]);
-            $input.select();
-        };
+          })
+          .focus()
+          .select();
+    };
+
+    this.destroy = function () {
+      $input.remove();
+    };
+
+    this.focus = function () {
+      $input.focus();
+    };
+
+    this.getValue = function () {
+      return $input.val();
+    };
+
+    this.setValue = function (val) {
+      $input.val(val);
+    };
+
+    this.loadValue = function (item) {
+      defaultValue = item[args.column.field] || "";
+      $input.val(defaultValue);
+      $input[0].defaultValue = defaultValue;
+      $input.select();
+    };
+
+    this.serializeValue = function () {
+      return $input.val();
+    };
+
+    this.applyValue = function (item, state) {
+      item[args.column.field] = state;
+    };
+
+    this.isValueChanged = function () {
+      return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
+    };
+
+    this.validate = function () {
+      if (args.column.validator) {
+        var validationResults = args.column.validator($input.val());
+        if (!validationResults.valid) {
+          return validationResults;
+        }
+      }
 
-        this.serializeValue = function () {
-            return $input.val();
-        };
+      return {
+        valid: true,
+        msg: null
+      };
+    };
 
-        this.applyValue = function (item, state) {
-            item[args.column.field] = state;
-        };
+    this.init();
+  }
 
-        this.isValueChanged = function () {
-            return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
-        };
+  function IntegerEditor(args) {
+    var $input;
+    var defaultValue;
+    var scope = this;
 
-        this.validate = function () {
-            return {
-                valid: true,
-                msg: null
-            };
-        };
+    this.init = function () {
+      $input = $("<INPUT type=text class='editor-text' />");
 
-        this.init();
-    }
+      $input.bind("keydown.nav", function (e) {
+        if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) {
+          e.stopImmediatePropagation();
+        }
+      });
+
+      $input.appendTo(args.container);
+      $input.focus().select();
+    };
+
+    this.destroy = function () {
+      $input.remove();
+    };
+
+    this.focus = function () {
+      $input.focus();
+    };
+
+    this.loadValue = function (item) {
+      defaultValue = item[args.column.field];
+      $input.val(defaultValue);
+      $input[0].defaultValue = defaultValue;
+      $input.select();
+    };
+
+    this.serializeValue = function () {
+      return parseInt($input.val(), 10) || 0;
+    };
+
+    this.applyValue = function (item, state) {
+      item[args.column.field] = state;
+    };
+
+    this.isValueChanged = function () {
+      return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
+    };
+
+    this.validate = function () {
+      if (isNaN($input.val())) {
+        return {
+          valid: false,
+          msg: "Please enter a valid integer"
+        };
+      }
+
+      return {
+        valid: true,
+        msg: null
+      };
+    };
+
+    this.init();
+  }
+
+  function DateEditor(args) {
+    var $input;
+    var defaultValue;
+    var scope = this;
+    var calendarOpen = false;
+
+    this.init = function () {
+      $input = $("<INPUT type=text class='editor-text' />");
+      $input.appendTo(args.container);
+      $input.focus().select();
+      $input.datepicker({
+        showOn: "button",
+        buttonImageOnly: true,
+        buttonImage: "../images/calendar.gif",
+        beforeShow: function () {
+          calendarOpen = true
+        },
+        onClose: function () {
+          calendarOpen = false
+        }
+      });
+      $input.width($input.width() - 18);
+    };
+
+    this.destroy = function () {
+      $.datepicker.dpDiv.stop(true, true);
+      $input.datepicker("hide");
+      $input.datepicker("destroy");
+      $input.remove();
+    };
+
+    this.show = function () {
+      if (calendarOpen) {
+        $.datepicker.dpDiv.stop(true, true).show();
+      }
+    };
+
+    this.hide = function () {
+      if (calendarOpen) {
+        $.datepicker.dpDiv.stop(true, true).hide();
+      }
+    };
+
+    this.position = function (position) {
+      if (!calendarOpen) {
+        return;
+      }
+      $.datepicker.dpDiv
+          .css("top", position.top + 30)
+          .css("left", position.left);
+    };
+
+    this.focus = function () {
+      $input.focus();
+    };
+
+    this.loadValue = function (item) {
+      defaultValue = item[args.column.field];
+      $input.val(defaultValue);
+      $input[0].defaultValue = defaultValue;
+      $input.select();
+    };
+
+    this.serializeValue = function () {
+      return $input.val();
+    };
+
+    this.applyValue = function (item, state) {
+      item[args.column.field] = state;
+    };
+
+    this.isValueChanged = function () {
+      return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
+    };
+
+    this.validate = function () {
+      return {
+        valid: true,
+        msg: null
+      };
+    };
+
+    this.init();
+  }
+
+  function YesNoSelectEditor(args) {
+    var $select;
+    var defaultValue;
+    var scope = this;
+
+    this.init = function () {
+      $select = $("<SELECT tabIndex='0' class='editor-yesno'><OPTION value='yes'>Yes</OPTION><OPTION value='no'>No</OPTION></SELECT>");
+      $select.appendTo(args.container);
+      $select.focus();
+    };
+
+    this.destroy = function () {
+      $select.remove();
+    };
+
+    this.focus = function () {
+      $select.focus();
+    };
+
+    this.loadValue = function (item) {
+      $select.val((defaultValue = item[args.column.field]) ? "yes" : "no");
+      $select.select();
+    };
+
+    this.serializeValue = function () {
+      return ($select.val() == "yes");
+    };
+
+    this.applyValue = function (item, state) {
+      item[args.column.field] = state;
+    };
+
+    this.isValueChanged = function () {
+      return ($select.val() != defaultValue);
+    };
+
+    this.validate = function () {
+      return {
+        valid: true,
+        msg: null
+      };
+    };
+
+    this.init();
+  }
+
+  function CheckboxEditor(args) {
+    var $select;
+    var defaultValue;
+    var scope = this;
+
+    this.init = function () {
+      $select = $("<INPUT type=checkbox value='true' class='editor-checkbox' hideFocus>");
+      $select.appendTo(args.container);
+      $select.focus();
+    };
+
+    this.destroy = function () {
+      $select.remove();
+    };
+
+    this.focus = function () {
+      $select.focus();
+    };
+
+    this.loadValue = function (item) {
+      defaultValue = !!item[args.column.field];
+      if (defaultValue) {
+        $select.prop('checked', true);
+      } else {
+        $select.prop('checked', false);
+      }
+    };
+
+    this.serializeValue = function () {
+      return $select.prop('checked');
+    };
+
+    this.applyValue = function (item, state) {
+      item[args.column.field] = state;
+    };
+
+    this.isValueChanged = function () {
+      return (this.serializeValue() !== defaultValue);
+    };
+
+    this.validate = function () {
+      return {
+        valid: true,
+        msg: null
+      };
+    };
+
+    this.init();
+  }
+
+  function PercentCompleteEditor(args) {
+    var $input, $picker;
+    var defaultValue;
+    var scope = this;
+
+    this.init = function () {
+      $input = $("<INPUT type=text class='editor-percentcomplete' />");
+      $input.width($(args.container).innerWidth() - 25);
+      $input.appendTo(args.container);
+
+      $picker = $("<div class='editor-percentcomplete-picker' />").appendTo(args.container);
+      $picker.append("<div class='editor-percentcomplete-helper'><div class='editor-percentcomplete-wrapper'><div class='editor-percentcomplete-slider' /><div class='editor-percentcomplete-buttons' /></div></div>");
+
+      $picker.find(".editor-percentcomplete-buttons").append("<button val=0>Not started</button><br/><button val=50>In Progress</button><br/><button val=100>Complete</button>");
+
+      $input.focus().select();
+
+      $picker.find(".editor-percentcomplete-slider").slider({
+        orientation: "vertical",
+        range: "min",
+        value: defaultValue,
+        slide: function (event, ui) {
+          $input.val(ui.value)
+        }
+      });
+
+      $picker.find(".editor-percentcomplete-buttons button").bind("click", function (e) {
+        $input.val($(this).attr("val"));
+        $picker.find(".editor-percentcomplete-slider").slider("value", $(this).attr("val"));
+      })
+    };
+
+    this.destroy = function () {
+      $input.remove();
+      $picker.remove();
+    };
+
+    this.focus = function () {
+      $input.focus();
+    };
+
+    this.loadValue = function (item) {
+      $input.val(defaultValue = item[args.column.field]);
+      $input.select();
+    };
+
+    this.serializeValue = function () {
+      return parseInt($input.val(), 10) || 0;
+    };
+
+    this.applyValue = function (item, state) {
+      item[args.column.field] = state;
+    };
+
+    this.isValueChanged = function () {
+      return (!($input.val() == "" && defaultValue == null)) && ((parseInt($input.val(), 10) || 0) != defaultValue);
+    };
+
+    this.validate = function () {
+      if (isNaN(parseInt($input.val(), 10))) {
+        return {
+          valid: false,
+          msg: "Please enter a valid positive number"
+        };
+      }
+
+      return {
+        valid: true,
+        msg: null
+      };
+    };
+
+    this.init();
+  }
+
+  /*
+   * An example of a "detached" editor.
+   * The UI is added onto document BODY and .position(), .show() and .hide() are implemented.
+   * KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter.
+   */
+  function LongTextEditor(args) {
+    var $input, $wrapper;
+    var defaultValue;
+    var scope = this;
+
+    this.init = function () {
+      var $container = $("body");
+
+      $wrapper = $("<DIV style='z-index:10000;position:absolute;background:white;padding:5px;border:3px solid gray; -moz-border-radius:10px; border-radius:10px;'/>")
+          .appendTo($container);
+
+      $input = $("<TEXTAREA hidefocus rows=5 style='backround:white;width:250px;height:80px;border:0;outline:0'>")
+          .appendTo($wrapper);
+
+      $("<DIV style='text-align:right'><BUTTON>Save</BUTTON><BUTTON>Cancel</BUTTON></DIV>")
+          .appendTo($wrapper);
+
+      $wrapper.find("button:first").bind("click", this.save);
+      $wrapper.find("button:last").bind("click", this.cancel);
+      $input.bind("keydown", this.handleKeyDown);
+
+      scope.position(args.position);
+      $input.focus().select();
+    };
+
+    this.handleKeyDown = function (e) {
+      if (e.which == $.ui.keyCode.ENTER && e.ctrlKey) {
+        scope.save();
+      } else if (e.which == $.ui.keyCode.ESCAPE) {
+        e.preventDefault();
+        scope.cancel();
+      } else if (e.which == $.ui.keyCode.TAB && e.shiftKey) {
+        e.preventDefault();
+        args.grid.navigatePrev();
+      } else if (e.which == $.ui.keyCode.TAB) {
+        e.preventDefault();
+        args.grid.navigateNext();
+      }
+    };
+
+    this.save = function () {
+      args.commitChanges();
+    };
+
+    this.cancel = function () {
+      $input.val(defaultValue);
+      args.cancelChanges();
+    };
+
+    this.hide = function () {
+      $wrapper.hide();
+    };
+
+    this.show = function () {
+      $wrapper.show();
+    };
+
+    this.position = function (position) {
+      $wrapper
+          .css("top", position.top - 5)
+          .css("left", position.left - 5)
+    };
+
+    this.destroy = function () {
+      $wrapper.remove();
+    };
+
+    this.focus = function () {
+      $input.focus();
+    };
+
+    this.loadValue = function (item) {
+      $input.val(defaultValue = item[args.column.field]);
+      $input.select();
+    };
+
+    this.serializeValue = function () {
+      return $input.val();
+    };
+
+    this.applyValue = function (item, state) {
+      item[args.column.field] = state;
+    };
+
+    this.isValueChanged = function () {
+      return (!($input.val() == "" && defaultValue == null)) && ($input.val() != defaultValue);
+    };
+
+    this.validate = function () {
+      return {
+        valid: true,
+        msg: null
+      };
+    };
+
+    this.init();
+  }
 })(jQuery);

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.formatters.js
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.formatters.js b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.formatters.js
index cc30b8c..a31aaf9 100755
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.formatters.js
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.formatters.js
@@ -1,55 +1,59 @@
 /***
  * Contains basic SlickGrid formatters.
+ * 
+ * NOTE:  These are merely examples.  You will most likely need to implement something more
+ *        robust/extensible/localizable/etc. for your use!
+ * 
  * @module Formatters
  * @namespace Slick
  */
 
 (function ($) {
-    // register namespace
-    $.extend(true, window, {
-        "Slick": {
-            "Formatters": {
-                "PercentComplete": PercentCompleteFormatter,
-                "PercentCompleteBar": PercentCompleteBarFormatter,
-                "YesNo": YesNoFormatter,
-                "Checkmark": CheckmarkFormatter
-            }
-        }
-    });
-
-    function PercentCompleteFormatter(row, cell, value, columnDef, dataContext) {
-        if (value == null || value === "") {
-            return "-";
-        } else if (value < 50) {
-            return "<span style='color:red;font-weight:bold;'>" + value + "%</span>";
-        } else {
-            return "<span style='color:green'>" + value + "%</span>";
-        }
+  // register namespace
+  $.extend(true, window, {
+    "Slick": {
+      "Formatters": {
+        "PercentComplete": PercentCompleteFormatter,
+        "PercentCompleteBar": PercentCompleteBarFormatter,
+        "YesNo": YesNoFormatter,
+        "Checkmark": CheckmarkFormatter
+      }
     }
+  });
+
+  function PercentCompleteFormatter(row, cell, value, columnDef, dataContext) {
+    if (value == null || value === "") {
+      return "-";
+    } else if (value < 50) {
+      return "<span style='color:red;font-weight:bold;'>" + value + "%</span>";
+    } else {
+      return "<span style='color:green'>" + value + "%</span>";
+    }
+  }
 
-    function PercentCompleteBarFormatter(row, cell, value, columnDef, dataContext) {
-        if (value == null || value === "") {
-            return "";
-        }
-
-        var color;
+  function PercentCompleteBarFormatter(row, cell, value, columnDef, dataContext) {
+    if (value == null || value === "") {
+      return "";
+    }
 
-        if (value < 30) {
-            color = "red";
-        } else if (value < 70) {
-            color = "silver";
-        } else {
-            color = "green";
-        }
+    var color;
 
-        return "<span class='percent-complete-bar' style='background:" + color + ";width:" + value + "%'></span>";
+    if (value < 30) {
+      color = "red";
+    } else if (value < 70) {
+      color = "silver";
+    } else {
+      color = "green";
     }
 
-    function YesNoFormatter(row, cell, value, columnDef, dataContext) {
-        return value ? "Yes" : "No";
-    }
+    return "<span class='percent-complete-bar' style='background:" + color + ";width:" + value + "%'></span>";
+  }
 
-    function CheckmarkFormatter(row, cell, value, columnDef, dataContext) {
-        return value ? "<img src='../images/tick.png'>" : "";
-    }
-})(jQuery);
\ No newline at end of file
+  function YesNoFormatter(row, cell, value, columnDef, dataContext) {
+    return value ? "Yes" : "No";
+  }
+
+  function CheckmarkFormatter(row, cell, value, columnDef, dataContext) {
+    return value ? "<img src='../images/tick.png'>" : "";
+  }
+})(jQuery);


[5/5] incubator-nifi git commit: NIFI-27: - Latest version of slickgrid.

Posted by mc...@apache.org.
NIFI-27:
- Latest version of slickgrid.

Project: http://git-wip-us.apache.org/repos/asf/incubator-nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-nifi/commit/eb880757
Tree: http://git-wip-us.apache.org/repos/asf/incubator-nifi/tree/eb880757
Diff: http://git-wip-us.apache.org/repos/asf/incubator-nifi/diff/eb880757

Branch: refs/heads/nifi-27
Commit: eb880757b244858e984822abeef265c9e082ca69
Parents: 0ded442
Author: Matt Gilman <ma...@gmail.com>
Authored: Wed Dec 10 10:12:43 2014 -0500
Committer: Matt Gilman <ma...@gmail.com>
Committed: Wed Dec 10 10:12:43 2014 -0500

----------------------------------------------------------------------
 .../src/main/webapp/WEB-INF/pages/canvas.jsp    |    2 +-
 .../src/main/webapp/WEB-INF/pages/cluster.jsp   |    2 +-
 .../src/main/webapp/WEB-INF/pages/counters.jsp  |    2 +-
 .../src/main/webapp/WEB-INF/pages/history.jsp   |    2 +-
 .../main/webapp/WEB-INF/pages/provenance.jsp    |    2 +-
 .../src/main/webapp/WEB-INF/pages/summary.jsp   |    2 +-
 .../src/main/webapp/WEB-INF/pages/templates.jsp |    2 +-
 .../src/main/webapp/WEB-INF/pages/users.jsp     |    2 +-
 .../js/jquery/jquery.event.drag-2.0.min.js      |  194 -
 .../js/jquery/jquery.event.drag-2.2.min.js      |    6 +
 .../webapp/js/jquery/slickgrid/MIT-LICENSE.txt  |   20 +
 .../js/jquery/slickgrid/css/images/collapse.gif |  Bin 0 -> 846 bytes
 .../js/jquery/slickgrid/css/images/expand.gif   |  Bin 0 -> 851 bytes
 .../slickgrid/css/slick-default-theme.css       |   68 +-
 .../js/jquery/slickgrid/css/slick.grid.css      |  173 +-
 .../slickgrid/plugins/slick.autotooltips.js     |  121 +-
 .../plugins/slick.cellrangedecorator.js         |  110 +-
 .../plugins/slick.cellrangeselector.js          |  194 +-
 .../plugins/slick.cellselectionmodel.js         |  210 +-
 .../plugins/slick.rowselectionmodel.js          |  325 +-
 .../webapp/js/jquery/slickgrid/slick.core.js    |  796 +--
 .../js/jquery/slickgrid/slick.dataview.js       | 1920 +++---
 .../webapp/js/jquery/slickgrid/slick.editors.js |  994 +--
 .../js/jquery/slickgrid/slick.formatters.js     |   86 +-
 .../webapp/js/jquery/slickgrid/slick.grid.js    | 6254 ++++++++++--------
 25 files changed, 6144 insertions(+), 5343 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp
index c8f2ff7..09c8207 100644
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp
@@ -48,7 +48,7 @@
         <script type="text/javascript" src="js/jquery/modal/jquery.modal.js?${project.version}"></script>
         <script type="text/javascript" src="js/jquery/minicolors/jquery.minicolors.min.js"></script>
         <script type="text/javascript" src="js/jquery/qtip2/jquery.qtip.min.js"></script>
-        <script type="text/javascript" src="js/jquery/jquery.event.drag-2.0.min.js"></script>
+        <script type="text/javascript" src="js/jquery/jquery.event.drag-2.2.min.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.cellrangeselector.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.cellselectionmodel.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.rowselectionmodel.js"></script>

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/cluster.jsp
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/cluster.jsp b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/cluster.jsp
index e3e7b86..a0bda24 100644
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/cluster.jsp
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/cluster.jsp
@@ -36,7 +36,7 @@
         <script type="text/javascript" src="js/jquery/jquery.each.js"></script>
         <script type="text/javascript" src="js/jquery/jquery-ui-1.8.10.custom.min.js"></script>
         <script type="text/javascript" src="js/jquery/qtip2/jquery.qtip.min.js"></script>
-        <script type="text/javascript" src="js/jquery/jquery.event.drag-2.0.min.js"></script>
+        <script type="text/javascript" src="js/jquery/jquery.event.drag-2.2.min.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.cellrangeselector.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.cellselectionmodel.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.rowselectionmodel.js"></script>

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/counters.jsp
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/counters.jsp b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/counters.jsp
index 21ee508..2678bf4 100644
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/counters.jsp
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/counters.jsp
@@ -36,7 +36,7 @@
         <script type="text/javascript" src="js/jquery/jquery.each.js"></script>
         <script type="text/javascript" src="js/jquery/jquery-ui-1.8.10.custom.min.js"></script>
         <script type="text/javascript" src="js/jquery/qtip2/jquery.qtip.min.js"></script>
-        <script type="text/javascript" src="js/jquery/jquery.event.drag-2.0.min.js"></script>
+        <script type="text/javascript" src="js/jquery/jquery.event.drag-2.2.min.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.cellrangeselector.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.cellselectionmodel.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.rowselectionmodel.js"></script>

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/history.jsp
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/history.jsp b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/history.jsp
index 61be727..19d9e90 100644
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/history.jsp
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/history.jsp
@@ -36,7 +36,7 @@
         <script type="text/javascript" src="js/jquery/jquery.each.js"></script>
         <script type="text/javascript" src="js/jquery/jquery-ui-1.8.10.custom.min.js"></script>
         <script type="text/javascript" src="js/jquery/qtip2/jquery.qtip.min.js"></script>
-        <script type="text/javascript" src="js/jquery/jquery.event.drag-2.0.min.js"></script>
+        <script type="text/javascript" src="js/jquery/jquery.event.drag-2.2.min.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.cellrangeselector.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.cellselectionmodel.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.rowselectionmodel.js"></script>

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/provenance.jsp
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/provenance.jsp b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/provenance.jsp
index 2f51918..f3c9a88 100644
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/provenance.jsp
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/provenance.jsp
@@ -38,7 +38,7 @@
         <script type="text/javascript" src="js/jquery/jquery.each.js"></script>
         <script type="text/javascript" src="js/jquery/jquery-ui-1.8.10.custom.min.js"></script>
         <script type="text/javascript" src="js/jquery/qtip2/jquery.qtip.min.js"></script>
-        <script type="text/javascript" src="js/jquery/jquery.event.drag-2.0.min.js"></script>
+        <script type="text/javascript" src="js/jquery/jquery.event.drag-2.2.min.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.cellrangeselector.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.cellselectionmodel.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.rowselectionmodel.js"></script>

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/summary.jsp
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/summary.jsp b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/summary.jsp
index b34f0e6..e8a8e4c 100644
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/summary.jsp
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/summary.jsp
@@ -38,7 +38,7 @@
         <script type="text/javascript" src="js/jquery/jquery.ellipsis.js"></script>
         <script type="text/javascript" src="js/jquery/jquery.each.js"></script>
         <script type="text/javascript" src="js/jquery/qtip2/jquery.qtip.min.js"></script>
-        <script type="text/javascript" src="js/jquery/jquery.event.drag-2.0.min.js"></script>
+        <script type="text/javascript" src="js/jquery/jquery.event.drag-2.2.min.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.cellrangeselector.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.cellselectionmodel.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.rowselectionmodel.js"></script>

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/templates.jsp
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/templates.jsp b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/templates.jsp
index c7c0293..889485f 100644
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/templates.jsp
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/templates.jsp
@@ -37,7 +37,7 @@
         <script type="text/javascript" src="js/jquery/jquery.each.js"></script>
         <script type="text/javascript" src="js/jquery/jquery-ui-1.8.10.custom.min.js"></script>
         <script type="text/javascript" src="js/jquery/qtip2/jquery.qtip.min.js"></script>
-        <script type="text/javascript" src="js/jquery/jquery.event.drag-2.0.min.js"></script>
+        <script type="text/javascript" src="js/jquery/jquery.event.drag-2.2.min.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.cellrangeselector.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.cellselectionmodel.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.rowselectionmodel.js"></script>

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/users.jsp
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/users.jsp b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/users.jsp
index 770dad5..9ff887b 100644
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/users.jsp
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/pages/users.jsp
@@ -38,7 +38,7 @@
         <script type="text/javascript" src="js/jquery/jquery.each.js"></script>
         <script type="text/javascript" src="js/jquery/jquery-ui-1.8.10.custom.min.js"></script>
         <script type="text/javascript" src="js/jquery/qtip2/jquery.qtip.min.js"></script>
-        <script type="text/javascript" src="js/jquery/jquery.event.drag-2.0.min.js"></script>
+        <script type="text/javascript" src="js/jquery/jquery.event.drag-2.2.min.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.cellrangeselector.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.cellselectionmodel.js"></script>
         <script type="text/javascript" src="js/jquery/slickgrid/plugins/slick.rowselectionmodel.js"></script>

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/jquery.event.drag-2.0.min.js
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/jquery.event.drag-2.0.min.js b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/jquery.event.drag-2.0.min.js
deleted file mode 100755
index 146b20a..0000000
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/jquery.event.drag-2.0.min.js
+++ /dev/null
@@ -1,194 +0,0 @@
-/*! 
- * jquery.event.drag - v 2.0.0 
- * Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
- * Open Source MIT License - http://threedubmedia.com/code/license
- */
-;
-(function (f) {
-    f.fn.drag = function (b, a, d) {
-        var e = typeof b == "string" ? b : "", k = f.isFunction(b) ? b : f.isFunction(a) ? a : null;
-        if (e.indexOf("drag") !== 0)
-            e = "drag" + e;
-        d = (b == k ? a : d) || {};
-        return k ? this.bind(e, d, k) : this.trigger(e)
-    };
-    var i = f.event, h = i.special, c = h.drag = {defaults: {which: 1, distance: 0, not: ":input", handle: null, relative: false, drop: true, click: false}, datakey: "dragdata", livekey: "livedrag", add: function (b) {
-            var a = f.data(this, c.datakey), d = b.data || {};
-            a.related += 1;
-            if (!a.live && b.selector) {
-                a.live = true;
-                i.add(this, "draginit." + c.livekey, c.delegate)
-            }
-            f.each(c.defaults, function (e) {
-                if (d[e] !== undefined)
-                    a[e] = d[e]
-            })
-        }, remove: function () {
-            f.data(this, c.datakey).related -= 1
-        }, setup: function () {
-            if (!f.data(this, c.datakey)) {
-                var b = f.extend({related: 0}, c.defaults);
-                f.data(this, c.datakey, b);
-                i.add(this, "mousedown", c.init, b);
-                this.attachEvent && this.attachEvent("ondragstart", c.dontstart)
-            }
-        }, teardown: function () {
-            if (!f.data(this, c.datakey).related) {
-                f.removeData(this, c.datakey);
-                i.remove(this, "mousedown", c.init);
-                i.remove(this, "draginit", c.delegate);
-                c.textselect(true);
-                this.detachEvent && this.detachEvent("ondragstart", c.dontstart)
-            }
-        }, init: function (b) {
-            var a = b.data, d;
-            if (!(a.which > 0 && b.which != a.which))
-                if (!f(b.target).is(a.not))
-                    if (!(a.handle && !f(b.target).closest(a.handle, b.currentTarget).length)) {
-                        a.propagates = 1;
-                        a.interactions = [c.interaction(this, a)];
-                        a.target = b.target;
-                        a.pageX = b.pageX;
-                        a.pageY = b.pageY;
-                        a.dragging = null;
-                        d = c.hijack(b, "draginit", a);
-                        if (a.propagates) {
-                            if ((d = c.flatten(d)) && d.length) {
-                                a.interactions = [];
-                                f.each(d, function () {
-                                    a.interactions.push(c.interaction(this, a))
-                                })
-                            }
-                            a.propagates = a.interactions.length;
-                            a.drop !== false && h.drop && h.drop.handler(b, a);
-                            c.textselect(false);
-                            i.add(document, "mousemove mouseup", c.handler, a);
-                            return false
-                        }
-                    }
-        }, interaction: function (b, a) {
-            return{drag: b, callback: new c.callback, droppable: [], offset: f(b)[a.relative ? "position" : "offset"]() || {top: 0, left: 0}}
-        }, handler: function (b) {
-            var a = b.data;
-            switch (b.type) {
-                case !a.dragging && "mousemove":
-                    if (Math.pow(b.pageX - a.pageX, 2) + Math.pow(b.pageY - a.pageY, 2) < Math.pow(a.distance, 2))
-                        break;
-                    b.target = a.target;
-                    c.hijack(b, "dragstart", a);
-                    if (a.propagates)
-                        a.dragging = true;
-                case "mousemove":
-                    if (a.dragging) {
-                        c.hijack(b, "drag", a);
-                        if (a.propagates) {
-                            a.drop !== false && h.drop && h.drop.handler(b, a);
-                            break
-                        }
-                        b.type = "mouseup"
-                    }
-                case "mouseup":
-                    i.remove(document, "mousemove mouseup", c.handler);
-                    if (a.dragging) {
-                        a.drop !== false && h.drop && h.drop.handler(b, a);
-                        c.hijack(b, "dragend", a)
-                    }
-                    c.textselect(true);
-                    if (a.click === false && a.dragging) {
-                        jQuery.event.triggered = true;
-                        setTimeout(function () {
-                            jQuery.event.triggered = false
-                        }, 20);
-                        a.dragging = false
-                    }
-                    break
-            }
-        }, delegate: function (b) {
-            var a = [], d, e = f.data(this, "events") || {};
-            f.each(e.live || [], function (k, j) {
-                if (j.preType.indexOf("drag") === 0)
-                    if (d = f(b.target).closest(j.selector, b.currentTarget)[0]) {
-                        i.add(d, j.origType + "." + c.livekey, j.origHandler, j.data);
-                        f.inArray(d, a) < 0 && a.push(d)
-                    }
-            });
-            if (!a.length)
-                return false;
-            return f(a).bind("dragend." + c.livekey, function () {
-                i.remove(this, "." + c.livekey)
-            })
-        }, hijack: function (b, a, d, e, k) {
-            if (d) {
-                var j = {event: b.originalEvent, type: b.type}, n = a.indexOf("drop") ? "drag" : "drop", l, o = e || 0, g, m;
-                e = !isNaN(e) ? e : d.interactions.length;
-                b.type = a;
-                b.originalEvent = null;
-                d.results = [];
-                do
-                    if (g = d.interactions[o])
-                        if (!(a !== "dragend" && g.cancelled)) {
-                            m = c.properties(b, d, g);
-                            g.results = [];
-                            f(k || g[n] || d.droppable).each(function (q, p) {
-                                l = (m.target = p) ? i.handle.call(p, b, m) : null;
-                                if (l === false) {
-                                    if (n == "drag") {
-                                        g.cancelled = true;
-                                        d.propagates -= 1
-                                    }
-                                    if (a == "drop")
-                                        g[n][q] = null
-                                } else if (a == "dropinit")
-                                    g.droppable.push(c.element(l) || p);
-                                if (a == "dragstart")
-                                    g.proxy = f(c.element(l) || g.drag)[0];
-                                g.results.push(l);
-                                delete b.result;
-                                if (a !== "dropinit")
-                                    return l
-                            });
-                            d.results[o] = c.flatten(g.results);
-                            if (a == "dropinit")
-                                g.droppable = c.flatten(g.droppable);
-                            a == "dragstart" && !g.cancelled && m.update()
-                        }
-                while (++o < e);
-                b.type = j.type;
-                b.originalEvent = j.event;
-                return c.flatten(d.results)
-            }
-        }, properties: function (b, a, d) {
-            var e = d.callback;
-            e.drag = d.drag;
-            e.proxy = d.proxy || d.drag;
-            e.startX = a.pageX;
-            e.startY = a.pageY;
-            e.deltaX = b.pageX - a.pageX;
-            e.deltaY = b.pageY - a.pageY;
-            e.originalX = d.offset.left;
-            e.originalY = d.offset.top;
-            e.offsetX = b.pageX - (a.pageX - e.originalX);
-            e.offsetY = b.pageY - (a.pageY - e.originalY);
-            e.drop = c.flatten((d.drop || []).slice());
-            e.available = c.flatten((d.droppable || []).slice());
-            return e
-        }, element: function (b) {
-            if (b && (b.jquery || b.nodeType == 1))
-                return b
-        }, flatten: function (b) {
-            return f.map(b, function (a) {
-                return a && a.jquery ? f.makeArray(a) : a && a.length ? c.flatten(a) : a
-            })
-        }, textselect: function (b) {
-            f(document)[b ? "unbind" : "bind"]("selectstart", c.dontstart).attr("unselectable", b ? "off" : "on").css("MozUserSelect", b ? "" : "none")
-        }, dontstart: function () {
-            return false
-        }, callback: function () {
-        }};
-    c.callback.prototype = {update: function () {
-            h.drop && this.available.length && f.each(this.available, function (b) {
-                h.drop.locate(this, b)
-            })
-        }};
-    h.draginit = h.dragstart = h.dragend = c
-})(jQuery);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/jquery.event.drag-2.2.min.js
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/jquery.event.drag-2.2.min.js b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/jquery.event.drag-2.2.min.js
new file mode 100644
index 0000000..cff8ed6
--- /dev/null
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/jquery.event.drag-2.2.min.js
@@ -0,0 +1,6 @@
+/*!
+ * jquery.event.drag - v 2.2
+ * Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
+ * Open Source MIT License - http://threedubmedia.com/code/license
+ */
+;(function(e){e.fn.drag=function(k,g,j){var i=typeof k=="string"?k:"",h=e.isFunction(k)?k:e.isFunction(g)?g:null;if(i.indexOf("drag")!==0){i="drag"+i}j=(k==h?g:j)||{};return h?this.bind(i,j,h):this.trigger(i)};var b=e.event,a=b.special,d=a.drag={defaults:{which:1,distance:0,not:":input",handle:null,relative:false,drop:true,click:false},datakey:"dragdata",noBubble:true,add:function(i){var h=e.data(this,d.datakey),g=i.data||{};h.related+=1;e.each(d.defaults,function(j,k){if(g[j]!==undefined){h[j]=g[j]}})},remove:function(){e.data(this,d.datakey).related-=1},setup:function(){if(e.data(this,d.datakey)){return}var g=e.extend({related:0},d.defaults);e.data(this,d.datakey,g);b.add(this,"touchstart mousedown",d.init,g);if(this.attachEvent){this.attachEvent("ondragstart",d.dontstart)}},teardown:function(){var g=e.data(this,d.datakey)||{};if(g.related){return}e.removeData(this,d.datakey);b.remove(this,"touchstart mousedown",d.init);d.textselect(true);if(this.detachEvent){this.detachEvent("ond
 ragstart",d.dontstart)}},init:function(i){if(d.touched){return}var g=i.data,h;if(i.which!=0&&g.which>0&&i.which!=g.which){return}if(e(i.target).is(g.not)){return}if(g.handle&&!e(i.target).closest(g.handle,i.currentTarget).length){return}d.touched=i.type=="touchstart"?this:null;g.propagates=1;g.mousedown=this;g.interactions=[d.interaction(this,g)];g.target=i.target;g.pageX=i.pageX;g.pageY=i.pageY;g.dragging=null;h=d.hijack(i,"draginit",g);if(!g.propagates){return}h=d.flatten(h);if(h&&h.length){g.interactions=[];e.each(h,function(){g.interactions.push(d.interaction(this,g))})}g.propagates=g.interactions.length;if(g.drop!==false&&a.drop){a.drop.handler(i,g)}d.textselect(false);if(d.touched){b.add(d.touched,"touchmove touchend",d.handler,g)}else{b.add(document,"mousemove mouseup",d.handler,g)}if(!d.touched||g.live){return false}},interaction:function(h,g){var i=e(h)[g.relative?"position":"offset"]()||{top:0,left:0};return{drag:h,callback:new d.callback(),droppable:[],offset:i}},handler:
 function(h){var g=h.data;switch(h.type){case !g.dragging&&"touchmove":h.preventDefault();case !g.dragging&&"mousemove":if(Math.pow(h.pageX-g.pageX,2)+Math.pow(h.pageY-g.pageY,2)<Math.pow(g.distance,2)){break}h.target=g.target;d.hijack(h,"dragstart",g);if(g.propagates){g.dragging=true}case"touchmove":h.preventDefault();case"mousemove":if(g.dragging){d.hijack(h,"drag",g);if(g.propagates){if(g.drop!==false&&a.drop){a.drop.handler(h,g)}break}h.type="mouseup"}case"touchend":case"mouseup":default:if(d.touched){b.remove(d.touched,"touchmove touchend",d.handler)}else{b.remove(document,"mousemove mouseup",d.handler)}if(g.dragging){if(g.drop!==false&&a.drop){a.drop.handler(h,g)}d.hijack(h,"dragend",g)}d.textselect(true);if(g.click===false&&g.dragging){e.data(g.mousedown,"suppress.click",new Date().getTime()+5)}g.dragging=d.touched=false;break}},hijack:function(h,o,r,p,k){if(!r){return}var q={event:h.originalEvent,type:h.type},m=o.indexOf("drop")?"drag":"drop",t,l=p||0,j,g,s,n=!isNaN(p)?p:r.in
 teractions.length;h.type=o;h.originalEvent=null;r.results=[];do{if(j=r.interactions[l]){if(o!=="dragend"&&j.cancelled){continue}s=d.properties(h,r,j);j.results=[];e(k||j[m]||r.droppable).each(function(u,i){s.target=i;h.isPropagationStopped=function(){return false};t=i?b.dispatch.call(i,h,s):null;if(t===false){if(m=="drag"){j.cancelled=true;r.propagates-=1}if(o=="drop"){j[m][u]=null}}else{if(o=="dropinit"){j.droppable.push(d.element(t)||i)}}if(o=="dragstart"){j.proxy=e(d.element(t)||j.drag)[0]}j.results.push(t);delete h.result;if(o!=="dropinit"){return t}});r.results[l]=d.flatten(j.results);if(o=="dropinit"){j.droppable=d.flatten(j.droppable)}if(o=="dragstart"&&!j.cancelled){s.update()}}}while(++l<n);h.type=q.type;h.originalEvent=q.event;return d.flatten(r.results)},properties:function(i,g,h){var j=h.callback;j.drag=h.drag;j.proxy=h.proxy||h.drag;j.startX=g.pageX;j.startY=g.pageY;j.deltaX=i.pageX-g.pageX;j.deltaY=i.pageY-g.pageY;j.originalX=h.offset.left;j.originalY=h.offset.top;j.of
 fsetX=j.originalX+j.deltaX;j.offsetY=j.originalY+j.deltaY;j.drop=d.flatten((h.drop||[]).slice());j.available=d.flatten((h.droppable||[]).slice());return j},element:function(g){if(g&&(g.jquery||g.nodeType==1)){return g}},flatten:function(g){return e.map(g,function(h){return h&&h.jquery?e.makeArray(h):h&&h.length?d.flatten(h):h})},textselect:function(g){e(document)[g?"unbind":"bind"]("selectstart",d.dontstart).css("MozUserSelect",g?"":"none");document.unselectable=g?"off":"on"},dontstart:function(){return false},callback:function(){}};d.callback.prototype={update:function(){if(a.drop&&this.available.length){e.each(this.available,function(g){a.drop.locate(this,g)})}}};var f=b.dispatch;b.dispatch=function(g){if(e.data(this,"suppress."+g.type)-new Date().getTime()>0){e.removeData(this,"suppress."+g.type);return}return f.apply(this,arguments)};var c=b.fixHooks.touchstart=b.fixHooks.touchmove=b.fixHooks.touchend=b.fixHooks.touchcancel={props:"clientX clientY pageX pageY screenX screenY".sp
 lit(" "),filter:function(h,i){if(i){var g=(i.touches&&i.touches[0])||(i.changedTouches&&i.changedTouches[0])||null;if(g){e.each(c.props,function(j,k){h[k]=g[k]})}}return h}};a.draginit=a.dragstart=a.dragend=d})(jQuery);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/MIT-LICENSE.txt
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/MIT-LICENSE.txt b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/MIT-LICENSE.txt
new file mode 100755
index 0000000..60f6542
--- /dev/null
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/MIT-LICENSE.txt
@@ -0,0 +1,20 @@
+Copyright (c) 2010 Michael Leibman, http://github.com/mleibman/slickgrid
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/images/collapse.gif
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/images/collapse.gif b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/images/collapse.gif
new file mode 100755
index 0000000..01e6914
Binary files /dev/null and b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/images/collapse.gif differ

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/images/expand.gif
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/images/expand.gif b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/images/expand.gif
new file mode 100755
index 0000000..1b24ef1
Binary files /dev/null and b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/images/expand.gif differ

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/slick-default-theme.css
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/slick-default-theme.css b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/slick-default-theme.css
index c68b703..6b45f3e 100755
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/slick-default-theme.css
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/slick-default-theme.css
@@ -31,24 +31,24 @@ classes should alter those!
 }
 
 .slick-headerrow {
-    background: #fafafa;
+  background: #fafafa;
 }
 
 .slick-headerrow-column {
-    background: #fafafa;
-    border-bottom: 0;
-    height: 100%;
+  background: #fafafa;
+  border-bottom: 0;
+  height: 100%;
 }
 
 .slick-row.ui-state-active {
-    background: #F5F7D7;
+  background: #F5F7D7;
 }
 
 .slick-row {
-    position: absolute;
-    background: white;
-    border: 0px;
-    line-height: 20px;
+  position: absolute;
+  background: white;
+  border: 0px;
+  line-height: 20px;
 }
 
 .slick-row:hover {
@@ -56,37 +56,37 @@ classes should alter those!
 }
 
 .slick-row.selected {
-    z-index: 10;
+  z-index: 10;
     background: #DFE8F6 !important;
 }
 
 .slick-cell {
-    padding-left: 4px;
-    padding-right: 4px;
+  padding-left: 4px;
+  padding-right: 4px;
     border-style: solid !important;
 }
 
 .slick-group {
-    border-bottom: 2px solid silver;
+  border-bottom: 2px solid silver;
 }
 
 .slick-group-toggle {
-    width: 9px;
-    height: 9px;
-    margin-right: 5px;
+  width: 9px;
+  height: 9px;
+  margin-right: 5px;
 }
 
 .slick-group-toggle.expanded {
-    background: url(../../../../images/collapse.gif) no-repeat center center;
+  background: url(images/collapse.gif) no-repeat center center;
 }
 
 .slick-group-toggle.collapsed {
-    background: url(../../../../images/expand.gif) no-repeat center center;
+  background: url(images/expand.gif) no-repeat center center;
 }
 
 .slick-group-totals {
-    color: gray;
-    background: white;
+  color: gray;
+  background: white;
 }
 
 .slick-cell.selected {
@@ -100,22 +100,36 @@ classes should alter those!
 }
 
 .slick-sortable-placeholder {
-    background: silver !important;
+  background: silver !important;
 }
 
-.slick-row[row$="1"], .slick-row[row$="3"], .slick-row[row$="5"], .slick-row[row$="7"], .slick-row[row$="9"] {
-    background: #fafafa;
+.slick-row.odd {
+  background: #fafafa;
 }
 
 .slick-row.ui-state-active {
-    background: #F5F7D7;
+  background: #F5F7D7;
 }
 
 .slick-row.loading {
-    opacity: 0.5;
-    filter: alpha(opacity = 50);
+  opacity: 0.5;
+  filter: alpha(opacity = 50);
 }
 
 .slick-cell.invalid {
-    border-color: red;
+  border-color: red;
+  -moz-animation-duration: 0.2s;
+  -webkit-animation-duration: 0.2s;
+  -moz-animation-name: slickgrid-invalid-hilite;
+  -webkit-animation-name: slickgrid-invalid-hilite;  
+}
+
+@-moz-keyframes slickgrid-invalid-hilite {
+  from { box-shadow: 0 0 6px red; }
+  to { box-shadow: none; }
+}
+
+@-webkit-keyframes slickgrid-invalid-hilite {
+  from { box-shadow: 0 0 6px red; }
+  to { box-shadow: none; }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/slick.grid.css
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/slick.grid.css b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/slick.grid.css
index 2e8f390..de94dbf 100755
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/slick.grid.css
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/css/slick.grid.css
@@ -6,153 +6,152 @@ classes should alter those!
 */
 
 .slick-header.ui-state-default, .slick-headerrow.ui-state-default {
-    width: 100%;
-    overflow: hidden;
-    border-left: 0px;
+  width: 100%;
+  overflow: hidden;
+  border-left: 0px;
 }
 
 .slick-header-columns, .slick-headerrow-columns {
-    width: 999999px;
-    position: relative;
-    white-space: nowrap;
-    cursor: default;
-    overflow: hidden;
+  position: relative;
+  white-space: nowrap;
+  cursor: default;
+  overflow: hidden;
 }
 
 .slick-header-column.ui-state-default {
-    position: relative;
-    display: inline-block;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    height: 16px;
-    line-height: 16px;
-    margin: 0;
-    padding: 4px;
-    border-right: 1px solid silver;
-    border-left: 0px;
-    border-top: 0px;
-    border-bottom: 0px;
-    float: left;
+  position: relative;
+  display: inline-block;
+  overflow: hidden;
+  -o-text-overflow: ellipsis;
+  text-overflow: ellipsis;
+  height: 16px;
+  line-height: 16px;
+  margin: 0;
+  padding: 4px;
+  border-right: 1px solid silver;
+  border-left: 0px;
+  border-top: 0px;
+  border-bottom: 0px;
+  float: left;
 }
 
 .slick-headerrow-column.ui-state-default {
-    padding: 4px;
+  padding: 4px;
 }
 
 .slick-header-column-sorted {
-    font-style: italic;
+  font-style: italic;
 }
 
 .slick-sort-indicator {
-    display: inline-block;
-    width: 8px;
-    height: 5px;
-    margin-left: 4px;
+  display: inline-block;
+  width: 8px;
+  height: 5px;
+  margin-left: 4px;
+  margin-top: 6px;
+  /*float: left;*/
 }
 
 .slick-sort-indicator-desc {
-    background: url(images/sort-desc.gif);
+  background: url(images/sort-desc.gif);
 }
 
 .slick-sort-indicator-asc {
-    background: url(images/sort-asc.gif);
+  background: url(images/sort-asc.gif);
 }
 
 .slick-resizable-handle {
-    position: absolute;
-    font-size: 0.1px;
-    display: block;
-    cursor: col-resize;
-    width: 4px;
-    right: 0px;
-    top: 0;
-    height: 100%;
+  position: absolute;
+  font-size: 0.1px;
+  display: block;
+  cursor: col-resize;
+  width: 4px;
+  right: 0px;
+  top: 0;
+  height: 100%;
 }
 
 .slick-sortable-placeholder {
-    background: silver;
+  background: silver;
 }
 
 .grid-canvas {
-    position: relative;
-    outline: 0;
+  position: relative;
+  outline: 0;
 }
 
 .slick-row.ui-widget-content, .slick-row.ui-state-active {
-    position: absolute;
-    border: 0px;
-    width: 100%;
+  position: absolute;
+  border: 0px;
+  width: 100%;
 }
 
 .slick-cell, .slick-headerrow-column {
-    position: absolute;
-
-    border: 1px solid transparent;
-    border-right: 1px dotted silver;
-    border-bottom-color: silver;
-
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    vertical-align: middle;
-    z-index: 1;
-    padding: 1px 2px 2px 1px;
-    margin: 0;
-
-    white-space: nowrap;
-
-    cursor: default;
+  position: absolute;
+  border: 1px solid transparent;
+  border-right: 1px dotted silver;
+  border-bottom-color: silver;
+  overflow: hidden;
+  -o-text-overflow: ellipsis;
+  text-overflow: ellipsis;
+  vertical-align: middle;
+  z-index: 1;
+  padding: 1px 2px 2px 1px;
+  margin: 0;
+  white-space: nowrap;
+  cursor: default;
 }
 
 .slick-group {
 }
 
 .slick-group-toggle {
-    display: inline-block;
+  display: inline-block;
 }
 
 .slick-cell.highlighted {
-    background: lightskyblue;
-    background: rgba(0, 0, 255, 0.2);
-    -webkit-transition: all 0.5s;
-    -moz-transition: all 0.5s;
-    transition: all 0.5s;
+  background: lightskyblue;
+  background: rgba(0, 0, 255, 0.2);
+  -webkit-transition: all 0.5s;
+  -moz-transition: all 0.5s;
+  -o-transition: all 0.5s;
+  transition: all 0.5s;
 }
 
 .slick-cell.flashing {
-    border: 1px solid red !important;
+  border: 1px solid red !important;
 }
 
 .slick-cell.editable {
-    z-index: 11;
-    overflow: visible;
-    background: white;
-    border-color: black;
-    border-style: solid;
+  z-index: 11;
+  overflow: visible;
+  background: white;
+  border-color: black;
+  border-style: solid;
 }
 
 .slick-cell:focus {
-    outline: none;
+  outline: none;
 }
 
 .slick-reorder-proxy {
-    display: inline-block;
-    background: blue;
-    opacity: 0.15;
-    filter: alpha(opacity = 15);
-    cursor: move;
+  display: inline-block;
+  background: blue;
+  opacity: 0.15;
+  filter: alpha(opacity = 15);
+  cursor: move;
 }
 
 .slick-reorder-guide {
-    display: inline-block;
-    height: 2px;
-    background: blue;
-    opacity: 0.7;
-    filter: alpha(opacity = 70);
+  display: inline-block;
+  height: 2px;
+  background: blue;
+  opacity: 0.7;
+  filter: alpha(opacity = 70);
 }
 
 .slick-selection {
-    z-index: 10;
-    position: absolute;
-    border: 2px dashed black;
-}
\ No newline at end of file
+  z-index: 10;
+  position: absolute;
+  border: 2px dashed black;
+}

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.autotooltips.js
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.autotooltips.js b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.autotooltips.js
index bce0bea..955684f 100755
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.autotooltips.js
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.autotooltips.js
@@ -1,48 +1,83 @@
 (function ($) {
-    // register namespace
-    $.extend(true, window, {
-        "Slick": {
-            "AutoTooltips": AutoTooltips
-        }
-    });
-
-
-    function AutoTooltips(options) {
-        var _grid;
-        var _self = this;
-        var _defaults = {
-            maxToolTipLength: null
-        };
-
-        function init(grid) {
-            options = $.extend(true, {}, _defaults, options);
-            _grid = grid;
-            _grid.onMouseEnter.subscribe(handleMouseEnter);
-        }
-
-        function destroy() {
-            _grid.onMouseEnter.unsubscribe(handleMouseEnter);
-        }
+  // Register namespace
+  $.extend(true, window, {
+    "Slick": {
+      "AutoTooltips": AutoTooltips
+    }
+  });
 
-        function handleMouseEnter(e, args) {
-            var cell = _grid.getCellFromEvent(e);
-            if (cell) {
-                var node = _grid.getCellNode(cell.row, cell.cell);
-                if ($(node).innerWidth() < node.scrollWidth) {
-                    var text = $.trim($(node).text());
-                    if (options.maxToolTipLength && text.length > options.maxToolTipLength) {
-                        text = text.substr(0, options.maxToolTipLength - 3) + "...";
-                    }
-                    $(node).attr("title", text);
-                } else {
-                    $(node).attr("title", "");
-                }
-            }
+  /**
+   * AutoTooltips plugin to show/hide tooltips when columns are too narrow to fit content.
+   * @constructor
+   * @param {boolean} [options.enableForCells=true]        - Enable tooltip for grid cells
+   * @param {boolean} [options.enableForHeaderCells=false] - Enable tooltip for header cells
+   * @param {number}  [options.maxToolTipLength=null]      - The maximum length for a tooltip
+   */
+  function AutoTooltips(options) {
+    var _grid;
+    var _self = this;
+    var _defaults = {
+      enableForCells: true,
+      enableForHeaderCells: false,
+      maxToolTipLength: null
+    };
+    
+    /**
+     * Initialize plugin.
+     */
+    function init(grid) {
+      options = $.extend(true, {}, _defaults, options);
+      _grid = grid;
+      if (options.enableForCells) _grid.onMouseEnter.subscribe(handleMouseEnter);
+      if (options.enableForHeaderCells) _grid.onHeaderMouseEnter.subscribe(handleHeaderMouseEnter);
+    }
+    
+    /**
+     * Destroy plugin.
+     */
+    function destroy() {
+      if (options.enableForCells) _grid.onMouseEnter.unsubscribe(handleMouseEnter);
+      if (options.enableForHeaderCells) _grid.onHeaderMouseEnter.unsubscribe(handleHeaderMouseEnter);
+    }
+    
+    /**
+     * Handle mouse entering grid cell to add/remove tooltip.
+     * @param {jQuery.Event} e - The event
+     */
+    function handleMouseEnter(e) {
+      var cell = _grid.getCellFromEvent(e);
+      if (cell) {
+        var $node = $(_grid.getCellNode(cell.row, cell.cell));
+        var text;
+        if ($node.innerWidth() < $node[0].scrollWidth) {
+          text = $.trim($node.text());
+          if (options.maxToolTipLength && text.length > options.maxToolTipLength) {
+            text = text.substr(0, options.maxToolTipLength - 3) + "...";
+          }
+        } else {
+          text = "";
         }
-
-        $.extend(this, {
-            "init": init,
-            "destroy": destroy
-        });
+        $node.attr("title", text);
+      }
+    }
+    
+    /**
+     * Handle mouse entering header cell to add/remove tooltip.
+     * @param {jQuery.Event} e     - The event
+     * @param {object} args.column - The column definition
+     */
+    function handleHeaderMouseEnter(e, args) {
+      var column = args.column,
+          $node = $(e.target).closest(".slick-header-column");
+      if (!column.toolTip) {
+        $node.attr("title", ($node.innerWidth() < $node[0].scrollWidth) ? column.name : "");
+      }
     }
+    
+    // Public API
+    $.extend(this, {
+      "init": init,
+      "destroy": destroy
+    });
+  }
 })(jQuery);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.cellrangedecorator.js
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.cellrangedecorator.js b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.cellrangedecorator.js
index eb2d506..0cbe71d 100755
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.cellrangedecorator.js
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.cellrangedecorator.js
@@ -1,64 +1,66 @@
 (function ($) {
-    // register namespace
-    $.extend(true, window, {
-        "Slick": {
-            "CellRangeDecorator": CellRangeDecorator
-        }
-    });
-
-    /***
-     * Displays an overlay on top of a given cell range.
-     *
-     * TODO:
-     * Currently, it blocks mouse events to DOM nodes behind it.
-     * Use FF and WebKit-specific "pointer-events" CSS style, or some kind of event forwarding.
-     * Could also construct the borders separately using 4 individual DIVs.
-     *
-     * @param {Grid} grid
-     * @param {Object} options
-     */
-    function CellRangeDecorator(grid, options) {
-        var _elem;
-        var _defaults = {
-            selectionCss: {
-                "zIndex": "9999",
-                "border": "2px dashed red"
-            }
-        };
+  // register namespace
+  $.extend(true, window, {
+    "Slick": {
+      "CellRangeDecorator": CellRangeDecorator
+    }
+  });
 
-        options = $.extend(true, {}, _defaults, options);
+  /***
+   * Displays an overlay on top of a given cell range.
+   *
+   * TODO:
+   * Currently, it blocks mouse events to DOM nodes behind it.
+   * Use FF and WebKit-specific "pointer-events" CSS style, or some kind of event forwarding.
+   * Could also construct the borders separately using 4 individual DIVs.
+   *
+   * @param {Grid} grid
+   * @param {Object} options
+   */
+  function CellRangeDecorator(grid, options) {
+    var _elem;
+    var _defaults = {
+      selectionCssClass: 'slick-range-decorator',
+      selectionCss: {
+        "zIndex": "9999",
+        "border": "2px dashed red"
+      }
+    };
 
+    options = $.extend(true, {}, _defaults, options);
 
-        function show(range) {
-            if (!_elem) {
-                _elem = $("<div></div>", {css: options.selectionCss})
-                        .css("position", "absolute")
-                        .appendTo(grid.getCanvasNode());
-            }
 
-            var from = grid.getCellNodeBox(range.fromRow, range.fromCell);
-            var to = grid.getCellNodeBox(range.toRow, range.toCell);
+    function show(range) {
+      if (!_elem) {
+        _elem = $("<div></div>", {css: options.selectionCss})
+            .addClass(options.selectionCssClass)
+            .css("position", "absolute")
+            .appendTo(grid.getCanvasNode());
+      }
 
-            _elem.css({
-                top: from.top - 1,
-                left: from.left - 1,
-                height: to.bottom - from.top - 2,
-                width: to.right - from.left - 2
-            });
+      var from = grid.getCellNodeBox(range.fromRow, range.fromCell);
+      var to = grid.getCellNodeBox(range.toRow, range.toCell);
 
-            return _elem;
-        }
+      _elem.css({
+        top: from.top - 1,
+        left: from.left - 1,
+        height: to.bottom - from.top - 2,
+        width: to.right - from.left - 2
+      });
 
-        function hide() {
-            if (_elem) {
-                _elem.remove();
-                _elem = null;
-            }
-        }
+      return _elem;
+    }
 
-        $.extend(this, {
-            "show": show,
-            "hide": hide
-        });
+    function hide() {
+      if (_elem) {
+        _elem.remove();
+        _elem = null;
+      }
     }
-})(jQuery);
\ No newline at end of file
+
+    $.extend(this, {
+      "show": show,
+      "hide": hide
+    });
+  }
+})(jQuery);

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.cellrangeselector.js
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.cellrangeselector.js b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.cellrangeselector.js
index 04d2f90..520b17f 100755
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.cellrangeselector.js
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.cellrangeselector.js
@@ -1,111 +1,113 @@
 (function ($) {
-    // register namespace
-    $.extend(true, window, {
-        "Slick": {
-            "CellRangeSelector": CellRangeSelector
-        }
-    });
+  // register namespace
+  $.extend(true, window, {
+    "Slick": {
+      "CellRangeSelector": CellRangeSelector
+    }
+  });
+
+
+  function CellRangeSelector(options) {
+    var _grid;
+    var _canvas;
+    var _dragging;
+    var _decorator;
+    var _self = this;
+    var _handler = new Slick.EventHandler();
+    var _defaults = {
+      selectionCss: {
+        "border": "2px dashed blue"
+      }
+    };
+
+
+    function init(grid) {
+      options = $.extend(true, {}, _defaults, options);
+      _decorator = new Slick.CellRangeDecorator(grid, options);
+      _grid = grid;
+      _canvas = _grid.getCanvasNode();
+      _handler
+        .subscribe(_grid.onDragInit, handleDragInit)
+        .subscribe(_grid.onDragStart, handleDragStart)
+        .subscribe(_grid.onDrag, handleDrag)
+        .subscribe(_grid.onDragEnd, handleDragEnd);
+    }
 
+    function destroy() {
+      _handler.unsubscribeAll();
+    }
 
-    function CellRangeSelector(options) {
-        var _grid;
-        var _canvas;
-        var _dragging;
-        var _decorator;
-        var _self = this;
-        var _defaults = {
-            selectionCss: {
-                "border": "2px dashed blue"
-            }
-        };
-
-
-        function init(grid) {
-            options = $.extend(true, {}, _defaults, options);
-            _decorator = new Slick.CellRangeDecorator(grid, options);
-            _grid = grid;
-            _canvas = _grid.getCanvasNode();
-            _grid.onDragInit.subscribe(handleDragInit);
-            _grid.onDragStart.subscribe(handleDragStart);
-            _grid.onDrag.subscribe(handleDrag);
-            _grid.onDragEnd.subscribe(handleDragEnd);
-        }
+    function handleDragInit(e, dd) {
+      // prevent the grid from cancelling drag'n'drop by default
+      e.stopImmediatePropagation();
+    }
 
-        function destroy() {
-            _grid.onDragInit.unsubscribe(handleDragInit);
-            _grid.onDragStart.unsubscribe(handleDragStart);
-            _grid.onDrag.unsubscribe(handleDrag);
-            _grid.onDragEnd.unsubscribe(handleDragEnd);
+    function handleDragStart(e, dd) {
+      var cell = _grid.getCellFromEvent(e);
+      if (_self.onBeforeCellRangeSelected.notify(cell) !== false) {
+        if (_grid.canCellBeSelected(cell.row, cell.cell)) {
+          _dragging = true;
+          e.stopImmediatePropagation();
         }
+      }
+      if (!_dragging) {
+        return;
+      }
 
-        function handleDragInit(e, dd) {
-            // prevent the grid from cancelling drag'n'drop by default
-            e.stopImmediatePropagation();
-        }
+      _grid.focus();
 
-        function handleDragStart(e, dd) {
-            var cell = _grid.getCellFromEvent(e);
-            if (_self.onBeforeCellRangeSelected.notify(cell) !== false) {
-                if (_grid.canCellBeSelected(cell.row, cell.cell)) {
-                    _dragging = true;
-                    e.stopImmediatePropagation();
-                }
-            }
-            if (!_dragging) {
-                return;
-            }
-
-            var start = _grid.getCellFromPoint(
-                    dd.startX - $(_canvas).offset().left,
-                    dd.startY - $(_canvas).offset().top);
-
-            dd.range = {start: start, end: {}};
-
-            return _decorator.show(new Slick.Range(start.row, start.cell));
-        }
+      var start = _grid.getCellFromPoint(
+          dd.startX - $(_canvas).offset().left,
+          dd.startY - $(_canvas).offset().top);
 
-        function handleDrag(e, dd) {
-            if (!_dragging) {
-                return;
-            }
-            e.stopImmediatePropagation();
+      dd.range = {start: start, end: {}};
 
-            var end = _grid.getCellFromPoint(
-                    e.pageX - $(_canvas).offset().left,
-                    e.pageY - $(_canvas).offset().top);
+      return _decorator.show(new Slick.Range(start.row, start.cell));
+    }
 
-            if (!_grid.canCellBeSelected(end.row, end.cell)) {
-                return;
-            }
+    function handleDrag(e, dd) {
+      if (!_dragging) {
+        return;
+      }
+      e.stopImmediatePropagation();
 
-            dd.range.end = end;
-            _decorator.show(new Slick.Range(dd.range.start.row, dd.range.start.cell, end.row, end.cell));
-        }
+      var end = _grid.getCellFromPoint(
+          e.pageX - $(_canvas).offset().left,
+          e.pageY - $(_canvas).offset().top);
 
-        function handleDragEnd(e, dd) {
-            if (!_dragging) {
-                return;
-            }
-
-            _dragging = false;
-            e.stopImmediatePropagation();
-
-            _decorator.hide();
-            _self.onCellRangeSelected.notify({
-                range: new Slick.Range(
-                        dd.range.start.row,
-                        dd.range.start.cell,
-                        dd.range.end.row,
-                        dd.range.end.cell
-                        )
-            });
-        }
+      if (!_grid.canCellBeSelected(end.row, end.cell)) {
+        return;
+      }
+
+      dd.range.end = end;
+      _decorator.show(new Slick.Range(dd.range.start.row, dd.range.start.cell, end.row, end.cell));
+    }
 
-        $.extend(this, {
-            "init": init,
-            "destroy": destroy,
-            "onBeforeCellRangeSelected": new Slick.Event(),
-            "onCellRangeSelected": new Slick.Event()
-        });
+    function handleDragEnd(e, dd) {
+      if (!_dragging) {
+        return;
+      }
+
+      _dragging = false;
+      e.stopImmediatePropagation();
+
+      _decorator.hide();
+      _self.onCellRangeSelected.notify({
+        range: new Slick.Range(
+            dd.range.start.row,
+            dd.range.start.cell,
+            dd.range.end.row,
+            dd.range.end.cell
+        )
+      });
     }
+
+    $.extend(this, {
+      "init": init,
+      "destroy": destroy,
+
+      "onBeforeCellRangeSelected": new Slick.Event(),
+      "onCellRangeSelected": new Slick.Event()
+    });
+  }
 })(jQuery);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.cellselectionmodel.js
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.cellselectionmodel.js b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.cellselectionmodel.js
index 9f0730a..74bc3eb 100755
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.cellselectionmodel.js
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.cellselectionmodel.js
@@ -1,90 +1,154 @@
 (function ($) {
-    // register namespace
-    $.extend(true, window, {
-        "Slick": {
-            "CellSelectionModel": CellSelectionModel
-        }
+  // register namespace
+  $.extend(true, window, {
+    "Slick": {
+      "CellSelectionModel": CellSelectionModel
+    }
+  });
+
+
+  function CellSelectionModel(options) {
+    var _grid;
+    var _canvas;
+    var _ranges = [];
+    var _self = this;
+    var _selector = new Slick.CellRangeSelector({
+      "selectionCss": {
+        "border": "2px solid black"
+      }
     });
+    var _options;
+    var _defaults = {
+      selectActiveCell: true
+    };
 
 
-    function CellSelectionModel(options) {
-        var _grid;
-        var _canvas;
-        var _ranges = [];
-        var _self = this;
-        var _selector = new Slick.CellRangeSelector({
-            "selectionCss": {
-                "border": "2px solid black"
-            }
-        });
-        var _options;
-        var _defaults = {
-            selectActiveCell: true
-        };
-
-
-        function init(grid) {
-            _options = $.extend(true, {}, _defaults, options);
-            _grid = grid;
-            _canvas = _grid.getCanvasNode();
-            _grid.onActiveCellChanged.subscribe(handleActiveCellChange);
-            grid.registerPlugin(_selector);
-            _selector.onCellRangeSelected.subscribe(handleCellRangeSelected);
-            _selector.onBeforeCellRangeSelected.subscribe(handleBeforeCellRangeSelected);
-        }
+    function init(grid) {
+      _options = $.extend(true, {}, _defaults, options);
+      _grid = grid;
+      _canvas = _grid.getCanvasNode();
+      _grid.onActiveCellChanged.subscribe(handleActiveCellChange);
+      _grid.onKeyDown.subscribe(handleKeyDown);
+      grid.registerPlugin(_selector);
+      _selector.onCellRangeSelected.subscribe(handleCellRangeSelected);
+      _selector.onBeforeCellRangeSelected.subscribe(handleBeforeCellRangeSelected);
+    }
 
-        function destroy() {
-            _grid.onActiveCellChanged.unsubscribe(handleActiveCellChange);
-            _selector.onCellRangeSelected.unsubscribe(handleCellRangeSelected);
-            _selector.onBeforeCellRangeSelected.unsubscribe(handleBeforeCellRangeSelected);
-            _grid.unregisterPlugin(_selector);
+    function destroy() {
+      _grid.onActiveCellChanged.unsubscribe(handleActiveCellChange);
+      _grid.onKeyDown.unsubscribe(handleKeyDown);
+      _selector.onCellRangeSelected.unsubscribe(handleCellRangeSelected);
+      _selector.onBeforeCellRangeSelected.unsubscribe(handleBeforeCellRangeSelected);
+      _grid.unregisterPlugin(_selector);
+    }
+
+    function removeInvalidRanges(ranges) {
+      var result = [];
+
+      for (var i = 0; i < ranges.length; i++) {
+        var r = ranges[i];
+        if (_grid.canCellBeSelected(r.fromRow, r.fromCell) && _grid.canCellBeSelected(r.toRow, r.toCell)) {
+          result.push(r);
         }
+      }
 
-        function removeInvalidRanges(ranges) {
-            var result = [];
+      return result;
+    }
 
-            for (var i = 0; i < ranges.length; i++) {
-                var r = ranges[i];
-                if (_grid.canCellBeSelected(r.fromRow, r.fromCell) && _grid.canCellBeSelected(r.toRow, r.toCell)) {
-                    result.push(r);
-                }
-            }
+    function setSelectedRanges(ranges) {
+      _ranges = removeInvalidRanges(ranges);
+      _self.onSelectedRangesChanged.notify(_ranges);
+    }
 
-            return result;
-        }
+    function getSelectedRanges() {
+      return _ranges;
+    }
 
-        function setSelectedRanges(ranges) {
-            _ranges = removeInvalidRanges(ranges);
-            _self.onSelectedRangesChanged.notify(_ranges);
-        }
+    function handleBeforeCellRangeSelected(e, args) {
+      if (_grid.getEditorLock().isActive()) {
+        e.stopPropagation();
+        return false;
+      }
+    }
 
-        function getSelectedRanges() {
-            return _ranges;
-        }
+    function handleCellRangeSelected(e, args) {
+      setSelectedRanges([args.range]);
+    }
 
-        function handleBeforeCellRangeSelected(e, args) {
-            if (_grid.getEditorLock().isActive()) {
-                e.stopPropagation();
-                return false;
-            }
-        }
+    function handleActiveCellChange(e, args) {
+      if (_options.selectActiveCell && args.row != null && args.cell != null) {
+        setSelectedRanges([new Slick.Range(args.row, args.cell)]);
+      }
+    }
+    
+    function handleKeyDown(e) {
+      /***
+       * Кey codes
+       * 37 left
+       * 38 up
+       * 39 right
+       * 40 down                     
+       */                                         
+      var ranges, last;
+      var active = _grid.getActiveCell(); 
 
-        function handleCellRangeSelected(e, args) {
-            setSelectedRanges([args.range]);
+      if ( active && e.shiftKey && !e.ctrlKey && !e.altKey && 
+          (e.which == 37 || e.which == 39 || e.which == 38 || e.which == 40) ) {
+      
+        ranges = getSelectedRanges();
+        if (!ranges.length)
+         ranges.push(new Slick.Range(active.row, active.cell));
+         
+        // keyboard can work with last range only          
+        last = ranges.pop();
+        
+        // can't handle selection out of active cell
+        if (!last.contains(active.row, active.cell))
+          last = new Slick.Range(active.row, active.cell);
+        
+        var dRow = last.toRow - last.fromRow,
+            dCell = last.toCell - last.fromCell,
+            // walking direction
+            dirRow = active.row == last.fromRow ? 1 : -1,
+            dirCell = active.cell == last.fromCell ? 1 : -1;
+                 
+        if (e.which == 37) {
+          dCell -= dirCell; 
+        } else if (e.which == 39) {
+          dCell += dirCell ; 
+        } else if (e.which == 38) {
+          dRow -= dirRow; 
+        } else if (e.which == 40) {
+          dRow += dirRow; 
         }
-
-        function handleActiveCellChange(e, args) {
-            if (_options.selectActiveCell) {
-                setSelectedRanges([new Slick.Range(args.row, args.cell)]);
-            }
+        
+        // define new selection range 
+        var new_last = new Slick.Range(active.row, active.cell, active.row + dirRow*dRow, active.cell + dirCell*dCell);
+        if (removeInvalidRanges([new_last]).length) {
+          ranges.push(new_last);
+          var viewRow = dirRow > 0 ? new_last.toRow : new_last.fromRow;
+          var viewCell = dirCell > 0 ? new_last.toCell : new_last.fromCell;
+         _grid.scrollRowIntoView(viewRow);
+         _grid.scrollCellIntoView(viewRow, viewCell);
         }
+        else 
+          ranges.push(last);
 
-        $.extend(this, {
-            "getSelectedRanges": getSelectedRanges,
-            "setSelectedRanges": setSelectedRanges,
-            "init": init,
-            "destroy": destroy,
-            "onSelectedRangesChanged": new Slick.Event()
-        });
+        setSelectedRanges(ranges);  
+       
+        e.preventDefault();
+        e.stopPropagation();        
+      }           
     }
-})(jQuery);
\ No newline at end of file
+
+    $.extend(this, {
+      "getSelectedRanges": getSelectedRanges,
+      "setSelectedRanges": setSelectedRanges,
+
+      "init": init,
+      "destroy": destroy,
+
+      "onSelectedRangesChanged": new Slick.Event()
+    });
+  }
+})(jQuery);

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.rowselectionmodel.js
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.rowselectionmodel.js b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.rowselectionmodel.js
index af966c0..0de8dd3 100755
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.rowselectionmodel.js
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/plugins/slick.rowselectionmodel.js
@@ -1,184 +1,187 @@
 (function ($) {
-    // register namespace
-    $.extend(true, window, {
-        "Slick": {
-            "RowSelectionModel": RowSelectionModel
-        }
-    });
+  // register namespace
+  $.extend(true, window, {
+    "Slick": {
+      "RowSelectionModel": RowSelectionModel
+    }
+  });
+
+  function RowSelectionModel(options) {
+    var _grid;
+    var _ranges = [];
+    var _self = this;
+    var _handler = new Slick.EventHandler();
+    var _inHandler;
+    var _options;
+    var _defaults = {
+      selectActiveRow: true
+    };
+
+    function init(grid) {
+      _options = $.extend(true, {}, _defaults, options);
+      _grid = grid;
+      _handler.subscribe(_grid.onActiveCellChanged,
+          wrapHandler(handleActiveCellChange));
+      _handler.subscribe(_grid.onKeyDown,
+          wrapHandler(handleKeyDown));
+      _handler.subscribe(_grid.onClick,
+          wrapHandler(handleClick));
+    }
 
-    function RowSelectionModel(options) {
-        var _grid;
-        var _ranges = [];
-        var _self = this;
-        var _handler = new Slick.EventHandler();
-        var _inHandler;
-        var _options;
-        var _defaults = {
-            selectActiveRow: true
-        };
-
-        function init(grid) {
-            _options = $.extend(true, {}, _defaults, options);
-            _grid = grid;
-            _handler.subscribe(_grid.onActiveCellChanged,
-                    wrapHandler(handleActiveCellChange));
-            _handler.subscribe(_grid.onKeyDown,
-                    wrapHandler(handleKeyDown));
-            _handler.subscribe(_grid.onClick,
-                    wrapHandler(handleClick));
-        }
+    function destroy() {
+      _handler.unsubscribeAll();
+    }
 
-        function destroy() {
-            _handler.unsubscribeAll();
+    function wrapHandler(handler) {
+      return function () {
+        if (!_inHandler) {
+          _inHandler = true;
+          handler.apply(this, arguments);
+          _inHandler = false;
         }
+      };
+    }
 
-        function wrapHandler(handler) {
-            return function () {
-                if (!_inHandler) {
-                    _inHandler = true;
-                    handler.apply(this, arguments);
-                    _inHandler = false;
-                }
-            };
+    function rangesToRows(ranges) {
+      var rows = [];
+      for (var i = 0; i < ranges.length; i++) {
+        for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
+          rows.push(j);
         }
+      }
+      return rows;
+    }
 
-        function rangesToRows(ranges) {
-            var rows = [];
-            for (var i = 0; i < ranges.length; i++) {
-                for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
-                    rows.push(j);
-                }
-            }
-            return rows;
-        }
+    function rowsToRanges(rows) {
+      var ranges = [];
+      var lastCell = _grid.getColumns().length - 1;
+      for (var i = 0; i < rows.length; i++) {
+        ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));
+      }
+      return ranges;
+    }
 
-        function rowsToRanges(rows) {
-            var ranges = [];
-            var lastCell = _grid.getColumns().length - 1;
-            for (var i = 0; i < rows.length; i++) {
-                ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));
-            }
-            return ranges;
-        }
+    function getRowsRange(from, to) {
+      var i, rows = [];
+      for (i = from; i <= to; i++) {
+        rows.push(i);
+      }
+      for (i = to; i < from; i++) {
+        rows.push(i);
+      }
+      return rows;
+    }
 
-        function getRowsRange(from, to) {
-            var i, rows = [];
-            for (i = from; i <= to; i++) {
-                rows.push(i);
-            }
-            for (i = to; i < from; i++) {
-                rows.push(i);
-            }
-            return rows;
-        }
+    function getSelectedRows() {
+      return rangesToRows(_ranges);
+    }
 
-        function getSelectedRows() {
-            return rangesToRows(_ranges);
-        }
+    function setSelectedRows(rows) {
+      setSelectedRanges(rowsToRanges(rows));
+    }
 
-        function setSelectedRows(rows) {
-            setSelectedRanges(rowsToRanges(rows));
-        }
+    function setSelectedRanges(ranges) {
+      _ranges = ranges;
+      _self.onSelectedRangesChanged.notify(_ranges);
+    }
 
-        function setSelectedRanges(ranges) {
-            _ranges = ranges;
-            _self.onSelectedRangesChanged.notify(_ranges);
-        }
+    function getSelectedRanges() {
+      return _ranges;
+    }
 
-        function getSelectedRanges() {
-            return _ranges;
-        }
+    function handleActiveCellChange(e, data) {
+      if (_options.selectActiveRow && data.row != null) {
+        setSelectedRanges([new Slick.Range(data.row, 0, data.row, _grid.getColumns().length - 1)]);
+      }
+    }
 
-        function handleActiveCellChange(e, data) {
-            if (_options.selectActiveRow) {
-                setSelectedRanges([new Slick.Range(data.row, 0, data.row, _grid.getColumns().length - 1)]);
-            }
+    function handleKeyDown(e) {
+      var activeRow = _grid.getActiveCell();
+      if (activeRow && e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey && (e.which == 38 || e.which == 40)) {
+        var selectedRows = getSelectedRows();
+        selectedRows.sort(function (x, y) {
+          return x - y
+        });
+
+        if (!selectedRows.length) {
+          selectedRows = [activeRow.row];
         }
 
-        function handleKeyDown(e) {
-            var activeRow = _grid.getActiveCell();
-            if (activeRow && e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey && (e.which == 38 || e.which == 40)) {
-                var selectedRows = getSelectedRows();
-                selectedRows.sort(function (x, y) {
-                    return x - y
-                });
-
-                if (!selectedRows.length) {
-                    selectedRows = [activeRow.row];
-                }
-
-                var top = selectedRows[0];
-                var bottom = selectedRows[selectedRows.length - 1];
-                var active;
-
-                if (e.which == 40) {
-                    active = activeRow.row < bottom || top == bottom ? ++bottom : ++top;
-                } else {
-                    active = activeRow.row < bottom ? --bottom : --top;
-                }
-
-                if (active >= 0 && active < _grid.getDataLength()) {
-                    _grid.scrollRowIntoView(active);
-                    _ranges = rowsToRanges(getRowsRange(top, bottom));
-                    setSelectedRanges(_ranges);
-                }
-
-                e.preventDefault();
-                e.stopPropagation();
-            }
+        var top = selectedRows[0];
+        var bottom = selectedRows[selectedRows.length - 1];
+        var active;
+
+        if (e.which == 40) {
+          active = activeRow.row < bottom || top == bottom ? ++bottom : ++top;
+        } else {
+          active = activeRow.row < bottom ? --bottom : --top;
         }
 
-        function handleClick(e) {
-            var cell = _grid.getCellFromEvent(e);
-            if (!cell || !_grid.canCellBeActive(cell.row, cell.cell)) {
-                return false;
-            }
-
-            var selection = rangesToRows(_ranges);
-            var idx = $.inArray(cell.row, selection);
-
-            if (!e.ctrlKey && !e.shiftKey && !e.metaKey) {
-                return false;
-            }
-            else if (_grid.getOptions().multiSelect) {
-                if (idx === -1 && (e.ctrlKey || e.metaKey)) {
-                    selection.push(cell.row);
-                    _grid.setActiveCell(cell.row, cell.cell);
-                } else if (idx !== -1 && (e.ctrlKey || e.metaKey)) {
-                    selection = $.grep(selection, function (o, i) {
-                        return (o !== cell.row);
-                    });
-                    _grid.setActiveCell(cell.row, cell.cell);
-                } else if (selection.length && e.shiftKey) {
-                    var last = selection.pop();
-                    var from = Math.min(cell.row, last);
-                    var to = Math.max(cell.row, last);
-                    selection = [];
-                    for (var i = from; i <= to; i++) {
-                        if (i !== last) {
-                            selection.push(i);
-                        }
-                    }
-                    selection.push(last);
-                    _grid.setActiveCell(cell.row, cell.cell);
-                }
-            }
-
-            _ranges = rowsToRanges(selection);
-            setSelectedRanges(_ranges);
-            e.stopImmediatePropagation();
-
-            return true;
+        if (active >= 0 && active < _grid.getDataLength()) {
+          _grid.scrollRowIntoView(active);
+          _ranges = rowsToRanges(getRowsRange(top, bottom));
+          setSelectedRanges(_ranges);
         }
 
-        $.extend(this, {
-            "getSelectedRows": getSelectedRows,
-            "setSelectedRows": setSelectedRows,
-            "getSelectedRanges": getSelectedRanges,
-            "setSelectedRanges": setSelectedRanges,
-            "init": init,
-            "destroy": destroy,
-            "onSelectedRangesChanged": new Slick.Event()
+        e.preventDefault();
+        e.stopPropagation();
+      }
+    }
+
+    function handleClick(e) {
+      var cell = _grid.getCellFromEvent(e);
+      if (!cell || !_grid.canCellBeActive(cell.row, cell.cell)) {
+        return false;
+      }
+
+      if (!_grid.getOptions().multiSelect || (
+          !e.ctrlKey && !e.shiftKey && !e.metaKey)) {
+        return false;
+      }
+
+      var selection = rangesToRows(_ranges);
+      var idx = $.inArray(cell.row, selection);
+
+      if (idx === -1 && (e.ctrlKey || e.metaKey)) {
+        selection.push(cell.row);
+        _grid.setActiveCell(cell.row, cell.cell);
+      } else if (idx !== -1 && (e.ctrlKey || e.metaKey)) {
+        selection = $.grep(selection, function (o, i) {
+          return (o !== cell.row);
         });
+        _grid.setActiveCell(cell.row, cell.cell);
+      } else if (selection.length && e.shiftKey) {
+        var last = selection.pop();
+        var from = Math.min(cell.row, last);
+        var to = Math.max(cell.row, last);
+        selection = [];
+        for (var i = from; i <= to; i++) {
+          if (i !== last) {
+            selection.push(i);
+          }
+        }
+        selection.push(last);
+        _grid.setActiveCell(cell.row, cell.cell);
+      }
+
+      _ranges = rowsToRanges(selection);
+      setSelectedRanges(_ranges);
+      e.stopImmediatePropagation();
+
+      return true;
     }
+
+    $.extend(this, {
+      "getSelectedRows": getSelectedRows,
+      "setSelectedRows": setSelectedRows,
+
+      "getSelectedRanges": getSelectedRanges,
+      "setSelectedRanges": setSelectedRanges,
+
+      "init": init,
+      "destroy": destroy,
+
+      "onSelectedRangesChanged": new Slick.Event()
+    });
+  }
 })(jQuery);
\ No newline at end of file


[4/5] incubator-nifi git commit: NIFI-27: - Latest version of slickgrid.

Posted by mc...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/eb880757/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.core.js
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.core.js b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.core.js
index aea3567..2f097b1 100755
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.core.js
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/jquery/slickgrid/slick.core.js
@@ -5,419 +5,463 @@
  */
 
 (function ($) {
-    // register namespace
-    $.extend(true, window, {
-        "Slick": {
-            "Event": Event,
-            "EventData": EventData,
-            "EventHandler": EventHandler,
-            "Range": Range,
-            "NonDataRow": NonDataItem,
-            "Group": Group,
-            "GroupTotals": GroupTotals,
-            "EditorLock": EditorLock,
-            /***
-             * A global singleton editor lock.
-             * @class GlobalEditorLock
-             * @static
-             * @constructor
-             */
-            "GlobalEditorLock": new EditorLock()
-        }
-    });
+  // register namespace
+  $.extend(true, window, {
+    "Slick": {
+      "Event": Event,
+      "EventData": EventData,
+      "EventHandler": EventHandler,
+      "Range": Range,
+      "NonDataRow": NonDataItem,
+      "Group": Group,
+      "GroupTotals": GroupTotals,
+      "EditorLock": EditorLock,
+
+      /***
+       * A global singleton editor lock.
+       * @class GlobalEditorLock
+       * @static
+       * @constructor
+       */
+      "GlobalEditorLock": new EditorLock()
+    }
+  });
+
+  /***
+   * An event object for passing data to event handlers and letting them control propagation.
+   * <p>This is pretty much identical to how W3C and jQuery implement events.</p>
+   * @class EventData
+   * @constructor
+   */
+  function EventData() {
+    var isPropagationStopped = false;
+    var isImmediatePropagationStopped = false;
 
     /***
-     * An event object for passing data to event handlers and letting them control propagation.
-     * <p>This is pretty much identical to how W3C and jQuery implement events.</p>
-     * @class EventData
-     * @constructor
+     * Stops event from propagating up the DOM tree.
+     * @method stopPropagation
      */
-    function EventData() {
-        var isPropagationStopped = false;
-        var isImmediatePropagationStopped = false;
-
-        /***
-         * Stops event from propagating up the DOM tree.
-         * @method stopPropagation
-         */
-        this.stopPropagation = function () {
-            isPropagationStopped = true;
-        };
-
-        /***
-         * Returns whether stopPropagation was called on this event object.
-         * @method isPropagationStopped
-         * @return {Boolean}
-         */
-        this.isPropagationStopped = function () {
-            return isPropagationStopped;
-        };
-
-        /***
-         * Prevents the rest of the handlers from being executed.
-         * @method stopImmediatePropagation
-         */
-        this.stopImmediatePropagation = function () {
-            isImmediatePropagationStopped = true;
-        };
-
-        /***
-         * Returns whether stopImmediatePropagation was called on this event object.\
-         * @method isImmediatePropagationStopped
-         * @return {Boolean}
-         */
-        this.isImmediatePropagationStopped = function () {
-            return isImmediatePropagationStopped;
-        }
-    }
+    this.stopPropagation = function () {
+      isPropagationStopped = true;
+    };
 
     /***
-     * A simple publisher-subscriber implementation.
-     * @class Event
-     * @constructor
+     * Returns whether stopPropagation was called on this event object.
+     * @method isPropagationStopped
+     * @return {Boolean}
      */
-    function Event() {
-        var handlers = [];
-
-        /***
-         * Adds an event handler to be called when the event is fired.
-         * <p>Event handler will receive two arguments - an <code>EventData</code> and the <code>data</code>
-         * object the event was fired with.<p>
-         * @method subscribe
-         * @param fn {Function} Event handler.
-         */
-        this.subscribe = function (fn) {
-            handlers.push(fn);
-        };
-
-        /***
-         * Removes an event handler added with <code>subscribe(fn)</code>.
-         * @method unsubscribe
-         * @param fn {Function} Event handler to be removed.
-         */
-        this.unsubscribe = function (fn) {
-            for (var i = handlers.length - 1; i >= 0; i--) {
-                if (handlers[i] === fn) {
-                    handlers.splice(i, 1);
-                }
-            }
-        };
-
-        /***
-         * Fires an event notifying all subscribers.
-         * @method notify
-         * @param args {Object} Additional data object to be passed to all handlers.
-         * @param e {EventData}
-         *      Optional.
-         *      An <code>EventData</code> object to be passed to all handlers.
-         *      For DOM events, an existing W3C/jQuery event object can be passed in.
-         * @param scope {Object}
-         *      Optional.
-         *      The scope ("this") within which the handler will be executed.
-         *      If not specified, the scope will be set to the <code>Event</code> instance.
-         */
-        this.notify = function (args, e, scope) {
-            e = e || new EventData();
-            scope = scope || this;
-
-            var returnValue;
-            for (var i = 0; i < handlers.length && !(e.isPropagationStopped() || e.isImmediatePropagationStopped()); i++) {
-                returnValue = handlers[i].call(scope, e, args);
-            }
-
-            return returnValue;
-        };
-    }
+    this.isPropagationStopped = function () {
+      return isPropagationStopped;
+    };
 
-    function EventHandler() {
-        var handlers = [];
-
-        this.subscribe = function (event, handler) {
-            handlers.push({
-                event: event,
-                handler: handler
-            });
-            event.subscribe(handler);
-        };
-
-        this.unsubscribe = function (event, handler) {
-            var i = handlers.length;
-            while (i--) {
-                if (handlers[i].event === event &&
-                        handlers[i].handler === handler) {
-                    handlers.splice(i, 1);
-                    event.unsubscribe(handler);
-                    return;
-                }
-            }
-        };
-
-        this.unsubscribeAll = function () {
-            var i = handlers.length;
-            while (i--) {
-                handlers[i].event.unsubscribe(handlers[i].handler);
-            }
-            handlers = [];
-        }
+    /***
+     * Prevents the rest of the handlers from being executed.
+     * @method stopImmediatePropagation
+     */
+    this.stopImmediatePropagation = function () {
+      isImmediatePropagationStopped = true;
+    };
+
+    /***
+     * Returns whether stopImmediatePropagation was called on this event object.\
+     * @method isImmediatePropagationStopped
+     * @return {Boolean}
+     */
+    this.isImmediatePropagationStopped = function () {
+      return isImmediatePropagationStopped;
     }
+  }
+
+  /***
+   * A simple publisher-subscriber implementation.
+   * @class Event
+   * @constructor
+   */
+  function Event() {
+    var handlers = [];
+
+    /***
+     * Adds an event handler to be called when the event is fired.
+     * <p>Event handler will receive two arguments - an <code>EventData</code> and the <code>data</code>
+     * object the event was fired with.<p>
+     * @method subscribe
+     * @param fn {Function} Event handler.
+     */
+    this.subscribe = function (fn) {
+      handlers.push(fn);
+    };
 
     /***
-     * A structure containing a range of cells.
-     * @class Range
-     * @constructor
-     * @param fromRow {Integer} Starting row.
-     * @param fromCell {Integer} Starting cell.
-     * @param toRow {Integer} Optional. Ending row. Defaults to <code>fromRow</code>.
-     * @param toCell {Integer} Optional. Ending cell. Defaults to <code>fromCell</code>.
+     * Removes an event handler added with <code>subscribe(fn)</code>.
+     * @method unsubscribe
+     * @param fn {Function} Event handler to be removed.
      */
-    function Range(fromRow, fromCell, toRow, toCell) {
-        if (toRow === undefined && toCell === undefined) {
-            toRow = fromRow;
-            toCell = fromCell;
+    this.unsubscribe = function (fn) {
+      for (var i = handlers.length - 1; i >= 0; i--) {
+        if (handlers[i] === fn) {
+          handlers.splice(i, 1);
         }
+      }
+    };
+
+    /***
+     * Fires an event notifying all subscribers.
+     * @method notify
+     * @param args {Object} Additional data object to be passed to all handlers.
+     * @param e {EventData}
+     *      Optional.
+     *      An <code>EventData</code> object to be passed to all handlers.
+     *      For DOM events, an existing W3C/jQuery event object can be passed in.
+     * @param scope {Object}
+     *      Optional.
+     *      The scope ("this") within which the handler will be executed.
+     *      If not specified, the scope will be set to the <code>Event</code> instance.
+     */
+    this.notify = function (args, e, scope) {
+      e = e || new EventData();
+      scope = scope || this;
 
-        /***
-         * @property fromRow
-         * @type {Integer}
-         */
-        this.fromRow = Math.min(fromRow, toRow);
-
-        /***
-         * @property fromCell
-         * @type {Integer}
-         */
-        this.fromCell = Math.min(fromCell, toCell);
-
-        /***
-         * @property toRow
-         * @type {Integer}
-         */
-        this.toRow = Math.max(fromRow, toRow);
-
-        /***
-         * @property toCell
-         * @type {Integer}
-         */
-        this.toCell = Math.max(fromCell, toCell);
-
-        /***
-         * Returns whether a range represents a single row.
-         * @method isSingleRow
-         * @return {Boolean}
-         */
-        this.isSingleRow = function () {
-            return this.fromRow == this.toRow;
-        };
-
-        /***
-         * Returns whether a range represents a single cell.
-         * @method isSingleCell
-         * @return {Boolean}
-         */
-        this.isSingleCell = function () {
-            return this.fromRow == this.toRow && this.fromCell == this.toCell;
-        };
-
-        /***
-         * Returns whether a range contains a given cell.
-         * @method contains
-         * @param row {Integer}
-         * @param cell {Integer}
-         * @return {Boolean}
-         */
-        this.contains = function (row, cell) {
-            return row >= this.fromRow && row <= this.toRow &&
-                    cell >= this.fromCell && cell <= this.toCell;
-        };
-
-        /***
-         * Returns a readable representation of a range.
-         * @method toString
-         * @return {String}
-         */
-        this.toString = function () {
-            if (this.isSingleCell()) {
-                return "(" + this.fromRow + ":" + this.fromCell + ")";
-            }
-            else {
-                return "(" + this.fromRow + ":" + this.fromCell + " - " + this.toRow + ":" + this.toCell + ")";
-            }
+      var returnValue;
+      for (var i = 0; i < handlers.length && !(e.isPropagationStopped() || e.isImmediatePropagationStopped()); i++) {
+        returnValue = handlers[i].call(scope, e, args);
+      }
+
+      return returnValue;
+    };
+  }
+
+  function EventHandler() {
+    var handlers = [];
+
+    this.subscribe = function (event, handler) {
+      handlers.push({
+        event: event,
+        handler: handler
+      });
+      event.subscribe(handler);
+
+      return this;  // allow chaining
+    };
+
+    this.unsubscribe = function (event, handler) {
+      var i = handlers.length;
+      while (i--) {
+        if (handlers[i].event === event &&
+            handlers[i].handler === handler) {
+          handlers.splice(i, 1);
+          event.unsubscribe(handler);
+          return;
         }
+      }
+
+      return this;  // allow chaining
+    };
+
+    this.unsubscribeAll = function () {
+      var i = handlers.length;
+      while (i--) {
+        handlers[i].event.unsubscribe(handlers[i].handler);
+      }
+      handlers = [];
+
+      return this;  // allow chaining
+    }
+  }
+
+  /***
+   * A structure containing a range of cells.
+   * @class Range
+   * @constructor
+   * @param fromRow {Integer} Starting row.
+   * @param fromCell {Integer} Starting cell.
+   * @param toRow {Integer} Optional. Ending row. Defaults to <code>fromRow</code>.
+   * @param toCell {Integer} Optional. Ending cell. Defaults to <code>fromCell</code>.
+   */
+  function Range(fromRow, fromCell, toRow, toCell) {
+    if (toRow === undefined && toCell === undefined) {
+      toRow = fromRow;
+      toCell = fromCell;
     }
 
+    /***
+     * @property fromRow
+     * @type {Integer}
+     */
+    this.fromRow = Math.min(fromRow, toRow);
 
     /***
-     * A base class that all special / non-data rows (like Group and GroupTotals) derive from.
-     * @class NonDataItem
-     * @constructor
+     * @property fromCell
+     * @type {Integer}
      */
-    function NonDataItem() {
-        this.__nonDataRow = true;
-    }
+    this.fromCell = Math.min(fromCell, toCell);
 
+    /***
+     * @property toRow
+     * @type {Integer}
+     */
+    this.toRow = Math.max(fromRow, toRow);
 
     /***
-     * Information about a group of rows.
-     * @class Group
-     * @extends Slick.NonDataItem
-     * @constructor
+     * @property toCell
+     * @type {Integer}
      */
-    function Group() {
-        this.__group = true;
-        this.__updated = false;
-
-        /***
-         * Number of rows in the group.
-         * @property count
-         * @type {Integer}
-         */
-        this.count = 0;
-
-        /***
-         * Grouping value.
-         * @property value
-         * @type {Object}
-         */
-        this.value = null;
-
-        /***
-         * Formatted display value of the group.
-         * @property title
-         * @type {String}
-         */
-        this.title = null;
-
-        /***
-         * Whether a group is collapsed.
-         * @property collapsed
-         * @type {Boolean}
-         */
-        this.collapsed = false;
-
-        /***
-         * GroupTotals, if any.
-         * @property totals
-         * @type {GroupTotals}
-         */
-        this.totals = null;
-    }
+    this.toCell = Math.max(fromCell, toCell);
+
+    /***
+     * Returns whether a range represents a single row.
+     * @method isSingleRow
+     * @return {Boolean}
+     */
+    this.isSingleRow = function () {
+      return this.fromRow == this.toRow;
+    };
 
-    Group.prototype = new NonDataItem();
+    /***
+     * Returns whether a range represents a single cell.
+     * @method isSingleCell
+     * @return {Boolean}
+     */
+    this.isSingleCell = function () {
+      return this.fromRow == this.toRow && this.fromCell == this.toCell;
+    };
 
     /***
-     * Compares two Group instances.
-     * @method equals
+     * Returns whether a range contains a given cell.
+     * @method contains
+     * @param row {Integer}
+     * @param cell {Integer}
      * @return {Boolean}
-     * @param group {Group} Group instance to compare to.
      */
-    Group.prototype.equals = function (group) {
-        return this.value === group.value &&
-                this.count === group.count &&
-                this.collapsed === group.collapsed;
+    this.contains = function (row, cell) {
+      return row >= this.fromRow && row <= this.toRow &&
+          cell >= this.fromCell && cell <= this.toCell;
     };
 
     /***
-     * Information about group totals.
-     * An instance of GroupTotals will be created for each totals row and passed to the aggregators
-     * so that they can store arbitrary data in it.  That data can later be accessed by group totals
-     * formatters during the display.
-     * @class GroupTotals
-     * @extends Slick.NonDataItem
-     * @constructor
+     * Returns a readable representation of a range.
+     * @method toString
+     * @return {String}
      */
-    function GroupTotals() {
-        this.__groupTotals = true;
-
-        /***
-         * Parent Group.
-         * @param group
-         * @type {Group}
-         */
-        this.group = null;
+    this.toString = function () {
+      if (this.isSingleCell()) {
+        return "(" + this.fromRow + ":" + this.fromCell + ")";
+      }
+      else {
+        return "(" + this.fromRow + ":" + this.fromCell + " - " + this.toRow + ":" + this.toCell + ")";
+      }
     }
+  }
+
+
+  /***
+   * A base class that all special / non-data rows (like Group and GroupTotals) derive from.
+   * @class NonDataItem
+   * @constructor
+   */
+  function NonDataItem() {
+    this.__nonDataRow = true;
+  }
+
+
+  /***
+   * Information about a group of rows.
+   * @class Group
+   * @extends Slick.NonDataItem
+   * @constructor
+   */
+  function Group() {
+    this.__group = true;
+
+    /**
+     * Grouping level, starting with 0.
+     * @property level
+     * @type {Number}
+     */
+    this.level = 0;
 
-    GroupTotals.prototype = new NonDataItem();
+    /***
+     * Number of rows in the group.
+     * @property count
+     * @type {Integer}
+     */
+    this.count = 0;
 
     /***
-     * A locking helper to track the active edit controller and ensure that only a single controller
-     * can be active at a time.  This prevents a whole class of state and validation synchronization
-     * issues.  An edit controller (such as SlickGrid) can query if an active edit is in progress
-     * and attempt a commit or cancel before proceeding.
-     * @class EditorLock
-     * @constructor
+     * Grouping value.
+     * @property value
+     * @type {Object}
      */
-    function EditorLock() {
-        var activeEditController = null;
-
-        /***
-         * Returns true if a specified edit controller is active (has the edit lock).
-         * If the parameter is not specified, returns true if any edit controller is active.
-         * @method isActive
-         * @param editController {EditController}
-         * @return {Boolean}
-         */
-        this.isActive = function (editController) {
-            return (editController ? activeEditController === editController : activeEditController !== null);
-        };
-
-        /***
-         * Sets the specified edit controller as the active edit controller (acquire edit lock).
-         * If another edit controller is already active, and exception will be thrown.
-         * @method activate
-         * @param editController {EditController} edit controller acquiring the lock
-         */
-        this.activate = function (editController) {
-            if (editController === activeEditController) { // already activated?
-                return;
-            }
-            if (activeEditController !== null) {
-                throw "SlickGrid.EditorLock.activate: an editController is still active, can't activate another editController";
-            }
-            if (!editController.commitCurrentEdit) {
-                throw "SlickGrid.EditorLock.activate: editController must implement .commitCurrentEdit()";
-            }
-            if (!editController.cancelCurrentEdit) {
-                throw "SlickGrid.EditorLock.activate: editController must implement .cancelCurrentEdit()";
-            }
-            activeEditController = editController;
-        };
-
-        /***
-         * Unsets the specified edit controller as the active edit controller (release edit lock).
-         * If the specified edit controller is not the active one, an exception will be thrown.
-         * @method deactivate
-         * @param editController {EditController} edit controller releasing the lock
-         */
-        this.deactivate = function (editController) {
-            if (activeEditController !== editController) {
-                throw "SlickGrid.EditorLock.deactivate: specified editController is not the currently active one";
-            }
-            activeEditController = null;
-        };
-
-        /***
-         * Attempts to commit the current edit by calling "commitCurrentEdit" method on the active edit
-         * controller and returns whether the commit attempt was successful (commit may fail due to validation
-         * errors, etc.).  Edit controller's "commitCurrentEdit" must return true if the commit has succeeded
-         * and false otherwise.  If no edit controller is active, returns true.
-         * @method commitCurrentEdit
-         * @return {Boolean}
-         */
-        this.commitCurrentEdit = function () {
-            return (activeEditController ? activeEditController.commitCurrentEdit() : true);
-        };
-
-        /***
-         * Attempts to cancel the current edit by calling "cancelCurrentEdit" method on the active edit
-         * controller and returns whether the edit was successfully cancelled.  If no edit controller is
-         * active, returns true.
-         * @method cancelCurrentEdit
-         * @return {Boolean}
-         */
-        this.cancelCurrentEdit = function cancelCurrentEdit() {
-            return (activeEditController ? activeEditController.cancelCurrentEdit() : true);
-        };
-    }
+    this.value = null;
+
+    /***
+     * Formatted display value of the group.
+     * @property title
+     * @type {String}
+     */
+    this.title = null;
+
+    /***
+     * Whether a group is collapsed.
+     * @property collapsed
+     * @type {Boolean}
+     */
+    this.collapsed = false;
+
+    /***
+     * GroupTotals, if any.
+     * @property totals
+     * @type {GroupTotals}
+     */
+    this.totals = null;
+
+    /**
+     * Rows that are part of the group.
+     * @property rows
+     * @type {Array}
+     */
+    this.rows = [];
+
+    /**
+     * Sub-groups that are part of the group.
+     * @property groups
+     * @type {Array}
+     */
+    this.groups = null;
+
+    /**
+     * A unique key used to identify the group.  This key can be used in calls to DataView
+     * collapseGroup() or expandGroup().
+     * @property groupingKey
+     * @type {Object}
+     */
+    this.groupingKey = null;
+  }
+
+  Group.prototype = new NonDataItem();
+
+  /***
+   * Compares two Group instances.
+   * @method equals
+   * @return {Boolean}
+   * @param group {Group} Group instance to compare to.
+   */
+  Group.prototype.equals = function (group) {
+    return this.value === group.value &&
+        this.count === group.count &&
+        this.collapsed === group.collapsed &&
+        this.title === group.title;
+  };
+
+  /***
+   * Information about group totals.
+   * An instance of GroupTotals will be created for each totals row and passed to the aggregators
+   * so that they can store arbitrary data in it.  That data can later be accessed by group totals
+   * formatters during the display.
+   * @class GroupTotals
+   * @extends Slick.NonDataItem
+   * @constructor
+   */
+  function GroupTotals() {
+    this.__groupTotals = true;
+
+    /***
+     * Parent Group.
+     * @param group
+     * @type {Group}
+     */
+    this.group = null;
+
+    /***
+     * Whether the totals have been fully initialized / calculated.
+     * Will be set to false for lazy-calculated group totals.
+     * @param initialized
+     * @type {Boolean}
+     */
+    this.initialized = false;
+  }
+
+  GroupTotals.prototype = new NonDataItem();
+
+  /***
+   * A locking helper to track the active edit controller and ensure that only a single controller
+   * can be active at a time.  This prevents a whole class of state and validation synchronization
+   * issues.  An edit controller (such as SlickGrid) can query if an active edit is in progress
+   * and attempt a commit or cancel before proceeding.
+   * @class EditorLock
+   * @constructor
+   */
+  function EditorLock() {
+    var activeEditController = null;
+
+    /***
+     * Returns true if a specified edit controller is active (has the edit lock).
+     * If the parameter is not specified, returns true if any edit controller is active.
+     * @method isActive
+     * @param editController {EditController}
+     * @return {Boolean}
+     */
+    this.isActive = function (editController) {
+      return (editController ? activeEditController === editController : activeEditController !== null);
+    };
+
+    /***
+     * Sets the specified edit controller as the active edit controller (acquire edit lock).
+     * If another edit controller is already active, and exception will be thrown.
+     * @method activate
+     * @param editController {EditController} edit controller acquiring the lock
+     */
+    this.activate = function (editController) {
+      if (editController === activeEditController) { // already activated?
+        return;
+      }
+      if (activeEditController !== null) {
+        throw "SlickGrid.EditorLock.activate: an editController is still active, can't activate another editController";
+      }
+      if (!editController.commitCurrentEdit) {
+        throw "SlickGrid.EditorLock.activate: editController must implement .commitCurrentEdit()";
+      }
+      if (!editController.cancelCurrentEdit) {
+        throw "SlickGrid.EditorLock.activate: editController must implement .cancelCurrentEdit()";
+      }
+      activeEditController = editController;
+    };
+
+    /***
+     * Unsets the specified edit controller as the active edit controller (release edit lock).
+     * If the specified edit controller is not the active one, an exception will be thrown.
+     * @method deactivate
+     * @param editController {EditController} edit controller releasing the lock
+     */
+    this.deactivate = function (editController) {
+      if (activeEditController !== editController) {
+        throw "SlickGrid.EditorLock.deactivate: specified editController is not the currently active one";
+      }
+      activeEditController = null;
+    };
+
+    /***
+     * Attempts to commit the current edit by calling "commitCurrentEdit" method on the active edit
+     * controller and returns whether the commit attempt was successful (commit may fail due to validation
+     * errors, etc.).  Edit controller's "commitCurrentEdit" must return true if the commit has succeeded
+     * and false otherwise.  If no edit controller is active, returns true.
+     * @method commitCurrentEdit
+     * @return {Boolean}
+     */
+    this.commitCurrentEdit = function () {
+      return (activeEditController ? activeEditController.commitCurrentEdit() : true);
+    };
+
+    /***
+     * Attempts to cancel the current edit by calling "cancelCurrentEdit" method on the active edit
+     * controller and returns whether the edit was successfully cancelled.  If no edit controller is
+     * active, returns true.
+     * @method cancelCurrentEdit
+     * @return {Boolean}
+     */
+    this.cancelCurrentEdit = function cancelCurrentEdit() {
+      return (activeEditController ? activeEditController.cancelCurrentEdit() : true);
+    };
+  }
 })(jQuery);