rails_slickgrid 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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));