rails_slickgrid 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2504 @@
1
+ /**
2
+ * @license
3
+ * (c) 2009-2010 Michael Leibman (michael.leibman@gmail.com)
4
+ * http://github.com/mleibman/slickgrid
5
+ * Distributed under MIT license.
6
+ * All rights reserved.
7
+ *
8
+ * SlickGrid v1.4.3
9
+ *
10
+ * TODO:
11
+ * - frozen columns
12
+ * - consistent events (EventHelper? jQuery events?)
13
+ *
14
+ *
15
+ * OPTIONS:
16
+ * rowHeight - (default 25px) Row height in pixels.
17
+ * enableAddRow - (default false) If true, a blank row will be displayed at the bottom - typing values in that row will add a new one.
18
+ * leaveSpaceForNewRows - (default false)
19
+ * editable - (default false) If false, no cells will be switched into edit mode.
20
+ * autoEdit - (default true) Cell will not automatically go into edit mode when selected.
21
+ * enableCellNavigation - (default true) If false, no cells will be selectable.
22
+ * enableCellRangeSelection - (default false) If true, user will be able to select a cell range. onCellRangeSelected event will be fired.
23
+ * defaultColumnWidth - (default 80px) Default column width in pixels (if columns[cell].width is not specified).
24
+ * enableColumnReorder - (default true) Allows the user to reorder columns.
25
+ * asyncEditorLoading - (default false) Makes cell editors load asynchronously after a small delay.
26
+ * This greatly increases keyboard navigation speed.
27
+ * asyncEditorLoadDelay - (default 100msec) Delay after which cell editor is loaded. Ignored unless asyncEditorLoading is true.
28
+ * forceFitColumns - (default false) Force column sizes to fit into the viewport (avoid horizontal scrolling).
29
+ * enableAsyncPostRender - (default false) If true, async post rendering will occur and asyncPostRender delegates on columns will be called.
30
+ * asyncPostRenderDelay - (default 60msec) Delay after which async post renderer delegate is called.
31
+ * autoHeight - (default false) If true, vertically resizes to fit all rows.
32
+ * editorLock - (default Slick.GlobalEditorLock) A Slick.EditorLock instance to use for controlling concurrent data edits.
33
+ * showSecondaryHeaderRow - (default false) If true, an extra blank (to be populated externally) row will be displayed just below the header columns.
34
+ * secondaryHeaderRowHeight - (default 25px) The height of the secondary header row.
35
+ * syncColumnCellResize - (default false) Synchronously resize column cells when column headers are resized
36
+ * rowCssClasses - (default null) A function which (given a row's data item as an argument) returns a space-delimited string of CSS classes that will be applied to the slick-row element. Note that this should be fast, as it is called every time a row is displayed.
37
+ * cellHighlightCssClass - (default "highlighted") A CSS class to apply to cells highlighted via setHighlightedCells().
38
+ * cellFlashingCssClass - (default "flashing") A CSS class to apply to flashing cells (flashCell()).
39
+ * formatterFactory - (default null) A factory object responsible to creating a formatter for a given cell.
40
+ * Must implement getFormatter(column).
41
+ * editorFactory - (default null) A factory object responsible to creating an editor for a given cell.
42
+ * Must implement getEditor(column).
43
+ * multiSelect - (default true) Enable multiple row selection.
44
+ *
45
+ * COLUMN DEFINITION (columns) OPTIONS:
46
+ * id - Column ID.
47
+ * name - Column name to put in the header.
48
+ * toolTip - Tooltip (if different from name).
49
+ * field - Property of the data context to bind to.
50
+ * formatter - (default 'return value || ""') Function responsible for rendering the contents of a cell. Signature: function formatter(row, cell, value, columnDef, dataContext) { ... return "..."; }
51
+ * editor - An Editor class.
52
+ * validator - An extra validation function to be passed to the editor.
53
+ * unselectable - If true, the cell cannot be selected (and therefore edited).
54
+ * cannotTriggerInsert - If true, a new row cannot be created from just the value of this cell.
55
+ * width - Width of the column in pixels.
56
+ * resizable - (default true) If false, the column cannot be resized.
57
+ * sortable - (default false) If true, the column can be sorted (onSort will be called).
58
+ * minWidth - Minimum allowed column width for resizing.
59
+ * maxWidth - Maximum allowed column width for resizing.
60
+ * cssClass - A CSS class to add to the cell.
61
+ * rerenderOnResize - Rerender the column when it is resized (useful for columns relying on cell width or adaptive formatters).
62
+ * asyncPostRender - Function responsible for manipulating the cell DOM node after it has been rendered (called in the background).
63
+ * behavior - Configures the column with one of several available predefined behaviors: "select", "move", "selectAndMove".
64
+ *
65
+ *
66
+ * EVENTS:
67
+ * onSort -
68
+ * onHeaderContextMenu -
69
+ * onHeaderClick -
70
+ * onClick -
71
+ * onDblClick -
72
+ * onContextMenu -
73
+ * onKeyDown -
74
+ * onAddNewRow -
75
+ * onValidationError -
76
+ * onViewportChanged -
77
+ * onSelectedRowsChanged -
78
+ * onColumnsReordered -
79
+ * onColumnsResized -
80
+ * onBeforeMoveRows -
81
+ * onMoveRows -
82
+ * onCellChange - Raised when cell has been edited. Args: row,cell,dataContext.
83
+ * onBeforeEditCell - Raised before a cell goes into edit mode. Return false to cancel. Args: row,cell,dataContext.
84
+ * onBeforeCellEditorDestroy - Raised before a cell editor is destroyed. Args: current cell editor.
85
+ * onBeforeDestroy - Raised just before the grid control is destroyed (part of the destroy() method).
86
+ * onCurrentCellChanged - Raised when the selected (active) cell changed. Args: {row:currentRow, cell:currentCell}.
87
+ * onCellRangeSelected - Raised when a user selects a range of cells. Args: {from:{row,cell}, to:{row,cell}}.
88
+ *
89
+ * NOTES:
90
+ * Cell/row DOM manipulations are done directly bypassing jQuery's DOM manipulation methods.
91
+ * This increases the speed dramatically, but can only be done safely because there are no event handlers
92
+ * or data associated with any cell/row DOM nodes. Cell editors must make sure they implement .destroy()
93
+ * and do proper cleanup.
94
+ *
95
+ *
96
+ * @param {Node} container Container node to create the grid in.
97
+ * @param {Array} or {Object} data An array of objects for databinding.
98
+ * @param {Array} columns An array of column definitions.
99
+ * @param {Object} options Grid options.
100
+ */
101
+
102
+ // make sure required JavaScript modules are loaded
103
+ if (typeof jQuery === "undefined") {
104
+ throw new Error("SlickGrid requires jquery module to be loaded");
105
+ }
106
+ if (!jQuery.fn.drag) {
107
+ throw new Error("SlickGrid requires jquery.event.drag module to be loaded");
108
+ }
109
+
110
+ (function($) {
111
+ var scrollbarDimensions; // shared across all grids on this page
112
+
113
+
114
+ //////////////////////////////////////////////////////////////////////////////////////////////
115
+ // EditorLock class implementation (available as Slick.EditorLock)
116
+
117
+ /** @constructor */
118
+ function EditorLock() {
119
+ /// <summary>
120
+ /// Track currently active edit controller and ensure
121
+ /// that onle a single controller can be active at a time.
122
+ /// Edit controller is an object that is responsible for
123
+ /// gory details of looking after editor in the browser,
124
+ /// and allowing EditorLock clients to either accept
125
+ /// or cancel editor changes without knowing any of the
126
+ /// implementation details. SlickGrid instance is used
127
+ /// as edit controller for cell editors.
128
+ /// </summary>
129
+
130
+ var currentEditController = null;
131
+
132
+ this.isActive = function isActive(editController) {
133
+ /// <summary>
134
+ /// Return true if the specified editController
135
+ /// is currently active in this lock instance
136
+ /// (i.e. if that controller acquired edit lock).
137
+ /// If invoked without parameters ("editorLock.isActive()"),
138
+ /// return true if any editController is currently
139
+ /// active in this lock instance.
140
+ /// </summary>
141
+ return (editController ? currentEditController === editController : currentEditController !== null);
142
+ };
143
+
144
+ this.activate = function activate(editController) {
145
+ /// <summary>
146
+ /// Set the specified editController as the active
147
+ /// controller in this lock instance (acquire edit lock).
148
+ /// If another editController is already active,
149
+ /// an error will be thrown (i.e. before calling
150
+ /// this method isActive() must be false,
151
+ /// afterwards isActive() will be true).
152
+ /// </summary>
153
+ if (editController === currentEditController) { // already activated?
154
+ return;
155
+ }
156
+ if (currentEditController !== null) {
157
+ throw "SlickGrid.EditorLock.activate: an editController is still active, can't activate another editController";
158
+ }
159
+ if (!editController.commitCurrentEdit) {
160
+ throw "SlickGrid.EditorLock.activate: editController must implement .commitCurrentEdit()";
161
+ }
162
+ if (!editController.cancelCurrentEdit) {
163
+ throw "SlickGrid.EditorLock.activate: editController must implement .cancelCurrentEdit()";
164
+ }
165
+ currentEditController = editController;
166
+ };
167
+
168
+ this.deactivate = function deactivate(editController) {
169
+ /// <summary>
170
+ /// Unset the specified editController as the active
171
+ /// controller in this lock instance (release edit lock).
172
+ /// If the specified editController is not the editController
173
+ /// that is currently active in this lock instance,
174
+ /// an error will be thrown.
175
+ /// </summary>
176
+ if (currentEditController !== editController) {
177
+ throw "SlickGrid.EditorLock.deactivate: specified editController is not the currently active one";
178
+ }
179
+ currentEditController = null;
180
+ };
181
+
182
+ this.commitCurrentEdit = function commitCurrentEdit() {
183
+ /// <summary>
184
+ /// Invoke the "commitCurrentEdit" method on the
185
+ /// editController that is active in this lock
186
+ /// instance and return the return value of that method
187
+ /// (if no controller is active, return true).
188
+ /// "commitCurrentEdit" is expected to return true
189
+ /// to indicate successful commit, false otherwise.
190
+ /// </summary>
191
+ return (currentEditController ? currentEditController.commitCurrentEdit() : true);
192
+ };
193
+
194
+ this.cancelCurrentEdit = function cancelCurrentEdit() {
195
+ /// <summary>
196
+ /// Invoke the "cancelCurrentEdit" method on the
197
+ /// editController that is active in this lock
198
+ /// instance (if no controller is active, do nothing).
199
+ /// Returns true if the edit was succesfully cancelled.
200
+ /// </summary>
201
+ return (currentEditController ? currentEditController.cancelCurrentEdit() : true);
202
+ };
203
+ } // end of EditorLock function (class)
204
+
205
+
206
+
207
+ //////////////////////////////////////////////////////////////////////////////////////////////
208
+ // SlickGrid class implementation (available as Slick.Grid)
209
+
210
+ /** @constructor */
211
+ function SlickGrid(container,data,columns,options) {
212
+ /// <summary>
213
+ /// Create and manage virtual grid in the specified $container,
214
+ /// connecting it to the specified data source. Data is presented
215
+ /// as a grid with the specified columns and data.length rows.
216
+ /// Options alter behaviour of the grid.
217
+ /// </summary>
218
+
219
+ // settings
220
+ var defaults = {
221
+ rowHeight: 25,
222
+ defaultColumnWidth: 80,
223
+ enableAddRow: false,
224
+ leaveSpaceForNewRows: false,
225
+ editable: false,
226
+ autoEdit: true,
227
+ enableCellNavigation: true,
228
+ enableCellRangeSelection: false,
229
+ enableColumnReorder: true,
230
+ asyncEditorLoading: false,
231
+ asyncEditorLoadDelay: 100,
232
+ forceFitColumns: false,
233
+ enableAsyncPostRender: false,
234
+ asyncPostRenderDelay: 60,
235
+ autoHeight: false,
236
+ editorLock: Slick.GlobalEditorLock,
237
+ showSecondaryHeaderRow: false,
238
+ secondaryHeaderRowHeight: 25,
239
+ syncColumnCellResize: false,
240
+ enableAutoTooltips: true,
241
+ toolTipMaxLength: null,
242
+ formatterFactory: null,
243
+ editorFactory: null,
244
+ cellHighlightCssClass: "highlighted",
245
+ cellFlashingCssClass: "flashing",
246
+ multiSelect: true
247
+ },
248
+ gridData, gridDataGetLength, gridDataGetItem;
249
+
250
+ var columnDefaults = {
251
+ name: "",
252
+ resizable: true,
253
+ sortable: false,
254
+ minWidth: 30
255
+ };
256
+
257
+ // scroller
258
+ var maxSupportedCssHeight; // browser's breaking point
259
+ var th; // virtual height
260
+ var h; // real scrollable height
261
+ var ph; // page height
262
+ var n; // number of pages
263
+ var cj; // "jumpiness" coefficient
264
+
265
+ var page = 0; // current page
266
+ var offset = 0; // current page offset
267
+ var scrollDir = 1;
268
+
269
+ // private
270
+ var $container;
271
+ var uid = "slickgrid_" + Math.round(1000000 * Math.random());
272
+ var self = this;
273
+ var $headerScroller;
274
+ var $headers;
275
+ var $secondaryHeaderScroller;
276
+ var $secondaryHeaders;
277
+ var $viewport;
278
+ var $canvas;
279
+ var $style;
280
+ var stylesheet;
281
+ var viewportH, viewportW;
282
+ var viewportHasHScroll;
283
+ var headerColumnWidthDiff, headerColumnHeightDiff, cellWidthDiff, cellHeightDiff; // padding+border
284
+ var absoluteColumnMinWidth;
285
+
286
+ var currentRow, currentCell;
287
+ var currentCellNode = null;
288
+ var currentEditor = null;
289
+ var serializedEditorValue;
290
+ var editController;
291
+
292
+ var rowsCache = {};
293
+ var renderedRows = 0;
294
+ var numVisibleRows;
295
+ var prevScrollTop = 0;
296
+ var scrollTop = 0;
297
+ var lastRenderedScrollTop = 0;
298
+ var prevScrollLeft = 0;
299
+ var avgRowRenderTime = 10;
300
+
301
+ var selectedRows = [];
302
+ var selectedRowsLookup = {};
303
+ var columnsById = {};
304
+ var highlightedCells;
305
+ var sortColumnId;
306
+ var sortAsc = true;
307
+
308
+ // async call handles
309
+ var h_editorLoader = null;
310
+ var h_render = null;
311
+ var h_postrender = null;
312
+ var postProcessedRows = {};
313
+ var postProcessToRow = null;
314
+ var postProcessFromRow = null;
315
+
316
+ // perf counters
317
+ var counter_rows_rendered = 0;
318
+ var counter_rows_removed = 0;
319
+
320
+
321
+ //////////////////////////////////////////////////////////////////////////////////////////////
322
+ // Initialization
323
+
324
+ function init() {
325
+ /// <summary>
326
+ /// Initialize 'this' (self) instance of a SlickGrid.
327
+ /// This function is called by the constructor.
328
+ /// </summary>
329
+
330
+ $container = $(container);
331
+
332
+ gridData = data;
333
+ gridDataGetLength = gridData.getLength || defaultGetLength;
334
+ gridDataGetItem = gridData.getItem || defaultGetItem;
335
+
336
+ maxSupportedCssHeight = getMaxSupportedCssHeight();
337
+
338
+ scrollbarDimensions = scrollbarDimensions || measureScrollbar(); // skip measurement if already have dimensions
339
+ options = $.extend({},defaults,options);
340
+ columnDefaults.width = options.defaultColumnWidth;
341
+
342
+ // validate loaded JavaScript modules against requested options
343
+ if (options.enableColumnReorder && !$.fn.sortable) {
344
+ throw new Error("SlickGrid's \"enableColumnReorder = true\" option requires jquery-ui.sortable module to be loaded");
345
+ }
346
+
347
+ editController = {
348
+ "commitCurrentEdit": commitCurrentEdit,
349
+ "cancelCurrentEdit": cancelCurrentEdit
350
+ };
351
+
352
+ $container
353
+ .empty()
354
+ .attr("tabIndex",0)
355
+ .attr("hideFocus",true)
356
+ .css("overflow","hidden")
357
+ .css("outline",0)
358
+ .addClass(uid)
359
+ .addClass("ui-widget");
360
+
361
+ // set up a positioning container if needed
362
+ if (!/relative|absolute|fixed/.test($container.css("position")))
363
+ $container.css("position","relative");
364
+
365
+ $headerScroller = $("<div class='slick-header ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
366
+ $headers = $("<div class='slick-header-columns' style='width:100000px; left:-10000px' />").appendTo($headerScroller);
367
+
368
+ $secondaryHeaderScroller = $("<div class='slick-header-secondary ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
369
+ $secondaryHeaders = $("<div class='slick-header-columns-secondary' style='width:100000px' />").appendTo($secondaryHeaderScroller);
370
+
371
+ if (!options.showSecondaryHeaderRow) {
372
+ $secondaryHeaderScroller.hide();
373
+ }
374
+
375
+ $viewport = $("<div class='slick-viewport' tabIndex='0' hideFocus style='width:100%;overflow-x:auto;outline:0;position:relative;overflow-y:auto;'>").appendTo($container);
376
+ $canvas = $("<div class='grid-canvas' tabIndex='0' hideFocus />").appendTo($viewport);
377
+
378
+ // header columns and cells may have different padding/border skewing width calculations (box-sizing, hello?)
379
+ // calculate the diff so we can set consistent sizes
380
+ measureCellPaddingAndBorder();
381
+
382
+ $viewport.height(
383
+ $container.innerHeight() -
384
+ $headerScroller.outerHeight() -
385
+ (options.showSecondaryHeaderRow ? $secondaryHeaderScroller.outerHeight() : 0));
386
+
387
+ // for usability reasons, all text selection in SlickGrid is disabled
388
+ // with the exception of input and textarea elements (selection must
389
+ // be enabled there so that editors work as expected); note that
390
+ // selection in grid cells (grid body) is already unavailable in
391
+ // all browsers except IE
392
+ disableSelection($headers); // disable all text selection in header (including input and textarea)
393
+ $viewport.bind("selectstart.ui", function (event) { return $(event.target).is("input,textarea"); }); // disable text selection in grid cells except in input and textarea elements (this is IE-specific, because selectstart event will only fire in IE)
394
+
395
+ createColumnHeaders();
396
+ setupColumnSort();
397
+ setupDragEvents();
398
+ createCssRules();
399
+
400
+ resizeAndRender();
401
+
402
+ bindAncestorScrollEvents();
403
+ $viewport.bind("scroll.slickgrid", handleScroll);
404
+ $container.bind("resize.slickgrid", resizeAndRender);
405
+ $canvas.bind("keydown.slickgrid", handleKeyDown);
406
+ $canvas.bind("click.slickgrid", handleClick);
407
+ $canvas.bind("dblclick.slickgrid", handleDblClick);
408
+ $canvas.bind("contextmenu.slickgrid", handleContextMenu);
409
+ $canvas.bind("mouseover.slickgrid", handleHover);
410
+ $headerScroller.bind("contextmenu.slickgrid", handleHeaderContextMenu);
411
+ $headerScroller.bind("click.slickgrid", handleHeaderClick);
412
+ }
413
+
414
+ function measureScrollbar() {
415
+ /// <summary>
416
+ /// Measure width of a vertical scrollbar
417
+ /// and height of a horizontal scrollbar.
418
+ /// </summary
419
+ /// <returns>
420
+ /// { width: pixelWidth, height: pixelHeight }
421
+ /// </returns>
422
+ var $c = $("<div style='position:absolute; top:-10000px; left:-10000px; width:100px; height:100px; overflow:scroll;'></div>").appendTo("body");
423
+ var dim = { width: $c.width() - $c[0].clientWidth, height: $c.height() - $c[0].clientHeight };
424
+ $c.remove();
425
+ return dim;
426
+ }
427
+
428
+ function setCanvasWidth(width) {
429
+ $canvas.width(width);
430
+ viewportHasHScroll = (width > viewportW - scrollbarDimensions.width);
431
+ }
432
+
433
+ function disableSelection($target) {
434
+ /// <summary>
435
+ /// Disable text selection (using mouse) in
436
+ /// the specified target.
437
+ /// </summary
438
+ if ($target && $target.jquery) {
439
+ $target.attr('unselectable', 'on').css('MozUserSelect', 'none').bind('selectstart.ui', function() { return false; }); // from jquery:ui.core.js 1.7.2
440
+ }
441
+ }
442
+
443
+ function defaultGetLength() {
444
+ /// <summary>
445
+ /// Default implementation of getLength method
446
+ /// returns the length of the array.
447
+ /// </summary
448
+ return gridData.length;
449
+ }
450
+
451
+ function defaultGetItem(i) {
452
+ /// <summary>
453
+ /// Default implementation of getItem method
454
+ /// returns the item at specified position in
455
+ /// the array.
456
+ /// </summary
457
+ return gridData[i];
458
+ }
459
+
460
+ function getMaxSupportedCssHeight() {
461
+ var increment = 1000000;
462
+ var supportedHeight = 0;
463
+ // FF reports the height back but still renders blank after ~6M px
464
+ var testUpTo = ($.browser.mozilla) ? 5000000 : 1000000000;
465
+ var div = $("<div style='display:none' />").appendTo(document.body);
466
+
467
+ while (supportedHeight <= testUpTo) {
468
+ div.css("height", supportedHeight + increment);
469
+ if (div.height() !== supportedHeight + increment)
470
+ break;
471
+ else
472
+ supportedHeight += increment;
473
+ }
474
+
475
+ div.remove();
476
+ return supportedHeight;
477
+ }
478
+
479
+ // TODO: this is static. need to handle page mutation.
480
+ function bindAncestorScrollEvents() {
481
+ var elem = $canvas[0];
482
+ while ((elem = elem.parentNode) != document.body) {
483
+ // bind to scroll containers only
484
+ if (elem == $viewport[0] || elem.scrollWidth != elem.clientWidth || elem.scrollHeight != elem.clientHeight)
485
+ $(elem).bind("scroll.slickgrid", handleCurrentCellPositionChange);
486
+ }
487
+ }
488
+
489
+ function unbindAncestorScrollEvents() {
490
+ $canvas.parents().unbind("scroll.slickgrid");
491
+ }
492
+
493
+ function createColumnHeaders() {
494
+ var i;
495
+
496
+ function hoverBegin() {
497
+ $(this).addClass("ui-state-hover");
498
+ }
499
+ function hoverEnd() {
500
+ $(this).removeClass("ui-state-hover");
501
+ }
502
+
503
+ $headers.empty();
504
+ columnsById = {};
505
+
506
+ for (i = 0; i < columns.length; i++) {
507
+ var m = columns[i] = $.extend({},columnDefaults,columns[i]);
508
+ columnsById[m.id] = i;
509
+
510
+ var header = $("<div class='ui-state-default slick-header-column' id='" + uid + m.id + "' />")
511
+ .html("<span class='slick-column-name'>" + m.name + "</span>")
512
+ .width((m.currentWidth || m.width) - headerColumnWidthDiff)
513
+ .attr("title", m.toolTip || m.name || "")
514
+ .data("fieldId", m.id)
515
+ .appendTo($headers);
516
+
517
+ if (options.enableColumnReorder || m.sortable) {
518
+ header.hover(hoverBegin, hoverEnd);
519
+ }
520
+
521
+ if (m.sortable) {
522
+ header.append("<span class='slick-sort-indicator' />");
523
+ }
524
+ }
525
+
526
+ setSortColumn(sortColumnId,sortAsc);
527
+ setupColumnResize();
528
+ if (options.enableColumnReorder) {
529
+ setupColumnReorder();
530
+ }
531
+ }
532
+
533
+ function setupColumnSort() {
534
+ $headers.click(function(e) {
535
+ if ($(e.target).hasClass("slick-resizable-handle")) {
536
+ return;
537
+ }
538
+
539
+ if (self.onSort) {
540
+ var $col = $(e.target).closest(".slick-header-column");
541
+ if (!$col.length)
542
+ return;
543
+
544
+ var column = columns[getSiblingIndex($col[0])];
545
+ if (column.sortable) {
546
+ if (!options.editorLock.commitCurrentEdit())
547
+ return;
548
+
549
+ if (column.id === sortColumnId) {
550
+ sortAsc = !sortAsc;
551
+ }
552
+ else {
553
+ sortColumnId = column.id;
554
+ sortAsc = true;
555
+ }
556
+
557
+ setSortColumn(sortColumnId,sortAsc);
558
+ self.onSort(column,sortAsc);
559
+ }
560
+ }
561
+ });
562
+ }
563
+
564
+ function setupColumnReorder() {
565
+ $headers.sortable({
566
+ containment: "parent",
567
+ axis: "x",
568
+ cursor: "default",
569
+ tolerance: "intersection",
570
+ helper: "clone",
571
+ placeholder: "slick-sortable-placeholder ui-state-default slick-header-column",
572
+ forcePlaceholderSize: true,
573
+ start: function(e, ui) { $(ui.helper).addClass("slick-header-column-active"); },
574
+ beforeStop: function(e, ui) { $(ui.helper).removeClass("slick-header-column-active"); },
575
+ stop: function(e) {
576
+ if (!options.editorLock.commitCurrentEdit()) {
577
+ $(this).sortable("cancel");
578
+ return;
579
+ }
580
+
581
+ var reorderedIds = $headers.sortable("toArray");
582
+ var reorderedColumns = [];
583
+ for (var i=0; i<reorderedIds.length; i++) {
584
+ reorderedColumns.push(columns[getColumnIndex(reorderedIds[i].replace(uid,""))]);
585
+ }
586
+ setColumns(reorderedColumns);
587
+
588
+ if (self.onColumnsReordered) {
589
+ self.onColumnsReordered();
590
+ }
591
+ e.stopPropagation();
592
+ setupColumnResize();
593
+ }
594
+ });
595
+ }
596
+
597
+ function setupColumnResize() {
598
+ var $col, j, c, pageX, columnElements, minPageX, maxPageX, firstResizable, lastResizable, originalCanvasWidth;
599
+ columnElements = $headers.children();
600
+ columnElements.find(".slick-resizable-handle").remove();
601
+ columnElements.each(function(i,e) {
602
+ if (columns[i].resizable) {
603
+ if (firstResizable === undefined) { firstResizable = i; }
604
+ lastResizable = i;
605
+ }
606
+ });
607
+ columnElements.each(function(i,e) {
608
+ if ((firstResizable !== undefined && i < firstResizable) || (options.forceFitColumns && i >= lastResizable)) { return; }
609
+ $col = $(e);
610
+ $("<div class='slick-resizable-handle' />")
611
+ .appendTo(e)
612
+ .bind("dragstart", function(e,dd) {
613
+ if (!options.editorLock.commitCurrentEdit()) { return false; }
614
+ pageX = e.pageX;
615
+ $(this).parent().addClass("slick-header-column-active");
616
+ var shrinkLeewayOnRight = null, stretchLeewayOnRight = null;
617
+ // lock each column's width option to current width
618
+ columnElements.each(function(i,e) { columns[i].previousWidth = $(e).outerWidth(); });
619
+ if (options.forceFitColumns) {
620
+ shrinkLeewayOnRight = 0;
621
+ stretchLeewayOnRight = 0;
622
+ // colums on right affect maxPageX/minPageX
623
+ for (j = i + 1; j < columnElements.length; j++) {
624
+ c = columns[j];
625
+ if (c.resizable) {
626
+ if (stretchLeewayOnRight !== null) {
627
+ if (c.maxWidth) {
628
+ stretchLeewayOnRight += c.maxWidth - c.previousWidth;
629
+ }
630
+ else {
631
+ stretchLeewayOnRight = null;
632
+ }
633
+ }
634
+ shrinkLeewayOnRight += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth);
635
+ }
636
+ }
637
+ }
638
+ var shrinkLeewayOnLeft = 0, stretchLeewayOnLeft = 0;
639
+ for (j = 0; j <= i; j++) {
640
+ // columns on left only affect minPageX
641
+ c = columns[j];
642
+ if (c.resizable) {
643
+ if (stretchLeewayOnLeft !== null) {
644
+ if (c.maxWidth) {
645
+ stretchLeewayOnLeft += c.maxWidth - c.previousWidth;
646
+ }
647
+ else {
648
+ stretchLeewayOnLeft = null;
649
+ }
650
+ }
651
+ shrinkLeewayOnLeft += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth);
652
+ }
653
+ }
654
+ if (shrinkLeewayOnRight === null) { shrinkLeewayOnRight = 100000; }
655
+ if (shrinkLeewayOnLeft === null) { shrinkLeewayOnLeft = 100000; }
656
+ if (stretchLeewayOnRight === null) { stretchLeewayOnRight = 100000; }
657
+ if (stretchLeewayOnLeft === null) { stretchLeewayOnLeft = 100000; }
658
+ maxPageX = pageX + Math.min(shrinkLeewayOnRight, stretchLeewayOnLeft);
659
+ minPageX = pageX - Math.min(shrinkLeewayOnLeft, stretchLeewayOnRight);
660
+ originalCanvasWidth = $canvas.width();
661
+ })
662
+ .bind("drag", function(e,dd) {
663
+ var actualMinWidth, d = Math.min(maxPageX, Math.max(minPageX, e.pageX)) - pageX, x, ci;
664
+ if (d < 0) { // shrink column
665
+ x = d;
666
+ for (j = i; j >= 0; j--) {
667
+ c = columns[j];
668
+ if (c.resizable) {
669
+ actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth);
670
+ if (x && c.previousWidth + x < actualMinWidth) {
671
+ x += c.previousWidth - actualMinWidth;
672
+ styleColumnWidth(j, actualMinWidth, options.syncColumnCellResize);
673
+ } else {
674
+ styleColumnWidth(j, c.previousWidth + x, options.syncColumnCellResize);
675
+ x = 0;
676
+ }
677
+ }
678
+ }
679
+
680
+ if (options.forceFitColumns) {
681
+ x = -d;
682
+ for (j = i + 1; j < columnElements.length; j++) {
683
+ c = columns[j];
684
+ if (c.resizable) {
685
+ if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {
686
+ x -= c.maxWidth - c.previousWidth;
687
+ styleColumnWidth(j, c.maxWidth, options.syncColumnCellResize);
688
+ } else {
689
+ styleColumnWidth(j, c.previousWidth + x, options.syncColumnCellResize);
690
+ x = 0;
691
+ }
692
+ }
693
+ }
694
+ } else if (options.syncColumnCellResize) {
695
+ setCanvasWidth(originalCanvasWidth + d);
696
+ }
697
+ } else { // stretch column
698
+ x = d;
699
+ for (j = i; j >= 0; j--) {
700
+ c = columns[j];
701
+ if (c.resizable) {
702
+ if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {
703
+ x -= c.maxWidth - c.previousWidth;
704
+ styleColumnWidth(j, c.maxWidth, options.syncColumnCellResize);
705
+ } else {
706
+ styleColumnWidth(j, c.previousWidth + x, options.syncColumnCellResize);
707
+ x = 0;
708
+ }
709
+ }
710
+ }
711
+
712
+ if (options.forceFitColumns) {
713
+ x = -d;
714
+ for (j = i + 1; j < columnElements.length; j++) {
715
+ c = columns[j];
716
+ if (c.resizable) {
717
+ actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth);
718
+ if (x && c.previousWidth + x < actualMinWidth) {
719
+ x += c.previousWidth - actualMinWidth;
720
+ styleColumnWidth(j, actualMinWidth, options.syncColumnCellResize);
721
+ } else {
722
+ styleColumnWidth(j, c.previousWidth + x, options.syncColumnCellResize);
723
+ x = 0;
724
+ }
725
+ }
726
+ }
727
+ } else if (options.syncColumnCellResize) {
728
+ setCanvasWidth(originalCanvasWidth + d);
729
+ }
730
+ }
731
+ })
732
+ .bind("dragend", function(e,dd) {
733
+ var newWidth;
734
+ $(this).parent().removeClass("slick-header-column-active");
735
+ for (j = 0; j < columnElements.length; j++) {
736
+ c = columns[j];
737
+ newWidth = $(columnElements[j]).outerWidth();
738
+
739
+ if (c.previousWidth !== newWidth && c.rerenderOnResize) {
740
+ removeAllRows();
741
+ }
742
+ if (options.forceFitColumns) {
743
+ c.width = Math.floor(c.width * (newWidth - c.previousWidth) / c.previousWidth) + c.width;
744
+ } else {
745
+ c.width = newWidth;
746
+ }
747
+ if (!options.syncColumnCellResize && c.previousWidth !== newWidth) {
748
+ styleColumnWidth(j, newWidth, true);
749
+ }
750
+ }
751
+ resizeCanvas();
752
+ if (self.onColumnsResized) {
753
+ self.onColumnsResized();
754
+ }
755
+ });
756
+ });
757
+ }
758
+
759
+ function setupDragEvents() {
760
+ var MOVE_ROWS = 1;
761
+ var SELECT_CELLS = 2;
762
+
763
+ function fixUpRange(range) {
764
+ var r1 = Math.min(range.start.row,range.end.row);
765
+ var c1 = Math.min(range.start.cell,range.end.cell);
766
+ var r2 = Math.max(range.start.row,range.end.row);
767
+ var c2 = Math.max(range.start.cell,range.end.cell);
768
+ return {
769
+ start: {row:r1, cell:c1},
770
+ end: {row:r2, cell:c2}
771
+ };
772
+ }
773
+
774
+ $canvas
775
+ .bind("draginit", function(e,dd) {
776
+ var $cell = $(e.target).closest(".slick-cell");
777
+ if ($cell.length === 0) { return false; }
778
+ if (parseInt($cell.parent().attr("row"), 10) >= gridDataGetLength())
779
+ return false;
780
+
781
+ var colDef = columns[getSiblingIndex($cell[0])];
782
+ if (colDef.behavior == "move" || colDef.behavior == "selectAndMove") {
783
+ dd.mode = MOVE_ROWS;
784
+ }
785
+ else if (options.enableCellRangeSelection) {
786
+ dd.mode = SELECT_CELLS;
787
+ }
788
+ else
789
+ return false;
790
+ })
791
+ .bind("dragstart", function(e,dd) {
792
+ if (!options.editorLock.commitCurrentEdit()) { return false; }
793
+ var row = parseInt($(e.target).closest(".slick-row").attr("row"), 10);
794
+
795
+ if (dd.mode == MOVE_ROWS) {
796
+ if (!selectedRowsLookup[row]) {
797
+ setSelectedRows([row]);
798
+ }
799
+
800
+ dd.selectionProxy = $("<div class='slick-reorder-proxy'/>")
801
+ .css("position", "absolute")
802
+ .css("zIndex", "99999")
803
+ .css("width", $(this).innerWidth())
804
+ .css("height", options.rowHeight*selectedRows.length)
805
+ .appendTo($viewport);
806
+
807
+ dd.guide = $("<div class='slick-reorder-guide'/>")
808
+ .css("position", "absolute")
809
+ .css("zIndex", "99998")
810
+ .css("width", $(this).innerWidth())
811
+ .css("top", -1000)
812
+ .appendTo($viewport);
813
+
814
+ dd.insertBefore = -1;
815
+ }
816
+
817
+ if (dd.mode == SELECT_CELLS) {
818
+ var start = getCellFromPoint(dd.startX - $canvas.offset().left, dd.startY - $canvas.offset().top);
819
+ if (!cellExists(start.row,start.cell))
820
+ return false;
821
+
822
+ dd.range = {start:start,end:{}};
823
+ return $("<div class='slick-selection'></div>").appendTo($canvas);
824
+ }
825
+ })
826
+ .bind("drag", function(e,dd) {
827
+ if (dd.mode == MOVE_ROWS) {
828
+ var top = e.pageY - $(this).offset().top;
829
+ dd.selectionProxy.css("top",top-5);
830
+
831
+ var insertBefore = Math.max(0,Math.min(Math.round(top/options.rowHeight),gridDataGetLength()));
832
+ if (insertBefore !== dd.insertBefore) {
833
+ if (self.onBeforeMoveRows && self.onBeforeMoveRows(getSelectedRows(),insertBefore) === false) {
834
+ dd.guide.css("top", -1000);
835
+ dd.canMove = false;
836
+ }
837
+ else {
838
+ dd.guide.css("top",insertBefore*options.rowHeight);
839
+ dd.canMove = true;
840
+ }
841
+ dd.insertBefore = insertBefore;
842
+ }
843
+ }
844
+
845
+ if (dd.mode == SELECT_CELLS) {
846
+ var end = getCellFromPoint(e.clientX - $canvas.offset().left, e.clientY - $canvas.offset().top);
847
+ if (!cellExists(end.row,end.cell))
848
+ return;
849
+
850
+ dd.range.end = end;
851
+ var r = fixUpRange(dd.range);
852
+ var from = getCellNodeBox(r.start.row,r.start.cell);
853
+ var to = getCellNodeBox(r.end.row,r.end.cell);
854
+ $(dd.proxy).css({
855
+ top: from.top,
856
+ left: from.left,
857
+ height: to.bottom - from.top - 2,
858
+ width: to.right - from.left - 2
859
+ });
860
+ }
861
+ })
862
+ .bind("dragend", function(e,dd) {
863
+ if (dd.mode == MOVE_ROWS) {
864
+ dd.guide.remove();
865
+ dd.selectionProxy.remove();
866
+ if (self.onMoveRows && dd.canMove) {
867
+ self.onMoveRows(getSelectedRows(),dd.insertBefore);
868
+ }
869
+ }
870
+
871
+ if (dd.mode == SELECT_CELLS) {
872
+ $(dd.proxy).remove();
873
+
874
+ if (self.onCellRangeSelected)
875
+ self.onCellRangeSelected(fixUpRange(dd.range));
876
+ }
877
+ });
878
+ }
879
+
880
+ function measureCellPaddingAndBorder() {
881
+ var tmp = $("<div class='ui-state-default slick-header-column' style='visibility:hidden'>-</div>").appendTo($headers);
882
+ headerColumnWidthDiff = tmp.outerWidth() - tmp.width();
883
+ headerColumnHeightDiff = tmp.outerHeight() - tmp.height();
884
+ tmp.remove();
885
+
886
+ var r = $("<div class='slick-row' />").appendTo($canvas);
887
+ tmp = $("<div class='slick-cell' id='' style='visibility:hidden'>-</div>").appendTo(r);
888
+ cellWidthDiff = tmp.outerWidth() - tmp.width();
889
+ cellHeightDiff = tmp.outerHeight() - tmp.height();
890
+ r.remove();
891
+
892
+ absoluteColumnMinWidth = Math.max(headerColumnWidthDiff,cellWidthDiff);
893
+ }
894
+
895
+ function createCssRules() {
896
+ $style = $("<style type='text/css' rel='stylesheet' />").appendTo($("head"));
897
+ var rowHeight = (options.rowHeight - cellHeightDiff);
898
+
899
+ var rules = [
900
+ "." + uid + " .slick-header-column { left: 10000px; }",
901
+ "." + uid + " .slick-header-columns-secondary { height:" + options.secondaryHeaderRowHeight + "px; }",
902
+ "." + uid + " .slick-cell { height:" + rowHeight + "px; }"
903
+ ];
904
+
905
+ for (var i=0; i<columns.length; i++) {
906
+ rules.push(
907
+ "." + uid + " .c" + i + " { " +
908
+ "width:" + ((columns[i].currentWidth || columns[i].width) - cellWidthDiff) + "px; " +
909
+ " } ");
910
+ }
911
+
912
+ if ($style[0].styleSheet) { // IE
913
+ $style[0].styleSheet.cssText = rules.join("");
914
+ }
915
+ else {
916
+ $style[0].appendChild(document.createTextNode(rules.join(" ")));
917
+ }
918
+
919
+ var sheets = document.styleSheets;
920
+ for (var i=0; i<sheets.length; i++) {
921
+ if ((sheets[i].ownerNode || sheets[i].owningElement) == $style[0]) {
922
+ stylesheet = sheets[i];
923
+ break;
924
+ }
925
+ }
926
+ }
927
+
928
+ function findCssRule(selector) {
929
+ var rules = (stylesheet.cssRules || stylesheet.rules);
930
+
931
+ for (var i=0; i<rules.length; i++) {
932
+ if (rules[i].selectorText == selector)
933
+ return rules[i];
934
+ }
935
+
936
+ return null;
937
+ }
938
+
939
+ function findCssRuleForCell(index) {
940
+ return findCssRule("." + uid + " .c" + index);
941
+ }
942
+
943
+ function removeCssRules() {
944
+ $style.remove();
945
+ }
946
+
947
+ function destroy() {
948
+ options.editorLock.cancelCurrentEdit();
949
+
950
+ if (self.onBeforeDestroy)
951
+ self.onBeforeDestroy();
952
+
953
+ if (options.enableColumnReorder && $headers.sortable)
954
+ $headers.sortable("destroy");
955
+
956
+ unbindAncestorScrollEvents();
957
+ $container.unbind(".slickgrid");
958
+ removeCssRules();
959
+
960
+ $canvas.unbind("draginit dragstart dragend drag");
961
+ $container.empty().removeClass(uid);
962
+ }
963
+
964
+
965
+ //////////////////////////////////////////////////////////////////////////////////////////////
966
+ // General
967
+
968
+ function getEditController() {
969
+ return editController;
970
+ }
971
+
972
+ function getColumnIndex(id) {
973
+ return columnsById[id];
974
+ }
975
+
976
+ function autosizeColumns() {
977
+ var i, c,
978
+ widths = [],
979
+ shrinkLeeway = 0,
980
+ viewportW = $viewport.innerWidth(), // may not be initialized yet
981
+ availWidth = (options.autoHeight ? viewportW : viewportW - scrollbarDimensions.width), // with AutoHeight, we do not need to accomodate the vertical scroll bar
982
+ total = 0,
983
+ existingTotal = 0;
984
+
985
+ for (i = 0; i < columns.length; i++) {
986
+ c = columns[i];
987
+ widths.push(c.width);
988
+ existingTotal += c.width;
989
+ shrinkLeeway += c.width - Math.max(c.minWidth || 0, absoluteColumnMinWidth);
990
+ }
991
+
992
+ total = existingTotal;
993
+
994
+ removeAllRows();
995
+
996
+ // shrink
997
+ while (total > availWidth) {
998
+ if (!shrinkLeeway) { return; }
999
+ var shrinkProportion = (total - availWidth) / shrinkLeeway;
1000
+ for (i = 0; i < columns.length && total > availWidth; i++) {
1001
+ c = columns[i];
1002
+ if (!c.resizable || c.minWidth === c.width || c.width === absoluteColumnMinWidth) { continue; }
1003
+ var shrinkSize = Math.floor(shrinkProportion * (c.width - Math.max(c.minWidth || 0, absoluteColumnMinWidth))) || 1;
1004
+ total -= shrinkSize;
1005
+ widths[i] -= shrinkSize;
1006
+ }
1007
+ }
1008
+
1009
+ // grow
1010
+ var previousTotal = total;
1011
+ while (total < availWidth) {
1012
+ var growProportion = availWidth / total;
1013
+ for (i = 0; i < columns.length && total < availWidth; i++) {
1014
+ c = columns[i];
1015
+ if (!c.resizable || c.maxWidth <= c.width) { continue; }
1016
+ var growSize = Math.min(Math.floor(growProportion * c.width) - c.width, (c.maxWidth - c.width) || 1000000) || 1;
1017
+ total += growSize;
1018
+ widths[i] += growSize;
1019
+ }
1020
+ if (previousTotal == total) break; // if total is not changing, will result in infinite loop
1021
+ previousTotal = total;
1022
+ }
1023
+
1024
+ for (i=0; i<columns.length; i++) {
1025
+ styleColumnWidth(i, columns[i].currentWidth = widths[i], true);
1026
+ }
1027
+
1028
+ resizeCanvas();
1029
+ }
1030
+
1031
+ function styleColumnWidth(index,width,styleCells) {
1032
+ columns[index].currentWidth = width;
1033
+ $headers.children().eq(index).css("width", width - headerColumnWidthDiff);
1034
+ if (styleCells) {
1035
+ findCssRuleForCell(index).style.width = (width - cellWidthDiff) + "px";
1036
+ }
1037
+ }
1038
+
1039
+ function setSortColumn(columnId, ascending) {
1040
+ sortColumnId = columnId;
1041
+ sortAsc = ascending;
1042
+ var columnIndex = getColumnIndex(sortColumnId);
1043
+
1044
+ $headers.children().removeClass("slick-header-column-sorted");
1045
+ $headers.find(".slick-sort-indicator").removeClass("slick-sort-indicator-asc slick-sort-indicator-desc");
1046
+
1047
+ if (columnIndex != null) {
1048
+ $headers.children().eq(columnIndex)
1049
+ .addClass("slick-header-column-sorted")
1050
+ .find(".slick-sort-indicator")
1051
+ .addClass(sortAsc ? "slick-sort-indicator-asc" : "slick-sort-indicator-desc");
1052
+ }
1053
+ }
1054
+
1055
+ function getSelectedRows() {
1056
+ return selectedRows.concat();
1057
+ }
1058
+
1059
+ function setSelectedRows(rows) {
1060
+ var i, row;
1061
+ var lookup = {};
1062
+ for (i=0; i<rows.length; i++) {
1063
+ lookup[rows[i]] = true;
1064
+ }
1065
+
1066
+ // unselect old rows
1067
+ for (i=0; i<selectedRows.length; i++) {
1068
+ row = selectedRows[i];
1069
+ if (rowsCache[row] && !lookup[row]) {
1070
+ $(rowsCache[row]).removeClass("ui-state-active selected");
1071
+ }
1072
+ }
1073
+
1074
+ // select new ones
1075
+ for (i=0; i<rows.length; i++) {
1076
+ row = rows[i];
1077
+ if (rowsCache[row] && !selectedRowsLookup[row]) {
1078
+ $(rowsCache[row]).addClass("ui-state-active selected");
1079
+ }
1080
+ }
1081
+
1082
+ selectedRows = rows.concat();
1083
+ selectedRowsLookup = lookup;
1084
+ }
1085
+
1086
+ function getColumns() {
1087
+ return columns;
1088
+ }
1089
+
1090
+ function setColumns(columnDefinitions) {
1091
+ columns = columnDefinitions;
1092
+ removeAllRows();
1093
+ createColumnHeaders();
1094
+ removeCssRules();
1095
+ createCssRules();
1096
+ resizeAndRender();
1097
+ handleScroll();
1098
+ }
1099
+
1100
+ function getOptions() {
1101
+ return options;
1102
+ }
1103
+
1104
+ function setOptions(args) {
1105
+ if (!options.editorLock.commitCurrentEdit()) {
1106
+ return;
1107
+ }
1108
+
1109
+ makeSelectedCellNormal();
1110
+
1111
+ if (options.enableAddRow !== args.enableAddRow) {
1112
+ removeRow(gridDataGetLength());
1113
+ }
1114
+
1115
+ options = $.extend(options,args);
1116
+
1117
+ render();
1118
+ }
1119
+
1120
+ function setData(newData,scrollToTop) {
1121
+ removeAllRows();
1122
+ data = newData;
1123
+ gridData = data;
1124
+ gridDataGetLength = gridData.getLength || defaultGetLength;
1125
+ gridDataGetItem = gridData.getItem || defaultGetItem;
1126
+ if (scrollToTop)
1127
+ scrollTo(0);
1128
+ }
1129
+
1130
+ function getData() {
1131
+ return gridData;
1132
+ }
1133
+
1134
+ function getSecondaryHeaderRow() {
1135
+ return $secondaryHeaders[0];
1136
+ }
1137
+
1138
+ function showSecondaryHeaderRow() {
1139
+ options.showSecondaryHeaderRow = true;
1140
+ $secondaryHeaderScroller.slideDown("fast", resizeCanvas);
1141
+ }
1142
+
1143
+ function hideSecondaryHeaderRow() {
1144
+ options.showSecondaryHeaderRow = false;
1145
+ $secondaryHeaderScroller.slideUp("fast", resizeCanvas);
1146
+ }
1147
+
1148
+ //////////////////////////////////////////////////////////////////////////////////////////////
1149
+ // Rendering / Scrolling
1150
+
1151
+ function scrollTo(y) {
1152
+ var oldOffset = offset;
1153
+
1154
+ page = Math.min(n-1, Math.floor(y / ph));
1155
+ offset = Math.round(page * cj);
1156
+ var newScrollTop = y - offset;
1157
+
1158
+ if (offset != oldOffset) {
1159
+ var range = getVisibleRange(newScrollTop);
1160
+ cleanupRows(range.top,range.bottom);
1161
+ updateRowPositions();
1162
+ }
1163
+
1164
+ if (prevScrollTop != newScrollTop) {
1165
+ scrollDir = (prevScrollTop + oldOffset < newScrollTop + offset) ? 1 : -1;
1166
+ $viewport[0].scrollTop = (lastRenderedScrollTop = scrollTop = prevScrollTop = newScrollTop);
1167
+
1168
+ if (self.onViewportChanged) {
1169
+ self.onViewportChanged();
1170
+ }
1171
+ }
1172
+ }
1173
+
1174
+ function defaultFormatter(row, cell, value, columnDef, dataContext) {
1175
+ return (value === null || value === undefined) ? "" : value;
1176
+ }
1177
+
1178
+ function getFormatter(column) {
1179
+ return column.formatter ||
1180
+ (options.formatterFactory && options.formatterFactory.getFormatter(column)) ||
1181
+ defaultFormatter;
1182
+ }
1183
+
1184
+ function getEditor(column) {
1185
+ return column.editor || (options.editorFactory && options.editorFactory.getEditor(column));
1186
+ }
1187
+
1188
+ function appendRowHtml(stringArray,row) {
1189
+ var d = gridDataGetItem(row);
1190
+ var dataLoading = row < gridDataGetLength() && !d;
1191
+ var cellCss;
1192
+ var css = "slick-row " +
1193
+ (dataLoading ? " loading" : "") +
1194
+ (selectedRowsLookup[row] ? " selected ui-state-active" : "") +
1195
+ (row % 2 == 1 ? ' odd' : ' even');
1196
+
1197
+ // if the user has specified a function to provide additional per-row css classes, call it here
1198
+ if (options.rowCssClasses) {
1199
+ css += ' ' + options.rowCssClasses(d);
1200
+ }
1201
+
1202
+ stringArray.push("<div class='ui-widget-content " + css + "' row='" + row + "' style='top:" + (options.rowHeight*row-offset) + "px'>");
1203
+
1204
+ for (var i=0, cols=columns.length; i<cols; i++) {
1205
+ var m = columns[i];
1206
+
1207
+ cellCss = "slick-cell c" + i + (m.cssClass ? " " + m.cssClass : "");
1208
+ if (highlightedCells && highlightedCells[row] && highlightedCells[row][m.id])
1209
+ cellCss += (" " + options.cellHighlightCssClass);
1210
+
1211
+ stringArray.push("<div class='" + cellCss + "'>");
1212
+
1213
+ // if there is a corresponding row (if not, this is the Add New row or this data hasn't been loaded yet)
1214
+ if (d) {
1215
+ stringArray.push(getFormatter(m)(row, i, d[m.field], m, d));
1216
+ }
1217
+
1218
+ stringArray.push("</div>");
1219
+ }
1220
+
1221
+ stringArray.push("</div>");
1222
+ }
1223
+
1224
+ function cleanupRows(rangeToKeep) {
1225
+ for (var i in rowsCache) {
1226
+ if (((i = parseInt(i, 10)) !== currentRow) && (i < rangeToKeep.top || i > rangeToKeep.bottom)) {
1227
+ removeRowFromCache(i);
1228
+ }
1229
+ }
1230
+ }
1231
+
1232
+ function invalidate() {
1233
+ updateRowCount();
1234
+ removeAllRows();
1235
+ render();
1236
+ }
1237
+
1238
+ function removeAllRows() {
1239
+ if (currentEditor) {
1240
+ makeSelectedCellNormal();
1241
+ }
1242
+ $canvas[0].innerHTML = "";
1243
+ rowsCache= {};
1244
+ postProcessedRows = {};
1245
+ counter_rows_removed += renderedRows;
1246
+ renderedRows = 0;
1247
+ }
1248
+
1249
+ function removeRowFromCache(row) {
1250
+ var node = rowsCache[row];
1251
+ if (!node) { return; }
1252
+ $canvas[0].removeChild(node);
1253
+
1254
+ delete rowsCache[row];
1255
+ delete postProcessedRows[row];
1256
+ renderedRows--;
1257
+ counter_rows_removed++;
1258
+ }
1259
+
1260
+ function removeRows(rows) {
1261
+ var i, rl, nl;
1262
+ if (!rows || !rows.length) { return; }
1263
+ scrollDir = 0;
1264
+ var nodes = [];
1265
+ for (i=0, rl=rows.length; i<rl; i++) {
1266
+ if (currentEditor && currentRow === i) {
1267
+ makeSelectedCellNormal();
1268
+ }
1269
+
1270
+ if (rowsCache[rows[i]]) {
1271
+ nodes.push(rows[i]);
1272
+ }
1273
+ }
1274
+
1275
+ if (renderedRows > 10 && nodes.length === renderedRows) {
1276
+ removeAllRows();
1277
+ }
1278
+ else {
1279
+ for (i=0, nl=nodes.length; i<nl; i++) {
1280
+ removeRowFromCache(nodes[i]);
1281
+ }
1282
+ }
1283
+ }
1284
+
1285
+ function removeRow(row) {
1286
+ removeRows([row]);
1287
+ }
1288
+
1289
+ function updateCell(row,cell) {
1290
+ if (!rowsCache[row]) { return; }
1291
+ var $cell = $(rowsCache[row]).children().eq(cell);
1292
+ if ($cell.length === 0) { return; }
1293
+
1294
+ var m = columns[cell], d = gridDataGetItem(row);
1295
+ if (currentEditor && currentRow === row && currentCell === cell) {
1296
+ currentEditor.loadValue(d);
1297
+ }
1298
+ else {
1299
+ $cell[0].innerHTML = d ? getFormatter(m)(row, cell, d[m.field], m, d) : "";
1300
+ invalidatePostProcessingResults(row);
1301
+ }
1302
+ }
1303
+
1304
+ function updateRow(row) {
1305
+ if (!rowsCache[row]) { return; }
1306
+
1307
+ $(rowsCache[row]).children().each(function(i) {
1308
+ var m = columns[i];
1309
+ if (row === currentRow && i === currentCell && currentEditor) {
1310
+ currentEditor.loadValue(gridDataGetItem(currentRow));
1311
+ }
1312
+ else if (gridDataGetItem(row)) {
1313
+ this.innerHTML = getFormatter(m)(row, i, gridDataGetItem(row)[m.field], m, gridDataGetItem(row));
1314
+ }
1315
+ else {
1316
+ this.innerHTML = "";
1317
+ }
1318
+ });
1319
+
1320
+ invalidatePostProcessingResults(row);
1321
+ }
1322
+
1323
+ function resizeCanvas() {
1324
+ var newViewportH = options.rowHeight * (gridDataGetLength() + (options.enableAddRow ? 1 : 0) + (options.leaveSpaceForNewRows? numVisibleRows - 1 : 0));
1325
+ if (options.autoHeight) { // use computed height to set both canvas _and_ divMainScroller, effectively hiding scroll bars.
1326
+ $viewport.height(newViewportH);
1327
+ }
1328
+ else {
1329
+ $viewport.height(
1330
+ $container.innerHeight() -
1331
+ $headerScroller.outerHeight() -
1332
+ (options.showSecondaryHeaderRow ? $secondaryHeaderScroller.outerHeight() : 0));
1333
+ }
1334
+
1335
+ viewportW = $viewport.innerWidth();
1336
+ viewportH = $viewport.innerHeight();
1337
+ numVisibleRows = Math.ceil(viewportH / options.rowHeight);
1338
+
1339
+ var totalWidth = 0;
1340
+ $headers.find(".slick-header-column").each(function() {
1341
+ totalWidth += $(this).outerWidth();
1342
+ });
1343
+ setCanvasWidth(totalWidth);
1344
+
1345
+ updateRowCount();
1346
+ render();
1347
+ }
1348
+
1349
+ function resizeAndRender() {
1350
+ if (options.forceFitColumns) {
1351
+ autosizeColumns();
1352
+ } else {
1353
+ resizeCanvas();
1354
+ }
1355
+ }
1356
+
1357
+ function updateRowCount() {
1358
+ var newRowCount = gridDataGetLength() + (options.enableAddRow?1:0) + (options.leaveSpaceForNewRows?numVisibleRows-1:0);
1359
+ var oldH = h;
1360
+
1361
+ // remove the rows that are now outside of the data range
1362
+ // this helps avoid redundant calls to .removeRow() when the size of the data decreased by thousands of rows
1363
+ var l = options.enableAddRow ? gridDataGetLength() : gridDataGetLength() - 1;
1364
+ for (var i in rowsCache) {
1365
+ if (i >= l) {
1366
+ removeRowFromCache(i);
1367
+ }
1368
+ }
1369
+ th = Math.max(options.rowHeight * newRowCount, viewportH - scrollbarDimensions.height);
1370
+ if (th < maxSupportedCssHeight) {
1371
+ // just one page
1372
+ h = ph = th;
1373
+ n = 1;
1374
+ cj = 0;
1375
+ }
1376
+ else {
1377
+ // break into pages
1378
+ h = maxSupportedCssHeight;
1379
+ ph = h / 100;
1380
+ n = Math.floor(th / ph);
1381
+ cj = (th - h) / (n - 1);
1382
+ }
1383
+
1384
+ if (h !== oldH) {
1385
+ $canvas.css("height",h);
1386
+ scrollTop = $viewport[0].scrollTop;
1387
+ }
1388
+
1389
+ var oldScrollTopInRange = (scrollTop + offset <= th - viewportH);
1390
+
1391
+ if (th == 0 || scrollTop == 0) {
1392
+ page = offset = 0;
1393
+ }
1394
+ else if (oldScrollTopInRange) {
1395
+ // maintain virtual position
1396
+ scrollTo(scrollTop+offset);
1397
+ }
1398
+ else {
1399
+ // scroll to bottom
1400
+ scrollTo(th-viewportH);
1401
+ }
1402
+
1403
+ if (h != oldH && options.autoHeight) {
1404
+ resizeCanvas();
1405
+ }
1406
+ }
1407
+
1408
+ function getVisibleRange(viewportTop) {
1409
+ if (viewportTop == null)
1410
+ viewportTop = scrollTop;
1411
+
1412
+ return {
1413
+ top: Math.floor((scrollTop+offset)/options.rowHeight),
1414
+ bottom: Math.ceil((scrollTop+offset+viewportH)/options.rowHeight)
1415
+ };
1416
+ }
1417
+
1418
+ function getRenderedRange(viewportTop) {
1419
+ var range = getVisibleRange(viewportTop);
1420
+ var buffer = Math.round(viewportH/options.rowHeight);
1421
+ var minBuffer = 3;
1422
+
1423
+ if (scrollDir == -1) {
1424
+ range.top -= buffer;
1425
+ range.bottom += minBuffer;
1426
+ }
1427
+ else if (scrollDir == 1) {
1428
+ range.top -= minBuffer;
1429
+ range.bottom += buffer;
1430
+ }
1431
+ else {
1432
+ range.top -= minBuffer;
1433
+ range.bottom += minBuffer;
1434
+ }
1435
+
1436
+ range.top = Math.max(0,range.top);
1437
+ range.bottom = Math.min(options.enableAddRow ? gridDataGetLength() : gridDataGetLength() - 1,range.bottom);
1438
+
1439
+ return range;
1440
+ }
1441
+
1442
+ function renderRows(range) {
1443
+ var i, l,
1444
+ parentNode = $canvas[0],
1445
+ rowsBefore = renderedRows,
1446
+ stringArray = [],
1447
+ rows = [],
1448
+ startTimestamp = new Date(),
1449
+ needToReselectCell = false;
1450
+
1451
+ for (i = range.top; i <= range.bottom; i++) {
1452
+ if (rowsCache[i]) { continue; }
1453
+ renderedRows++;
1454
+ rows.push(i);
1455
+ appendRowHtml(stringArray,i);
1456
+ if (currentCellNode && currentRow === i)
1457
+ needToReselectCell = true;
1458
+ counter_rows_rendered++;
1459
+ }
1460
+
1461
+ var x = document.createElement("div");
1462
+ x.innerHTML = stringArray.join("");
1463
+
1464
+ for (i = 0, l = x.childNodes.length; i < l; i++) {
1465
+ rowsCache[rows[i]] = parentNode.appendChild(x.firstChild);
1466
+ }
1467
+
1468
+ if (needToReselectCell) {
1469
+ currentCellNode = $(rowsCache[currentRow]).children().eq(currentCell)[0];
1470
+ setSelectedCell(currentCellNode,false);
1471
+ }
1472
+
1473
+ if (renderedRows - rowsBefore > 5) {
1474
+ avgRowRenderTime = (new Date() - startTimestamp) / (renderedRows - rowsBefore);
1475
+ }
1476
+ }
1477
+
1478
+ function startPostProcessing() {
1479
+ if (!options.enableAsyncPostRender) { return; }
1480
+ clearTimeout(h_postrender);
1481
+ h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay);
1482
+ }
1483
+
1484
+ function invalidatePostProcessingResults(row) {
1485
+ delete postProcessedRows[row];
1486
+ postProcessFromRow = Math.min(postProcessFromRow,row);
1487
+ postProcessToRow = Math.max(postProcessToRow,row);
1488
+ startPostProcessing();
1489
+ }
1490
+
1491
+ function updateRowPositions() {
1492
+ for (var row in rowsCache) {
1493
+ rowsCache[row].style.top = (row*options.rowHeight-offset) + "px";
1494
+ }
1495
+ }
1496
+
1497
+ function render() {
1498
+ var visible = getVisibleRange();
1499
+ var rendered = getRenderedRange();
1500
+
1501
+ // remove rows no longer in the viewport
1502
+ cleanupRows(rendered);
1503
+
1504
+ // add new rows
1505
+ renderRows(rendered);
1506
+
1507
+ postProcessFromRow = visible.top;
1508
+ postProcessToRow = Math.min(options.enableAddRow ? gridDataGetLength() : gridDataGetLength() - 1, visible.bottom);
1509
+ startPostProcessing();
1510
+
1511
+ lastRenderedScrollTop = scrollTop;
1512
+ h_render = null;
1513
+ }
1514
+
1515
+ function handleScroll() {
1516
+ scrollTop = $viewport[0].scrollTop;
1517
+ var scrollLeft = $viewport[0].scrollLeft;
1518
+ var scrollDist = Math.abs(scrollTop - prevScrollTop);
1519
+
1520
+ if (scrollLeft !== prevScrollLeft) {
1521
+ prevScrollLeft = scrollLeft;
1522
+ $headerScroller[0].scrollLeft = scrollLeft;
1523
+ $secondaryHeaderScroller[0].scrollLeft = scrollLeft;
1524
+ }
1525
+
1526
+ if (!scrollDist) return;
1527
+
1528
+ scrollDir = prevScrollTop < scrollTop ? 1 : -1;
1529
+ prevScrollTop = scrollTop;
1530
+
1531
+ // switch virtual pages if needed
1532
+ if (scrollDist < viewportH) {
1533
+ scrollTo(scrollTop + offset);
1534
+ }
1535
+ else {
1536
+ var oldOffset = offset;
1537
+ page = Math.min(n - 1, Math.floor(scrollTop * ((th - viewportH) / (h - viewportH)) * (1 / ph)));
1538
+ offset = Math.round(page * cj);
1539
+ if (oldOffset != offset)
1540
+ removeAllRows();
1541
+ }
1542
+
1543
+ if (h_render)
1544
+ clearTimeout(h_render);
1545
+
1546
+ if (Math.abs(lastRenderedScrollTop - scrollTop) < viewportH)
1547
+ render();
1548
+ else
1549
+ h_render = setTimeout(render, 50);
1550
+
1551
+ if (self.onViewportChanged) {
1552
+ self.onViewportChanged();
1553
+ }
1554
+ }
1555
+
1556
+ function asyncPostProcessRows() {
1557
+ while (postProcessFromRow <= postProcessToRow) {
1558
+ var row = (scrollDir >= 0) ? postProcessFromRow++ : postProcessToRow--;
1559
+ var rowNode = rowsCache[row];
1560
+ if (!rowNode || postProcessedRows[row] || row>=gridDataGetLength()) { continue; }
1561
+
1562
+ var d = gridDataGetItem(row), cellNodes = rowNode.childNodes;
1563
+ for (var i=0, j=0, l=columns.length; i<l; ++i) {
1564
+ var m = columns[i];
1565
+ if (m.asyncPostRender) { m.asyncPostRender(cellNodes[j], postProcessFromRow, d, m); }
1566
+ ++j;
1567
+ }
1568
+
1569
+ postProcessedRows[row] = true;
1570
+ h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay);
1571
+ return;
1572
+ }
1573
+ }
1574
+
1575
+ function setHighlightedCells(cellsToHighlight) {
1576
+ var i, $cell, hasHighlight, hadHighlight;
1577
+
1578
+ for (var row in rowsCache) {
1579
+ for (i=0; i<columns.length; i++) {
1580
+ hadHighlight = highlightedCells && highlightedCells[row] && highlightedCells[row][columns[i].id];
1581
+ hasHighlight = cellsToHighlight && cellsToHighlight[row] && cellsToHighlight[row][columns[i].id];
1582
+
1583
+ if (hadHighlight != hasHighlight) {
1584
+ $cell = $(rowsCache[row]).children().eq(i);
1585
+ if ($cell.length) {
1586
+ $cell.toggleClass(options.cellHighlightCssClass);
1587
+ }
1588
+ }
1589
+ }
1590
+ }
1591
+
1592
+ highlightedCells = cellsToHighlight;
1593
+ }
1594
+
1595
+ function flashCell(row, cell, speed) {
1596
+ speed = speed || 100;
1597
+ if (rowsCache[row]) {
1598
+ var $cell = $(rowsCache[row]).children().eq(cell);
1599
+
1600
+ function toggleCellClass(times) {
1601
+ if (!times) return;
1602
+ setTimeout(function() {
1603
+ $cell.queue(function() {
1604
+ $cell.toggleClass(options.cellFlashingCssClass).dequeue();
1605
+ toggleCellClass(times-1);
1606
+ });
1607
+ },
1608
+ speed);
1609
+ }
1610
+
1611
+ toggleCellClass(4);
1612
+ }
1613
+ }
1614
+
1615
+ //////////////////////////////////////////////////////////////////////////////////////////////
1616
+ // Interactivity
1617
+
1618
+ function getSiblingIndex(node) {
1619
+ var idx = 0;
1620
+ while (node && node.previousSibling) {
1621
+ idx++;
1622
+ node = node.previousSibling;
1623
+ }
1624
+ return idx;
1625
+ }
1626
+
1627
+ function handleKeyDown(e) {
1628
+ // give registered handler chance to process the keyboard event
1629
+ var handled = (self.onKeyDown && // a handler must be registered
1630
+ !options.editorLock.isActive() && // grid must not be in edit mode;
1631
+ self.onKeyDown(e, currentRow, currentCell)); // handler must return truthy-value to indicate it handled the event
1632
+
1633
+ if (!handled) {
1634
+ if (!e.shiftKey && !e.altKey && !e.ctrlKey) {
1635
+ if (e.which == 27) {
1636
+ if (!options.editorLock.isActive()) {
1637
+ return; // no editing mode to cancel, allow bubbling and default processing (exit without cancelling the event)
1638
+ }
1639
+ cancelEditAndSetFocus();
1640
+ }
1641
+ else if (e.which == 37) {
1642
+ navigateLeft();
1643
+ }
1644
+ else if (e.which == 39) {
1645
+ navigateRight();
1646
+ }
1647
+ else if (e.which == 38) {
1648
+ navigateUp();
1649
+ }
1650
+ else if (e.which == 40) {
1651
+ navigateDown();
1652
+ }
1653
+ else if (e.which == 9) {
1654
+ navigateNext();
1655
+ }
1656
+ else if (e.which == 13) {
1657
+ if (options.editable) {
1658
+ if (currentEditor) {
1659
+ // adding new row
1660
+ if (currentRow === defaultGetLength()) {
1661
+ navigateDown();
1662
+ }
1663
+ else {
1664
+ commitEditAndSetFocus();
1665
+ }
1666
+ } else {
1667
+ if (options.editorLock.commitCurrentEdit()) {
1668
+ makeSelectedCellEditable();
1669
+ }
1670
+ }
1671
+ }
1672
+ }
1673
+ else
1674
+ return;
1675
+ }
1676
+ else if (e.which == 9 && e.shiftKey && !e.ctrlKey && !e.altKey) {
1677
+ navigatePrev();
1678
+ }
1679
+ else
1680
+ return;
1681
+ }
1682
+
1683
+ // the event has been handled so don't let parent element (bubbling/propagation) or browser (default) handle it
1684
+ e.stopPropagation();
1685
+ e.preventDefault();
1686
+ try {
1687
+ e.originalEvent.keyCode = 0; // prevent default behaviour for special keys in IE browsers (F3, F5, etc.)
1688
+ }
1689
+ catch (error) {} // ignore exceptions - setting the original event's keycode throws access denied exception for "Ctrl" (hitting control key only, nothing else), "Shift" (maybe others)
1690
+ }
1691
+
1692
+ function handleClick(e) {
1693
+ var $cell = $(e.target).closest(".slick-cell", $canvas);
1694
+ if ($cell.length === 0) { return; }
1695
+
1696
+ // are we editing this cell?
1697
+ if (currentCellNode === $cell[0] && currentEditor !== null) { return; }
1698
+
1699
+ var row = parseInt($cell.parent().attr("row"), 10);
1700
+ var cell = getSiblingIndex($cell[0]);
1701
+ var validated = null;
1702
+ var c = columns[cell];
1703
+ var item = gridDataGetItem(row);
1704
+
1705
+ // is this a 'select' column or a Ctrl/Shift-click?
1706
+ if (item && (c.behavior === "selectAndMove" || c.behavior === "select" || (e.ctrlKey || e.shiftKey))) {
1707
+ // grid must not be in edit mode
1708
+ validated = options.editorLock.commitCurrentEdit();
1709
+ if (validated) {
1710
+ var selection = getSelectedRows();
1711
+ var idx = $.inArray(row, selection);
1712
+
1713
+ if (!e.ctrlKey && !e.shiftKey && !e.metaKey) {
1714
+ selection = [row];
1715
+ }
1716
+ else if (options.multiSelect) {
1717
+ if (idx === -1 && (e.ctrlKey || e.metaKey)) {
1718
+ selection.push(row);
1719
+ }
1720
+ else if (idx !== -1 && (e.ctrlKey || e.metaKey)) {
1721
+ selection = $.grep(selection, function(o, i) { return (o !== row); });
1722
+ }
1723
+ else if (selection.length && e.shiftKey) {
1724
+ var last = selection.pop();
1725
+ var from = Math.min(row, last);
1726
+ var to = Math.max(row, last);
1727
+ selection = [];
1728
+ for (var i = from; i <= to; i++) {
1729
+ if (i !== last) {
1730
+ selection.push(i);
1731
+ }
1732
+ }
1733
+ selection.push(last);
1734
+ }
1735
+ }
1736
+ resetCurrentCell();
1737
+ setSelectedRows(selection);
1738
+ if (self.onSelectedRowsChanged) {
1739
+ self.onSelectedRowsChanged();
1740
+ }
1741
+
1742
+ if (!$.browser.msie) {
1743
+ $canvas[0].focus();
1744
+ }
1745
+
1746
+ return false;
1747
+ }
1748
+ }
1749
+
1750
+ // do we have any registered handlers?
1751
+ if (item && self.onClick) {
1752
+ // grid must not be in edit mode
1753
+ validated = options.editorLock.commitCurrentEdit();
1754
+ if (validated) {
1755
+ // handler will return true if the event was handled
1756
+ if (self.onClick(e, row, cell)) {
1757
+ e.stopPropagation();
1758
+ e.preventDefault();
1759
+ return false;
1760
+ }
1761
+ }
1762
+ }
1763
+
1764
+ if (options.enableCellNavigation && !columns[cell].unselectable) {
1765
+ // commit current edit before proceeding
1766
+ if (validated === true || (validated === null && options.editorLock.commitCurrentEdit())) {
1767
+ scrollRowIntoView(row,false);
1768
+ setSelectedCellAndRow($cell[0], (row === defaultGetLength()) || options.autoEdit);
1769
+ }
1770
+ }
1771
+ }
1772
+
1773
+ function handleContextMenu(e) {
1774
+ var $cell = $(e.target).closest(".slick-cell", $canvas);
1775
+ if ($cell.length === 0) { return; }
1776
+
1777
+ // are we editing this cell?
1778
+ if (currentCellNode === $cell[0] && currentEditor !== null) { return; }
1779
+
1780
+ var row = parseInt($cell.parent().attr("row"), 10);
1781
+ var cell = getSiblingIndex($cell[0]);
1782
+ var validated = null;
1783
+
1784
+ // do we have any registered handlers?
1785
+ if (gridDataGetItem(row) && self.onContextMenu) {
1786
+ // grid must not be in edit mode
1787
+ validated = options.editorLock.commitCurrentEdit();
1788
+ if (validated) {
1789
+ // handler will return true if the event was handled
1790
+ if (self.onContextMenu(e, row, cell)) {
1791
+ e.stopPropagation();
1792
+ e.preventDefault();
1793
+ return false;
1794
+ }
1795
+ }
1796
+ }
1797
+ }
1798
+
1799
+ function handleDblClick(e) {
1800
+ var $cell = $(e.target).closest(".slick-cell", $canvas);
1801
+ if ($cell.length === 0) { return; }
1802
+
1803
+ // are we editing this cell?
1804
+ if (currentCellNode === $cell[0] && currentEditor !== null) { return; }
1805
+
1806
+ var row = parseInt($cell.parent().attr("row"), 10);
1807
+ var cell = getSiblingIndex($cell[0]);
1808
+ var validated = null;
1809
+
1810
+ // do we have any registered handlers?
1811
+ if (gridDataGetItem(row) && self.onDblClick) {
1812
+ // grid must not be in edit mode
1813
+ validated = options.editorLock.commitCurrentEdit();
1814
+ if (validated) {
1815
+ // handler will return true if the event was handled
1816
+ if (self.onDblClick(e, row, cell)) {
1817
+ e.stopPropagation();
1818
+ e.preventDefault();
1819
+ return false;
1820
+ }
1821
+ }
1822
+ }
1823
+
1824
+ if (options.editable) {
1825
+ gotoCell(row, cell, true);
1826
+ }
1827
+ }
1828
+
1829
+ function handleHeaderContextMenu(e) {
1830
+ if (self.onHeaderContextMenu && options.editorLock.commitCurrentEdit()) {
1831
+ e.preventDefault();
1832
+ var selectedElement = $(e.target).closest(".slick-header-column", ".slick-header-columns");
1833
+ self.onHeaderContextMenu(e, columns[self.getColumnIndex(selectedElement.data("fieldId"))]);
1834
+ }
1835
+ }
1836
+
1837
+ function handleHeaderClick(e) {
1838
+ var $col = $(e.target).closest(".slick-header-column");
1839
+ if ($col.length ==0) { return; }
1840
+ var column = columns[getSiblingIndex($col[0])];
1841
+
1842
+ if (self.onHeaderClick && options.editorLock.commitCurrentEdit()) {
1843
+ e.preventDefault();
1844
+ self.onHeaderClick(e, column);
1845
+ }
1846
+ }
1847
+
1848
+ function handleHover(e) {
1849
+ if (!options.enableAutoTooltips) return;
1850
+ var $cell = $(e.target).closest(".slick-cell",$canvas);
1851
+ if ($cell.length) {
1852
+ if ($cell.innerWidth() < $cell[0].scrollWidth) {
1853
+ var text = $.trim($cell.text());
1854
+ $cell.attr("title", (options.toolTipMaxLength && text.length > options.toolTipMaxLength) ? text.substr(0, options.toolTipMaxLength - 3) + "..." : text);
1855
+ }
1856
+ else {
1857
+ $cell.attr("title","");
1858
+ }
1859
+ }
1860
+ }
1861
+
1862
+ function cellExists(row,cell) {
1863
+ return !(row < 0 || row >= gridDataGetLength() || cell < 0 || cell >= columns.length);
1864
+ }
1865
+
1866
+ function getCellFromPoint(x,y) {
1867
+ var row = Math.floor((y+offset)/options.rowHeight);
1868
+ var cell = 0;
1869
+
1870
+ var w = 0;
1871
+ for (var i=0; i<columns.length && w<x; i++) {
1872
+ w += columns[i].width;
1873
+ cell++;
1874
+ }
1875
+
1876
+ return {row:row,cell:cell-1};
1877
+ }
1878
+
1879
+ function getCellFromEvent(e) {
1880
+ var $cell = $(e.target).closest(".slick-cell", $canvas);
1881
+ if (!$cell.length)
1882
+ return null;
1883
+
1884
+ return {
1885
+ row: $cell.parent().attr("row") | 0,
1886
+ cell: getSiblingIndex($cell[0])
1887
+ };
1888
+ }
1889
+
1890
+ function getCellNodeBox(row,cell) {
1891
+ if (!cellExists(row,cell))
1892
+ return null;
1893
+
1894
+ var y1 = row * options.rowHeight - offset;
1895
+ var y2 = y1 + options.rowHeight - 1;
1896
+ var x1 = 0;
1897
+ for (var i=0; i<cell; i++) {
1898
+ x1 += columns[i].width;
1899
+ }
1900
+ var x2 = x1 + columns[cell].width;
1901
+
1902
+ return {
1903
+ top: y1,
1904
+ left: x1,
1905
+ bottom: y2,
1906
+ right: x2
1907
+ };
1908
+ }
1909
+
1910
+ //////////////////////////////////////////////////////////////////////////////////////////////
1911
+ // Cell switching
1912
+
1913
+ function resetCurrentCell() {
1914
+ setSelectedCell(null,false);
1915
+ }
1916
+
1917
+ function focusOnCurrentCell() {
1918
+ // lazily enable the cell to receive keyboard focus
1919
+ $(currentCellNode)
1920
+ .attr("tabIndex",0)
1921
+ .attr("hideFocus",true);
1922
+
1923
+ // IE7 tries to scroll the viewport so that the item being focused is aligned to the left border
1924
+ // IE-specific .setActive() sets the focus, but doesn't scroll
1925
+ if ($.browser.msie && parseInt($.browser.version) < 8)
1926
+ currentCellNode.setActive();
1927
+ else
1928
+ currentCellNode.focus();
1929
+
1930
+ var left = $(currentCellNode).position().left,
1931
+ right = left + $(currentCellNode).outerWidth(),
1932
+ scrollLeft = $viewport.scrollLeft(),
1933
+ scrollRight = scrollLeft + $viewport.width();
1934
+
1935
+ if (left < scrollLeft)
1936
+ $viewport.scrollLeft(left);
1937
+ else if (right > scrollRight)
1938
+ $viewport.scrollLeft(Math.min(left, right - $viewport[0].clientWidth));
1939
+ }
1940
+
1941
+ function setSelectedCell(newCell,editMode) {
1942
+ if (currentCellNode !== null) {
1943
+ makeSelectedCellNormal();
1944
+ $(currentCellNode).removeClass("selected");
1945
+ }
1946
+
1947
+ currentCellNode = newCell;
1948
+
1949
+ if (currentCellNode != null) {
1950
+ currentRow = parseInt($(currentCellNode).parent().attr("row"), 10);
1951
+ currentCell = getSiblingIndex(currentCellNode);
1952
+
1953
+ $(currentCellNode).addClass("selected");
1954
+
1955
+ if (options.editable && editMode && isCellPotentiallyEditable(currentRow,currentCell)) {
1956
+ clearTimeout(h_editorLoader);
1957
+
1958
+ if (options.asyncEditorLoading) {
1959
+ h_editorLoader = setTimeout(makeSelectedCellEditable, options.asyncEditorLoadDelay);
1960
+ }
1961
+ else {
1962
+ makeSelectedCellEditable();
1963
+ }
1964
+ }
1965
+ else {
1966
+ focusOnCurrentCell()
1967
+ }
1968
+ if (self.onCurrentCellChanged)
1969
+ self.onCurrentCellChanged(getCurrentCell());
1970
+ }
1971
+ else {
1972
+ currentRow = null;
1973
+ currentCell = null;
1974
+ }
1975
+ }
1976
+
1977
+ function setSelectedCellAndRow(newCell,editMode) {
1978
+ setSelectedCell(newCell,editMode);
1979
+
1980
+ if (newCell) {
1981
+ setSelectedRows([currentRow]);
1982
+ }
1983
+ else {
1984
+ setSelectedRows([]);
1985
+ }
1986
+
1987
+ if (self.onSelectedRowsChanged) {
1988
+ self.onSelectedRowsChanged();
1989
+ }
1990
+ }
1991
+
1992
+ function clearTextSelection() {
1993
+ if (document.selection && document.selection.empty) {
1994
+ document.selection.empty();
1995
+ }
1996
+ else if (window.getSelection) {
1997
+ var sel = window.getSelection();
1998
+ if (sel && sel.removeAllRanges) {
1999
+ sel.removeAllRanges();
2000
+ }
2001
+ }
2002
+ }
2003
+
2004
+ function isCellPotentiallyEditable(row,cell) {
2005
+ // is the data for this row loaded?
2006
+ if (row < gridDataGetLength() && !gridDataGetItem(row)) {
2007
+ return false;
2008
+ }
2009
+
2010
+ // are we in the Add New row? can we create new from this cell?
2011
+ if (columns[cell].cannotTriggerInsert && row >= gridDataGetLength()) {
2012
+ return false;
2013
+ }
2014
+
2015
+ // does this cell have an editor?
2016
+ if (!getEditor(columns[cell])) {
2017
+ return false;
2018
+ }
2019
+
2020
+ return true;
2021
+ }
2022
+
2023
+ function makeSelectedCellNormal() {
2024
+ if (!currentEditor) { return; }
2025
+
2026
+ if (self.onBeforeCellEditorDestroy) {
2027
+ self.onBeforeCellEditorDestroy(currentEditor);
2028
+ }
2029
+ currentEditor.destroy();
2030
+ currentEditor = null;
2031
+
2032
+ if (currentCellNode) {
2033
+ $(currentCellNode).removeClass("editable invalid");
2034
+
2035
+ if (gridDataGetItem(currentRow)) {
2036
+ var column = columns[currentCell];
2037
+ currentCellNode.innerHTML = getFormatter(column)(currentRow, currentCell, gridDataGetItem(currentRow)[column.field], column, gridDataGetItem(currentRow));
2038
+ invalidatePostProcessingResults(currentRow);
2039
+ }
2040
+ }
2041
+
2042
+ // if there previously was text selected on a page (such as selected text in the edit cell just removed),
2043
+ // IE can't set focus to anything else correctly
2044
+ if ($.browser.msie) { clearTextSelection(); }
2045
+
2046
+ options.editorLock.deactivate(editController);
2047
+ }
2048
+
2049
+ function makeSelectedCellEditable() {
2050
+ if (!currentCellNode) { return; }
2051
+ if (!options.editable) {
2052
+ throw "Grid : makeSelectedCellEditable : should never get called when options.editable is false";
2053
+ }
2054
+
2055
+ // cancel pending async call if there is one
2056
+ clearTimeout(h_editorLoader);
2057
+
2058
+ if (!isCellPotentiallyEditable(currentRow,currentCell)) {
2059
+ return;
2060
+ }
2061
+
2062
+ if (self.onBeforeEditCell && self.onBeforeEditCell(currentRow,currentCell,gridDataGetItem(currentRow)) === false) {
2063
+ focusOnCurrentCell();
2064
+ return;
2065
+ }
2066
+
2067
+ options.editorLock.activate(editController);
2068
+ $(currentCellNode).addClass("editable");
2069
+
2070
+ currentCellNode.innerHTML = "";
2071
+
2072
+ var columnDef = columns[currentCell];
2073
+ var item = gridDataGetItem(currentRow);
2074
+
2075
+ currentEditor = new (getEditor(columnDef))({
2076
+ grid: self,
2077
+ gridPosition: absBox($container[0]),
2078
+ position: absBox(currentCellNode),
2079
+ container: currentCellNode,
2080
+ column: columnDef,
2081
+ item: item || {},
2082
+ commitChanges: commitEditAndSetFocus,
2083
+ cancelChanges: cancelEditAndSetFocus
2084
+ });
2085
+
2086
+ if (item)
2087
+ currentEditor.loadValue(item);
2088
+
2089
+ serializedEditorValue = currentEditor.serializeValue();
2090
+
2091
+ if (currentEditor.position)
2092
+ handleCurrentCellPositionChange();
2093
+ }
2094
+
2095
+ function commitEditAndSetFocus() {
2096
+ // if the commit fails, it would do so due to a validation error
2097
+ // if so, do not steal the focus from the editor
2098
+ if (options.editorLock.commitCurrentEdit()) {
2099
+ focusOnCurrentCell();
2100
+
2101
+ if (options.autoEdit) {
2102
+ navigateDown();
2103
+ }
2104
+ }
2105
+ }
2106
+
2107
+ function cancelEditAndSetFocus() {
2108
+ if (options.editorLock.cancelCurrentEdit()) {
2109
+ focusOnCurrentCell();
2110
+ }
2111
+ }
2112
+
2113
+ function absBox(elem) {
2114
+ var box = {top:elem.offsetTop, left:elem.offsetLeft, bottom:0, right:0, width:$(elem).outerWidth(), height:$(elem).outerHeight(), visible:true};
2115
+ box.bottom = box.top + box.height;
2116
+ box.right = box.left + box.width;
2117
+
2118
+ // walk up the tree
2119
+ var offsetParent = elem.offsetParent;
2120
+ while ((elem = elem.parentNode) != document.body) {
2121
+ if (box.visible && elem.scrollHeight != elem.offsetHeight && $(elem).css("overflowY") != "visible")
2122
+ box.visible = box.bottom > elem.scrollTop && box.top < elem.scrollTop + elem.clientHeight;
2123
+
2124
+ if (box.visible && elem.scrollWidth != elem.offsetWidth && $(elem).css("overflowX") != "visible")
2125
+ box.visible = box.right > elem.scrollLeft && box.left < elem.scrollLeft + elem.clientWidth;
2126
+
2127
+ box.left -= elem.scrollLeft;
2128
+ box.top -= elem.scrollTop;
2129
+
2130
+ if (elem === offsetParent) {
2131
+ box.left += elem.offsetLeft;
2132
+ box.top += elem.offsetTop;
2133
+ offsetParent = elem.offsetParent;
2134
+ }
2135
+
2136
+ box.bottom = box.top + box.height;
2137
+ box.right = box.left + box.width;
2138
+ }
2139
+
2140
+ return box;
2141
+ }
2142
+
2143
+ function getCurrentCellPosition(){
2144
+ return absBox(currentCellNode);
2145
+ }
2146
+
2147
+ function getGridPosition(){
2148
+ return absBox($container[0])
2149
+ }
2150
+
2151
+ function handleCurrentCellPositionChange() {
2152
+ if (!currentCellNode) return;
2153
+ var cellBox;
2154
+
2155
+ if (self.onCurrentCellPositionChanged){
2156
+ cellBox = getCurrentCellPosition();
2157
+ self.onCurrentCellPositionChanged(cellBox);
2158
+ }
2159
+
2160
+ if (currentEditor) {
2161
+ cellBox = cellBox || getCurrentCellPosition();
2162
+ if (currentEditor.show && currentEditor.hide) {
2163
+ if (!cellBox.visible)
2164
+ currentEditor.hide();
2165
+ else
2166
+ currentEditor.show();
2167
+ }
2168
+
2169
+ if (currentEditor.position)
2170
+ currentEditor.position(cellBox);
2171
+ }
2172
+ }
2173
+
2174
+ function getCellEditor() {
2175
+ return currentEditor;
2176
+ }
2177
+
2178
+ function getCurrentCell() {
2179
+ if (!currentCellNode)
2180
+ return null;
2181
+ else
2182
+ return {row: currentRow, cell: currentCell};
2183
+ }
2184
+
2185
+ function getCurrentCellNode() {
2186
+ return currentCellNode;
2187
+ }
2188
+
2189
+ function scrollRowIntoView(row, doPaging) {
2190
+ var rowAtTop = row * options.rowHeight;
2191
+ var rowAtBottom = (row + 1) * options.rowHeight - viewportH + (viewportHasHScroll?scrollbarDimensions.height:0);
2192
+
2193
+ // need to page down?
2194
+ if ((row + 1) * options.rowHeight > scrollTop + viewportH + offset) {
2195
+ scrollTo(doPaging ? rowAtTop : rowAtBottom);
2196
+ render();
2197
+ }
2198
+
2199
+ // or page up?
2200
+ else if (row * options.rowHeight < scrollTop + offset) {
2201
+ scrollTo(doPaging ? rowAtBottom : rowAtTop);
2202
+ render();
2203
+ }
2204
+ }
2205
+
2206
+ function gotoDir(dy, dx, rollover) {
2207
+ if (!currentCellNode || !options.enableCellNavigation) { return; }
2208
+ if (!options.editorLock.commitCurrentEdit()) { return; }
2209
+
2210
+ function selectableCellFilter() {
2211
+ return !columns[getSiblingIndex(this)].unselectable
2212
+ }
2213
+
2214
+ var nextRow = rowsCache[currentRow + dy];
2215
+ var nextCell = (nextRow && currentCell + dx >= 0)
2216
+ ? $(nextRow).children().eq(currentCell+dx).filter(selectableCellFilter)
2217
+ : null;
2218
+
2219
+ if (nextCell && !nextCell.length) {
2220
+ var nodes = $(nextRow).children()
2221
+ .filter(function(index) { return (dx>0) ? index > currentCell + dx : index < currentCell + dx })
2222
+ .filter(selectableCellFilter);
2223
+
2224
+ if (nodes && nodes.length) {
2225
+ nextCell = (dx>0)
2226
+ ? nodes.eq(0)
2227
+ : nodes.eq(nodes.length-1);
2228
+ }
2229
+ }
2230
+
2231
+ if (rollover && dy === 0 && !(nextRow && nextCell && nextCell.length)) {
2232
+ if (!nextCell || !nextCell.length) {
2233
+ nextRow = rowsCache[currentRow + dy + ((dx>0)?1:-1)];
2234
+ var nodes = $(nextRow).children().filter(selectableCellFilter);
2235
+ if (dx > 0) {
2236
+ nextCell = nextRow
2237
+ ? nodes.eq(0)
2238
+ : null;
2239
+ }
2240
+ else {
2241
+ nextCell = nextRow
2242
+ ? nodes.eq(nodes.length-1)
2243
+ : null;
2244
+ }
2245
+ }
2246
+ }
2247
+
2248
+ if (nextRow && nextCell && nextCell.length) {
2249
+ // if selecting the 'add new' row, start editing right away
2250
+ var row = parseInt($(nextRow).attr("row"), 10);
2251
+ var isAddNewRow = (row == defaultGetLength());
2252
+ scrollRowIntoView(row,!isAddNewRow);
2253
+ setSelectedCellAndRow(nextCell[0], isAddNewRow || options.autoEdit);
2254
+
2255
+ // if no editor was created, set the focus back on the cell
2256
+ if (!currentEditor) {
2257
+ focusOnCurrentCell();
2258
+ }
2259
+ }
2260
+ else {
2261
+ focusOnCurrentCell();
2262
+ }
2263
+ }
2264
+
2265
+ function gotoCell(row, cell, forceEdit) {
2266
+ if (row > gridDataGetLength() || row < 0 || cell >= columns.length || cell < 0) { return; }
2267
+ if (!options.enableCellNavigation || columns[cell].unselectable) { return; }
2268
+
2269
+ if (!options.editorLock.commitCurrentEdit()) { return; }
2270
+
2271
+ scrollRowIntoView(row,false);
2272
+
2273
+ var newCell = null;
2274
+ if (!columns[cell].unselectable) {
2275
+ newCell = $(rowsCache[row]).children().eq(cell)[0];
2276
+ }
2277
+
2278
+ // if selecting the 'add new' row, start editing right away
2279
+ setSelectedCellAndRow(newCell, forceEdit || (row === gridDataGetLength()) || options.autoEdit);
2280
+
2281
+ // if no editor was created, set the focus back on the cell
2282
+ if (!currentEditor) {
2283
+ focusOnCurrentCell();
2284
+ }
2285
+ }
2286
+
2287
+ function navigateUp() {
2288
+ gotoDir(-1, 0, false);
2289
+ }
2290
+
2291
+ function navigateDown() {
2292
+ gotoDir(1, 0, false);
2293
+ }
2294
+
2295
+ function navigateLeft() {
2296
+ gotoDir(0, -1, false);
2297
+ }
2298
+
2299
+ function navigateRight() {
2300
+ gotoDir(0, 1, false);
2301
+ }
2302
+
2303
+ function navigatePrev() {
2304
+ gotoDir(0, -1, true);
2305
+ }
2306
+
2307
+ function navigateNext() {
2308
+ gotoDir(0, 1, true);
2309
+ }
2310
+
2311
+ //////////////////////////////////////////////////////////////////////////////////////////////
2312
+ // IEditor implementation for the editor lock
2313
+
2314
+ function commitCurrentEdit() {
2315
+ var item = gridDataGetItem(currentRow);
2316
+ var column = columns[currentCell];
2317
+
2318
+ if (currentEditor) {
2319
+ if (currentEditor.isValueChanged()) {
2320
+ var validationResults = currentEditor.validate();
2321
+
2322
+ if (validationResults.valid) {
2323
+ if (currentRow < gridDataGetLength()) {
2324
+ var editCommand = {
2325
+ row: currentRow,
2326
+ cell: currentCell,
2327
+ editor: currentEditor,
2328
+ serializedValue: currentEditor.serializeValue(),
2329
+ prevSerializedValue: serializedEditorValue,
2330
+ execute: function() {
2331
+ this.editor.applyValue(item,this.serializedValue);
2332
+ updateRow(this.row);
2333
+ },
2334
+ undo: function() {
2335
+ this.editor.applyValue(item,this.prevSerializedValue);
2336
+ updateRow(this.row);
2337
+ }
2338
+ };
2339
+
2340
+ if (options.editCommandHandler) {
2341
+ makeSelectedCellNormal();
2342
+ options.editCommandHandler(item,column,editCommand);
2343
+
2344
+ }
2345
+ else {
2346
+ editCommand.execute();
2347
+ makeSelectedCellNormal();
2348
+ }
2349
+
2350
+ if (self.onCellChange) {
2351
+ self.onCellChange(currentRow,currentCell,item);
2352
+ }
2353
+ }
2354
+ else if (self.onAddNewRow) {
2355
+ var newItem = {};
2356
+ currentEditor.applyValue(newItem,currentEditor.serializeValue());
2357
+ makeSelectedCellNormal();
2358
+ self.onAddNewRow(newItem,column);
2359
+ }
2360
+
2361
+ // check whether the lock has been re-acquired by event handlers
2362
+ return !options.editorLock.isActive();
2363
+ }
2364
+ else {
2365
+ // TODO: remove and put in onValidationError handlers in examples
2366
+ $(currentCellNode).addClass("invalid");
2367
+ $(currentCellNode).stop(true,true).effect("highlight", {color:"red"}, 300);
2368
+
2369
+ if (self.onValidationError) {
2370
+ self.onValidationError(currentCellNode, validationResults, currentRow, currentCell, column);
2371
+ }
2372
+
2373
+ currentEditor.focus();
2374
+ return false;
2375
+ }
2376
+ }
2377
+
2378
+ makeSelectedCellNormal();
2379
+ }
2380
+ return true;
2381
+ }
2382
+
2383
+ function cancelCurrentEdit() {
2384
+ makeSelectedCellNormal();
2385
+ return true;
2386
+ }
2387
+
2388
+
2389
+ //////////////////////////////////////////////////////////////////////////////////////////////
2390
+ // Debug
2391
+
2392
+ this.debug = function() {
2393
+ var s = "";
2394
+
2395
+ s += ("\n" + "counter_rows_rendered: " + counter_rows_rendered);
2396
+ s += ("\n" + "counter_rows_removed: " + counter_rows_removed);
2397
+ s += ("\n" + "renderedRows: " + renderedRows);
2398
+ s += ("\n" + "numVisibleRows: " + numVisibleRows);
2399
+ s += ("\n" + "maxSupportedCssHeight: " + maxSupportedCssHeight);
2400
+ s += ("\n" + "n(umber of pages): " + n);
2401
+ s += ("\n" + "(current) page: " + page);
2402
+ s += ("\n" + "page height (ph): " + ph);
2403
+ s += ("\n" + "scrollDir: " + scrollDir);
2404
+
2405
+ alert(s);
2406
+ };
2407
+
2408
+ // a debug helper to be able to access private members
2409
+ this.eval = function(expr) {
2410
+ return eval(expr);
2411
+ };
2412
+
2413
+ init();
2414
+
2415
+
2416
+ //////////////////////////////////////////////////////////////////////////////////////////////
2417
+ // Public API
2418
+
2419
+ $.extend(this, {
2420
+ "slickGridVersion": "1.4.3",
2421
+
2422
+ // Events
2423
+ "onSort": null,
2424
+ "onHeaderContextMenu": null,
2425
+ "onClick": null,
2426
+ "onDblClick": null,
2427
+ "onContextMenu": null,
2428
+ "onKeyDown": null,
2429
+ "onAddNewRow": null,
2430
+ "onValidationError": null,
2431
+ "onViewportChanged": null,
2432
+ "onSelectedRowsChanged": null,
2433
+ "onColumnsReordered": null,
2434
+ "onColumnsResized": null,
2435
+ "onBeforeMoveRows": null,
2436
+ "onMoveRows": null,
2437
+ "onCellChange": null,
2438
+ "onBeforeEditCell": null,
2439
+ "onBeforeCellEditorDestroy": null,
2440
+ "onBeforeDestroy": null,
2441
+ "onCurrentCellChanged": null,
2442
+ "onCurrentCellPositionChanged": null,
2443
+ "onCellRangeSelected": null,
2444
+
2445
+ // Methods
2446
+ "getColumns": getColumns,
2447
+ "setColumns": setColumns,
2448
+ "getOptions": getOptions,
2449
+ "setOptions": setOptions,
2450
+ "getData": getData,
2451
+ "setData": setData,
2452
+ "destroy": destroy,
2453
+ "getColumnIndex": getColumnIndex,
2454
+ "autosizeColumns": autosizeColumns,
2455
+ "updateCell": updateCell,
2456
+ "updateRow": updateRow,
2457
+ "removeRow": removeRow,
2458
+ "removeRows": removeRows,
2459
+ "removeAllRows": removeAllRows,
2460
+ "render": render,
2461
+ "invalidate": invalidate,
2462
+ "setHighlightedCells": setHighlightedCells,
2463
+ "flashCell": flashCell,
2464
+ "getViewport": getVisibleRange,
2465
+ "resizeCanvas": resizeCanvas,
2466
+ "updateRowCount": updateRowCount,
2467
+ "getCellFromPoint": getCellFromPoint,
2468
+ "getCellFromEvent": getCellFromEvent,
2469
+ "getCurrentCell": getCurrentCell,
2470
+ "getCurrentCellNode": getCurrentCellNode,
2471
+ "resetCurrentCell": resetCurrentCell,
2472
+ "navigatePrev": navigatePrev,
2473
+ "navigateNext": navigateNext,
2474
+ "navigateUp": navigateUp,
2475
+ "navigateDown": navigateDown,
2476
+ "navigateLeft": navigateLeft,
2477
+ "navigateRight": navigateRight,
2478
+ "gotoCell": gotoCell,
2479
+ "editCurrentCell": makeSelectedCellEditable,
2480
+ "getCellEditor": getCellEditor,
2481
+ "scrollRowIntoView": scrollRowIntoView,
2482
+ "getSelectedRows": getSelectedRows,
2483
+ "setSelectedRows": setSelectedRows,
2484
+ "getSecondaryHeaderRow": getSecondaryHeaderRow,
2485
+ "showSecondaryHeaderRow": showSecondaryHeaderRow,
2486
+ "hideSecondaryHeaderRow": hideSecondaryHeaderRow,
2487
+ "setSortColumn": setSortColumn,
2488
+ "getCurrentCellPosition" : getCurrentCellPosition,
2489
+ "getGridPosition": getGridPosition,
2490
+
2491
+ // IEditor implementation
2492
+ "getEditController": getEditController
2493
+ });
2494
+ }
2495
+
2496
+ // Slick.Grid
2497
+ $.extend(true, window, {
2498
+ Slick: {
2499
+ Grid: SlickGrid,
2500
+ EditorLock: EditorLock,
2501
+ GlobalEditorLock: new EditorLock()
2502
+ }
2503
+ });
2504
+ }(jQuery));