slickgrid added to third-party
[myslice.git] / third-party / slickgrid-2.1 / slick.grid.js
1 /**\r
2  * @license\r
3  * (c) 2009-2012 Michael Leibman\r
4  * michael{dot}leibman{at}gmail{dot}com\r
5  * http://github.com/mleibman/slickgrid\r
6  *\r
7  * Distributed under MIT license.\r
8  * All rights reserved.\r
9  *\r
10  * SlickGrid v2.1\r
11  *\r
12  * NOTES:\r
13  *     Cell/row DOM manipulations are done directly bypassing jQuery's DOM manipulation methods.\r
14  *     This increases the speed dramatically, but can only be done safely because there are no event handlers\r
15  *     or data associated with any cell/row DOM nodes.  Cell editors must make sure they implement .destroy()\r
16  *     and do proper cleanup.\r
17  */\r
18 \r
19 // make sure required JavaScript modules are loaded\r
20 if (typeof jQuery === "undefined") {\r
21   throw "SlickGrid requires jquery module to be loaded";\r
22 }\r
23 if (!jQuery.fn.drag) {\r
24   throw "SlickGrid requires jquery.event.drag module to be loaded";\r
25 }\r
26 if (typeof Slick === "undefined") {\r
27   throw "slick.core.js not loaded";\r
28 }\r
29 \r
30 \r
31 (function ($) {\r
32   // Slick.Grid\r
33   $.extend(true, window, {\r
34     Slick: {\r
35       Grid: SlickGrid\r
36     }\r
37   });\r
38 \r
39   // shared across all grids on the page\r
40   var scrollbarDimensions;\r
41   var maxSupportedCssHeight;  // browser's breaking point\r
42 \r
43   //////////////////////////////////////////////////////////////////////////////////////////////\r
44   // SlickGrid class implementation (available as Slick.Grid)\r
45 \r
46   /**\r
47    * Creates a new instance of the grid.\r
48    * @class SlickGrid\r
49    * @constructor\r
50    * @param {Node}              container   Container node to create the grid in.\r
51    * @param {Array,Object}      data        An array of objects for databinding.\r
52    * @param {Array}             columns     An array of column definitions.\r
53    * @param {Object}            options     Grid options.\r
54    **/\r
55   function SlickGrid(container, data, columns, options) {\r
56     // settings\r
57     var defaults = {\r
58       explicitInitialization: false,\r
59       rowHeight: 25,\r
60       defaultColumnWidth: 80,\r
61       enableAddRow: false,\r
62       leaveSpaceForNewRows: false,\r
63       editable: false,\r
64       autoEdit: true,\r
65       enableCellNavigation: true,\r
66       enableColumnReorder: true,\r
67       asyncEditorLoading: false,\r
68       asyncEditorLoadDelay: 100,\r
69       forceFitColumns: false,\r
70       enableAsyncPostRender: false,\r
71       asyncPostRenderDelay: 50,\r
72       autoHeight: false,\r
73       editorLock: Slick.GlobalEditorLock,\r
74       showHeaderRow: false,\r
75       headerRowHeight: 25,\r
76       showTopPanel: false,\r
77       topPanelHeight: 25,\r
78       formatterFactory: null,\r
79       editorFactory: null,\r
80       cellFlashingCssClass: "flashing",\r
81       selectedCellCssClass: "selected",\r
82       multiSelect: true,\r
83       enableTextSelectionOnCells: false,\r
84       dataItemColumnValueExtractor: null,\r
85       fullWidthRows: false,\r
86       multiColumnSort: false,\r
87       defaultFormatter: defaultFormatter,\r
88       forceSyncScrolling: false\r
89     };\r
90 \r
91     var columnDefaults = {\r
92       name: "",\r
93       resizable: true,\r
94       sortable: false,\r
95       minWidth: 30,\r
96       rerenderOnResize: false,\r
97       headerCssClass: null,\r
98       defaultSortAsc: true,\r
99       focusable: true,\r
100       selectable: true\r
101     };\r
102 \r
103     // scroller\r
104     var th;   // virtual height\r
105     var h;    // real scrollable height\r
106     var ph;   // page height\r
107     var n;    // number of pages\r
108     var cj;   // "jumpiness" coefficient\r
109 \r
110     var page = 0;       // current page\r
111     var offset = 0;     // current page offset\r
112     var vScrollDir = 1;\r
113 \r
114     // private\r
115     var initialized = false;\r
116     var $container;\r
117     var uid = "slickgrid_" + Math.round(1000000 * Math.random());\r
118     var self = this;\r
119     var $focusSink, $focusSink2;\r
120     var $headerScroller;\r
121     var $headers;\r
122     var $headerRow, $headerRowScroller, $headerRowSpacer;\r
123     var $topPanelScroller;\r
124     var $topPanel;\r
125     var $viewport;\r
126     var $canvas;\r
127     var $style;\r
128     var $boundAncestors;\r
129     var stylesheet, columnCssRulesL, columnCssRulesR;\r
130     var viewportH, viewportW;\r
131     var canvasWidth;\r
132     var viewportHasHScroll, viewportHasVScroll;\r
133     var headerColumnWidthDiff = 0, headerColumnHeightDiff = 0, // border+padding\r
134         cellWidthDiff = 0, cellHeightDiff = 0;\r
135     var absoluteColumnMinWidth;\r
136     var numberOfRows = 0;\r
137 \r
138     var tabbingDirection = 1;\r
139     var activePosX;\r
140     var activeRow, activeCell;\r
141     var activeCellNode = null;\r
142     var currentEditor = null;\r
143     var serializedEditorValue;\r
144     var editController;\r
145 \r
146     var rowsCache = {};\r
147     var renderedRows = 0;\r
148     var numVisibleRows;\r
149     var prevScrollTop = 0;\r
150     var scrollTop = 0;\r
151     var lastRenderedScrollTop = 0;\r
152     var lastRenderedScrollLeft = 0;\r
153     var prevScrollLeft = 0;\r
154     var scrollLeft = 0;\r
155 \r
156     var selectionModel;\r
157     var selectedRows = [];\r
158 \r
159     var plugins = [];\r
160     var cellCssClasses = {};\r
161 \r
162     var columnsById = {};\r
163     var sortColumns = [];\r
164     var columnPosLeft = [];\r
165     var columnPosRight = [];\r
166 \r
167 \r
168     // async call handles\r
169     var h_editorLoader = null;\r
170     var h_render = null;\r
171     var h_postrender = null;\r
172     var postProcessedRows = {};\r
173     var postProcessToRow = null;\r
174     var postProcessFromRow = null;\r
175 \r
176     // perf counters\r
177     var counter_rows_rendered = 0;\r
178     var counter_rows_removed = 0;\r
179 \r
180 \r
181     //////////////////////////////////////////////////////////////////////////////////////////////\r
182     // Initialization\r
183 \r
184     function init() {\r
185       $container = $(container);\r
186       if ($container.length < 1) {\r
187         throw new Error("SlickGrid requires a valid container, " + container + " does not exist in the DOM.");\r
188       }\r
189 \r
190       // calculate these only once and share between grid instances\r
191       maxSupportedCssHeight = maxSupportedCssHeight || getMaxSupportedCssHeight();\r
192       scrollbarDimensions = scrollbarDimensions || measureScrollbar();\r
193 \r
194       options = $.extend({}, defaults, options);\r
195       validateAndEnforceOptions();\r
196       columnDefaults.width = options.defaultColumnWidth;\r
197 \r
198       columnsById = {};\r
199       for (var i = 0; i < columns.length; i++) {\r
200         var m = columns[i] = $.extend({}, columnDefaults, columns[i]);\r
201         columnsById[m.id] = i;\r
202         if (m.minWidth && m.width < m.minWidth) {\r
203           m.width = m.minWidth;\r
204         }\r
205         if (m.maxWidth && m.width > m.maxWidth) {\r
206           m.width = m.maxWidth;\r
207         }\r
208       }\r
209 \r
210       // validate loaded JavaScript modules against requested options\r
211       if (options.enableColumnReorder && !$.fn.sortable) {\r
212         throw new Error("SlickGrid's 'enableColumnReorder = true' option requires jquery-ui.sortable module to be loaded");\r
213       }\r
214 \r
215       editController = {\r
216         "commitCurrentEdit": commitCurrentEdit,\r
217         "cancelCurrentEdit": cancelCurrentEdit\r
218       };\r
219 \r
220       $container\r
221           .empty()\r
222           .css("overflow", "hidden")\r
223           .css("outline", 0)\r
224           .addClass(uid)\r
225           .addClass("ui-widget");\r
226 \r
227       // set up a positioning container if needed\r
228       if (!/relative|absolute|fixed/.test($container.css("position"))) {\r
229         $container.css("position", "relative");\r
230       }\r
231 \r
232       $focusSink = $("<div tabIndex='0' hideFocus style='position:fixed;width:0;height:0;top:0;left:0;outline:0;'></div>").appendTo($container);\r
233 \r
234       $headerScroller = $("<div class='slick-header ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);\r
235       $headers = $("<div class='slick-header-columns' style='left:-1000px' />").appendTo($headerScroller);\r
236       $headers.width(getHeadersWidth());\r
237 \r
238       $headerRowScroller = $("<div class='slick-headerrow ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);\r
239       $headerRow = $("<div class='slick-headerrow-columns' />").appendTo($headerRowScroller);\r
240       $headerRowSpacer = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>")\r
241           .css("width", getCanvasWidth() + scrollbarDimensions.width + "px")\r
242           .appendTo($headerRowScroller);\r
243 \r
244       $topPanelScroller = $("<div class='slick-top-panel-scroller ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);\r
245       $topPanel = $("<div class='slick-top-panel' style='width:10000px' />").appendTo($topPanelScroller);\r
246 \r
247       if (!options.showTopPanel) {\r
248         $topPanelScroller.hide();\r
249       }\r
250 \r
251       if (!options.showHeaderRow) {\r
252         $headerRowScroller.hide();\r
253       }\r
254 \r
255       $viewport = $("<div class='slick-viewport' style='width:100%;overflow:auto;outline:0;position:relative;;'>").appendTo($container);\r
256       $viewport.css("overflow-y", options.autoHeight ? "hidden" : "auto");\r
257 \r
258       $canvas = $("<div class='grid-canvas' />").appendTo($viewport);\r
259 \r
260       $focusSink2 = $focusSink.clone().appendTo($container);\r
261 \r
262       if (!options.explicitInitialization) {\r
263         finishInitialization();\r
264       }\r
265     }\r
266 \r
267     function finishInitialization() {\r
268       if (!initialized) {\r
269         initialized = true;\r
270 \r
271         viewportW = parseFloat($.css($container[0], "width", true));\r
272 \r
273         // header columns and cells may have different padding/border skewing width calculations (box-sizing, hello?)\r
274         // calculate the diff so we can set consistent sizes\r
275         measureCellPaddingAndBorder();\r
276 \r
277         // for usability reasons, all text selection in SlickGrid is disabled\r
278         // with the exception of input and textarea elements (selection must\r
279         // be enabled there so that editors work as expected); note that\r
280         // selection in grid cells (grid body) is already unavailable in\r
281         // all browsers except IE\r
282         disableSelection($headers); // disable all text selection in header (including input and textarea)\r
283 \r
284         if (!options.enableTextSelectionOnCells) {\r
285           // disable text selection in grid cells except in input and textarea elements\r
286           // (this is IE-specific, because selectstart event will only fire in IE)\r
287           $viewport.bind("selectstart.ui", function (event) {\r
288             return $(event.target).is("input,textarea");\r
289           });\r
290         }\r
291 \r
292         updateColumnCaches();\r
293         createColumnHeaders();\r
294         setupColumnSort();\r
295         createCssRules();\r
296         resizeCanvas();\r
297         bindAncestorScrollEvents();\r
298 \r
299         $container\r
300             .bind("resize.slickgrid", resizeCanvas);\r
301         $viewport\r
302             .bind("scroll", handleScroll);\r
303         $headerScroller\r
304             .bind("contextmenu", handleHeaderContextMenu)\r
305             .bind("click", handleHeaderClick)\r
306             .delegate(".slick-header-column", "mouseenter", handleHeaderMouseEnter)\r
307             .delegate(".slick-header-column", "mouseleave", handleHeaderMouseLeave);\r
308         $headerRowScroller\r
309             .bind("scroll", handleHeaderRowScroll);\r
310         $focusSink.add($focusSink2)\r
311             .bind("keydown", handleKeyDown);\r
312         $canvas\r
313             .bind("keydown", handleKeyDown)\r
314             .bind("click", handleClick)\r
315             .bind("dblclick", handleDblClick)\r
316             .bind("contextmenu", handleContextMenu)\r
317             .bind("draginit", handleDragInit)\r
318             .bind("dragstart", {distance: 3}, handleDragStart)\r
319             .bind("drag", handleDrag)\r
320             .bind("dragend", handleDragEnd)\r
321             .delegate(".slick-cell", "mouseenter", handleMouseEnter)\r
322             .delegate(".slick-cell", "mouseleave", handleMouseLeave);\r
323       }\r
324     }\r
325 \r
326     function registerPlugin(plugin) {\r
327       plugins.unshift(plugin);\r
328       plugin.init(self);\r
329     }\r
330 \r
331     function unregisterPlugin(plugin) {\r
332       for (var i = plugins.length; i >= 0; i--) {\r
333         if (plugins[i] === plugin) {\r
334           if (plugins[i].destroy) {\r
335             plugins[i].destroy();\r
336           }\r
337           plugins.splice(i, 1);\r
338           break;\r
339         }\r
340       }\r
341     }\r
342 \r
343     function setSelectionModel(model) {\r
344       if (selectionModel) {\r
345         selectionModel.onSelectedRangesChanged.unsubscribe(handleSelectedRangesChanged);\r
346         if (selectionModel.destroy) {\r
347           selectionModel.destroy();\r
348         }\r
349       }\r
350 \r
351       selectionModel = model;\r
352       if (selectionModel) {\r
353         selectionModel.init(self);\r
354         selectionModel.onSelectedRangesChanged.subscribe(handleSelectedRangesChanged);\r
355       }\r
356     }\r
357 \r
358     function getSelectionModel() {\r
359       return selectionModel;\r
360     }\r
361 \r
362     function getCanvasNode() {\r
363       return $canvas[0];\r
364     }\r
365 \r
366     function measureScrollbar() {\r
367       var $c = $("<div style='position:absolute; top:-10000px; left:-10000px; width:100px; height:100px; overflow:scroll;'></div>").appendTo("body");\r
368       var dim = {\r
369         width: $c.width() - $c[0].clientWidth,\r
370         height: $c.height() - $c[0].clientHeight\r
371       };\r
372       $c.remove();\r
373       return dim;\r
374     }\r
375 \r
376     function getHeadersWidth() {\r
377       var headersWidth = 0;\r
378       for (var i = 0, ii = columns.length; i < ii; i++) {\r
379         var width = columns[i].width;\r
380         headersWidth += width;\r
381       }\r
382       headersWidth += scrollbarDimensions.width;\r
383       return Math.max(headersWidth, viewportW) + 1000;\r
384     }\r
385 \r
386     function getCanvasWidth() {\r
387       var availableWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW;\r
388       var rowWidth = 0;\r
389       var i = columns.length;\r
390       while (i--) {\r
391         rowWidth += columns[i].width;\r
392       }\r
393       return options.fullWidthRows ? Math.max(rowWidth, availableWidth) : rowWidth;\r
394     }\r
395 \r
396     function updateCanvasWidth(forceColumnWidthsUpdate) {\r
397       var oldCanvasWidth = canvasWidth;\r
398       canvasWidth = getCanvasWidth();\r
399 \r
400       if (canvasWidth != oldCanvasWidth) {\r
401         $canvas.width(canvasWidth);\r
402         $headerRow.width(canvasWidth);\r
403         $headers.width(getHeadersWidth());\r
404         viewportHasHScroll = (canvasWidth > viewportW - scrollbarDimensions.width);\r
405       }\r
406 \r
407       $headerRowSpacer.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0));\r
408 \r
409       if (canvasWidth != oldCanvasWidth || forceColumnWidthsUpdate) {\r
410         applyColumnWidths();\r
411       }\r
412     }\r
413 \r
414     function disableSelection($target) {\r
415       if ($target && $target.jquery) {\r
416         $target\r
417             .attr("unselectable", "on")\r
418             .css("MozUserSelect", "none")\r
419             .bind("selectstart.ui", function () {\r
420               return false;\r
421             }); // from jquery:ui.core.js 1.7.2\r
422       }\r
423     }\r
424 \r
425     function getMaxSupportedCssHeight() {\r
426       var supportedHeight = 1000000;\r
427       // FF reports the height back but still renders blank after ~6M px\r
428       var testUpTo = navigator.userAgent.toLowerCase().match(/firefox/) ? 6000000 : 1000000000;\r
429       var div = $("<div style='display:none' />").appendTo(document.body);\r
430 \r
431       while (true) {\r
432         var test = supportedHeight * 2;\r
433         div.css("height", test);\r
434         if (test > testUpTo || div.height() !== test) {\r
435           break;\r
436         } else {\r
437           supportedHeight = test;\r
438         }\r
439       }\r
440 \r
441       div.remove();\r
442       return supportedHeight;\r
443     }\r
444 \r
445     // TODO:  this is static.  need to handle page mutation.\r
446     function bindAncestorScrollEvents() {\r
447       var elem = $canvas[0];\r
448       while ((elem = elem.parentNode) != document.body && elem != null) {\r
449         // bind to scroll containers only\r
450         if (elem == $viewport[0] || elem.scrollWidth != elem.clientWidth || elem.scrollHeight != elem.clientHeight) {\r
451           var $elem = $(elem);\r
452           if (!$boundAncestors) {\r
453             $boundAncestors = $elem;\r
454           } else {\r
455             $boundAncestors = $boundAncestors.add($elem);\r
456           }\r
457           $elem.bind("scroll." + uid, handleActiveCellPositionChange);\r
458         }\r
459       }\r
460     }\r
461 \r
462     function unbindAncestorScrollEvents() {\r
463       if (!$boundAncestors) {\r
464         return;\r
465       }\r
466       $boundAncestors.unbind("scroll." + uid);\r
467       $boundAncestors = null;\r
468     }\r
469 \r
470     function updateColumnHeader(columnId, title, toolTip) {\r
471       if (!initialized) { return; }\r
472       var idx = getColumnIndex(columnId);\r
473       if (idx == null) {\r
474         return;\r
475       }\r
476 \r
477       var columnDef = columns[idx];\r
478       var $header = $headers.children().eq(idx);\r
479       if ($header) {\r
480         if (title !== undefined) {\r
481           columns[idx].name = title;\r
482         }\r
483         if (toolTip !== undefined) {\r
484           columns[idx].toolTip = toolTip;\r
485         }\r
486 \r
487         trigger(self.onBeforeHeaderCellDestroy, {\r
488           "node": $header[0],\r
489           "column": columnDef\r
490         });\r
491 \r
492         $header\r
493             .attr("title", toolTip || "")\r
494             .children().eq(0).html(title);\r
495 \r
496         trigger(self.onHeaderCellRendered, {\r
497           "node": $header[0],\r
498           "column": columnDef\r
499         });\r
500       }\r
501     }\r
502 \r
503     function getHeaderRow() {\r
504       return $headerRow[0];\r
505     }\r
506 \r
507     function getHeaderRowColumn(columnId) {\r
508       var idx = getColumnIndex(columnId);\r
509       var $header = $headerRow.children().eq(idx);\r
510       return $header && $header[0];\r
511     }\r
512 \r
513     function createColumnHeaders() {\r
514       function onMouseEnter() {\r
515         $(this).addClass("ui-state-hover");\r
516       }\r
517 \r
518       function onMouseLeave() {\r
519         $(this).removeClass("ui-state-hover");\r
520       }\r
521 \r
522       $headers.find(".slick-header-column")\r
523         .each(function() {\r
524           var columnDef = $(this).data("column");\r
525           if (columnDef) {\r
526             trigger(self.onBeforeHeaderCellDestroy, {\r
527               "node": this,\r
528               "column": columnDef\r
529             });\r
530           }\r
531         });\r
532       $headers.empty();\r
533       $headers.width(getHeadersWidth());\r
534 \r
535       $headerRow.find(".slick-headerrow-column")\r
536         .each(function() {\r
537           var columnDef = $(this).data("column");\r
538           if (columnDef) {\r
539             trigger(self.onBeforeHeaderRowCellDestroy, {\r
540               "node": this,\r
541               "column": columnDef\r
542             });\r
543           }\r
544         });\r
545       $headerRow.empty();\r
546 \r
547       for (var i = 0; i < columns.length; i++) {\r
548         var m = columns[i];\r
549 \r
550         var header = $("<div class='ui-state-default slick-header-column' />")\r
551             .html("<span class='slick-column-name'>" + m.name + "</span>")\r
552             .width(m.width - headerColumnWidthDiff)\r
553             .attr("id", "" + uid + m.id)\r
554             .attr("title", m.toolTip || "")\r
555             .data("column", m)\r
556             .addClass(m.headerCssClass || "")\r
557             .appendTo($headers);\r
558 \r
559         if (options.enableColumnReorder || m.sortable) {\r
560           header\r
561             .on('mouseenter', onMouseEnter)\r
562             .on('mouseleave', onMouseLeave);\r
563         }\r
564 \r
565         if (m.sortable) {\r
566           header.addClass("slick-header-sortable");\r
567           header.append("<span class='slick-sort-indicator' />");\r
568         }\r
569 \r
570         trigger(self.onHeaderCellRendered, {\r
571           "node": header[0],\r
572           "column": m\r
573         });\r
574 \r
575         if (options.showHeaderRow) {\r
576           var headerRowCell = $("<div class='ui-state-default slick-headerrow-column l" + i + " r" + i + "'></div>")\r
577               .data("column", m)\r
578               .appendTo($headerRow);\r
579 \r
580           trigger(self.onHeaderRowCellRendered, {\r
581             "node": headerRowCell[0],\r
582             "column": m\r
583           });\r
584         }\r
585       }\r
586 \r
587       setSortColumns(sortColumns);\r
588       setupColumnResize();\r
589       if (options.enableColumnReorder) {\r
590         setupColumnReorder();\r
591       }\r
592     }\r
593 \r
594     function setupColumnSort() {\r
595       $headers.click(function (e) {\r
596         // temporary workaround for a bug in jQuery 1.7.1 (http://bugs.jquery.com/ticket/11328)\r
597         e.metaKey = e.metaKey || e.ctrlKey;\r
598 \r
599         if ($(e.target).hasClass("slick-resizable-handle")) {\r
600           return;\r
601         }\r
602 \r
603         var $col = $(e.target).closest(".slick-header-column");\r
604         if (!$col.length) {\r
605           return;\r
606         }\r
607 \r
608         var column = $col.data("column");\r
609         if (column.sortable) {\r
610           if (!getEditorLock().commitCurrentEdit()) {\r
611             return;\r
612           }\r
613 \r
614           var sortOpts = null;\r
615           var i = 0;\r
616           for (; i < sortColumns.length; i++) {\r
617             if (sortColumns[i].columnId == column.id) {\r
618               sortOpts = sortColumns[i];\r
619               sortOpts.sortAsc = !sortOpts.sortAsc;\r
620               break;\r
621             }\r
622           }\r
623 \r
624           if (e.metaKey && options.multiColumnSort) {\r
625             if (sortOpts) {\r
626               sortColumns.splice(i, 1);\r
627             }\r
628           }\r
629           else {\r
630             if ((!e.shiftKey && !e.metaKey) || !options.multiColumnSort) {\r
631               sortColumns = [];\r
632             }\r
633 \r
634             if (!sortOpts) {\r
635               sortOpts = { columnId: column.id, sortAsc: column.defaultSortAsc };\r
636               sortColumns.push(sortOpts);\r
637             } else if (sortColumns.length == 0) {\r
638               sortColumns.push(sortOpts);\r
639             }\r
640           }\r
641 \r
642           setSortColumns(sortColumns);\r
643 \r
644           if (!options.multiColumnSort) {\r
645             trigger(self.onSort, {\r
646               multiColumnSort: false,\r
647               sortCol: column,\r
648               sortAsc: sortOpts.sortAsc}, e);\r
649           } else {\r
650             trigger(self.onSort, {\r
651               multiColumnSort: true,\r
652               sortCols: $.map(sortColumns, function(col) {\r
653                 return {sortCol: columns[getColumnIndex(col.columnId)], sortAsc: col.sortAsc };\r
654               })}, e);\r
655           }\r
656         }\r
657       });\r
658     }\r
659 \r
660     function setupColumnReorder() {\r
661       $headers.filter(":ui-sortable").sortable("destroy");\r
662       $headers.sortable({\r
663         containment: "parent",\r
664         distance: 3,\r
665         axis: "x",\r
666         cursor: "default",\r
667         tolerance: "intersection",\r
668         helper: "clone",\r
669         placeholder: "slick-sortable-placeholder ui-state-default slick-header-column",\r
670         forcePlaceholderSize: true,\r
671         start: function (e, ui) {\r
672           $(ui.helper).addClass("slick-header-column-active");\r
673         },\r
674         beforeStop: function (e, ui) {\r
675           $(ui.helper).removeClass("slick-header-column-active");\r
676         },\r
677         stop: function (e) {\r
678           if (!getEditorLock().commitCurrentEdit()) {\r
679             $(this).sortable("cancel");\r
680             return;\r
681           }\r
682 \r
683           var reorderedIds = $headers.sortable("toArray");\r
684           var reorderedColumns = [];\r
685           for (var i = 0; i < reorderedIds.length; i++) {\r
686             reorderedColumns.push(columns[getColumnIndex(reorderedIds[i].replace(uid, ""))]);\r
687           }\r
688           setColumns(reorderedColumns);\r
689 \r
690           trigger(self.onColumnsReordered, {});\r
691           e.stopPropagation();\r
692           setupColumnResize();\r
693         }\r
694       });\r
695     }\r
696 \r
697     function setupColumnResize() {\r
698       var $col, j, c, pageX, columnElements, minPageX, maxPageX, firstResizable, lastResizable;\r
699       columnElements = $headers.children();\r
700       columnElements.find(".slick-resizable-handle").remove();\r
701       columnElements.each(function (i, e) {\r
702         if (columns[i].resizable) {\r
703           if (firstResizable === undefined) {\r
704             firstResizable = i;\r
705           }\r
706           lastResizable = i;\r
707         }\r
708       });\r
709       if (firstResizable === undefined) {\r
710         return;\r
711       }\r
712       columnElements.each(function (i, e) {\r
713         if (i < firstResizable || (options.forceFitColumns && i >= lastResizable)) {\r
714           return;\r
715         }\r
716         $col = $(e);\r
717         $("<div class='slick-resizable-handle' />")\r
718             .appendTo(e)\r
719             .bind("dragstart", function (e, dd) {\r
720               if (!getEditorLock().commitCurrentEdit()) {\r
721                 return false;\r
722               }\r
723               pageX = e.pageX;\r
724               $(this).parent().addClass("slick-header-column-active");\r
725               var shrinkLeewayOnRight = null, stretchLeewayOnRight = null;\r
726               // lock each column's width option to current width\r
727               columnElements.each(function (i, e) {\r
728                 columns[i].previousWidth = $(e).outerWidth();\r
729               });\r
730               if (options.forceFitColumns) {\r
731                 shrinkLeewayOnRight = 0;\r
732                 stretchLeewayOnRight = 0;\r
733                 // colums on right affect maxPageX/minPageX\r
734                 for (j = i + 1; j < columnElements.length; j++) {\r
735                   c = columns[j];\r
736                   if (c.resizable) {\r
737                     if (stretchLeewayOnRight !== null) {\r
738                       if (c.maxWidth) {\r
739                         stretchLeewayOnRight += c.maxWidth - c.previousWidth;\r
740                       } else {\r
741                         stretchLeewayOnRight = null;\r
742                       }\r
743                     }\r
744                     shrinkLeewayOnRight += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth);\r
745                   }\r
746                 }\r
747               }\r
748               var shrinkLeewayOnLeft = 0, stretchLeewayOnLeft = 0;\r
749               for (j = 0; j <= i; j++) {\r
750                 // columns on left only affect minPageX\r
751                 c = columns[j];\r
752                 if (c.resizable) {\r
753                   if (stretchLeewayOnLeft !== null) {\r
754                     if (c.maxWidth) {\r
755                       stretchLeewayOnLeft += c.maxWidth - c.previousWidth;\r
756                     } else {\r
757                       stretchLeewayOnLeft = null;\r
758                     }\r
759                   }\r
760                   shrinkLeewayOnLeft += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth);\r
761                 }\r
762               }\r
763               if (shrinkLeewayOnRight === null) {\r
764                 shrinkLeewayOnRight = 100000;\r
765               }\r
766               if (shrinkLeewayOnLeft === null) {\r
767                 shrinkLeewayOnLeft = 100000;\r
768               }\r
769               if (stretchLeewayOnRight === null) {\r
770                 stretchLeewayOnRight = 100000;\r
771               }\r
772               if (stretchLeewayOnLeft === null) {\r
773                 stretchLeewayOnLeft = 100000;\r
774               }\r
775               maxPageX = pageX + Math.min(shrinkLeewayOnRight, stretchLeewayOnLeft);\r
776               minPageX = pageX - Math.min(shrinkLeewayOnLeft, stretchLeewayOnRight);\r
777             })\r
778             .bind("drag", function (e, dd) {\r
779               var actualMinWidth, d = Math.min(maxPageX, Math.max(minPageX, e.pageX)) - pageX, x;\r
780               if (d < 0) { // shrink column\r
781                 x = d;\r
782                 for (j = i; j >= 0; j--) {\r
783                   c = columns[j];\r
784                   if (c.resizable) {\r
785                     actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth);\r
786                     if (x && c.previousWidth + x < actualMinWidth) {\r
787                       x += c.previousWidth - actualMinWidth;\r
788                       c.width = actualMinWidth;\r
789                     } else {\r
790                       c.width = c.previousWidth + x;\r
791                       x = 0;\r
792                     }\r
793                   }\r
794                 }\r
795 \r
796                 if (options.forceFitColumns) {\r
797                   x = -d;\r
798                   for (j = i + 1; j < columnElements.length; j++) {\r
799                     c = columns[j];\r
800                     if (c.resizable) {\r
801                       if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {\r
802                         x -= c.maxWidth - c.previousWidth;\r
803                         c.width = c.maxWidth;\r
804                       } else {\r
805                         c.width = c.previousWidth + x;\r
806                         x = 0;\r
807                       }\r
808                     }\r
809                   }\r
810                 }\r
811               } else { // stretch column\r
812                 x = d;\r
813                 for (j = i; j >= 0; j--) {\r
814                   c = columns[j];\r
815                   if (c.resizable) {\r
816                     if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {\r
817                       x -= c.maxWidth - c.previousWidth;\r
818                       c.width = c.maxWidth;\r
819                     } else {\r
820                       c.width = c.previousWidth + x;\r
821                       x = 0;\r
822                     }\r
823                   }\r
824                 }\r
825 \r
826                 if (options.forceFitColumns) {\r
827                   x = -d;\r
828                   for (j = i + 1; j < columnElements.length; j++) {\r
829                     c = columns[j];\r
830                     if (c.resizable) {\r
831                       actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth);\r
832                       if (x && c.previousWidth + x < actualMinWidth) {\r
833                         x += c.previousWidth - actualMinWidth;\r
834                         c.width = actualMinWidth;\r
835                       } else {\r
836                         c.width = c.previousWidth + x;\r
837                         x = 0;\r
838                       }\r
839                     }\r
840                   }\r
841                 }\r
842               }\r
843               applyColumnHeaderWidths();\r
844               if (options.syncColumnCellResize) {\r
845                 applyColumnWidths();\r
846               }\r
847             })\r
848             .bind("dragend", function (e, dd) {\r
849               var newWidth;\r
850               $(this).parent().removeClass("slick-header-column-active");\r
851               for (j = 0; j < columnElements.length; j++) {\r
852                 c = columns[j];\r
853                 newWidth = $(columnElements[j]).outerWidth();\r
854 \r
855                 if (c.previousWidth !== newWidth && c.rerenderOnResize) {\r
856                   invalidateAllRows();\r
857                 }\r
858               }\r
859               updateCanvasWidth(true);\r
860               render();\r
861               trigger(self.onColumnsResized, {});\r
862             });\r
863       });\r
864     }\r
865 \r
866     function getVBoxDelta($el) {\r
867       var p = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"];\r
868       var delta = 0;\r
869       $.each(p, function (n, val) {\r
870         delta += parseFloat($el.css(val)) || 0;\r
871       });\r
872       return delta;\r
873     }\r
874 \r
875     function measureCellPaddingAndBorder() {\r
876       var el;\r
877       var h = ["borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight"];\r
878       var v = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"];\r
879 \r
880       el = $("<div class='ui-state-default slick-header-column' style='visibility:hidden'>-</div>").appendTo($headers);\r
881       headerColumnWidthDiff = headerColumnHeightDiff = 0;\r
882       $.each(h, function (n, val) {\r
883         headerColumnWidthDiff += parseFloat(el.css(val)) || 0;\r
884       });\r
885       $.each(v, function (n, val) {\r
886         headerColumnHeightDiff += parseFloat(el.css(val)) || 0;\r
887       });\r
888       el.remove();\r
889 \r
890       var r = $("<div class='slick-row' />").appendTo($canvas);\r
891       el = $("<div class='slick-cell' id='' style='visibility:hidden'>-</div>").appendTo(r);\r
892       cellWidthDiff = cellHeightDiff = 0;\r
893       $.each(h, function (n, val) {\r
894         cellWidthDiff += parseFloat(el.css(val)) || 0;\r
895       });\r
896       $.each(v, function (n, val) {\r
897         cellHeightDiff += parseFloat(el.css(val)) || 0;\r
898       });\r
899       r.remove();\r
900 \r
901       absoluteColumnMinWidth = Math.max(headerColumnWidthDiff, cellWidthDiff);\r
902     }\r
903 \r
904     function createCssRules() {\r
905       $style = $("<style type='text/css' rel='stylesheet' />").appendTo($("head"));\r
906       var rowHeight = (options.rowHeight - cellHeightDiff);\r
907       var rules = [\r
908         "." + uid + " .slick-header-column { left: 1000px; }",\r
909         "." + uid + " .slick-top-panel { height:" + options.topPanelHeight + "px; }",\r
910         "." + uid + " .slick-headerrow-columns { height:" + options.headerRowHeight + "px; }",\r
911         "." + uid + " .slick-cell { height:" + rowHeight + "px; }",\r
912         "." + uid + " .slick-row { height:" + options.rowHeight + "px; }"\r
913       ];\r
914 \r
915       for (var i = 0; i < columns.length; i++) {\r
916         rules.push("." + uid + " .l" + i + " { }");\r
917         rules.push("." + uid + " .r" + i + " { }");\r
918       }\r
919 \r
920       if ($style[0].styleSheet) { // IE\r
921         $style[0].styleSheet.cssText = rules.join(" ");\r
922       } else {\r
923         $style[0].appendChild(document.createTextNode(rules.join(" ")));\r
924       }\r
925     }\r
926 \r
927     function getColumnCssRules(idx) {\r
928       if (!stylesheet) {\r
929         var sheets = document.styleSheets;\r
930         for (var i = 0; i < sheets.length; i++) {\r
931           if ((sheets[i].ownerNode || sheets[i].owningElement) == $style[0]) {\r
932             stylesheet = sheets[i];\r
933             break;\r
934           }\r
935         }\r
936 \r
937         if (!stylesheet) {\r
938           throw new Error("Cannot find stylesheet.");\r
939         }\r
940 \r
941         // find and cache column CSS rules\r
942         columnCssRulesL = [];\r
943         columnCssRulesR = [];\r
944         var cssRules = (stylesheet.cssRules || stylesheet.rules);\r
945         var matches, columnIdx;\r
946         for (var i = 0; i < cssRules.length; i++) {\r
947           var selector = cssRules[i].selectorText;\r
948           if (matches = /\.l\d+/.exec(selector)) {\r
949             columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10);\r
950             columnCssRulesL[columnIdx] = cssRules[i];\r
951           } else if (matches = /\.r\d+/.exec(selector)) {\r
952             columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10);\r
953             columnCssRulesR[columnIdx] = cssRules[i];\r
954           }\r
955         }\r
956       }\r
957 \r
958       return {\r
959         "left": columnCssRulesL[idx],\r
960         "right": columnCssRulesR[idx]\r
961       };\r
962     }\r
963 \r
964     function removeCssRules() {\r
965       $style.remove();\r
966       stylesheet = null;\r
967     }\r
968 \r
969     function destroy() {\r
970       getEditorLock().cancelCurrentEdit();\r
971 \r
972       trigger(self.onBeforeDestroy, {});\r
973 \r
974       var i = plugins.length;\r
975       while(i--) {\r
976         unregisterPlugin(plugins[i]);\r
977       }\r
978 \r
979       if (options.enableColumnReorder) {\r
980           $headers.filter(":ui-sortable").sortable("destroy");\r
981       }\r
982 \r
983       unbindAncestorScrollEvents();\r
984       $container.unbind(".slickgrid");\r
985       removeCssRules();\r
986 \r
987       $canvas.unbind("draginit dragstart dragend drag");\r
988       $container.empty().removeClass(uid);\r
989     }\r
990 \r
991 \r
992     //////////////////////////////////////////////////////////////////////////////////////////////\r
993     // General\r
994 \r
995     function trigger(evt, args, e) {\r
996       e = e || new Slick.EventData();\r
997       args = args || {};\r
998       args.grid = self;\r
999       return evt.notify(args, e, self);\r
1000     }\r
1001 \r
1002     function getEditorLock() {\r
1003       return options.editorLock;\r
1004     }\r
1005 \r
1006     function getEditController() {\r
1007       return editController;\r
1008     }\r
1009 \r
1010     function getColumnIndex(id) {\r
1011       return columnsById[id];\r
1012     }\r
1013 \r
1014     function autosizeColumns() {\r
1015       var i, c,\r
1016           widths = [],\r
1017           shrinkLeeway = 0,\r
1018           total = 0,\r
1019           prevTotal,\r
1020           availWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW;\r
1021 \r
1022       for (i = 0; i < columns.length; i++) {\r
1023         c = columns[i];\r
1024         widths.push(c.width);\r
1025         total += c.width;\r
1026         if (c.resizable) {\r
1027           shrinkLeeway += c.width - Math.max(c.minWidth, absoluteColumnMinWidth);\r
1028         }\r
1029       }\r
1030 \r
1031       // shrink\r
1032       prevTotal = total;\r
1033       while (total > availWidth && shrinkLeeway) {\r
1034         var shrinkProportion = (total - availWidth) / shrinkLeeway;\r
1035         for (i = 0; i < columns.length && total > availWidth; i++) {\r
1036           c = columns[i];\r
1037           var width = widths[i];\r
1038           if (!c.resizable || width <= c.minWidth || width <= absoluteColumnMinWidth) {\r
1039             continue;\r
1040           }\r
1041           var absMinWidth = Math.max(c.minWidth, absoluteColumnMinWidth);\r
1042           var shrinkSize = Math.floor(shrinkProportion * (width - absMinWidth)) || 1;\r
1043           shrinkSize = Math.min(shrinkSize, width - absMinWidth);\r
1044           total -= shrinkSize;\r
1045           shrinkLeeway -= shrinkSize;\r
1046           widths[i] -= shrinkSize;\r
1047         }\r
1048         if (prevTotal == total) {  // avoid infinite loop\r
1049           break;\r
1050         }\r
1051         prevTotal = total;\r
1052       }\r
1053 \r
1054       // grow\r
1055       prevTotal = total;\r
1056       while (total < availWidth) {\r
1057         var growProportion = availWidth / total;\r
1058         for (i = 0; i < columns.length && total < availWidth; i++) {\r
1059           c = columns[i];\r
1060           if (!c.resizable || c.maxWidth <= c.width) {\r
1061             continue;\r
1062           }\r
1063           var growSize = Math.min(Math.floor(growProportion * c.width) - c.width, (c.maxWidth - c.width) || 1000000) || 1;\r
1064           total += growSize;\r
1065           widths[i] += growSize;\r
1066         }\r
1067         if (prevTotal == total) {  // avoid infinite loop\r
1068           break;\r
1069         }\r
1070         prevTotal = total;\r
1071       }\r
1072 \r
1073       var reRender = false;\r
1074       for (i = 0; i < columns.length; i++) {\r
1075         if (columns[i].rerenderOnResize && columns[i].width != widths[i]) {\r
1076           reRender = true;\r
1077         }\r
1078         columns[i].width = widths[i];\r
1079       }\r
1080 \r
1081       applyColumnHeaderWidths();\r
1082       updateCanvasWidth(true);\r
1083       if (reRender) {\r
1084         invalidateAllRows();\r
1085         render();\r
1086       }\r
1087     }\r
1088 \r
1089     function applyColumnHeaderWidths() {\r
1090       if (!initialized) { return; }\r
1091       var h;\r
1092       for (var i = 0, headers = $headers.children(), ii = headers.length; i < ii; i++) {\r
1093         h = $(headers[i]);\r
1094         if (h.width() !== columns[i].width - headerColumnWidthDiff) {\r
1095           h.width(columns[i].width - headerColumnWidthDiff);\r
1096         }\r
1097       }\r
1098 \r
1099       updateColumnCaches();\r
1100     }\r
1101 \r
1102     function applyColumnWidths() {\r
1103       var x = 0, w, rule;\r
1104       for (var i = 0; i < columns.length; i++) {\r
1105         w = columns[i].width;\r
1106 \r
1107         rule = getColumnCssRules(i);\r
1108         rule.left.style.left = x + "px";\r
1109         rule.right.style.right = (canvasWidth - x - w) + "px";\r
1110 \r
1111         x += columns[i].width;\r
1112       }\r
1113     }\r
1114 \r
1115     function setSortColumn(columnId, ascending) {\r
1116       setSortColumns([{ columnId: columnId, sortAsc: ascending}]);\r
1117     }\r
1118 \r
1119     function setSortColumns(cols) {\r
1120       sortColumns = cols;\r
1121 \r
1122       var headerColumnEls = $headers.children();\r
1123       headerColumnEls\r
1124           .removeClass("slick-header-column-sorted")\r
1125           .find(".slick-sort-indicator")\r
1126               .removeClass("slick-sort-indicator-asc slick-sort-indicator-desc");\r
1127 \r
1128       $.each(sortColumns, function(i, col) {\r
1129         if (col.sortAsc == null) {\r
1130           col.sortAsc = true;\r
1131         }\r
1132         var columnIndex = getColumnIndex(col.columnId);\r
1133         if (columnIndex != null) {\r
1134           headerColumnEls.eq(columnIndex)\r
1135               .addClass("slick-header-column-sorted")\r
1136               .find(".slick-sort-indicator")\r
1137                   .addClass(col.sortAsc ? "slick-sort-indicator-asc" : "slick-sort-indicator-desc");\r
1138         }\r
1139       });\r
1140     }\r
1141 \r
1142     function getSortColumns() {\r
1143       return sortColumns;\r
1144     }\r
1145 \r
1146     function handleSelectedRangesChanged(e, ranges) {\r
1147       selectedRows = [];\r
1148       var hash = {};\r
1149       for (var i = 0; i < ranges.length; i++) {\r
1150         for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {\r
1151           if (!hash[j]) {  // prevent duplicates\r
1152             selectedRows.push(j);\r
1153             hash[j] = {};\r
1154           }\r
1155           for (var k = ranges[i].fromCell; k <= ranges[i].toCell; k++) {\r
1156             if (canCellBeSelected(j, k)) {\r
1157               hash[j][columns[k].id] = options.selectedCellCssClass;\r
1158             }\r
1159           }\r
1160         }\r
1161       }\r
1162 \r
1163       setCellCssStyles(options.selectedCellCssClass, hash);\r
1164 \r
1165       trigger(self.onSelectedRowsChanged, {rows: getSelectedRows()}, e);\r
1166     }\r
1167 \r
1168     function getColumns() {\r
1169       return columns;\r
1170     }\r
1171 \r
1172     function updateColumnCaches() {\r
1173       // Pre-calculate cell boundaries.\r
1174       columnPosLeft = [];\r
1175       columnPosRight = [];\r
1176       var x = 0;\r
1177       for (var i = 0, ii = columns.length; i < ii; i++) {\r
1178         columnPosLeft[i] = x;\r
1179         columnPosRight[i] = x + columns[i].width;\r
1180         x += columns[i].width;\r
1181       }\r
1182     }\r
1183 \r
1184     function setColumns(columnDefinitions) {\r
1185       columns = columnDefinitions;\r
1186 \r
1187       columnsById = {};\r
1188       for (var i = 0; i < columns.length; i++) {\r
1189         var m = columns[i] = $.extend({}, columnDefaults, columns[i]);\r
1190         columnsById[m.id] = i;\r
1191         if (m.minWidth && m.width < m.minWidth) {\r
1192           m.width = m.minWidth;\r
1193         }\r
1194         if (m.maxWidth && m.width > m.maxWidth) {\r
1195           m.width = m.maxWidth;\r
1196         }\r
1197       }\r
1198 \r
1199       updateColumnCaches();\r
1200 \r
1201       if (initialized) {\r
1202         invalidateAllRows();\r
1203         createColumnHeaders();\r
1204         removeCssRules();\r
1205         createCssRules();\r
1206         resizeCanvas();\r
1207         applyColumnWidths();\r
1208         handleScroll();\r
1209       }\r
1210     }\r
1211 \r
1212     function getOptions() {\r
1213       return options;\r
1214     }\r
1215 \r
1216     function setOptions(args) {\r
1217       if (!getEditorLock().commitCurrentEdit()) {\r
1218         return;\r
1219       }\r
1220 \r
1221       makeActiveCellNormal();\r
1222 \r
1223       if (options.enableAddRow !== args.enableAddRow) {\r
1224         invalidateRow(getDataLength());\r
1225       }\r
1226 \r
1227       options = $.extend(options, args);\r
1228       validateAndEnforceOptions();\r
1229 \r
1230       $viewport.css("overflow-y", options.autoHeight ? "hidden" : "auto");\r
1231       render();\r
1232     }\r
1233 \r
1234     function validateAndEnforceOptions() {\r
1235       if (options.autoHeight) {\r
1236         options.leaveSpaceForNewRows = false;\r
1237       }\r
1238     }\r
1239 \r
1240     function setData(newData, scrollToTop) {\r
1241       data = newData;\r
1242       invalidateAllRows();\r
1243       updateRowCount();\r
1244       if (scrollToTop) {\r
1245         scrollTo(0);\r
1246       }\r
1247     }\r
1248 \r
1249     function getData() {\r
1250       return data;\r
1251     }\r
1252 \r
1253     function getDataLength() {\r
1254       if (data.getLength) {\r
1255         return data.getLength();\r
1256       } else {\r
1257         return data.length;\r
1258       }\r
1259     }\r
1260 \r
1261     function getDataItem(i) {\r
1262       if (data.getItem) {\r
1263         return data.getItem(i);\r
1264       } else {\r
1265         return data[i];\r
1266       }\r
1267     }\r
1268 \r
1269     function getTopPanel() {\r
1270       return $topPanel[0];\r
1271     }\r
1272 \r
1273     function setTopPanelVisibility(visible) {\r
1274       if (options.showTopPanel != visible) {\r
1275         options.showTopPanel = visible;\r
1276         if (visible) {\r
1277           $topPanelScroller.slideDown("fast", resizeCanvas);\r
1278         } else {\r
1279           $topPanelScroller.slideUp("fast", resizeCanvas);\r
1280         }\r
1281       }\r
1282     }\r
1283 \r
1284     function setHeaderRowVisibility(visible) {\r
1285       if (options.showHeaderRow != visible) {\r
1286         options.showHeaderRow = visible;\r
1287         if (visible) {\r
1288           $headerRowScroller.slideDown("fast", resizeCanvas);\r
1289         } else {\r
1290           $headerRowScroller.slideUp("fast", resizeCanvas);\r
1291         }\r
1292       }\r
1293     }\r
1294 \r
1295     function getContainerNode() {\r
1296       return $container.get(0);\r
1297     }\r
1298 \r
1299     //////////////////////////////////////////////////////////////////////////////////////////////\r
1300     // Rendering / Scrolling\r
1301 \r
1302     function getRowTop(row) {\r
1303       return options.rowHeight * row - offset;\r
1304     }\r
1305 \r
1306     function getRowFromPosition(y) {\r
1307       return Math.floor((y + offset) / options.rowHeight);\r
1308     }\r
1309 \r
1310     function scrollTo(y) {\r
1311       y = Math.max(y, 0);\r
1312       y = Math.min(y, th - viewportH + (viewportHasHScroll ? scrollbarDimensions.height : 0));\r
1313 \r
1314       var oldOffset = offset;\r
1315 \r
1316       page = Math.min(n - 1, Math.floor(y / ph));\r
1317       offset = Math.round(page * cj);\r
1318       var newScrollTop = y - offset;\r
1319 \r
1320       if (offset != oldOffset) {\r
1321         var range = getVisibleRange(newScrollTop);\r
1322         cleanupRows(range);\r
1323         updateRowPositions();\r
1324       }\r
1325 \r
1326       if (prevScrollTop != newScrollTop) {\r
1327         vScrollDir = (prevScrollTop + oldOffset < newScrollTop + offset) ? 1 : -1;\r
1328         $viewport[0].scrollTop = (lastRenderedScrollTop = scrollTop = prevScrollTop = newScrollTop);\r
1329 \r
1330         trigger(self.onViewportChanged, {});\r
1331       }\r
1332     }\r
1333 \r
1334     function defaultFormatter(row, cell, value, columnDef, dataContext) {\r
1335       if (value == null) {\r
1336         return "";\r
1337       } else {\r
1338         return (value + "").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");\r
1339       }\r
1340     }\r
1341 \r
1342     function getFormatter(row, column) {\r
1343       var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);\r
1344 \r
1345       // look up by id, then index\r
1346       var columnOverrides = rowMetadata &&\r
1347           rowMetadata.columns &&\r
1348           (rowMetadata.columns[column.id] || rowMetadata.columns[getColumnIndex(column.id)]);\r
1349 \r
1350       return (columnOverrides && columnOverrides.formatter) ||\r
1351           (rowMetadata && rowMetadata.formatter) ||\r
1352           column.formatter ||\r
1353           (options.formatterFactory && options.formatterFactory.getFormatter(column)) ||\r
1354           options.defaultFormatter;\r
1355     }\r
1356 \r
1357     function getEditor(row, cell) {\r
1358       var column = columns[cell];\r
1359       var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);\r
1360       var columnMetadata = rowMetadata && rowMetadata.columns;\r
1361 \r
1362       if (columnMetadata && columnMetadata[column.id] && columnMetadata[column.id].editor !== undefined) {\r
1363         return columnMetadata[column.id].editor;\r
1364       }\r
1365       if (columnMetadata && columnMetadata[cell] && columnMetadata[cell].editor !== undefined) {\r
1366         return columnMetadata[cell].editor;\r
1367       }\r
1368 \r
1369       return column.editor || (options.editorFactory && options.editorFactory.getEditor(column));\r
1370     }\r
1371 \r
1372     function getDataItemValueForColumn(item, columnDef) {\r
1373       if (options.dataItemColumnValueExtractor) {\r
1374         return options.dataItemColumnValueExtractor(item, columnDef);\r
1375       }\r
1376       return item[columnDef.field];\r
1377     }\r
1378 \r
1379     function appendRowHtml(stringArray, row, range, dataLength) {\r
1380       var d = getDataItem(row);\r
1381       var dataLoading = row < dataLength && !d;\r
1382       var rowCss = "slick-row" +\r
1383           (dataLoading ? " loading" : "") +\r
1384           (row === activeRow ? " active" : "") +\r
1385           (row % 2 == 1 ? " odd" : " even");\r
1386 \r
1387       var metadata = data.getItemMetadata && data.getItemMetadata(row);\r
1388 \r
1389       if (metadata && metadata.cssClasses) {\r
1390         rowCss += " " + metadata.cssClasses;\r
1391       }\r
1392 \r
1393       stringArray.push("<div class='ui-widget-content " + rowCss + "' style='top:" + getRowTop(row) + "px'>");\r
1394 \r
1395       var colspan, m;\r
1396       for (var i = 0, ii = columns.length; i < ii; i++) {\r
1397         m = columns[i];\r
1398         colspan = 1;\r
1399         if (metadata && metadata.columns) {\r
1400           var columnData = metadata.columns[m.id] || metadata.columns[i];\r
1401           colspan = (columnData && columnData.colspan) || 1;\r
1402           if (colspan === "*") {\r
1403             colspan = ii - i;\r
1404           }\r
1405         }\r
1406 \r
1407         // Do not render cells outside of the viewport.\r
1408         if (columnPosRight[Math.min(ii - 1, i + colspan - 1)] > range.leftPx) {\r
1409           if (columnPosLeft[i] > range.rightPx) {\r
1410             // All columns to the right are outside the range.\r
1411             break;\r
1412           }\r
1413 \r
1414           appendCellHtml(stringArray, row, i, colspan, d);\r
1415         }\r
1416 \r
1417         if (colspan > 1) {\r
1418           i += (colspan - 1);\r
1419         }\r
1420       }\r
1421 \r
1422       stringArray.push("</div>");\r
1423     }\r
1424 \r
1425     function appendCellHtml(stringArray, row, cell, colspan, item) {\r
1426       var m = columns[cell];\r
1427       var cellCss = "slick-cell l" + cell + " r" + Math.min(columns.length - 1, cell + colspan - 1) +\r
1428           (m.cssClass ? " " + m.cssClass : "");\r
1429       if (row === activeRow && cell === activeCell) {\r
1430         cellCss += (" active");\r
1431       }\r
1432 \r
1433       // TODO:  merge them together in the setter\r
1434       for (var key in cellCssClasses) {\r
1435         if (cellCssClasses[key][row] && cellCssClasses[key][row][m.id]) {\r
1436           cellCss += (" " + cellCssClasses[key][row][m.id]);\r
1437         }\r
1438       }\r
1439 \r
1440       stringArray.push("<div class='" + cellCss + "'>");\r
1441 \r
1442       // if there is a corresponding row (if not, this is the Add New row or this data hasn't been loaded yet)\r
1443       if (item) {\r
1444         var value = getDataItemValueForColumn(item, m);\r
1445         stringArray.push(getFormatter(row, m)(row, cell, value, m, item));\r
1446       }\r
1447 \r
1448       stringArray.push("</div>");\r
1449 \r
1450       rowsCache[row].cellRenderQueue.push(cell);\r
1451       rowsCache[row].cellColSpans[cell] = colspan;\r
1452     }\r
1453 \r
1454 \r
1455     function cleanupRows(rangeToKeep) {\r
1456       for (var i in rowsCache) {\r
1457         if (((i = parseInt(i, 10)) !== activeRow) && (i < rangeToKeep.top || i > rangeToKeep.bottom)) {\r
1458           removeRowFromCache(i);\r
1459         }\r
1460       }\r
1461     }\r
1462 \r
1463     function invalidate() {\r
1464       updateRowCount();\r
1465       invalidateAllRows();\r
1466       render();\r
1467     }\r
1468 \r
1469     function invalidateAllRows() {\r
1470       if (currentEditor) {\r
1471         makeActiveCellNormal();\r
1472       }\r
1473       for (var row in rowsCache) {\r
1474         removeRowFromCache(row);\r
1475       }\r
1476     }\r
1477 \r
1478     function removeRowFromCache(row) {\r
1479       var cacheEntry = rowsCache[row];\r
1480       if (!cacheEntry) {\r
1481         return;\r
1482       }\r
1483       $canvas[0].removeChild(cacheEntry.rowNode);\r
1484       delete rowsCache[row];\r
1485       delete postProcessedRows[row];\r
1486       renderedRows--;\r
1487       counter_rows_removed++;\r
1488     }\r
1489 \r
1490     function invalidateRows(rows) {\r
1491       var i, rl;\r
1492       if (!rows || !rows.length) {\r
1493         return;\r
1494       }\r
1495       vScrollDir = 0;\r
1496       for (i = 0, rl = rows.length; i < rl; i++) {\r
1497         if (currentEditor && activeRow === rows[i]) {\r
1498           makeActiveCellNormal();\r
1499         }\r
1500         if (rowsCache[rows[i]]) {\r
1501           removeRowFromCache(rows[i]);\r
1502         }\r
1503       }\r
1504     }\r
1505 \r
1506     function invalidateRow(row) {\r
1507       invalidateRows([row]);\r
1508     }\r
1509 \r
1510     function updateCell(row, cell) {\r
1511       var cellNode = getCellNode(row, cell);\r
1512       if (!cellNode) {\r
1513         return;\r
1514       }\r
1515 \r
1516       var m = columns[cell], d = getDataItem(row);\r
1517       if (currentEditor && activeRow === row && activeCell === cell) {\r
1518         currentEditor.loadValue(d);\r
1519       } else {\r
1520         cellNode.innerHTML = d ? getFormatter(row, m)(row, cell, getDataItemValueForColumn(d, m), m, d) : "";\r
1521         invalidatePostProcessingResults(row);\r
1522       }\r
1523     }\r
1524 \r
1525     function updateRow(row) {\r
1526       var cacheEntry = rowsCache[row];\r
1527       if (!cacheEntry) {\r
1528         return;\r
1529       }\r
1530 \r
1531       ensureCellNodesInRowsCache(row);\r
1532 \r
1533       var d = getDataItem(row);\r
1534 \r
1535       for (var columnIdx in cacheEntry.cellNodesByColumnIdx) {\r
1536         if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) {\r
1537           continue;\r
1538         }\r
1539 \r
1540         columnIdx = columnIdx | 0;\r
1541         var m = columns[columnIdx],\r
1542             node = cacheEntry.cellNodesByColumnIdx[columnIdx];\r
1543 \r
1544         if (row === activeRow && columnIdx === activeCell && currentEditor) {\r
1545           currentEditor.loadValue(d);\r
1546         } else if (d) {\r
1547           node.innerHTML = getFormatter(row, m)(row, columnIdx, getDataItemValueForColumn(d, m), m, d);\r
1548         } else {\r
1549           node.innerHTML = "";\r
1550         }\r
1551       }\r
1552 \r
1553       invalidatePostProcessingResults(row);\r
1554     }\r
1555 \r
1556     function getViewportHeight() {\r
1557       return parseFloat($.css($container[0], "height", true)) -\r
1558           parseFloat($.css($container[0], "paddingTop", true)) -\r
1559           parseFloat($.css($container[0], "paddingBottom", true)) -\r
1560           parseFloat($.css($headerScroller[0], "height")) - getVBoxDelta($headerScroller) -\r
1561           (options.showTopPanel ? options.topPanelHeight + getVBoxDelta($topPanelScroller) : 0) -\r
1562           (options.showHeaderRow ? options.headerRowHeight + getVBoxDelta($headerRowScroller) : 0);\r
1563     }\r
1564 \r
1565     function resizeCanvas() {\r
1566       if (!initialized) { return; }\r
1567       if (options.autoHeight) {\r
1568         viewportH = options.rowHeight * (getDataLength() + (options.enableAddRow ? 1 : 0));\r
1569       } else {\r
1570         viewportH = getViewportHeight();\r
1571       }\r
1572 \r
1573       numVisibleRows = Math.ceil(viewportH / options.rowHeight);\r
1574       viewportW = parseFloat($.css($container[0], "width", true));\r
1575       if (!options.autoHeight) {\r
1576         $viewport.height(viewportH);\r
1577       }\r
1578 \r
1579       if (options.forceFitColumns) {\r
1580         autosizeColumns();\r
1581       }\r
1582 \r
1583       updateRowCount();\r
1584       handleScroll();\r
1585       render();\r
1586     }\r
1587 \r
1588     function updateRowCount() {\r
1589       var dataLength = getDataLength();\r
1590       if (!initialized) { return; }\r
1591       numberOfRows = dataLength +\r
1592           (options.enableAddRow ? 1 : 0) +\r
1593           (options.leaveSpaceForNewRows ? numVisibleRows - 1 : 0);\r
1594 \r
1595       var oldViewportHasVScroll = viewportHasVScroll;\r
1596       // with autoHeight, we do not need to accommodate the vertical scroll bar\r
1597       viewportHasVScroll = !options.autoHeight && (numberOfRows * options.rowHeight > viewportH);\r
1598 \r
1599       // remove the rows that are now outside of the data range\r
1600       // this helps avoid redundant calls to .removeRow() when the size of the data decreased by thousands of rows\r
1601       var l = options.enableAddRow ? dataLength : dataLength - 1;\r
1602       for (var i in rowsCache) {\r
1603         if (i >= l) {\r
1604           removeRowFromCache(i);\r
1605         }\r
1606       }\r
1607 \r
1608       if (activeCellNode && activeRow > l) {\r
1609         resetActiveCell();\r
1610       }\r
1611 \r
1612       var oldH = h;\r
1613       th = Math.max(options.rowHeight * numberOfRows, viewportH - scrollbarDimensions.height);\r
1614       if (th < maxSupportedCssHeight) {\r
1615         // just one page\r
1616         h = ph = th;\r
1617         n = 1;\r
1618         cj = 0;\r
1619       } else {\r
1620         // break into pages\r
1621         h = maxSupportedCssHeight;\r
1622         ph = h / 100;\r
1623         n = Math.floor(th / ph);\r
1624         cj = (th - h) / (n - 1);\r
1625       }\r
1626 \r
1627       if (h !== oldH) {\r
1628         $canvas.css("height", h);\r
1629         scrollTop = $viewport[0].scrollTop;\r
1630       }\r
1631 \r
1632       var oldScrollTopInRange = (scrollTop + offset <= th - viewportH);\r
1633 \r
1634       if (th == 0 || scrollTop == 0) {\r
1635         page = offset = 0;\r
1636       } else if (oldScrollTopInRange) {\r
1637         // maintain virtual position\r
1638         scrollTo(scrollTop + offset);\r
1639       } else {\r
1640         // scroll to bottom\r
1641         scrollTo(th - viewportH);\r
1642       }\r
1643 \r
1644       if (h != oldH && options.autoHeight) {\r
1645         resizeCanvas();\r
1646       }\r
1647 \r
1648       if (options.forceFitColumns && oldViewportHasVScroll != viewportHasVScroll) {\r
1649         autosizeColumns();\r
1650       }\r
1651       updateCanvasWidth(false);\r
1652     }\r
1653 \r
1654     function getVisibleRange(viewportTop, viewportLeft) {\r
1655       if (viewportTop == null) {\r
1656         viewportTop = scrollTop;\r
1657       }\r
1658       if (viewportLeft == null) {\r
1659         viewportLeft = scrollLeft;\r
1660       }\r
1661 \r
1662       return {\r
1663         top: getRowFromPosition(viewportTop),\r
1664         bottom: getRowFromPosition(viewportTop + viewportH) + 1,\r
1665         leftPx: viewportLeft,\r
1666         rightPx: viewportLeft + viewportW\r
1667       };\r
1668     }\r
1669 \r
1670     function getRenderedRange(viewportTop, viewportLeft) {\r
1671       var range = getVisibleRange(viewportTop, viewportLeft);\r
1672       var buffer = Math.round(viewportH / options.rowHeight);\r
1673       var minBuffer = 3;\r
1674 \r
1675       if (vScrollDir == -1) {\r
1676         range.top -= buffer;\r
1677         range.bottom += minBuffer;\r
1678       } else if (vScrollDir == 1) {\r
1679         range.top -= minBuffer;\r
1680         range.bottom += buffer;\r
1681       } else {\r
1682         range.top -= minBuffer;\r
1683         range.bottom += minBuffer;\r
1684       }\r
1685 \r
1686       range.top = Math.max(0, range.top);\r
1687       range.bottom = Math.min(options.enableAddRow ? getDataLength() : getDataLength() - 1, range.bottom);\r
1688 \r
1689       range.leftPx -= viewportW;\r
1690       range.rightPx += viewportW;\r
1691 \r
1692       range.leftPx = Math.max(0, range.leftPx);\r
1693       range.rightPx = Math.min(canvasWidth, range.rightPx);\r
1694 \r
1695       return range;\r
1696     }\r
1697 \r
1698     function ensureCellNodesInRowsCache(row) {\r
1699       var cacheEntry = rowsCache[row];\r
1700       if (cacheEntry) {\r
1701         if (cacheEntry.cellRenderQueue.length) {\r
1702           var lastChild = cacheEntry.rowNode.lastChild;\r
1703           while (cacheEntry.cellRenderQueue.length) {\r
1704             var columnIdx = cacheEntry.cellRenderQueue.pop();\r
1705             cacheEntry.cellNodesByColumnIdx[columnIdx] = lastChild;\r
1706             lastChild = lastChild.previousSibling;\r
1707           }\r
1708         }\r
1709       }\r
1710     }\r
1711 \r
1712     function cleanUpCells(range, row) {\r
1713       var totalCellsRemoved = 0;\r
1714       var cacheEntry = rowsCache[row];\r
1715 \r
1716       // Remove cells outside the range.\r
1717       var cellsToRemove = [];\r
1718       for (var i in cacheEntry.cellNodesByColumnIdx) {\r
1719         // I really hate it when people mess with Array.prototype.\r
1720         if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(i)) {\r
1721           continue;\r
1722         }\r
1723 \r
1724         // This is a string, so it needs to be cast back to a number.\r
1725         i = i | 0;\r
1726 \r
1727         var colspan = cacheEntry.cellColSpans[i];\r
1728         if (columnPosLeft[i] > range.rightPx ||\r
1729           columnPosRight[Math.min(columns.length - 1, i + colspan - 1)] < range.leftPx) {\r
1730           if (!(row == activeRow && i == activeCell)) {\r
1731             cellsToRemove.push(i);\r
1732           }\r
1733         }\r
1734       }\r
1735 \r
1736       var cellToRemove;\r
1737       while ((cellToRemove = cellsToRemove.pop()) != null) {\r
1738         cacheEntry.rowNode.removeChild(cacheEntry.cellNodesByColumnIdx[cellToRemove]);\r
1739         delete cacheEntry.cellColSpans[cellToRemove];\r
1740         delete cacheEntry.cellNodesByColumnIdx[cellToRemove];\r
1741         if (postProcessedRows[row]) {\r
1742           delete postProcessedRows[row][cellToRemove];\r
1743         }\r
1744         totalCellsRemoved++;\r
1745       }\r
1746     }\r
1747 \r
1748     function cleanUpAndRenderCells(range) {\r
1749       var cacheEntry;\r
1750       var stringArray = [];\r
1751       var processedRows = [];\r
1752       var cellsAdded;\r
1753       var totalCellsAdded = 0;\r
1754       var colspan;\r
1755 \r
1756       for (var row = range.top, btm = range.bottom; row <= btm; row++) {\r
1757         cacheEntry = rowsCache[row];\r
1758         if (!cacheEntry) {\r
1759           continue;\r
1760         }\r
1761 \r
1762         // cellRenderQueue populated in renderRows() needs to be cleared first\r
1763         ensureCellNodesInRowsCache(row);\r
1764 \r
1765         cleanUpCells(range, row);\r
1766 \r
1767         // Render missing cells.\r
1768         cellsAdded = 0;\r
1769 \r
1770         var metadata = data.getItemMetadata && data.getItemMetadata(row);\r
1771         metadata = metadata && metadata.columns;\r
1772 \r
1773         var d = getDataItem(row);\r
1774 \r
1775         // TODO:  shorten this loop (index? heuristics? binary search?)\r
1776         for (var i = 0, ii = columns.length; i < ii; i++) {\r
1777           // Cells to the right are outside the range.\r
1778           if (columnPosLeft[i] > range.rightPx) {\r
1779             break;\r
1780           }\r
1781 \r
1782           // Already rendered.\r
1783           if ((colspan = cacheEntry.cellColSpans[i]) != null) {\r
1784             i += (colspan > 1 ? colspan - 1 : 0);\r
1785             continue;\r
1786           }\r
1787 \r
1788           colspan = 1;\r
1789           if (metadata) {\r
1790             var columnData = metadata[columns[i].id] || metadata[i];\r
1791             colspan = (columnData && columnData.colspan) || 1;\r
1792             if (colspan === "*") {\r
1793               colspan = ii - i;\r
1794             }\r
1795           }\r
1796 \r
1797           if (columnPosRight[Math.min(ii - 1, i + colspan - 1)] > range.leftPx) {\r
1798             appendCellHtml(stringArray, row, i, colspan, d);\r
1799             cellsAdded++;\r
1800           }\r
1801 \r
1802           i += (colspan > 1 ? colspan - 1 : 0);\r
1803         }\r
1804 \r
1805         if (cellsAdded) {\r
1806           totalCellsAdded += cellsAdded;\r
1807           processedRows.push(row);\r
1808         }\r
1809       }\r
1810 \r
1811       if (!stringArray.length) {\r
1812         return;\r
1813       }\r
1814 \r
1815       var x = document.createElement("div");\r
1816       x.innerHTML = stringArray.join("");\r
1817 \r
1818       var processedRow;\r
1819       var node;\r
1820       while ((processedRow = processedRows.pop()) != null) {\r
1821         cacheEntry = rowsCache[processedRow];\r
1822         var columnIdx;\r
1823         while ((columnIdx = cacheEntry.cellRenderQueue.pop()) != null) {\r
1824           node = x.lastChild;\r
1825           cacheEntry.rowNode.appendChild(node);\r
1826           cacheEntry.cellNodesByColumnIdx[columnIdx] = node;\r
1827         }\r
1828       }\r
1829     }\r
1830 \r
1831     function renderRows(range) {\r
1832       var parentNode = $canvas[0],\r
1833           stringArray = [],\r
1834           rows = [],\r
1835           needToReselectCell = false,\r
1836           dataLength = getDataLength();\r
1837 \r
1838       for (var i = range.top, ii = range.bottom; i <= ii; i++) {\r
1839         if (rowsCache[i]) {\r
1840           continue;\r
1841         }\r
1842         renderedRows++;\r
1843         rows.push(i);\r
1844 \r
1845         // Create an entry right away so that appendRowHtml() can\r
1846         // start populatating it.\r
1847         rowsCache[i] = {\r
1848           "rowNode": null,\r
1849 \r
1850           // ColSpans of rendered cells (by column idx).\r
1851           // Can also be used for checking whether a cell has been rendered.\r
1852           "cellColSpans": [],\r
1853 \r
1854           // Cell nodes (by column idx).  Lazy-populated by ensureCellNodesInRowsCache().\r
1855           "cellNodesByColumnIdx": [],\r
1856 \r
1857           // Column indices of cell nodes that have been rendered, but not yet indexed in\r
1858           // cellNodesByColumnIdx.  These are in the same order as cell nodes added at the\r
1859           // end of the row.\r
1860           "cellRenderQueue": []\r
1861         };\r
1862 \r
1863         appendRowHtml(stringArray, i, range, dataLength);\r
1864         if (activeCellNode && activeRow === i) {\r
1865           needToReselectCell = true;\r
1866         }\r
1867         counter_rows_rendered++;\r
1868       }\r
1869 \r
1870       if (!rows.length) { return; }\r
1871 \r
1872       var x = document.createElement("div");\r
1873       x.innerHTML = stringArray.join("");\r
1874 \r
1875       for (var i = 0, ii = rows.length; i < ii; i++) {\r
1876         rowsCache[rows[i]].rowNode = parentNode.appendChild(x.firstChild);\r
1877       }\r
1878 \r
1879       if (needToReselectCell) {\r
1880         activeCellNode = getCellNode(activeRow, activeCell);\r
1881       }\r
1882     }\r
1883 \r
1884     function startPostProcessing() {\r
1885       if (!options.enableAsyncPostRender) {\r
1886         return;\r
1887       }\r
1888       clearTimeout(h_postrender);\r
1889       h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay);\r
1890     }\r
1891 \r
1892     function invalidatePostProcessingResults(row) {\r
1893       delete postProcessedRows[row];\r
1894       postProcessFromRow = Math.min(postProcessFromRow, row);\r
1895       postProcessToRow = Math.max(postProcessToRow, row);\r
1896       startPostProcessing();\r
1897     }\r
1898 \r
1899     function updateRowPositions() {\r
1900       for (var row in rowsCache) {\r
1901         rowsCache[row].rowNode.style.top = getRowTop(row) + "px";\r
1902       }\r
1903     }\r
1904 \r
1905     function render() {\r
1906       if (!initialized) { return; }\r
1907       var visible = getVisibleRange();\r
1908       var rendered = getRenderedRange();\r
1909 \r
1910       // remove rows no longer in the viewport\r
1911       cleanupRows(rendered);\r
1912 \r
1913       // add new rows & missing cells in existing rows\r
1914       if (lastRenderedScrollLeft != scrollLeft) {\r
1915         cleanUpAndRenderCells(rendered);\r
1916       }\r
1917 \r
1918       // render missing rows\r
1919       renderRows(rendered);\r
1920 \r
1921       postProcessFromRow = visible.top;\r
1922       postProcessToRow = Math.min(options.enableAddRow ? getDataLength() : getDataLength() - 1, visible.bottom);\r
1923       startPostProcessing();\r
1924 \r
1925       lastRenderedScrollTop = scrollTop;\r
1926       lastRenderedScrollLeft = scrollLeft;\r
1927       h_render = null;\r
1928     }\r
1929 \r
1930     function handleHeaderRowScroll() {\r
1931       var scrollLeft = $headerRowScroller[0].scrollLeft;\r
1932       if (scrollLeft != $viewport[0].scrollLeft) {\r
1933         $viewport[0].scrollLeft = scrollLeft;\r
1934       }\r
1935     }\r
1936 \r
1937     function handleScroll() {\r
1938       scrollTop = $viewport[0].scrollTop;\r
1939       scrollLeft = $viewport[0].scrollLeft;\r
1940       var vScrollDist = Math.abs(scrollTop - prevScrollTop);\r
1941       var hScrollDist = Math.abs(scrollLeft - prevScrollLeft);\r
1942 \r
1943       if (hScrollDist) {\r
1944         prevScrollLeft = scrollLeft;\r
1945         $headerScroller[0].scrollLeft = scrollLeft;\r
1946         $topPanelScroller[0].scrollLeft = scrollLeft;\r
1947         $headerRowScroller[0].scrollLeft = scrollLeft;\r
1948       }\r
1949 \r
1950       if (vScrollDist) {\r
1951         vScrollDir = prevScrollTop < scrollTop ? 1 : -1;\r
1952         prevScrollTop = scrollTop;\r
1953 \r
1954         // switch virtual pages if needed\r
1955         if (vScrollDist < viewportH) {\r
1956           scrollTo(scrollTop + offset);\r
1957         } else {\r
1958           var oldOffset = offset;\r
1959           if (h == viewportH) {\r
1960             page = 0;\r
1961           } else {\r
1962             page = Math.min(n - 1, Math.floor(scrollTop * ((th - viewportH) / (h - viewportH)) * (1 / ph)));\r
1963           }\r
1964           offset = Math.round(page * cj);\r
1965           if (oldOffset != offset) {\r
1966             invalidateAllRows();\r
1967           }\r
1968         }\r
1969       }\r
1970 \r
1971       if (hScrollDist || vScrollDist) {\r
1972         if (h_render) {\r
1973           clearTimeout(h_render);\r
1974         }\r
1975 \r
1976         if (Math.abs(lastRenderedScrollTop - scrollTop) > 20 ||\r
1977             Math.abs(lastRenderedScrollLeft - scrollLeft) > 20) {\r
1978           if (options.forceSyncScrolling || (\r
1979               Math.abs(lastRenderedScrollTop - scrollTop) < viewportH &&\r
1980               Math.abs(lastRenderedScrollLeft - scrollLeft) < viewportW)) {\r
1981             render();\r
1982           } else {\r
1983             h_render = setTimeout(render, 50);\r
1984           }\r
1985 \r
1986           trigger(self.onViewportChanged, {});\r
1987         }\r
1988       }\r
1989 \r
1990       trigger(self.onScroll, {scrollLeft: scrollLeft, scrollTop: scrollTop});\r
1991     }\r
1992 \r
1993     function asyncPostProcessRows() {\r
1994       while (postProcessFromRow <= postProcessToRow) {\r
1995         var row = (vScrollDir >= 0) ? postProcessFromRow++ : postProcessToRow--;\r
1996         var cacheEntry = rowsCache[row];\r
1997         if (!cacheEntry || row >= getDataLength()) {\r
1998           continue;\r
1999         }\r
2000 \r
2001         if (!postProcessedRows[row]) {\r
2002           postProcessedRows[row] = {};\r
2003         }\r
2004 \r
2005         ensureCellNodesInRowsCache(row);\r
2006         for (var columnIdx in cacheEntry.cellNodesByColumnIdx) {\r
2007           if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) {\r
2008             continue;\r
2009           }\r
2010 \r
2011           columnIdx = columnIdx | 0;\r
2012 \r
2013           var m = columns[columnIdx];\r
2014           if (m.asyncPostRender && !postProcessedRows[row][columnIdx]) {\r
2015             var node = cacheEntry.cellNodesByColumnIdx[columnIdx];\r
2016             if (node) {\r
2017               m.asyncPostRender(node, row, getDataItem(row), m);\r
2018             }\r
2019             postProcessedRows[row][columnIdx] = true;\r
2020           }\r
2021         }\r
2022 \r
2023         h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay);\r
2024         return;\r
2025       }\r
2026     }\r
2027 \r
2028     function updateCellCssStylesOnRenderedRows(addedHash, removedHash) {\r
2029       var node, columnId, addedRowHash, removedRowHash;\r
2030       for (var row in rowsCache) {\r
2031         removedRowHash = removedHash && removedHash[row];\r
2032         addedRowHash = addedHash && addedHash[row];\r
2033 \r
2034         if (removedRowHash) {\r
2035           for (columnId in removedRowHash) {\r
2036             if (!addedRowHash || removedRowHash[columnId] != addedRowHash[columnId]) {\r
2037               node = getCellNode(row, getColumnIndex(columnId));\r
2038               if (node) {\r
2039                 $(node).removeClass(removedRowHash[columnId]);\r
2040               }\r
2041             }\r
2042           }\r
2043         }\r
2044 \r
2045         if (addedRowHash) {\r
2046           for (columnId in addedRowHash) {\r
2047             if (!removedRowHash || removedRowHash[columnId] != addedRowHash[columnId]) {\r
2048               node = getCellNode(row, getColumnIndex(columnId));\r
2049               if (node) {\r
2050                 $(node).addClass(addedRowHash[columnId]);\r
2051               }\r
2052             }\r
2053           }\r
2054         }\r
2055       }\r
2056     }\r
2057 \r
2058     function addCellCssStyles(key, hash) {\r
2059       if (cellCssClasses[key]) {\r
2060         throw "addCellCssStyles: cell CSS hash with key '" + key + "' already exists.";\r
2061       }\r
2062 \r
2063       cellCssClasses[key] = hash;\r
2064       updateCellCssStylesOnRenderedRows(hash, null);\r
2065 \r
2066       trigger(self.onCellCssStylesChanged, { "key": key, "hash": hash });\r
2067     }\r
2068 \r
2069     function removeCellCssStyles(key) {\r
2070       if (!cellCssClasses[key]) {\r
2071         return;\r
2072       }\r
2073 \r
2074       updateCellCssStylesOnRenderedRows(null, cellCssClasses[key]);\r
2075       delete cellCssClasses[key];\r
2076 \r
2077       trigger(self.onCellCssStylesChanged, { "key": key, "hash": null });\r
2078     }\r
2079 \r
2080     function setCellCssStyles(key, hash) {\r
2081       var prevHash = cellCssClasses[key];\r
2082 \r
2083       cellCssClasses[key] = hash;\r
2084       updateCellCssStylesOnRenderedRows(hash, prevHash);\r
2085 \r
2086       trigger(self.onCellCssStylesChanged, { "key": key, "hash": hash });\r
2087     }\r
2088 \r
2089     function getCellCssStyles(key) {\r
2090       return cellCssClasses[key];\r
2091     }\r
2092 \r
2093     function flashCell(row, cell, speed) {\r
2094       speed = speed || 100;\r
2095       if (rowsCache[row]) {\r
2096         var $cell = $(getCellNode(row, cell));\r
2097 \r
2098         function toggleCellClass(times) {\r
2099           if (!times) {\r
2100             return;\r
2101           }\r
2102           setTimeout(function () {\r
2103                 $cell.queue(function () {\r
2104                   $cell.toggleClass(options.cellFlashingCssClass).dequeue();\r
2105                   toggleCellClass(times - 1);\r
2106                 });\r
2107               },\r
2108               speed);\r
2109         }\r
2110 \r
2111         toggleCellClass(4);\r
2112       }\r
2113     }\r
2114 \r
2115     //////////////////////////////////////////////////////////////////////////////////////////////\r
2116     // Interactivity\r
2117 \r
2118     function handleDragInit(e, dd) {\r
2119       var cell = getCellFromEvent(e);\r
2120       if (!cell || !cellExists(cell.row, cell.cell)) {\r
2121         return false;\r
2122       }\r
2123 \r
2124       var retval = trigger(self.onDragInit, dd, e);\r
2125       if (e.isImmediatePropagationStopped()) {\r
2126         return retval;\r
2127       }\r
2128 \r
2129       // if nobody claims to be handling drag'n'drop by stopping immediate propagation,\r
2130       // cancel out of it\r
2131       return false;\r
2132     }\r
2133 \r
2134     function handleDragStart(e, dd) {\r
2135       var cell = getCellFromEvent(e);\r
2136       if (!cell || !cellExists(cell.row, cell.cell)) {\r
2137         return false;\r
2138       }\r
2139 \r
2140       var retval = trigger(self.onDragStart, dd, e);\r
2141       if (e.isImmediatePropagationStopped()) {\r
2142         return retval;\r
2143       }\r
2144 \r
2145       return false;\r
2146     }\r
2147 \r
2148     function handleDrag(e, dd) {\r
2149       return trigger(self.onDrag, dd, e);\r
2150     }\r
2151 \r
2152     function handleDragEnd(e, dd) {\r
2153       trigger(self.onDragEnd, dd, e);\r
2154     }\r
2155 \r
2156     function handleKeyDown(e) {\r
2157       trigger(self.onKeyDown, {row: activeRow, cell: activeCell}, e);\r
2158       var handled = e.isImmediatePropagationStopped();\r
2159 \r
2160       if (!handled) {\r
2161         if (!e.shiftKey && !e.altKey && !e.ctrlKey) {\r
2162           if (e.which == 27) {\r
2163             if (!getEditorLock().isActive()) {\r
2164               return; // no editing mode to cancel, allow bubbling and default processing (exit without cancelling the event)\r
2165             }\r
2166             cancelEditAndSetFocus();\r
2167           } else if (e.which == 37) {\r
2168             handled = navigateLeft();\r
2169           } else if (e.which == 39) {\r
2170             handled = navigateRight();\r
2171           } else if (e.which == 38) {\r
2172             handled = navigateUp();\r
2173           } else if (e.which == 40) {\r
2174             handled = navigateDown();\r
2175           } else if (e.which == 9) {\r
2176             handled = navigateNext();\r
2177           } else if (e.which == 13) {\r
2178             if (options.editable) {\r
2179               if (currentEditor) {\r
2180                 // adding new row\r
2181                 if (activeRow === getDataLength()) {\r
2182                   navigateDown();\r
2183                 } else {\r
2184                   commitEditAndSetFocus();\r
2185                 }\r
2186               } else {\r
2187                 if (getEditorLock().commitCurrentEdit()) {\r
2188                   makeActiveCellEditable();\r
2189                 }\r
2190               }\r
2191             }\r
2192             handled = true;\r
2193           }\r
2194         } else if (e.which == 9 && e.shiftKey && !e.ctrlKey && !e.altKey) {\r
2195           handled = navigatePrev();\r
2196         }\r
2197       }\r
2198 \r
2199       if (handled) {\r
2200         // the event has been handled so don't let parent element (bubbling/propagation) or browser (default) handle it\r
2201         e.stopPropagation();\r
2202         e.preventDefault();\r
2203         try {\r
2204           e.originalEvent.keyCode = 0; // prevent default behaviour for special keys in IE browsers (F3, F5, etc.)\r
2205         }\r
2206         // ignore exceptions - setting the original event's keycode throws access denied exception for "Ctrl"\r
2207         // (hitting control key only, nothing else), "Shift" (maybe others)\r
2208         catch (error) {\r
2209         }\r
2210       }\r
2211     }\r
2212 \r
2213     function handleClick(e) {\r
2214       if (!currentEditor) {\r
2215         // if this click resulted in some cell child node getting focus,\r
2216         // don't steal it back - keyboard events will still bubble up\r
2217         if (e.target != document.activeElement) {\r
2218           setFocus();\r
2219         }\r
2220       }\r
2221 \r
2222       var cell = getCellFromEvent(e);\r
2223       if (!cell || (currentEditor !== null && activeRow == cell.row && activeCell == cell.cell)) {\r
2224         return;\r
2225       }\r
2226 \r
2227       trigger(self.onClick, {row: cell.row, cell: cell.cell}, e);\r
2228       if (e.isImmediatePropagationStopped()) {\r
2229         return;\r
2230       }\r
2231 \r
2232       if ((activeCell != cell.cell || activeRow != cell.row) && canCellBeActive(cell.row, cell.cell)) {\r
2233         if (!getEditorLock().isActive() || getEditorLock().commitCurrentEdit()) {\r
2234           scrollRowIntoView(cell.row, false);\r
2235           setActiveCellInternal(getCellNode(cell.row, cell.cell), (cell.row === getDataLength()) || options.autoEdit);\r
2236         }\r
2237       }\r
2238     }\r
2239 \r
2240     function handleContextMenu(e) {\r
2241       var $cell = $(e.target).closest(".slick-cell", $canvas);\r
2242       if ($cell.length === 0) {\r
2243         return;\r
2244       }\r
2245 \r
2246       // are we editing this cell?\r
2247       if (activeCellNode === $cell[0] && currentEditor !== null) {\r
2248         return;\r
2249       }\r
2250 \r
2251       trigger(self.onContextMenu, {}, e);\r
2252     }\r
2253 \r
2254     function handleDblClick(e) {\r
2255       var cell = getCellFromEvent(e);\r
2256       if (!cell || (currentEditor !== null && activeRow == cell.row && activeCell == cell.cell)) {\r
2257         return;\r
2258       }\r
2259 \r
2260       trigger(self.onDblClick, {row: cell.row, cell: cell.cell}, e);\r
2261       if (e.isImmediatePropagationStopped()) {\r
2262         return;\r
2263       }\r
2264 \r
2265       if (options.editable) {\r
2266         gotoCell(cell.row, cell.cell, true);\r
2267       }\r
2268     }\r
2269 \r
2270     function handleHeaderMouseEnter(e) {\r
2271       trigger(self.onHeaderMouseEnter, {\r
2272         "column": $(this).data("column")\r
2273       }, e);\r
2274     }\r
2275 \r
2276     function handleHeaderMouseLeave(e) {\r
2277       trigger(self.onHeaderMouseLeave, {\r
2278         "column": $(this).data("column")\r
2279       }, e);\r
2280     }\r
2281 \r
2282     function handleHeaderContextMenu(e) {\r
2283       var $header = $(e.target).closest(".slick-header-column", ".slick-header-columns");\r
2284       var column = $header && $header.data("column");\r
2285       trigger(self.onHeaderContextMenu, {column: column}, e);\r
2286     }\r
2287 \r
2288     function handleHeaderClick(e) {\r
2289       var $header = $(e.target).closest(".slick-header-column", ".slick-header-columns");\r
2290       var column = $header && $header.data("column");\r
2291       if (column) {\r
2292         trigger(self.onHeaderClick, {column: column}, e);\r
2293       }\r
2294     }\r
2295 \r
2296     function handleMouseEnter(e) {\r
2297       trigger(self.onMouseEnter, {}, e);\r
2298     }\r
2299 \r
2300     function handleMouseLeave(e) {\r
2301       trigger(self.onMouseLeave, {}, e);\r
2302     }\r
2303 \r
2304     function cellExists(row, cell) {\r
2305       return !(row < 0 || row >= getDataLength() || cell < 0 || cell >= columns.length);\r
2306     }\r
2307 \r
2308     function getCellFromPoint(x, y) {\r
2309       var row = getRowFromPosition(y);\r
2310       var cell = 0;\r
2311 \r
2312       var w = 0;\r
2313       for (var i = 0; i < columns.length && w < x; i++) {\r
2314         w += columns[i].width;\r
2315         cell++;\r
2316       }\r
2317 \r
2318       if (cell < 0) {\r
2319         cell = 0;\r
2320       }\r
2321 \r
2322       return {row: row, cell: cell - 1};\r
2323     }\r
2324 \r
2325     function getCellFromNode(cellNode) {\r
2326       // read column number from .l<columnNumber> CSS class\r
2327       var cls = /l\d+/.exec(cellNode.className);\r
2328       if (!cls) {\r
2329         throw "getCellFromNode: cannot get cell - " + cellNode.className;\r
2330       }\r
2331       return parseInt(cls[0].substr(1, cls[0].length - 1), 10);\r
2332     }\r
2333 \r
2334     function getRowFromNode(rowNode) {\r
2335       for (var row in rowsCache) {\r
2336         if (rowsCache[row].rowNode === rowNode) {\r
2337           return row | 0;\r
2338         }\r
2339       }\r
2340 \r
2341       return null;\r
2342     }\r
2343 \r
2344     function getCellFromEvent(e) {\r
2345       var $cell = $(e.target).closest(".slick-cell", $canvas);\r
2346       if (!$cell.length) {\r
2347         return null;\r
2348       }\r
2349 \r
2350       var row = getRowFromNode($cell[0].parentNode);\r
2351       var cell = getCellFromNode($cell[0]);\r
2352 \r
2353       if (row == null || cell == null) {\r
2354         return null;\r
2355       } else {\r
2356         return {\r
2357           "row": row,\r
2358           "cell": cell\r
2359         };\r
2360       }\r
2361     }\r
2362 \r
2363     function getCellNodeBox(row, cell) {\r
2364       if (!cellExists(row, cell)) {\r
2365         return null;\r
2366       }\r
2367 \r
2368       var y1 = getRowTop(row);\r
2369       var y2 = y1 + options.rowHeight - 1;\r
2370       var x1 = 0;\r
2371       for (var i = 0; i < cell; i++) {\r
2372         x1 += columns[i].width;\r
2373       }\r
2374       var x2 = x1 + columns[cell].width;\r
2375 \r
2376       return {\r
2377         top: y1,\r
2378         left: x1,\r
2379         bottom: y2,\r
2380         right: x2\r
2381       };\r
2382     }\r
2383 \r
2384     //////////////////////////////////////////////////////////////////////////////////////////////\r
2385     // Cell switching\r
2386 \r
2387     function resetActiveCell() {\r
2388       setActiveCellInternal(null, false);\r
2389     }\r
2390 \r
2391     function setFocus() {\r
2392       if (tabbingDirection == -1) {\r
2393         $focusSink[0].focus();\r
2394       } else {\r
2395         $focusSink2[0].focus();\r
2396       }\r
2397     }\r
2398 \r
2399     function scrollCellIntoView(row, cell, doPaging) {\r
2400       scrollRowIntoView(row, doPaging);\r
2401 \r
2402       var colspan = getColspan(row, cell);\r
2403       var left = columnPosLeft[cell],\r
2404         right = columnPosRight[cell + (colspan > 1 ? colspan - 1 : 0)],\r
2405         scrollRight = scrollLeft + viewportW;\r
2406 \r
2407       if (left < scrollLeft) {\r
2408         $viewport.scrollLeft(left);\r
2409         handleScroll();\r
2410         render();\r
2411       } else if (right > scrollRight) {\r
2412         $viewport.scrollLeft(Math.min(left, right - $viewport[0].clientWidth));\r
2413         handleScroll();\r
2414         render();\r
2415       }\r
2416     }\r
2417 \r
2418     function setActiveCellInternal(newCell, editMode) {\r
2419       if (activeCellNode !== null) {\r
2420         makeActiveCellNormal();\r
2421         $(activeCellNode).removeClass("active");\r
2422         if (rowsCache[activeRow]) {\r
2423           $(rowsCache[activeRow].rowNode).removeClass("active");\r
2424         }\r
2425       }\r
2426 \r
2427       var activeCellChanged = (activeCellNode !== newCell);\r
2428       activeCellNode = newCell;\r
2429 \r
2430       if (activeCellNode != null) {\r
2431         activeRow = getRowFromNode(activeCellNode.parentNode);\r
2432         activeCell = activePosX = getCellFromNode(activeCellNode);\r
2433 \r
2434         $(activeCellNode).addClass("active");\r
2435         $(rowsCache[activeRow].rowNode).addClass("active");\r
2436 \r
2437         if (options.editable && editMode && isCellPotentiallyEditable(activeRow, activeCell)) {\r
2438           clearTimeout(h_editorLoader);\r
2439 \r
2440           if (options.asyncEditorLoading) {\r
2441             h_editorLoader = setTimeout(function () {\r
2442               makeActiveCellEditable();\r
2443             }, options.asyncEditorLoadDelay);\r
2444           } else {\r
2445             makeActiveCellEditable();\r
2446           }\r
2447         }\r
2448       } else {\r
2449         activeRow = activeCell = null;\r
2450       }\r
2451 \r
2452       if (activeCellChanged) {\r
2453         trigger(self.onActiveCellChanged, getActiveCell());\r
2454       }\r
2455     }\r
2456 \r
2457     function clearTextSelection() {\r
2458       if (document.selection && document.selection.empty) {\r
2459         try {\r
2460           //IE fails here if selected element is not in dom\r
2461           document.selection.empty();\r
2462         } catch (e) { }\r
2463       } else if (window.getSelection) {\r
2464         var sel = window.getSelection();\r
2465         if (sel && sel.removeAllRanges) {\r
2466           sel.removeAllRanges();\r
2467         }\r
2468       }\r
2469     }\r
2470 \r
2471     function isCellPotentiallyEditable(row, cell) {\r
2472       // is the data for this row loaded?\r
2473       if (row < getDataLength() && !getDataItem(row)) {\r
2474         return false;\r
2475       }\r
2476 \r
2477       // are we in the Add New row?  can we create new from this cell?\r
2478       if (columns[cell].cannotTriggerInsert && row >= getDataLength()) {\r
2479         return false;\r
2480       }\r
2481 \r
2482       // does this cell have an editor?\r
2483       if (!getEditor(row, cell)) {\r
2484         return false;\r
2485       }\r
2486 \r
2487       return true;\r
2488     }\r
2489 \r
2490     function makeActiveCellNormal() {\r
2491       if (!currentEditor) {\r
2492         return;\r
2493       }\r
2494       trigger(self.onBeforeCellEditorDestroy, {editor: currentEditor});\r
2495       currentEditor.destroy();\r
2496       currentEditor = null;\r
2497 \r
2498       if (activeCellNode) {\r
2499         var d = getDataItem(activeRow);\r
2500         $(activeCellNode).removeClass("editable invalid");\r
2501         if (d) {\r
2502           var column = columns[activeCell];\r
2503           var formatter = getFormatter(activeRow, column);\r
2504           activeCellNode.innerHTML = formatter(activeRow, activeCell, getDataItemValueForColumn(d, column), column, d);\r
2505           invalidatePostProcessingResults(activeRow);\r
2506         }\r
2507       }\r
2508 \r
2509       // if there previously was text selected on a page (such as selected text in the edit cell just removed),\r
2510       // IE can't set focus to anything else correctly\r
2511       if (navigator.userAgent.toLowerCase().match(/msie/)) {\r
2512         clearTextSelection();\r
2513       }\r
2514 \r
2515       getEditorLock().deactivate(editController);\r
2516     }\r
2517 \r
2518     function makeActiveCellEditable(editor) {\r
2519       if (!activeCellNode) {\r
2520         return;\r
2521       }\r
2522       if (!options.editable) {\r
2523         throw "Grid : makeActiveCellEditable : should never get called when options.editable is false";\r
2524       }\r
2525 \r
2526       // cancel pending async call if there is one\r
2527       clearTimeout(h_editorLoader);\r
2528 \r
2529       if (!isCellPotentiallyEditable(activeRow, activeCell)) {\r
2530         return;\r
2531       }\r
2532 \r
2533       var columnDef = columns[activeCell];\r
2534       var item = getDataItem(activeRow);\r
2535 \r
2536       if (trigger(self.onBeforeEditCell, {row: activeRow, cell: activeCell, item: item, column: columnDef}) === false) {\r
2537         setFocus();\r
2538         return;\r
2539       }\r
2540 \r
2541       getEditorLock().activate(editController);\r
2542       $(activeCellNode).addClass("editable");\r
2543 \r
2544       // don't clear the cell if a custom editor is passed through\r
2545       if (!editor) {\r
2546         activeCellNode.innerHTML = "";\r
2547       }\r
2548 \r
2549       currentEditor = new (editor || getEditor(activeRow, activeCell))({\r
2550         grid: self,\r
2551         gridPosition: absBox($container[0]),\r
2552         position: absBox(activeCellNode),\r
2553         container: activeCellNode,\r
2554         column: columnDef,\r
2555         item: item || {},\r
2556         commitChanges: commitEditAndSetFocus,\r
2557         cancelChanges: cancelEditAndSetFocus\r
2558       });\r
2559 \r
2560       if (item) {\r
2561         currentEditor.loadValue(item);\r
2562       }\r
2563 \r
2564       serializedEditorValue = currentEditor.serializeValue();\r
2565 \r
2566       if (currentEditor.position) {\r
2567         handleActiveCellPositionChange();\r
2568       }\r
2569     }\r
2570 \r
2571     function commitEditAndSetFocus() {\r
2572       // if the commit fails, it would do so due to a validation error\r
2573       // if so, do not steal the focus from the editor\r
2574       if (getEditorLock().commitCurrentEdit()) {\r
2575         setFocus();\r
2576         if (options.autoEdit) {\r
2577           navigateDown();\r
2578         }\r
2579       }\r
2580     }\r
2581 \r
2582     function cancelEditAndSetFocus() {\r
2583       if (getEditorLock().cancelCurrentEdit()) {\r
2584         setFocus();\r
2585       }\r
2586     }\r
2587 \r
2588     function absBox(elem) {\r
2589       var box = {\r
2590         top: elem.offsetTop,\r
2591         left: elem.offsetLeft,\r
2592         bottom: 0,\r
2593         right: 0,\r
2594         width: $(elem).outerWidth(),\r
2595         height: $(elem).outerHeight(),\r
2596         visible: true};\r
2597       box.bottom = box.top + box.height;\r
2598       box.right = box.left + box.width;\r
2599 \r
2600       // walk up the tree\r
2601       var offsetParent = elem.offsetParent;\r
2602       while ((elem = elem.parentNode) != document.body) {\r
2603         if (box.visible && elem.scrollHeight != elem.offsetHeight && $(elem).css("overflowY") != "visible") {\r
2604           box.visible = box.bottom > elem.scrollTop && box.top < elem.scrollTop + elem.clientHeight;\r
2605         }\r
2606 \r
2607         if (box.visible && elem.scrollWidth != elem.offsetWidth && $(elem).css("overflowX") != "visible") {\r
2608           box.visible = box.right > elem.scrollLeft && box.left < elem.scrollLeft + elem.clientWidth;\r
2609         }\r
2610 \r
2611         box.left -= elem.scrollLeft;\r
2612         box.top -= elem.scrollTop;\r
2613 \r
2614         if (elem === offsetParent) {\r
2615           box.left += elem.offsetLeft;\r
2616           box.top += elem.offsetTop;\r
2617           offsetParent = elem.offsetParent;\r
2618         }\r
2619 \r
2620         box.bottom = box.top + box.height;\r
2621         box.right = box.left + box.width;\r
2622       }\r
2623 \r
2624       return box;\r
2625     }\r
2626 \r
2627     function getActiveCellPosition() {\r
2628       return absBox(activeCellNode);\r
2629     }\r
2630 \r
2631     function getGridPosition() {\r
2632       return absBox($container[0])\r
2633     }\r
2634 \r
2635     function handleActiveCellPositionChange() {\r
2636       if (!activeCellNode) {\r
2637         return;\r
2638       }\r
2639 \r
2640       trigger(self.onActiveCellPositionChanged, {});\r
2641 \r
2642       if (currentEditor) {\r
2643         var cellBox = getActiveCellPosition();\r
2644         if (currentEditor.show && currentEditor.hide) {\r
2645           if (!cellBox.visible) {\r
2646             currentEditor.hide();\r
2647           } else {\r
2648             currentEditor.show();\r
2649           }\r
2650         }\r
2651 \r
2652         if (currentEditor.position) {\r
2653           currentEditor.position(cellBox);\r
2654         }\r
2655       }\r
2656     }\r
2657 \r
2658     function getCellEditor() {\r
2659       return currentEditor;\r
2660     }\r
2661 \r
2662     function getActiveCell() {\r
2663       if (!activeCellNode) {\r
2664         return null;\r
2665       } else {\r
2666         return {row: activeRow, cell: activeCell};\r
2667       }\r
2668     }\r
2669 \r
2670     function getActiveCellNode() {\r
2671       return activeCellNode;\r
2672     }\r
2673 \r
2674     function scrollRowIntoView(row, doPaging) {\r
2675       var rowAtTop = row * options.rowHeight;\r
2676       var rowAtBottom = (row + 1) * options.rowHeight - viewportH + (viewportHasHScroll ? scrollbarDimensions.height : 0);\r
2677 \r
2678       // need to page down?\r
2679       if ((row + 1) * options.rowHeight > scrollTop + viewportH + offset) {\r
2680         scrollTo(doPaging ? rowAtTop : rowAtBottom);\r
2681         render();\r
2682       }\r
2683       // or page up?\r
2684       else if (row * options.rowHeight < scrollTop + offset) {\r
2685         scrollTo(doPaging ? rowAtBottom : rowAtTop);\r
2686         render();\r
2687       }\r
2688     }\r
2689 \r
2690     function scrollRowToTop(row) {\r
2691       scrollTo(row * options.rowHeight);\r
2692       render();\r
2693     }\r
2694 \r
2695     function getColspan(row, cell) {\r
2696       var metadata = data.getItemMetadata && data.getItemMetadata(row);\r
2697       if (!metadata || !metadata.columns) {\r
2698         return 1;\r
2699       }\r
2700 \r
2701       var columnData = metadata.columns[columns[cell].id] || metadata.columns[cell];\r
2702       var colspan = (columnData && columnData.colspan);\r
2703       if (colspan === "*") {\r
2704         colspan = columns.length - cell;\r
2705       } else {\r
2706         colspan = colspan || 1;\r
2707       }\r
2708 \r
2709       return colspan;\r
2710     }\r
2711 \r
2712     function findFirstFocusableCell(row) {\r
2713       var cell = 0;\r
2714       while (cell < columns.length) {\r
2715         if (canCellBeActive(row, cell)) {\r
2716           return cell;\r
2717         }\r
2718         cell += getColspan(row, cell);\r
2719       }\r
2720       return null;\r
2721     }\r
2722 \r
2723     function findLastFocusableCell(row) {\r
2724       var cell = 0;\r
2725       var lastFocusableCell = null;\r
2726       while (cell < columns.length) {\r
2727         if (canCellBeActive(row, cell)) {\r
2728           lastFocusableCell = cell;\r
2729         }\r
2730         cell += getColspan(row, cell);\r
2731       }\r
2732       return lastFocusableCell;\r
2733     }\r
2734 \r
2735     function gotoRight(row, cell, posX) {\r
2736       if (cell >= columns.length) {\r
2737         return null;\r
2738       }\r
2739 \r
2740       do {\r
2741         cell += getColspan(row, cell);\r
2742       }\r
2743       while (cell < columns.length && !canCellBeActive(row, cell));\r
2744 \r
2745       if (cell < columns.length) {\r
2746         return {\r
2747           "row": row,\r
2748           "cell": cell,\r
2749           "posX": cell\r
2750         };\r
2751       }\r
2752       return null;\r
2753     }\r
2754 \r
2755     function gotoLeft(row, cell, posX) {\r
2756       if (cell <= 0) {\r
2757         return null;\r
2758       }\r
2759 \r
2760       var firstFocusableCell = findFirstFocusableCell(row);\r
2761       if (firstFocusableCell === null || firstFocusableCell >= cell) {\r
2762         return null;\r
2763       }\r
2764 \r
2765       var prev = {\r
2766         "row": row,\r
2767         "cell": firstFocusableCell,\r
2768         "posX": firstFocusableCell\r
2769       };\r
2770       var pos;\r
2771       while (true) {\r
2772         pos = gotoRight(prev.row, prev.cell, prev.posX);\r
2773         if (!pos) {\r
2774           return null;\r
2775         }\r
2776         if (pos.cell >= cell) {\r
2777           return prev;\r
2778         }\r
2779         prev = pos;\r
2780       }\r
2781     }\r
2782 \r
2783     function gotoDown(row, cell, posX) {\r
2784       var prevCell;\r
2785       while (true) {\r
2786         if (++row >= getDataLength() + (options.enableAddRow ? 1 : 0)) {\r
2787           return null;\r
2788         }\r
2789 \r
2790         prevCell = cell = 0;\r
2791         while (cell <= posX) {\r
2792           prevCell = cell;\r
2793           cell += getColspan(row, cell);\r
2794         }\r
2795 \r
2796         if (canCellBeActive(row, prevCell)) {\r
2797           return {\r
2798             "row": row,\r
2799             "cell": prevCell,\r
2800             "posX": posX\r
2801           };\r
2802         }\r
2803       }\r
2804     }\r
2805 \r
2806     function gotoUp(row, cell, posX) {\r
2807       var prevCell;\r
2808       while (true) {\r
2809         if (--row < 0) {\r
2810           return null;\r
2811         }\r
2812 \r
2813         prevCell = cell = 0;\r
2814         while (cell <= posX) {\r
2815           prevCell = cell;\r
2816           cell += getColspan(row, cell);\r
2817         }\r
2818 \r
2819         if (canCellBeActive(row, prevCell)) {\r
2820           return {\r
2821             "row": row,\r
2822             "cell": prevCell,\r
2823             "posX": posX\r
2824           };\r
2825         }\r
2826       }\r
2827     }\r
2828 \r
2829     function gotoNext(row, cell, posX) {\r
2830       if (row == null && cell == null) {\r
2831         row = cell = posX = 0;\r
2832         if (canCellBeActive(row, cell)) {\r
2833           return {\r
2834             "row": row,\r
2835             "cell": cell,\r
2836             "posX": cell\r
2837           };\r
2838         }\r
2839       }\r
2840 \r
2841       var pos = gotoRight(row, cell, posX);\r
2842       if (pos) {\r
2843         return pos;\r
2844       }\r
2845 \r
2846       var firstFocusableCell = null;\r
2847       while (++row < getDataLength() + (options.enableAddRow ? 1 : 0)) {\r
2848         firstFocusableCell = findFirstFocusableCell(row);\r
2849         if (firstFocusableCell !== null) {\r
2850           return {\r
2851             "row": row,\r
2852             "cell": firstFocusableCell,\r
2853             "posX": firstFocusableCell\r
2854           };\r
2855         }\r
2856       }\r
2857       return null;\r
2858     }\r
2859 \r
2860     function gotoPrev(row, cell, posX) {\r
2861       if (row == null && cell == null) {\r
2862         row = getDataLength() + (options.enableAddRow ? 1 : 0) - 1;\r
2863         cell = posX = columns.length - 1;\r
2864         if (canCellBeActive(row, cell)) {\r
2865           return {\r
2866             "row": row,\r
2867             "cell": cell,\r
2868             "posX": cell\r
2869           };\r
2870         }\r
2871       }\r
2872 \r
2873       var pos;\r
2874       var lastSelectableCell;\r
2875       while (!pos) {\r
2876         pos = gotoLeft(row, cell, posX);\r
2877         if (pos) {\r
2878           break;\r
2879         }\r
2880         if (--row < 0) {\r
2881           return null;\r
2882         }\r
2883 \r
2884         cell = 0;\r
2885         lastSelectableCell = findLastFocusableCell(row);\r
2886         if (lastSelectableCell !== null) {\r
2887           pos = {\r
2888             "row": row,\r
2889             "cell": lastSelectableCell,\r
2890             "posX": lastSelectableCell\r
2891           };\r
2892         }\r
2893       }\r
2894       return pos;\r
2895     }\r
2896 \r
2897     function navigateRight() {\r
2898       return navigate("right");\r
2899     }\r
2900 \r
2901     function navigateLeft() {\r
2902       return navigate("left");\r
2903     }\r
2904 \r
2905     function navigateDown() {\r
2906       return navigate("down");\r
2907     }\r
2908 \r
2909     function navigateUp() {\r
2910       return navigate("up");\r
2911     }\r
2912 \r
2913     function navigateNext() {\r
2914       return navigate("next");\r
2915     }\r
2916 \r
2917     function navigatePrev() {\r
2918       return navigate("prev");\r
2919     }\r
2920 \r
2921     /**\r
2922      * @param {string} dir Navigation direction.\r
2923      * @return {boolean} Whether navigation resulted in a change of active cell.\r
2924      */\r
2925     function navigate(dir) {\r
2926       if (!options.enableCellNavigation) {\r
2927         return false;\r
2928       }\r
2929 \r
2930       if (!activeCellNode && dir != "prev" && dir != "next") {\r
2931         return false;\r
2932       }\r
2933 \r
2934       if (!getEditorLock().commitCurrentEdit()) {\r
2935         return true;\r
2936       }\r
2937       setFocus();\r
2938 \r
2939       var tabbingDirections = {\r
2940         "up": -1,\r
2941         "down": 1,\r
2942         "left": -1,\r
2943         "right": 1,\r
2944         "prev": -1,\r
2945         "next": 1\r
2946       };\r
2947       tabbingDirection = tabbingDirections[dir];\r
2948 \r
2949       var stepFunctions = {\r
2950         "up": gotoUp,\r
2951         "down": gotoDown,\r
2952         "left": gotoLeft,\r
2953         "right": gotoRight,\r
2954         "prev": gotoPrev,\r
2955         "next": gotoNext\r
2956       };\r
2957       var stepFn = stepFunctions[dir];\r
2958       var pos = stepFn(activeRow, activeCell, activePosX);\r
2959       if (pos) {\r
2960         var isAddNewRow = (pos.row == getDataLength());\r
2961         scrollCellIntoView(pos.row, pos.cell, !isAddNewRow);\r
2962         setActiveCellInternal(getCellNode(pos.row, pos.cell), isAddNewRow || options.autoEdit);\r
2963         activePosX = pos.posX;\r
2964         return true;\r
2965       } else {\r
2966         setActiveCellInternal(getCellNode(activeRow, activeCell), (activeRow == getDataLength()) || options.autoEdit);\r
2967         return false;\r
2968       }\r
2969     }\r
2970 \r
2971     function getCellNode(row, cell) {\r
2972       if (rowsCache[row]) {\r
2973         ensureCellNodesInRowsCache(row);\r
2974         return rowsCache[row].cellNodesByColumnIdx[cell];\r
2975       }\r
2976       return null;\r
2977     }\r
2978 \r
2979     function setActiveCell(row, cell) {\r
2980       if (!initialized) { return; }\r
2981       if (row > getDataLength() || row < 0 || cell >= columns.length || cell < 0) {\r
2982         return;\r
2983       }\r
2984 \r
2985       if (!options.enableCellNavigation) {\r
2986         return;\r
2987       }\r
2988 \r
2989       scrollCellIntoView(row, cell, false);\r
2990       setActiveCellInternal(getCellNode(row, cell), false);\r
2991     }\r
2992 \r
2993     function canCellBeActive(row, cell) {\r
2994       if (!options.enableCellNavigation || row >= getDataLength() + (options.enableAddRow ? 1 : 0) ||\r
2995           row < 0 || cell >= columns.length || cell < 0) {\r
2996         return false;\r
2997       }\r
2998 \r
2999       var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);\r
3000       if (rowMetadata && typeof rowMetadata.focusable === "boolean") {\r
3001         return rowMetadata.focusable;\r
3002       }\r
3003 \r
3004       var columnMetadata = rowMetadata && rowMetadata.columns;\r
3005       if (columnMetadata && columnMetadata[columns[cell].id] && typeof columnMetadata[columns[cell].id].focusable === "boolean") {\r
3006         return columnMetadata[columns[cell].id].focusable;\r
3007       }\r
3008       if (columnMetadata && columnMetadata[cell] && typeof columnMetadata[cell].focusable === "boolean") {\r
3009         return columnMetadata[cell].focusable;\r
3010       }\r
3011 \r
3012       return columns[cell].focusable;\r
3013     }\r
3014 \r
3015     function canCellBeSelected(row, cell) {\r
3016       if (row >= getDataLength() || row < 0 || cell >= columns.length || cell < 0) {\r
3017         return false;\r
3018       }\r
3019 \r
3020       var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);\r
3021       if (rowMetadata && typeof rowMetadata.selectable === "boolean") {\r
3022         return rowMetadata.selectable;\r
3023       }\r
3024 \r
3025       var columnMetadata = rowMetadata && rowMetadata.columns && (rowMetadata.columns[columns[cell].id] || rowMetadata.columns[cell]);\r
3026       if (columnMetadata && typeof columnMetadata.selectable === "boolean") {\r
3027         return columnMetadata.selectable;\r
3028       }\r
3029 \r
3030       return columns[cell].selectable;\r
3031     }\r
3032 \r
3033     function gotoCell(row, cell, forceEdit) {\r
3034       if (!initialized) { return; }\r
3035       if (!canCellBeActive(row, cell)) {\r
3036         return;\r
3037       }\r
3038 \r
3039       if (!getEditorLock().commitCurrentEdit()) {\r
3040         return;\r
3041       }\r
3042 \r
3043       scrollCellIntoView(row, cell, false);\r
3044 \r
3045       var newCell = getCellNode(row, cell);\r
3046 \r
3047       // if selecting the 'add new' row, start editing right away\r
3048       setActiveCellInternal(newCell, forceEdit || (row === getDataLength()) || options.autoEdit);\r
3049 \r
3050       // if no editor was created, set the focus back on the grid\r
3051       if (!currentEditor) {\r
3052         setFocus();\r
3053       }\r
3054     }\r
3055 \r
3056 \r
3057     //////////////////////////////////////////////////////////////////////////////////////////////\r
3058     // IEditor implementation for the editor lock\r
3059 \r
3060     function commitCurrentEdit() {\r
3061       var item = getDataItem(activeRow);\r
3062       var column = columns[activeCell];\r
3063 \r
3064       if (currentEditor) {\r
3065         if (currentEditor.isValueChanged()) {\r
3066           var validationResults = currentEditor.validate();\r
3067 \r
3068           if (validationResults.valid) {\r
3069             if (activeRow < getDataLength()) {\r
3070               var editCommand = {\r
3071                 row: activeRow,\r
3072                 cell: activeCell,\r
3073                 editor: currentEditor,\r
3074                 serializedValue: currentEditor.serializeValue(),\r
3075                 prevSerializedValue: serializedEditorValue,\r
3076                 execute: function () {\r
3077                   this.editor.applyValue(item, this.serializedValue);\r
3078                   updateRow(this.row);\r
3079                 },\r
3080                 undo: function () {\r
3081                   this.editor.applyValue(item, this.prevSerializedValue);\r
3082                   updateRow(this.row);\r
3083                 }\r
3084               };\r
3085 \r
3086               if (options.editCommandHandler) {\r
3087                 makeActiveCellNormal();\r
3088                 options.editCommandHandler(item, column, editCommand);\r
3089               } else {\r
3090                 editCommand.execute();\r
3091                 makeActiveCellNormal();\r
3092               }\r
3093 \r
3094               trigger(self.onCellChange, {\r
3095                 row: activeRow,\r
3096                 cell: activeCell,\r
3097                 item: item\r
3098               });\r
3099             } else {\r
3100               var newItem = {};\r
3101               currentEditor.applyValue(newItem, currentEditor.serializeValue());\r
3102               makeActiveCellNormal();\r
3103               trigger(self.onAddNewRow, {item: newItem, column: column});\r
3104             }\r
3105 \r
3106             // check whether the lock has been re-acquired by event handlers\r
3107             return !getEditorLock().isActive();\r
3108           } else {\r
3109             // Re-add the CSS class to trigger transitions, if any.\r
3110             $(activeCellNode).removeClass("invalid");\r
3111             $(activeCellNode).width();  // force layout\r
3112             $(activeCellNode).addClass("invalid");\r
3113 \r
3114             trigger(self.onValidationError, {\r
3115               editor: currentEditor,\r
3116               cellNode: activeCellNode,\r
3117               validationResults: validationResults,\r
3118               row: activeRow,\r
3119               cell: activeCell,\r
3120               column: column\r
3121             });\r
3122 \r
3123             currentEditor.focus();\r
3124             return false;\r
3125           }\r
3126         }\r
3127 \r
3128         makeActiveCellNormal();\r
3129       }\r
3130       return true;\r
3131     }\r
3132 \r
3133     function cancelCurrentEdit() {\r
3134       makeActiveCellNormal();\r
3135       return true;\r
3136     }\r
3137 \r
3138     function rowsToRanges(rows) {\r
3139       var ranges = [];\r
3140       var lastCell = columns.length - 1;\r
3141       for (var i = 0; i < rows.length; i++) {\r
3142         ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));\r
3143       }\r
3144       return ranges;\r
3145     }\r
3146 \r
3147     function getSelectedRows() {\r
3148       if (!selectionModel) {\r
3149         throw "Selection model is not set";\r
3150       }\r
3151       return selectedRows;\r
3152     }\r
3153 \r
3154     function setSelectedRows(rows) {\r
3155       if (!selectionModel) {\r
3156         throw "Selection model is not set";\r
3157       }\r
3158       selectionModel.setSelectedRanges(rowsToRanges(rows));\r
3159     }\r
3160 \r
3161 \r
3162     //////////////////////////////////////////////////////////////////////////////////////////////\r
3163     // Debug\r
3164 \r
3165     this.debug = function () {\r
3166       var s = "";\r
3167 \r
3168       s += ("\n" + "counter_rows_rendered:  " + counter_rows_rendered);\r
3169       s += ("\n" + "counter_rows_removed:  " + counter_rows_removed);\r
3170       s += ("\n" + "renderedRows:  " + renderedRows);\r
3171       s += ("\n" + "numVisibleRows:  " + numVisibleRows);\r
3172       s += ("\n" + "maxSupportedCssHeight:  " + maxSupportedCssHeight);\r
3173       s += ("\n" + "n(umber of pages):  " + n);\r
3174       s += ("\n" + "(current) page:  " + page);\r
3175       s += ("\n" + "page height (ph):  " + ph);\r
3176       s += ("\n" + "vScrollDir:  " + vScrollDir);\r
3177 \r
3178       alert(s);\r
3179     };\r
3180 \r
3181     // a debug helper to be able to access private members\r
3182     this.eval = function (expr) {\r
3183       return eval(expr);\r
3184     };\r
3185 \r
3186     //////////////////////////////////////////////////////////////////////////////////////////////\r
3187     // Public API\r
3188 \r
3189     $.extend(this, {\r
3190       "slickGridVersion": "2.1",\r
3191 \r
3192       // Events\r
3193       "onScroll": new Slick.Event(),\r
3194       "onSort": new Slick.Event(),\r
3195       "onHeaderMouseEnter": new Slick.Event(),\r
3196       "onHeaderMouseLeave": new Slick.Event(),\r
3197       "onHeaderContextMenu": new Slick.Event(),\r
3198       "onHeaderClick": new Slick.Event(),\r
3199       "onHeaderCellRendered": new Slick.Event(),\r
3200       "onBeforeHeaderCellDestroy": new Slick.Event(),\r
3201       "onHeaderRowCellRendered": new Slick.Event(),\r
3202       "onBeforeHeaderRowCellDestroy": new Slick.Event(),\r
3203       "onMouseEnter": new Slick.Event(),\r
3204       "onMouseLeave": new Slick.Event(),\r
3205       "onClick": new Slick.Event(),\r
3206       "onDblClick": new Slick.Event(),\r
3207       "onContextMenu": new Slick.Event(),\r
3208       "onKeyDown": new Slick.Event(),\r
3209       "onAddNewRow": new Slick.Event(),\r
3210       "onValidationError": new Slick.Event(),\r
3211       "onViewportChanged": new Slick.Event(),\r
3212       "onColumnsReordered": new Slick.Event(),\r
3213       "onColumnsResized": new Slick.Event(),\r
3214       "onCellChange": new Slick.Event(),\r
3215       "onBeforeEditCell": new Slick.Event(),\r
3216       "onBeforeCellEditorDestroy": new Slick.Event(),\r
3217       "onBeforeDestroy": new Slick.Event(),\r
3218       "onActiveCellChanged": new Slick.Event(),\r
3219       "onActiveCellPositionChanged": new Slick.Event(),\r
3220       "onDragInit": new Slick.Event(),\r
3221       "onDragStart": new Slick.Event(),\r
3222       "onDrag": new Slick.Event(),\r
3223       "onDragEnd": new Slick.Event(),\r
3224       "onSelectedRowsChanged": new Slick.Event(),\r
3225       "onCellCssStylesChanged": new Slick.Event(),\r
3226 \r
3227       // Methods\r
3228       "registerPlugin": registerPlugin,\r
3229       "unregisterPlugin": unregisterPlugin,\r
3230       "getColumns": getColumns,\r
3231       "setColumns": setColumns,\r
3232       "getColumnIndex": getColumnIndex,\r
3233       "updateColumnHeader": updateColumnHeader,\r
3234       "setSortColumn": setSortColumn,\r
3235       "setSortColumns": setSortColumns,\r
3236       "getSortColumns": getSortColumns,\r
3237       "autosizeColumns": autosizeColumns,\r
3238       "getOptions": getOptions,\r
3239       "setOptions": setOptions,\r
3240       "getData": getData,\r
3241       "getDataLength": getDataLength,\r
3242       "getDataItem": getDataItem,\r
3243       "setData": setData,\r
3244       "getSelectionModel": getSelectionModel,\r
3245       "setSelectionModel": setSelectionModel,\r
3246       "getSelectedRows": getSelectedRows,\r
3247       "setSelectedRows": setSelectedRows,\r
3248       "getContainerNode": getContainerNode,\r
3249 \r
3250       "render": render,\r
3251       "invalidate": invalidate,\r
3252       "invalidateRow": invalidateRow,\r
3253       "invalidateRows": invalidateRows,\r
3254       "invalidateAllRows": invalidateAllRows,\r
3255       "updateCell": updateCell,\r
3256       "updateRow": updateRow,\r
3257       "getViewport": getVisibleRange,\r
3258       "getRenderedRange": getRenderedRange,\r
3259       "resizeCanvas": resizeCanvas,\r
3260       "updateRowCount": updateRowCount,\r
3261       "scrollRowIntoView": scrollRowIntoView,\r
3262       "scrollRowToTop": scrollRowToTop,\r
3263       "scrollCellIntoView": scrollCellIntoView,\r
3264       "getCanvasNode": getCanvasNode,\r
3265       "focus": setFocus,\r
3266 \r
3267       "getCellFromPoint": getCellFromPoint,\r
3268       "getCellFromEvent": getCellFromEvent,\r
3269       "getActiveCell": getActiveCell,\r
3270       "setActiveCell": setActiveCell,\r
3271       "getActiveCellNode": getActiveCellNode,\r
3272       "getActiveCellPosition": getActiveCellPosition,\r
3273       "resetActiveCell": resetActiveCell,\r
3274       "editActiveCell": makeActiveCellEditable,\r
3275       "getCellEditor": getCellEditor,\r
3276       "getCellNode": getCellNode,\r
3277       "getCellNodeBox": getCellNodeBox,\r
3278       "canCellBeSelected": canCellBeSelected,\r
3279       "canCellBeActive": canCellBeActive,\r
3280       "navigatePrev": navigatePrev,\r
3281       "navigateNext": navigateNext,\r
3282       "navigateUp": navigateUp,\r
3283       "navigateDown": navigateDown,\r
3284       "navigateLeft": navigateLeft,\r
3285       "navigateRight": navigateRight,\r
3286       "gotoCell": gotoCell,\r
3287       "getTopPanel": getTopPanel,\r
3288       "setTopPanelVisibility": setTopPanelVisibility,\r
3289       "setHeaderRowVisibility": setHeaderRowVisibility,\r
3290       "getHeaderRow": getHeaderRow,\r
3291       "getHeaderRowColumn": getHeaderRowColumn,\r
3292       "getGridPosition": getGridPosition,\r
3293       "flashCell": flashCell,\r
3294       "addCellCssStyles": addCellCssStyles,\r
3295       "setCellCssStyles": setCellCssStyles,\r
3296       "removeCellCssStyles": removeCellCssStyles,\r
3297       "getCellCssStyles": getCellCssStyles,\r
3298 \r
3299       "init": finishInitialization,\r
3300       "destroy": destroy,\r
3301 \r
3302       // IEditor implementation\r
3303       "getEditorLock": getEditorLock,\r
3304       "getEditController": getEditController\r
3305     });\r
3306 \r
3307     init();\r
3308   }\r
3309 }(jQuery));\r