From 557c7c6b738ac15c4287ef37a0c23c44b3bb10ae Mon Sep 17 00:00:00 2001 From: Simon Bremer Date: Thu, 29 Jun 2017 16:27:14 +0200 Subject: [PATCH 1/3] Updated slick filed from https://github.com/ddomingues/X-SlickGrid --- qgrid/grid.py | 2 +- qgrid/qgridjs/lib/slick.grid.css | 1986 +++++++++++++++++++++++++++--- qgrid/qgridjs/qgrid.widget.js | 6 +- 3 files changed, 1827 insertions(+), 167 deletions(-) diff --git a/qgrid/grid.py b/qgrid/grid.py index bdf2bc55..6e4a5a70 100644 --- a/qgrid/grid.py +++ b/qgrid/grid.py @@ -31,7 +31,7 @@ def template_contents(filename): SLICK_GRID_CSS = template_contents('slickgrid.css.template') SLICK_GRID_JS = template_contents('slickgrid.js.template') -REMOTE_URL = ("https://cdn.rawgit.com/quantopian/qgrid/" +REMOTE_URL = ("https://cdn.rawgit.com/sbremer/qgrid/" "73eaa7adf1762f66eaf4d30ed9cbf385a7e9d9fa/qgrid/qgridjs/") LOCAL_URL = "/nbextensions/qgridjs" diff --git a/qgrid/qgridjs/lib/slick.grid.css b/qgrid/qgridjs/lib/slick.grid.css index 547a3245..70a1a3fe 100644 --- a/qgrid/qgridjs/lib/slick.grid.css +++ b/qgrid/qgridjs/lib/slick.grid.css @@ -1,163 +1,1823 @@ -/* -* @license -* (c) 2009-2013 Michael Leibman -* michael{dot}leibman{at}gmail{dot}com -* http://github.com/mleibman/slickgrid -* -* Distributed under MIT license. -* All rights reserved. -* -IMPORTANT: -In order to preserve the uniform grid appearance, all cell styles need to have padding, margin and border sizes. -No built-in (selected, editable, highlight, flashing, invalid, loading, :focus) or user-specified CSS -classes should alter those! -*/ - -.slick-header.ui-state-default, .slick-headerrow.ui-state-default { - width: 100%; - overflow: hidden; - border-left: 0px; -} - -.slick-header-columns, .slick-headerrow-columns { - position: relative; - white-space: nowrap; - cursor: default; - overflow: hidden; -} - -.slick-header-column.ui-state-default { - 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; -} - -.slick-header-column-sorted { - font-style: italic; -} - -.slick-sort-indicator { - display: inline-block; - width: 8px; - height: 5px; - margin-left: 4px; -} - -.slick-sort-indicator-desc { - background: url(sort_desc.png); -} - -.slick-sort-indicator-asc { - background: url(sort_asc.png); -} - -.slick-resizable-handle { - position: absolute; - font-size: 0.1px; - display: block; - cursor: col-resize; - width: 4px; - right: 0px; - top: 0; - height: 100%; -} - -.slick-sortable-placeholder { - background: silver; -} - -.grid-canvas { - position: relative; - outline: 0; -} - -.slick-row.ui-widget-content, .slick-row.ui-state-active { - 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; - -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; -} - -.slick-cell.highlighted { - 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; -} - -.slick-cell.editable { - z-index: 11; - overflow: visible; - background: white; - border-color: black; - border-style: solid; -} - -.slick-cell:focus { - outline: none; -} - -.slick-reorder-proxy { - 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); -} - -.slick-selection { - z-index: 10; - position: absolute; - border: 2px dashed black; -} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + X-SlickGrid/slick.grid.css at master · ddomingues/X-SlickGrid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content +
+ + + + + + + + + + + + + +
+ +
+ +
+
+ + + +
+
+
+ + + + + +
+
+ +
    +
  • +
    + +
    + + + +
    +
    +
    + + Notifications +
    + + + +
    +
    +
    +
    +
  • + +
  • + +
    +
    + + +
    +
    + + +
    + +
  • + +
  • + + + Fork + + + + + +
  • +
+ +

+ + /X-SlickGrid + + + forked from mleibman/SlickGrid + +

+ +
+ +
+ +
+
+ + + Permalink + + + + + + + +
+ Fetching contributors… +
+ +
+ + Cannot retrieve contributors at this time +
+
+
+
+
+ +
+ Raw + Blame + History +
+ + +
+ +
+ +
+ +
+ 213 lines (181 sloc) + + 3.83 KB +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
/*
IMPORTANT:
In order to preserve the uniform grid appearance, all cell styles need to have padding, margin and border sizes.
No built-in (selected, editable, highlight, flashing, invalid, loading, :focus) or user-specified CSS
classes should alter those!
*/
+
.slick-header.ui-state-default, .slick-headerrow.ui-state-default, .slick-footerrow.ui-state-default, .slick-group-header.ui-state-default {
width: 100%;
overflow: hidden;
border-left: 0px;
}
+
.slick-header-columns, .slick-headerrow-columns, .slick-footerrow-columns, .slick-group-header-columns {
position: relative;
white-space: nowrap;
cursor: default;
overflow: hidden;
}
+
.slick-header-column.ui-state-default, .slick-group-header-column.ui-state-default {
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-footerrow-column.ui-state-default {
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
margin: 0;
padding: 4px;
border-right: 1px solid silver;
border-left: 0px;
border-top: 0px;
border-bottom: 0px;
float: left;
line-height: 20px;
vertical-align: middle;
}
+
.slick-headerrow-column.ui-state-default, .slick-footerrow-column.ui-state-default {
padding: 4px;
}
+
.slick-header-column-sorted {
font-style: italic;
}
+
.slick-sort-indicator {
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);
}
+
.slick-sort-indicator-asc {
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%;
}
+
.slick-resizable-handle-hover {
background-color: #ccc;
}
+
.slick-sortable-placeholder {
background: silver;
}
+
.grid-canvas {
position: relative;
outline: 0;
overflow: hidden;
}
+
.slick-row.ui-widget-content, .slick-row.ui-state-active {
position: absolute;
border: 0px;
width: 100%;
}
+
.slick-cell, .slick-headerrow-column, .slick-footerrow-column {
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;
}
+
.slick-cell.highlighted {
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;
}
+
.slick-cell.editable {
z-index: 11;
overflow: visible;
background: white;
border-color: black;
border-style: solid;
}
+
.slick-cell:focus {
outline: none;
}
+
.slick-reorder-proxy {
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);
}
+
.slick-selection {
z-index: 10;
position: absolute;
border: 2px dashed black;
}
+
.slick-pane {
position: absolute;
outline: 0;
overflow: hidden;
width: 100%;
}
+
.slick-pane-header {
display: block;
}
+
.slick-header {
overflow: hidden;
position: relative;
}
+
.slick-headerrow {
overflow: hidden;
position: relative;
}
+
.slick-top-panel-scroller {
overflow: hidden;
position: relative;
}
+
.slick-top-panel {
width: 10000px
}
+
.slick-viewport {
position: relative;
outline: 0;
width: 100%;
}
+ +
+ +
+ + + + +
+ +
+ +
+
+ +
+ + + + + + +
+ + + You can't perform that action at this time. +
+ + + + + + + + + + +
+ + You signed in with another tab or window. Reload to refresh your session. + You signed out in another tab or window. Reload to refresh your session. +
+ + + + + + diff --git a/qgrid/qgridjs/qgrid.widget.js b/qgrid/qgridjs/qgrid.widget.js index 1308d8dd..ecbb60e6 100644 --- a/qgrid/qgridjs/qgrid.widget.js +++ b/qgrid/qgridjs/qgrid.widget.js @@ -27,11 +27,11 @@ define([path], function(widget) { var path_dictionary = { jquery_drag: cdn_base_url + "/lib/jquery.event.drag-2.2", - slick_core: cdn_base_url + "/lib/slick.core.2.2", - slick_data_view: cdn_base_url + "/lib/slick.dataview.2.2", + slick_core: cdn_base_url + "/lib/slick.core", + slick_data_view: cdn_base_url + "/lib/slick.dataview", slick_check_box_column: cdn_base_url + "/lib/slick.checkboxselectcolumn", slick_row_selection_model: cdn_base_url + "/lib/slick.rowselectionmodel", - slick_grid: cdn_base_url + "/lib/slick.grid.2.2", + slick_grid: cdn_base_url + "/lib/slick.grid", data_grid: cdn_base_url + "/qgrid", date_filter: cdn_base_url + "/qgrid.datefilter", text_filter: cdn_base_url + "/qgrid.textfilter", From 233e0524ba024a64fb6c66daae7911ffcb17498c Mon Sep 17 00:00:00 2001 From: Simon Bremer Date: Thu, 29 Jun 2017 16:36:38 +0200 Subject: [PATCH 2/3] Now including files; --- qgrid/qgridjs/lib/slick.core.js | 653 ++++ qgrid/qgridjs/lib/slick.dataview.js | 1194 +++++++ qgrid/qgridjs/lib/slick.grid.css | 2035 ++---------- qgrid/qgridjs/lib/slick.grid.js | 4740 +++++++++++++++++++++++++++ 4 files changed, 6799 insertions(+), 1823 deletions(-) create mode 100644 qgrid/qgridjs/lib/slick.core.js create mode 100644 qgrid/qgridjs/lib/slick.dataview.js create mode 100644 qgrid/qgridjs/lib/slick.grid.js diff --git a/qgrid/qgridjs/lib/slick.core.js b/qgrid/qgridjs/lib/slick.core.js new file mode 100644 index 00000000..57217449 --- /dev/null +++ b/qgrid/qgridjs/lib/slick.core.js @@ -0,0 +1,653 @@ +/*** + * Contains core SlickGrid classes. + * @module Core + * @namespace Slick + */ + +// CommonJS, AMD or browser globals +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS + module.exports = factory(require('jquery')); + } else { + // Browser globals + factory(jQuery); + } +}(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(), + "TreeColumns": TreeColumns + } + }); + + /*** + * An event object for passing data to event handlers and letting them control propagation. + *

This is pretty much identical to how W3C and jQuery implement events.

+ * @class EventData + * @constructor + */ + 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; + } + } + + /*** + * A simple publisher-subscriber implementation. + * @class Event + * @constructor + */ + function Event() { + var handlers = []; + + /*** + * Adds an event handler to be called when the event is fired. + *

Event handler will receive two arguments - an EventData and the data + * object the event was fired with.

+ * @method subscribe + * @param fn {Function} Event handler. + */ + this.subscribe = function (fn) { + handlers.push(fn); + }; + + /*** + * Removes an event handler added with subscribe(fn). + * @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 EventData 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 Event 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; + }; + } + + 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 fromRow. + * @param toCell {Integer} Optional. Ending cell. Defaults to fromCell. + */ + 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); + + /*** + * @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 + ")"; + } + } + } + + + /*** + * 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; + + /*** + * 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; + + /** + * 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); + }; + } + + /** + * + * @param {Array} treeColumns Array com levels of columns + * @returns {{hasDepth: 'hasDepth', getTreeColumns: 'getTreeColumns', extractColumns: 'extractColumns', getDepth: 'getDepth', getColumnsInDepth: 'getColumnsInDepth', getColumnsInGroup: 'getColumnsInGroup', visibleColumns: 'visibleColumns', filter: 'filter', reOrder: reOrder}} + * @constructor + */ + function TreeColumns(treeColumns) { + + var columnsById = {}; + + function init() { + mapToId(treeColumns); + } + + function mapToId(columns) { + columns + .forEach(function (column) { + columnsById[column.id] = column; + + if (column.columns) + mapToId(column.columns); + }); + } + + function filter(node, condition) { + + return node.filter(function (column) { + + var valid = condition.call(column); + + if (valid && column.columns) + column.columns = filter(column.columns, condition); + + return valid && (!column.columns || column.columns.length); + }); + + } + + function sort(columns, grid) { + columns + .sort(function (a, b) { + var indexA = getOrDefault(grid.getColumnIndex(a.id)), + indexB = getOrDefault(grid.getColumnIndex(b.id)); + + return indexA - indexB; + }) + .forEach(function (column) { + if (column.columns) + sort(column.columns, grid); + }); + } + + function getOrDefault(value) { + return typeof value === 'undefined' ? -1 : value; + } + + function getDepth(node) { + if (node.length) + for (var i in node) + return getDepth(node[i]); + else if (node.columns) + return 1 + getDepth(node.columns); + else + return 1; + } + + function getColumnsInDepth(node, depth, current) { + var columns = []; + current = current || 0; + + if (depth == current) { + + if (node.length) + node.forEach(function(n) { + if (n.columns) + n.extractColumns = function() { + return extractColumns(n); + }; + }); + + return node; + } else + for (var i in node) + if (node[i].columns) { + columns = columns.concat(getColumnsInDepth(node[i].columns, depth, current + 1)); + } + + return columns; + } + + function extractColumns(node) { + var result = []; + + if (node.hasOwnProperty('length')) { + + for (var i = 0; i < node.length; i++) + result = result.concat(extractColumns(node[i])); + + } else { + + if (node.hasOwnProperty('columns')) + + result = result.concat(extractColumns(node.columns)); + + else + return node; + + } + + return result; + } + + function cloneTreeColumns() { + return $.extend(true, [], treeColumns); + } + + init(); + + this.hasDepth = function () { + + for (var i in treeColumns) + if (treeColumns[i].hasOwnProperty('columns')) + return true; + + return false; + }; + + this.getTreeColumns = function () { + return treeColumns; + }; + + this.extractColumns = function () { + return this.hasDepth()? extractColumns(treeColumns): treeColumns; + }; + + this.getDepth = function () { + return getDepth(treeColumns); + }; + + this.getColumnsInDepth = function (depth) { + return getColumnsInDepth(treeColumns, depth); + }; + + this.getColumnsInGroup = function (groups) { + return extractColumns(groups); + }; + + this.visibleColumns = function () { + return filter(cloneTreeColumns(), function () { + return this.visible; + }); + }; + + this.filter = function (condition) { + return filter(cloneTreeColumns(), condition); + }; + + this.reOrder = function (grid) { + return sort(treeColumns, grid); + }; + + this.getById = function (id) { + return columnsById[id]; + }; + + this.getInIds = function (ids) { + return ids.map(function (id) { + return columnsById[id]; + }); + } + } + +})); + + diff --git a/qgrid/qgridjs/lib/slick.dataview.js b/qgrid/qgridjs/lib/slick.dataview.js new file mode 100644 index 00000000..883537f8 --- /dev/null +++ b/qgrid/qgridjs/lib/slick.dataview.js @@ -0,0 +1,1194 @@ +(function (factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery'], factory); + } else if (typeof exports === 'object') { + module.exports = factory(require('jquery')); + } else { + factory(jQuery); + } +}(function ($) { + $.extend(true, window, { + Slick: { + Data: { + DataView: DataView, + Aggregators: { + Avg: AvgAggregator, + WeightedAvg: WeightedAvgAggregator, + 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(); + } + + /*** + * 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(); + } + + 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]); + } + + toggledGroupsByLevel[i] = {}; + } + + 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 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; + } + } + 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]; + } + } + 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; + } + } + // 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); + } + + if (options.groupItemMetadataProvider) { + return options.groupItemMetadataProvider.getRowMetadata(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); + } + + /** + * @param level {Number} Optional level to expand. If not specified, applies to all levels. + */ + function expandAllGroups(level) { + expandCollapseAllGroups(level, false); + } + + function expandCollapseGroup(args, collapse) { + var opts = resolveLevelAndGroupingKey(args); + toggledGroupsByLevel[opts.level][opts.groupingKey] = groupingInfos[opts.level].collapsed ^ collapse; + refresh(); + } + + function resolveLevelAndGroupingKey(args) { + var arg0 = args[0]; + if (args.length === 1 && arg0.indexOf(groupingDelimiter) !== -1) { + return {level: arg0.split(groupingDelimiter).length - 1, groupingKey: arg0}; + } else { + return {level: args.length - 1, groupingKey: args.join(groupingDelimiter)}; + } + } + + /** + * @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); + expandCollapseGroup(args, 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); + expandCollapseGroup(args, false); + } + + function getGroups() { + return groups; + } + + function getOrCreateGroup(groupsByVal, val, level, parentGroup, groups) { + var 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; + } + + return group; + } + + 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 = getOrCreateGroup(groupsByVal, val, level, parentGroup, groups); + } + + for (var i = 0, l = rows.length; i < l; i++) { + r = rows[i]; + val = gi.getterIsAFn ? gi.getter(r) : r[gi.getter]; + + group = getOrCreateGroup(groupsByVal, val, level, parentGroup, groups); + + group.rows[group.count++] = r; + } + + if (level < groupingInfos.length - 1) { + for (var i = 0; i < groups.length; i++) { + group = groups[i]; + group.groups = extractGroups(group.rows, group); + } + } + + 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]); + } + } + } + + 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); + } + 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; + } + + // Do a depth-first aggregation so that parent group aggregators can access subgroup totals. + if (g.groups) { + addTotals(g.groups, level + 1); + } + + if (gi.aggregators.length && ( + gi.aggregateEmpty || g.rows.length || (g.groups && g.groups.length))) { + addGroupTotals(g); + } + + 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]; + } + } + + if (g.totals && gi.displayTotalsRow && (!g.collapsed || gi.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\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]; + } + } + + return retval; + } + + 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; + } + } + + 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(); + } + + // 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; + } + + 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; + } + } + } + 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); + } + } + + 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); + } + } + + /*** + * 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; + } + + 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)); + } + }); + + this.onRowsChanged.subscribe(update); + + this.onRowCountChanged.subscribe(update); + + return onSelectedRowIdsChanged; + } + + 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]; + } + } + + 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; + } + } + + 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 WeightedAvgAggregator(field, weightedField) { + this.field_ = field; + this.weightedField_ = weightedField; + + this.init = function () { + this.sum_ = 0; + this.weightedSum_ = 0; + }; + + this.accumulate = function (item) { + var val = item[this.field_]; + var valWeighted = item[this.weightedField_]; + if (this.isValid(val) && this.isValid(valWeighted)) { + this.weightedSum_ += parseFloat(valWeighted); + this.sum_ += parseFloat(val) * parseFloat(valWeighted); + } + }; + + this.storeResult = function (groupTotals) { + if (!groupTotals.avg) { + groupTotals.avg = {}; + } + + if (this.sum_ && this.weightedSum_) { + groupTotals.avg[this.field_] = this.sum_ / this.weightedSum_; + } + }; + + this.isValid = function (val) { + return val !== null && val !== "" && val !== NaN; + }; + } + + 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 +})); diff --git a/qgrid/qgridjs/lib/slick.grid.css b/qgrid/qgridjs/lib/slick.grid.css index 70a1a3fe..7fbfdb59 100644 --- a/qgrid/qgridjs/lib/slick.grid.css +++ b/qgrid/qgridjs/lib/slick.grid.css @@ -1,1823 +1,212 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - X-SlickGrid/slick.grid.css at master · ddomingues/X-SlickGrid - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- Skip to content -
- - - - - - - - - - - - - -
- -
- -
-
- - - -
-
-
- - - - - -
-
- -
    -
  • -
    - -
    - - - -
    -
    -
    - - Notifications -
    - - - -
    -
    -
    -
    -
  • - -
  • - -
    -
    - - -
    -
    - - -
    - -
  • - -
  • - - - Fork - - - - - -
  • -
- -

- - /X-SlickGrid - - - forked from mleibman/SlickGrid - -

- -
- -
- -
-
- - - Permalink - - - - - - - -
- Fetching contributors… -
- -
- - Cannot retrieve contributors at this time -
-
-
-
-
- -
- Raw - Blame - History -
- - -
- -
- -
- -
- 213 lines (181 sloc) - - 3.83 KB -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/*
IMPORTANT:
In order to preserve the uniform grid appearance, all cell styles need to have padding, margin and border sizes.
No built-in (selected, editable, highlight, flashing, invalid, loading, :focus) or user-specified CSS
classes should alter those!
*/
-
.slick-header.ui-state-default, .slick-headerrow.ui-state-default, .slick-footerrow.ui-state-default, .slick-group-header.ui-state-default {
width: 100%;
overflow: hidden;
border-left: 0px;
}
-
.slick-header-columns, .slick-headerrow-columns, .slick-footerrow-columns, .slick-group-header-columns {
position: relative;
white-space: nowrap;
cursor: default;
overflow: hidden;
}
-
.slick-header-column.ui-state-default, .slick-group-header-column.ui-state-default {
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-footerrow-column.ui-state-default {
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
margin: 0;
padding: 4px;
border-right: 1px solid silver;
border-left: 0px;
border-top: 0px;
border-bottom: 0px;
float: left;
line-height: 20px;
vertical-align: middle;
}
-
.slick-headerrow-column.ui-state-default, .slick-footerrow-column.ui-state-default {
padding: 4px;
}
-
.slick-header-column-sorted {
font-style: italic;
}
-
.slick-sort-indicator {
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);
}
-
.slick-sort-indicator-asc {
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%;
}
-
.slick-resizable-handle-hover {
background-color: #ccc;
}
-
.slick-sortable-placeholder {
background: silver;
}
-
.grid-canvas {
position: relative;
outline: 0;
overflow: hidden;
}
-
.slick-row.ui-widget-content, .slick-row.ui-state-active {
position: absolute;
border: 0px;
width: 100%;
}
-
.slick-cell, .slick-headerrow-column, .slick-footerrow-column {
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;
}
-
.slick-cell.highlighted {
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;
}
-
.slick-cell.editable {
z-index: 11;
overflow: visible;
background: white;
border-color: black;
border-style: solid;
}
-
.slick-cell:focus {
outline: none;
}
-
.slick-reorder-proxy {
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);
}
-
.slick-selection {
z-index: 10;
position: absolute;
border: 2px dashed black;
}
-
.slick-pane {
position: absolute;
outline: 0;
overflow: hidden;
width: 100%;
}
-
.slick-pane-header {
display: block;
}
-
.slick-header {
overflow: hidden;
position: relative;
}
-
.slick-headerrow {
overflow: hidden;
position: relative;
}
-
.slick-top-panel-scroller {
overflow: hidden;
position: relative;
}
-
.slick-top-panel {
width: 10000px
}
-
.slick-viewport {
position: relative;
outline: 0;
width: 100%;
}
- -
- -
- - - - -
- -
- -
-
- -
- - - - - - -
- - - You can't perform that action at this time. -
- - - - - - - - - - -
- - You signed in with another tab or window. Reload to refresh your session. - You signed out in another tab or window. Reload to refresh your session. -
- - - - - - +/* +IMPORTANT: +In order to preserve the uniform grid appearance, all cell styles need to have padding, margin and border sizes. +No built-in (selected, editable, highlight, flashing, invalid, loading, :focus) or user-specified CSS +classes should alter those! +*/ + +.slick-header.ui-state-default, .slick-headerrow.ui-state-default, .slick-footerrow.ui-state-default, .slick-group-header.ui-state-default { + width: 100%; + overflow: hidden; + border-left: 0px; +} + +.slick-header-columns, .slick-headerrow-columns, .slick-footerrow-columns, .slick-group-header-columns { + position: relative; + white-space: nowrap; + cursor: default; + overflow: hidden; +} + +.slick-header-column.ui-state-default, .slick-group-header-column.ui-state-default { + 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-footerrow-column.ui-state-default { + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + margin: 0; + padding: 4px; + border-right: 1px solid silver; + border-left: 0px; + border-top: 0px; + border-bottom: 0px; + float: left; + line-height: 20px; + vertical-align: middle; +} + +.slick-headerrow-column.ui-state-default, .slick-footerrow-column.ui-state-default { + padding: 4px; +} + +.slick-header-column-sorted { + font-style: italic; +} + +.slick-sort-indicator { + 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); +} + +.slick-sort-indicator-asc { + 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%; +} + +.slick-resizable-handle-hover { + background-color: #ccc; +} + +.slick-sortable-placeholder { + background: silver; +} + +.grid-canvas { + position: relative; + outline: 0; + overflow: hidden; +} + +.slick-row.ui-widget-content, .slick-row.ui-state-active { + position: absolute; + border: 0px; + width: 100%; +} + +.slick-cell, .slick-headerrow-column, .slick-footerrow-column { + 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; +} + +.slick-cell.highlighted { + 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; +} + +.slick-cell.editable { + z-index: 11; + overflow: visible; + background: white; + border-color: black; + border-style: solid; +} + +.slick-cell:focus { + outline: none; +} + +.slick-reorder-proxy { + 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); +} + +.slick-selection { + z-index: 10; + position: absolute; + border: 2px dashed black; +} + +.slick-pane { + position: absolute; + outline: 0; + overflow: hidden; + width: 100%; +} + +.slick-pane-header { + display: block; +} + +.slick-header { + overflow: hidden; + position: relative; +} + +.slick-headerrow { + overflow: hidden; + position: relative; +} + +.slick-top-panel-scroller { + overflow: hidden; + position: relative; +} + +.slick-top-panel { + width: 10000px +} + +.slick-viewport { + position: relative; + outline: 0; + width: 100%; +} diff --git a/qgrid/qgridjs/lib/slick.grid.js b/qgrid/qgridjs/lib/slick.grid.js new file mode 100644 index 00000000..5ee019e1 --- /dev/null +++ b/qgrid/qgridjs/lib/slick.grid.js @@ -0,0 +1,4740 @@ +/** + * @license + * (c) 2009-2013 Michael Leibman + * michael{dot}leibman{at}gmail{dot}com + * http://github.com/mleibman/slickgrid + * + * Distributed under MIT license. + * All rights reserved. + * + * SlickGrid v2.2 + * + * 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. + * + */ +(function (factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery'], factory); + } else if (typeof exports === 'object') { + module.exports = factory(require('jquery')); + } else { + factory(jQuery); + } +}(function ($) { + + // 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"; + } + + // 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: 50, + autoHeight: false, + editorLock: Slick.GlobalEditorLock, + showHeaderRow: false, + headerRowHeight: 25, + showFooterRow: false, + footerRowHeight: 25, + showTopPanel: false, + topPanelHeight: 25, + formatterFactory: null, + editorFactory: null, + cellFlashingCssClass: "flashing", + selectedCellCssClass: "selected", + multiSelect: true, + enableTextSelectionOnCells: false, + dataItemColumnValueExtractor: null, + frozenBottom: false, + frozenColumn: -1, + frozenRow: -1, + fullWidthRows: false, + multiColumnSort: false, + defaultFormatter: defaultFormatter, + forceSyncScrolling: false, + addNewRowCssClass: "new-row", + doPaging: true + }; + + var columnDefaults = { + name: "", + resizable: true, + sortable: false, + minWidth: 30, + rerenderOnResize: false, + headerCssClass: null, + defaultSortAsc: true, + focusable: true, + selectable: true + }; + + // 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 vScrollDir = 1; + + // private + var initialized = false; + var $container; + var uid = "slickgrid_" + Math.round(1000000 * Math.random()); + var self = this; + var $focusSink, $focusSink2; + var $groupHeaders = $(); + var $headerScroller; + var $headers; + var $headerRow, $headerRowScroller, $headerRowSpacerL, $headerRowSpacerR; + var $footerRow, $footerRowScroller, $footerRowSpacerL, $footerRowSpacerR; + var $topPanelScroller; + var $topPanel; + var $viewport; + var $canvas; + var $style; + var $boundAncestors; + var treeColumns; + var stylesheet, columnCssRulesL, columnCssRulesR; + var viewportH, viewportW; + var canvasWidth, canvasWidthL, canvasWidthR; + var headersWidth, headersWidthL, headersWidthR; + var viewportHasHScroll, viewportHasVScroll; + var headerColumnWidthDiff = 0, headerColumnHeightDiff = 0, // border+padding + cellWidthDiff = 0, cellHeightDiff = 0; + var absoluteColumnMinWidth; + var hasFrozenRows = false; + var frozenRowsHeight = 0; + var actualFrozenRow = -1; + var paneTopH = 0; + var paneBottomH = 0; + var viewportTopH = 0; + var viewportBottomH = 0; + var topPanelH = 0; + var headerRowH = 0; + var footerRowH = 0; + + var tabbingDirection = 1; + var $activeCanvasNode; + var $activeViewportNode; + var activePosX; + var activeRow, activeCell; + var activeCellNode = null; + var currentEditor = null; + var serializedEditorValue; + var editController; + + var rowsCache = {}; + var renderedRows = 0; + var numVisibleRows = 0; + var prevScrollTop = 0; + var scrollTop = 0; + var lastRenderedScrollTop = 0; + var lastRenderedScrollLeft = 0; + var prevScrollLeft = 0; + var scrollLeft = 0; + + var selectionModel; + var selectedRows = []; + + var plugins = []; + var cellCssClasses = {}; + + var columnsById = {}; + var sortColumns = []; + var columnPosLeft = []; + var columnPosRight = []; + + + // 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; + + // These two variables work around a bug with inertial scrolling in Webkit/Blink on Mac. + // See http://crbug.com/312427. + var rowNodeFromLastMouseWheelEvent; // this node must not be deleted while inertial scrolling + var zombieRowNodeFromLastMouseWheelEvent; // node that was hidden instead of getting deleted + + var $paneHeaderL; + var $paneHeaderR; + var $paneTopL; + var $paneTopR; + var $paneBottomL; + var $paneBottomR; + + var $headerScrollerL; + var $headerScrollerR; + + var $headerL; + var $headerR; + + var $groupHeadersL; + var $groupHeadersR; + + var $headerRowScrollerL; + var $headerRowScrollerR; + + var $footerRowScrollerL; + var $footerRowScrollerR; + + var $headerRowL; + var $headerRowR; + + var $footerRowL; + var $footerRowR; + + var $topPanelScrollerL; + var $topPanelScrollerR; + + var $topPanelL; + var $topPanelR; + + var $viewportTopL; + var $viewportTopR; + var $viewportBottomL; + var $viewportBottomR; + + var $canvasTopL; + var $canvasTopR; + var $canvasBottomL; + var $canvasBottomR; + + var $viewportScrollContainerX; + var $viewportScrollContainerY; + var $headerScrollContainer; + var $headerRowScrollContainer; + var $footerRowScrollContainer; + + ////////////////////////////////////////////////////////////////////////////////////////////// + // 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); + validateAndEnforceOptions(); + columnDefaults.width = options.defaultColumnWidth; + + treeColumns = new Slick.TreeColumns(columns); + columns = treeColumns.extractColumns(); + + columnsById = {}; + for (var i = 0; i < columns.length; i++) { + var m = columns[i] = $.extend({}, columnDefaults, columns[i]); + columnsById[m.id] = i; + if (m.minWidth && m.width < m.minWidth) { + m.width = m.minWidth; + } + if (m.maxWidth && m.width > m.maxWidth) { + m.width = m.maxWidth; + } + } + + // 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 = $("
").appendTo($container); + + // Containers used for scrolling frozen columns and rows + $paneHeaderL = $("
").appendTo($container); + $paneHeaderR = $("
").appendTo($container); + $paneTopL = $("
").appendTo($container); + $paneTopR = $("
").appendTo($container); + $paneBottomL = $("
").appendTo($container); + $paneBottomR = $("
").appendTo($container); + + // Append the header scroller containers + $headerScrollerL = $("
").appendTo($paneHeaderL); + $headerScrollerR = $("
").appendTo($paneHeaderR); + + // Cache the header scroller containers + $headerScroller = $().add($headerScrollerL).add($headerScrollerR); + + if (treeColumns.hasDepth()) { + $groupHeadersL = [], $groupHeadersR = []; + for (var index = 0; index < treeColumns.getDepth() - 1; index++) { + $groupHeadersL[index] = $("
").appendTo($headerScrollerL); + $groupHeadersR[index] = $("
").appendTo($headerScrollerR); + } + + $groupHeaders = $().add($groupHeadersL).add($groupHeadersR); + } + + // Append the columnn containers to the headers + $headerL = $("
").appendTo($headerScrollerL); + $headerR = $("
").appendTo($headerScrollerR); + + // Cache the header columns + $headers = $().add($headerL).add($headerR); + + $headerRowScrollerL = $("
").appendTo($paneTopL); + $headerRowScrollerR = $("
").appendTo($paneTopR); + + $headerRowScroller = $().add($headerRowScrollerL).add($headerRowScrollerR); + + $headerRowSpacerL = $("
") + .css("width", getCanvasWidth() + scrollbarDimensions.width + "px") + .appendTo($headerRowScrollerL); + $headerRowSpacerR = $("
") + .css("width", getCanvasWidth() + scrollbarDimensions.width + "px") + .appendTo($headerRowScrollerR); + + + $headerRowL = $("
").appendTo($headerRowScrollerL); + $headerRowR = $("
").appendTo($headerRowScrollerR); + + $headerRow = $().add($headerRowL).add($headerRowR); + + // Append the top panel scroller + $topPanelScrollerL = $("
").appendTo($paneTopL); + $topPanelScrollerR = $("
").appendTo($paneTopR); + + $topPanelScroller = $().add($topPanelScrollerL).add($topPanelScrollerR); + + // Append the top panel + $topPanelL = $("
").appendTo($topPanelScrollerL); + $topPanelR = $("
").appendTo($topPanelScrollerR); + + $topPanel = $().add($topPanelL).add($topPanelR); + + if (!options.showTopPanel) { + $topPanelScroller.hide(); + } + + if (!options.showHeaderRow) { + $headerRowScroller.hide(); + } + + // Append the viewport containers + $viewportTopL = $("
").appendTo($paneTopL); + $viewportTopR = $("
").appendTo($paneTopR); + $viewportBottomL = $("
").appendTo($paneBottomL); + $viewportBottomR = $("
").appendTo($paneBottomR); + + // Cache the viewports + $viewport = $().add($viewportTopL).add($viewportTopR).add($viewportBottomL).add($viewportBottomR); + + + // Default the active viewport to the top left + $activeViewportNode = $viewportTopL; + + // Append the canvas containers + $canvasTopL = $("
").appendTo($viewportTopL); + $canvasTopR = $("
").appendTo($viewportTopR); + $canvasBottomL = $("
").appendTo($viewportBottomL); + $canvasBottomR = $("
").appendTo($viewportBottomR); + + // Cache the canvases + $canvas = $().add($canvasTopL).add($canvasTopR).add($canvasBottomL).add($canvasBottomR); + + // Default the active canvas to the top left + $activeCanvasNode = $canvasTopL; + + // footer Row + $footerRowScrollerR = $("
").appendTo($paneTopR); + $footerRowScrollerL = $("
").appendTo($paneTopL); + + $footerRowScroller = $().add($footerRowScrollerL).add($footerRowScrollerR); + + $footerRowSpacerL = $("
") + .css("width", getCanvasWidth() + scrollbarDimensions.width + "px") + .appendTo($footerRowScrollerL); + $footerRowSpacerR = $("
") + .css("width", getCanvasWidth() + scrollbarDimensions.width + "px") + .appendTo($footerRowScrollerR); + + + $footerRowL = $("
").appendTo($footerRowScrollerL); + $footerRowR = $("
").appendTo($footerRowScrollerR); + + $footerRow = $().add($footerRowL).add($footerRowR); + + if (!options.showFooterRow) { + $footerRowScroller.hide(); + } + + $focusSink2 = $focusSink.clone().appendTo($container); + + if (!options.explicitInitialization) { + finishInitialization(); + } + } + + function finishInitialization() { + if (!initialized) { + initialized = true; + + getViewportWidth(); + getViewportHeight(); + + // 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"); + }); + } + + setFrozenOptions(); + setPaneVisibility(); + setScroller(); + setOverflow(); + + updateColumnCaches(); + createColumnHeaders(); + createColumnGroupHeaders(); + createColumnFooter(); + setupColumnSort(); + createCssRules(); + resizeCanvas(); + bindAncestorScrollEvents(); + + $container + .bind("resize.slickgrid", resizeCanvas); + $viewport + .on("scroll", handleScroll); + + if (jQuery.fn.mousewheel && ( options.frozenColumn > -1 || hasFrozenRows )) { + $viewport + .on("mousewheel", handleMouseWheel); + } + + $headerScroller + .bind("contextmenu", handleHeaderContextMenu) + .bind("click", handleHeaderClick) + .delegate(".slick-header-column", "mouseenter", handleHeaderMouseEnter) + .delegate(".slick-header-column", "mouseleave", handleHeaderMouseLeave); + $headerRowScroller + .bind("scroll", handleHeaderRowScroll); + + $footerRowScroller + .bind("scroll", handleFooterRowScroll); + + $focusSink.add($focusSink2) + .bind("keydown", handleKeyDown); + $canvas + .bind("keydown", handleKeyDown) + .bind("click", handleClick) + .bind("dblclick", handleDblClick) + .bind("contextmenu", handleContextMenu) + .bind("draginit", handleDragInit) + .bind("dragstart", {distance: 3}, handleDragStart) + .bind("drag", handleDrag) + .bind("dragend", handleDragEnd) + .delegate(".slick-cell", "mouseenter", handleMouseEnter) + .delegate(".slick-cell", "mouseleave", handleMouseLeave); + + // Work around http://crbug.com/312427. + if (navigator.userAgent.toLowerCase().match(/webkit/) && + navigator.userAgent.toLowerCase().match(/macintosh/)) { + $canvas.on("mousewheel", handleMouseWheel); + } + } + } + + function hasFrozenColumns() { + return options.frozenColumn > -1; + } + + 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 getActiveCanvasNode(element) { + setActiveCanvasNode(element); + + return $activeCanvasNode[0]; + } + + function getCanvases() { + return $canvas; + } + + function setActiveCanvasNode(element) { + if (element) { + $activeCanvasNode = $(element.target).closest('.grid-canvas'); + } + } + + function getViewportNode() { + return $viewport[0]; + } + + function getActiveViewportNode(element) { + setActiveViewPortNode(element); + + return $activeViewportNode[0]; + } + + function setActiveViewportNode(element) { + if (element) { + $activeViewportNode = $(element.target).closest('.slick-viewport'); + } + } + + function measureScrollbar() { + var $c = $("
").appendTo("body"); + var dim = { + width: $c.width() - $c[0].clientWidth, + height: $c.height() - $c[0].clientHeight + }; + $c.remove(); + return dim; + } + + function getHeadersWidth() { + headersWidth = headersWidthL = headersWidthR = 0; + + for (var i = 0, ii = columns.length; i < ii; i++) { + var width = columns[ i ].width; + + if (( options.frozenColumn ) > -1 && ( i > options.frozenColumn )) { + headersWidthR += width; + } else { + headersWidthL += width; + } + } + + if (hasFrozenColumns()) { + headersWidthL = headersWidthL + 1000; + + headersWidthR = Math.max(headersWidthR, viewportW) + headersWidthL; + headersWidthR += scrollbarDimensions.width; + } else { + headersWidthL += scrollbarDimensions.width; + headersWidthL = Math.max(headersWidthL, viewportW) + 1000; + } + + headersWidth = headersWidthL + headersWidthR; + } + + function getHeadersWidthL() { + headersWidthL =0; + + columns.forEach(function(column, i) { + if (!(( options.frozenColumn ) > -1 && ( i > options.frozenColumn ))) + headersWidthL += column.width; + }); + + if (hasFrozenColumns()) { + headersWidthL += 1000; + } else { + headersWidthL += scrollbarDimensions.width; + headersWidthL = Math.max(headersWidthL, viewportW) + 1000; + } + + return headersWidthL; + } + + function getHeadersWidthR() { + headersWidthR =0; + + columns.forEach(function(column, i) { + if (( options.frozenColumn ) > -1 && ( i > options.frozenColumn )) + headersWidthR += column.width; + }); + + if (hasFrozenColumns()) { + headersWidthR = Math.max(headersWidthR, viewportW) + getHeadersWidthL(); + headersWidthR += scrollbarDimensions.width; + } + + return headersWidthR; + } + + function getCanvasWidth() { + var availableWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW; + + var i = columns.length; + + canvasWidthL = canvasWidthR = 0; + + while (i--) { + if (hasFrozenColumns() && (i > options.frozenColumn)) { + canvasWidthR += columns[i].width; + } else { + canvasWidthL += columns[i].width; + } + } + + var totalRowWidth = canvasWidthL + canvasWidthR; + + return options.fullWidthRows ? Math.max(totalRowWidth, availableWidth) : totalRowWidth; + } + + function updateCanvasWidth(forceColumnWidthsUpdate) { + var oldCanvasWidth = canvasWidth; + var oldCanvasWidthL = canvasWidthL; + var oldCanvasWidthR = canvasWidthR; + var widthChanged; + canvasWidth = getCanvasWidth(); + + widthChanged = canvasWidth !== oldCanvasWidth || canvasWidthL !== oldCanvasWidthL || canvasWidthR !== oldCanvasWidthR; + + if (widthChanged || hasFrozenColumns() || hasFrozenRows) { + $canvasTopL.width(canvasWidthL); + + getHeadersWidth(); + + $headerL.width(headersWidthL); + $headerR.width(headersWidthR); + + if (hasFrozenColumns()) { + $canvasTopR.width(canvasWidthR); + + $paneHeaderL.width(canvasWidthL); + $paneHeaderR.css('left', canvasWidthL); + $paneHeaderR.css('width', viewportW - canvasWidthL); + + $paneTopL.width(canvasWidthL); + $paneTopR.css('left', canvasWidthL); + $paneTopR.css('width', viewportW - canvasWidthL); + + $headerRowScrollerL.width(canvasWidthL); + $headerRowScrollerR.width(viewportW - canvasWidthL); + + $headerRowL.width(canvasWidthL); + $headerRowR.width(canvasWidthR); + + $footerRowScrollerL.width(canvasWidthL); + $footerRowScrollerR.width(viewportW - canvasWidthL); + + $footerRowL.width(canvasWidthL); + $footerRowR.width(canvasWidthR); + + $viewportTopL.width(canvasWidthL); + $viewportTopR.width(viewportW - canvasWidthL); + + if (hasFrozenRows) { + $paneBottomL.width(canvasWidthL); + $paneBottomR.css('left', canvasWidthL); + + $viewportBottomL.width(canvasWidthL); + $viewportBottomR.width(viewportW - canvasWidthL); + + $canvasBottomL.width(canvasWidthL); + $canvasBottomR.width(canvasWidthR); + } + } else { + $paneHeaderL.width('100%'); + + $paneTopL.width('100%'); + + $headerRowScrollerL.width('100%'); + + $headerRowL.width(canvasWidth); + + $footerRowScrollerL.width('100%'); + + $footerRowL.width(canvasWidth); + + $viewportTopL.width('100%'); + + if (hasFrozenRows) { + $viewportBottomL.width('100%'); + $canvasBottomL.width(canvasWidthL); + } + } + + viewportHasHScroll = (canvasWidth > viewportW - scrollbarDimensions.width); + } + + $headerRowSpacerL.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0)); + $headerRowSpacerR.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0)); + + $footerRowSpacerL.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0)); + $footerRowSpacerR.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0)); + + if (widthChanged || 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 supportedHeight = 1000000; + // FF reports the height back but still renders blank after ~6M px + var testUpTo = navigator.userAgent.toLowerCase().match(/firefox/) ? 6000000 : 1000000000; + var div = $("
").appendTo(document.body); + + while (true) { + var test = supportedHeight * 2; + div.css("height", test); + if (test > testUpTo || div.height() !== test) { + break; + } else { + supportedHeight = test; + } + } + + div.remove(); + return supportedHeight; + } + + // TODO: this is static. need to handle page mutation. + function bindAncestorScrollEvents() { + var elem = (hasFrozenRows && !options.frozenBottom) ? $canvasBottomL[0] : $canvasTopL[0]; + while ((elem = elem.parentNode) != document.body && elem != null) { + // bind to scroll containers only + if (elem == $viewportTopL[0] || elem.scrollWidth != elem.clientWidth || elem.scrollHeight != elem.clientHeight) { + var $elem = $(elem); + if (!$boundAncestors) { + $boundAncestors = $elem; + } else { + $boundAncestors = $boundAncestors.add($elem); + } + $elem.bind("scroll." + uid, handleActiveCellPositionChange); + } + } + } + + function unbindAncestorScrollEvents() { + if (!$boundAncestors) { + return; + } + $boundAncestors.unbind("scroll." + uid); + $boundAncestors = null; + } + + function updateColumnHeader(columnId, title, toolTip) { + if (!initialized) { return; } + var idx = getColumnIndex(columnId); + if (idx == null) { + return; + } + + var columnDef = columns[idx]; + var $header = $headers.children().eq(idx); + if ($header) { + if (title !== undefined) { + columns[idx].name = title; + } + if (toolTip !== undefined) { + columns[idx].toolTip = toolTip; + } + + trigger(self.onBeforeHeaderCellDestroy, { + "node": $header[0], + "column": columnDef + }); + + $header + .attr("title", toolTip || "") + .children().eq(0).html(title); + + trigger(self.onHeaderCellRendered, { + "node": $header[0], + "column": columnDef + }); + } + } + + function getHeaderRow() { + return hasFrozenColumns() ? $headerRow : $headerRow[0]; + } + + function getHeaderRowColumn(columnId) { + var idx = getColumnIndex(columnId); + + var $headerRowTarget; + + if (hasFrozenColumns()) { + if (idx <= options.frozenColumn) { + $headerRowTarget = $headerRowL; + } else { + $headerRowTarget = $headerRowR; + + idx -= options.frozenColumn + 1; + } + } else { + $headerRowTarget = $headerRowL; + } + + var $header = $headerRowTarget.children().eq(idx); + return $header && $header[0]; + } + + function getFooterRow() { + return hasFrozenColumns() ? $footerRow : $footerRow[0]; + } + + function getFooterRowColumn(columnId) { + var idx = getColumnIndex(columnId); + + var $footerRowTarget; + + if (hasFrozenColumns()) { + if (idx <= options.frozenColumn) { + $footerRowTarget = $footerRowL; + } else { + $footerRowTarget = $footerRowR; + + idx -= options.frozenColumn + 1; + } + } else { + $footerRowTarget = $footerRowL; + } + + var $footer = $footerRowTarget.children().eq(idx); + return $footer && $footer[0]; + } + + function createColumnFooter() { + $footerRow.find(".slick-footerrow-column") + .each(function () { + var columnDef = $(this).data("column"); + if (columnDef) { + trigger(self.onBeforeFooterRowCellDestroy, { + "node": this, + "column": columnDef + }); + } + }); + + $footerRowL.empty(); + $footerRowR.empty(); + + for (var i = 0; i < columns.length; i++) { + var m = columns[i]; + + var footerRowCell = $("
") + .data("column", m) + .addClass(hasFrozenColumns() && i <= options.frozenColumn? 'frozen': '') + .appendTo(hasFrozenColumns() && (i > options.frozenColumn)? $footerRowR: $footerRowL); + + trigger(self.onFooterRowCellRendered, { + "node": footerRowCell[0], + "column": m + }); + } + } + + function createColumnGroupHeaders() { + var columnsLength = 0; + var frozenColumnsValid = false; + + if (!treeColumns.hasDepth()) + return; + + for (var index = 0; index < $groupHeadersL.length; index++) { + + $groupHeadersL[index].empty(); + $groupHeadersR[index].empty(); + + var groupColumns = treeColumns.getColumnsInDepth(index); + + for (var indexGroup in groupColumns) { + var m = groupColumns[indexGroup]; + + columnsLength += m.extractColumns().length; + + if (hasFrozenColumns() && index == 0 && (columnsLength-1) === options.frozenColumn) + frozenColumnsValid = true; + + $("
") + .html("" + m.name + "") + .attr("id", "" + uid + m.id) + .attr("title", m.toolTip || "") + .data("column", m) + .addClass(m.headerCssClass || "") + .addClass(hasFrozenColumns() && (columnsLength - 1) > options.frozenColumn? 'frozen': '') + .appendTo(hasFrozenColumns() && (columnsLength - 1) > options.frozenColumn? $groupHeadersR[index]: $groupHeadersL[index]); + } + + if (hasFrozenColumns() && index == 0 && !frozenColumnsValid) { + $groupHeadersL[index].empty(); + $groupHeadersR[index].empty(); + alert("All columns of group should to be grouped!"); + break; + } + } + + applyColumnGroupHeaderWidths(); + } + + function createColumnHeaders() { + function onMouseEnter() { + $(this).addClass("ui-state-hover"); + } + + function onMouseLeave() { + $(this).removeClass("ui-state-hover"); + } + + $headers.find(".slick-header-column") + .each(function() { + var columnDef = $(this).data("column"); + if (columnDef) { + trigger(self.onBeforeHeaderCellDestroy, { + "node": this, + "column": columnDef + }); + } + }); + + $headerL.empty(); + $headerR.empty(); + + getHeadersWidth(); + + $headerL.width(headersWidthL); + $headerR.width(headersWidthR); + + $headerRow.find(".slick-headerrow-column") + .each(function() { + var columnDef = $(this).data("column"); + if (columnDef) { + trigger(self.onBeforeHeaderRowCellDestroy, { + "node": this, + "column": columnDef + }); + } + }); + + $headerRowL.empty(); + $headerRowR.empty(); + + for (var i = 0; i < columns.length; i++) { + var m = columns[i]; + + var $headerTarget = hasFrozenColumns() ? ((i <= options.frozenColumn) ? $headerL : $headerR) : $headerL; + var $headerRowTarget = hasFrozenColumns() ? ((i <= options.frozenColumn) ? $headerRowL : $headerRowR) : $headerRowL; + + var header = $("
") + .html("" + m.name + "") + .width(m.width - headerColumnWidthDiff) + .attr("id", "" + uid + m.id) + .attr("title", m.toolTip || "") + .data("column", m) + .addClass(m.headerCssClass || "") + .addClass(hasFrozenColumns() && i <= options.frozenColumn? 'frozen': '') + .appendTo($headerTarget); + + if (options.enableColumnReorder || m.sortable) { + header + .on('mouseenter', onMouseEnter) + .on('mouseleave', onMouseLeave); + } + + if (m.sortable) { + header.addClass("slick-header-sortable"); + header.append(""); + } + + trigger(self.onHeaderCellRendered, { + "node": header[0], + "column": m + }); + + if (options.showHeaderRow) { + var headerRowCell = $("
") + .data("column", m) + .appendTo($headerRowTarget); + + trigger(self.onHeaderRowCellRendered, { + "node": headerRowCell[0], + "column": m + }); + } + } + + 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 = $col.data("column"); + 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: column.defaultSortAsc }; + 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 currentPositionInHeader(id) { + var currentPosition = 0; + $headers.find('.slick-header-column').each(function (i) { + if (this.id == id) { + currentPosition = i; + return false; + } + }); + + return currentPosition; + } + + function limitPositionInGroup(idColumn) { + var groupColumnOfPreviousPosition, + startLimit = 0, + endLimit = 0; + + treeColumns + .getColumnsInDepth($groupHeadersL.length - 1) + .some(function (groupColumn) { + startLimit = endLimit; + endLimit += groupColumn.columns.length; + + groupColumn.columns.some(function (column) { + + if (column.id === idColumn) + groupColumnOfPreviousPosition = groupColumn; + + return groupColumnOfPreviousPosition; + }); + + return groupColumnOfPreviousPosition; + }); + + endLimit--; + + return { + start: startLimit, + end: endLimit, + group: groupColumnOfPreviousPosition + } + } + + function remove(arr, elem) { + var index = arr.lastIndexOf(elem); + if(index > -1) { + arr.splice(index, 1); + remove(arr, elem); + } + } + + function columnPositionValidInGroup($item) { + var currentPosition = currentPositionInHeader($item[0].id); + var limit = limitPositionInGroup($item.data('column').id); + var positionValid = limit.start <= currentPosition && currentPosition <= limit.end; + + return { + limit: limit, + valid: positionValid, + message: positionValid? '': 'Column "'.concat($item.text(), '" can be reordered only within the "', limit.group.name, '" group!') + }; + } + + function setupColumnReorder() { + $headers.filter(":ui-sortable").sortable("destroy"); + var columnScrollTimer = null; + + function scrollColumnsRight() { + $viewportScrollContainerX[0].scrollLeft = $viewportScrollContainerX[0].scrollLeft + 10; + } + + function scrollColumnsLeft() { + $viewportScrollContainerX[0].scrollLeft = $viewportScrollContainerX[0].scrollLeft - 10; + } + + var canDragScroll; + $headers.sortable({ + containment: "parent", + distance: 3, + axis: "x", + cursor: "default", + tolerance: "intersection", + helper: "clone", + placeholder: "slick-sortable-placeholder ui-state-default slick-header-column", + start: function (e, ui) { + ui.placeholder.width(ui.helper.outerWidth() - headerColumnWidthDiff); + canDragScroll = !hasFrozenColumns() || + (ui.placeholder.offset().left + ui.placeholder.width()) > $viewportScrollContainerX.offset().left; + $(ui.helper).addClass("slick-header-column-active"); + }, + beforeStop: function (e, ui) { + $(ui.helper).removeClass("slick-header-column-active"); + }, + sort: function (e, ui) { + if (canDragScroll && e.originalEvent.pageX > $container[0].clientWidth) { + if (!(columnScrollTimer)) { + columnScrollTimer = setInterval( + scrollColumnsRight, 100); + } + } else if (canDragScroll && e.originalEvent.pageX < $viewportScrollContainerX.offset().left) { + if (!(columnScrollTimer)) { + columnScrollTimer = setInterval( + scrollColumnsLeft, 100); + } + } else { + clearInterval(columnScrollTimer); + columnScrollTimer = null; + } + }, + stop: function (e, ui) { + var cancel = false; + clearInterval(columnScrollTimer); + columnScrollTimer = null; + var limit = null; + + if (treeColumns.hasDepth()) { + var validPositionInGroup = columnPositionValidInGroup(ui.item); + limit = validPositionInGroup.limit; + + cancel = !validPositionInGroup.valid; + + if (cancel) + alert(validPositionInGroup.message); + } + + if (cancel || !getEditorLock().commitCurrentEdit()) { + $(this).sortable("cancel"); + return; + } + + var reorderedIds = $headerL.sortable("toArray"); + reorderedIds = reorderedIds.concat($headerR.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, { impactedColumns : getImpactedColumns( limit ) }); + e.stopPropagation(); + setupColumnResize(); + } + }); + } + + function getImpactedColumns( limit ) { + var impactedColumns = []; + + if( limit != undefined ) { + + for( var i = limit.start; i <= limit.end; i++ ) { + impactedColumns.push( columns[i] ); + } + } + else { + + impactedColumns = columns; + } + + return impactedColumns; + } + + function setupColumnResize() { + var $col, j, k, 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); + $("
") + .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; + + var newCanvasWidthL = 0, newCanvasWidthR = 0; + + 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; + } + } + } + + for (k = 0; k <= i; k++) { + c = columns[k]; + + if (hasFrozenColumns() && (k > options.frozenColumn)) { + newCanvasWidthR += c.width; + } else { + newCanvasWidthL += c.width; + } + } + + 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; + } + + if (hasFrozenColumns() && (j > options.frozenColumn)) { + newCanvasWidthR += c.width; + } else { + newCanvasWidthL += c.width; + } + } + } + } else { + for (j = i + 1; j < columnElements.length; j++) { + c = columns[j]; + + if (hasFrozenColumns() && (j > options.frozenColumn)) { + newCanvasWidthR += c.width; + } else { + newCanvasWidthL += c.width; + } + } + } + } else { // stretch column + x = d; + + var newCanvasWidthL = 0, newCanvasWidthR = 0; + + 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; + } + } + } + + for (k = 0; k <= i; k++) { + c = columns[k]; + + if (hasFrozenColumns() && (k > options.frozenColumn)) { + newCanvasWidthR += c.width; + } else { + newCanvasWidthL += c.width; + } + } + + 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; + } + + if (hasFrozenColumns() && (j > options.frozenColumn)) { + newCanvasWidthR += c.width; + } else { + newCanvasWidthL += c.width; + } + } + } + } else { + for (j = i + 1; j < columnElements.length; j++) { + c = columns[j]; + + if (hasFrozenColumns() && (j > options.frozenColumn)) { + newCanvasWidthR += c.width; + } else { + newCanvasWidthL += c.width; + } + } + } + } + + if (hasFrozenColumns() && newCanvasWidthL != canvasWidthL) { + $headerL.width(newCanvasWidthL + 1000); + $paneHeaderR.css('left', newCanvasWidthL); + } + + applyColumnHeaderWidths(); + applyColumnGroupHeaderWidths(); + if (options.syncColumnCellResize) { +// updateCanvasWidth() + 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 setFrozenOptions() { + options.frozenColumn = ( options.frozenColumn >= 0 + && options.frozenColumn < columns.length + ) + ? parseInt(options.frozenColumn) + : -1; + + options.frozenRow = ( options.frozenRow >= 0 + && options.frozenRow < numVisibleRows + ) + ? parseInt(options.frozenRow) + : -1; + + if (options.frozenRow > -1) { + hasFrozenRows = true; + frozenRowsHeight = ( options.frozenRow ) * options.rowHeight; + + var dataLength = getDataLength() || this.data.length; + + actualFrozenRow = ( options.frozenBottom ) + ? ( dataLength - options.frozenRow ) + : options.frozenRow; + } else { + hasFrozenRows = false; + } + } + + function setPaneVisibility() { + if (hasFrozenColumns()) { + $paneHeaderR.show(); + $paneTopR.show(); + + if (hasFrozenRows) { + $paneBottomL.show(); + $paneBottomR.show(); + } else { + $paneBottomR.hide(); + $paneBottomL.hide(); + } + } else { + $paneHeaderR.hide(); + $paneTopR.hide(); + $paneBottomR.hide(); + + if (hasFrozenRows) { + $paneBottomL.show(); + } else { + $paneBottomR.hide(); + $paneBottomL.hide(); + } + } + } + + function setOverflow() { + $viewportTopL.css({ + 'overflow-x': ( hasFrozenColumns() ) ? ( hasFrozenRows ? 'hidden' : 'scroll' ) : ( hasFrozenRows ? 'hidden' : 'auto' ), + 'overflow-y': ( hasFrozenColumns() ) ? ( hasFrozenRows ? 'hidden' : 'hidden' ) : ( hasFrozenRows ? 'scroll' : 'auto' ) + }); + + $viewportTopR.css({ + 'overflow-x': ( hasFrozenColumns() ) ? ( hasFrozenRows ? 'hidden' : 'scroll' ) : ( hasFrozenRows ? 'hidden' : 'auto' ), + 'overflow-y': ( hasFrozenColumns() ) ? ( hasFrozenRows ? 'scroll' : 'auto' ) : ( hasFrozenRows ? 'scroll' : 'auto' ) + }); + + $viewportBottomL.css({ + 'overflow-x': ( hasFrozenColumns() ) ? ( hasFrozenRows ? 'scroll' : 'auto' ): ( hasFrozenRows ? 'auto' : 'auto' ), + 'overflow-y': ( hasFrozenColumns() ) ? ( hasFrozenRows ? 'hidden' : 'hidden' ): ( hasFrozenRows ? 'scroll' : 'auto' ) + }); + + $viewportBottomR.css({ + 'overflow-x': ( hasFrozenColumns() ) ? ( hasFrozenRows ? 'scroll' : 'auto' ) : ( hasFrozenRows ? 'auto' : 'auto' ), + 'overflow-y': ( hasFrozenColumns() ) ? ( hasFrozenRows ? 'auto' : 'auto' ) : ( hasFrozenRows ? 'auto' : 'auto' ) + }); + } + + function setScroller() { + if (hasFrozenColumns()) { + $headerScrollContainer = $headerScrollerR; + $headerRowScrollContainer = $headerRowScrollerR; + $footerRowScrollContainer = $footerRowScrollerR + + if (hasFrozenRows) { + if (options.frozenBottom) { + $viewportScrollContainerX = $viewportBottomR; + $viewportScrollContainerY = $viewportTopR; + } else { + $viewportScrollContainerX = $viewportScrollContainerY = $viewportBottomR; + } + } else { + $viewportScrollContainerX = $viewportScrollContainerY = $viewportTopR; + } + } else { + $headerScrollContainer = $headerScrollerL; + $headerRowScrollContainer = $headerRowScrollerL; + $footerRowScrollContainer = $footerRowScrollerL; + + if (hasFrozenRows) { + if (options.frozenBottom) { + $viewportScrollContainerX = $viewportBottomL; + $viewportScrollContainerY = $viewportTopL; + } else { + $viewportScrollContainerX = $viewportScrollContainerY = $viewportBottomL; + } + } else { + $viewportScrollContainerX = $viewportScrollContainerY = $viewportTopL; + } + } + } + + function measureCellPaddingAndBorder() { + var el; + var h = ["borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight"]; + var v = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"]; + + el = $("").appendTo($headers); + headerColumnWidthDiff = headerColumnHeightDiff = 0; + if (el.css("box-sizing") != "border-box" && el.css("-moz-box-sizing") != "border-box" && el.css("-webkit-box-sizing") != "border-box") { + $.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 = $("
").appendTo($canvas); + el = $("").appendTo(r); + cellWidthDiff = cellHeightDiff = 0; + if (el.css("box-sizing") != "border-box" && el.css("-moz-box-sizing") != "border-box" && el.css("-webkit-box-sizing") != "border-box") { + $.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 = $("