slickgrid-rails 0.2.0 → 0.3.0

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.
Files changed (36) hide show
  1. data/.gitignore +2 -0
  2. data/README.md +9 -0
  3. data/Rakefile +33 -0
  4. data/slickgrid-rails.gemspec +1 -2
  5. data/vendor/assets/javascripts/slick/controls/columnpicker.css +31 -0
  6. data/vendor/assets/javascripts/slick/controls/columnpicker.js +32 -0
  7. data/vendor/assets/javascripts/slick/controls/pager.css +41 -0
  8. data/vendor/assets/javascripts/slick/controls/pager.js +4 -8
  9. data/vendor/assets/javascripts/slick/core.js +35 -1
  10. data/vendor/assets/javascripts/slick/dataview.js +240 -85
  11. data/vendor/assets/javascripts/slick/editors.js +5 -5
  12. data/vendor/assets/javascripts/slick/formatters.js +5 -1
  13. data/vendor/assets/javascripts/slick/grid.css +157 -0
  14. data/vendor/assets/javascripts/slick/grid.js +770 -269
  15. data/vendor/assets/javascripts/slick/groupitemmetadataprovider.js +15 -10
  16. data/vendor/assets/javascripts/slick/plugins/autotooltips.js +49 -14
  17. data/vendor/assets/javascripts/slick/plugins/cellrangeselector.js +9 -8
  18. data/vendor/assets/javascripts/slick/plugins/cellselectionmodel.js +62 -2
  19. data/vendor/assets/javascripts/slick/plugins/checkboxselectcolumn.js +8 -9
  20. data/vendor/assets/javascripts/slick/plugins/headerbuttons.css +39 -0
  21. data/vendor/assets/javascripts/slick/plugins/headerbuttons.js +177 -0
  22. data/vendor/assets/javascripts/slick/plugins/headermenu.css +59 -0
  23. data/vendor/assets/javascripts/slick/plugins/headermenu.js +275 -0
  24. data/vendor/assets/javascripts/slick/plugins/rowmovemanager.js +17 -11
  25. data/vendor/assets/javascripts/slick/plugins/rowselectionmodel.js +1 -1
  26. data/vendor/assets/javascripts/slick/remotemodel.js +164 -0
  27. data/vendor/assets/stylesheets/slick/controls/columnpicker.css +31 -0
  28. data/vendor/assets/stylesheets/slick/controls/pager.css +41 -0
  29. data/vendor/assets/stylesheets/slick/grid.css +157 -0
  30. data/vendor/assets/stylesheets/slick/plugins/headerbuttons.css +39 -0
  31. data/vendor/assets/stylesheets/slick/plugins/headermenu.css +59 -0
  32. metadata +51 -17
  33. data/.rvmrc +0 -1
  34. data/Gemfile.lock +0 -85
  35. data/fetch.sh +0 -8
  36. data/lib/slickgrid/rails/version.rb +0 -6
@@ -302,7 +302,7 @@
302
302
  };
303
303
 
304
304
  this.loadValue = function (item) {
305
- defaultValue = item[args.column.field];
305
+ defaultValue = !!item[args.column.field];
306
306
  if (defaultValue) {
307
307
  $select.attr("checked", "checked");
308
308
  } else {
@@ -311,7 +311,7 @@
311
311
  };
312
312
 
313
313
  this.serializeValue = function () {
314
- return $select.attr("checked");
314
+ return !!$select.attr("checked");
315
315
  };
316
316
 
317
317
  this.applyValue = function (item, state) {
@@ -319,7 +319,7 @@
319
319
  };
320
320
 
321
321
  this.isValueChanged = function () {
322
- return ($select.attr("checked") != defaultValue);
322
+ return (this.serializeValue() !== defaultValue);
323
323
  };
324
324
 
325
325
  this.validate = function () {
@@ -445,10 +445,10 @@
445
445
  scope.cancel();
446
446
  } else if (e.which == $.ui.keyCode.TAB && e.shiftKey) {
447
447
  e.preventDefault();
448
- grid.navigatePrev();
448
+ args.grid.navigatePrev();
449
449
  } else if (e.which == $.ui.keyCode.TAB) {
450
450
  e.preventDefault();
451
- grid.navigateNext();
451
+ args.grid.navigateNext();
452
452
  }
453
453
  };
454
454
 
@@ -1,5 +1,9 @@
1
1
  /***
2
2
  * Contains basic SlickGrid formatters.
3
+ *
4
+ * NOTE: These are merely examples. You will most likely need to implement something more
5
+ * robust/extensible/localizable/etc. for your use!
6
+ *
3
7
  * @module Formatters
4
8
  * @namespace Slick
5
9
  */
@@ -52,4 +56,4 @@
52
56
  function CheckmarkFormatter(row, cell, value, columnDef, dataContext) {
53
57
  return value ? "<img src='../images/tick.png'>" : "";
54
58
  }
55
- })(jQuery);
59
+ })(jQuery);
@@ -0,0 +1,157 @@
1
+ /*
2
+ IMPORTANT:
3
+ In order to preserve the uniform grid appearance, all cell styles need to have padding, margin and border sizes.
4
+ No built-in (selected, editable, highlight, flashing, invalid, loading, :focus) or user-specified CSS
5
+ classes should alter those!
6
+ */
7
+
8
+ .slick-header.ui-state-default, .slick-headerrow.ui-state-default {
9
+ width: 100%;
10
+ overflow: hidden;
11
+ border-left: 0px;
12
+ }
13
+
14
+ .slick-header-columns, .slick-headerrow-columns {
15
+ position: relative;
16
+ white-space: nowrap;
17
+ cursor: default;
18
+ overflow: hidden;
19
+ }
20
+
21
+ .slick-header-column.ui-state-default {
22
+ position: relative;
23
+ display: inline-block;
24
+ overflow: hidden;
25
+ -o-text-overflow: ellipsis;
26
+ text-overflow: ellipsis;
27
+ height: 16px;
28
+ line-height: 16px;
29
+ margin: 0;
30
+ padding: 4px;
31
+ border-right: 1px solid silver;
32
+ border-left: 0px;
33
+ border-top: 0px;
34
+ border-bottom: 0px;
35
+ float: left;
36
+ }
37
+
38
+ .slick-headerrow-column.ui-state-default {
39
+ padding: 4px;
40
+ }
41
+
42
+ .slick-header-column-sorted {
43
+ font-style: italic;
44
+ }
45
+
46
+ .slick-sort-indicator {
47
+ display: inline-block;
48
+ width: 8px;
49
+ height: 5px;
50
+ margin-left: 4px;
51
+ margin-top: 6px;
52
+ float: left;
53
+ }
54
+
55
+ .slick-sort-indicator-desc {
56
+ background: image-url("slick/sort-desc.gif");
57
+ }
58
+
59
+ .slick-sort-indicator-asc {
60
+ background: image-url("slick/sort-asc.gif");
61
+ }
62
+
63
+ .slick-resizable-handle {
64
+ position: absolute;
65
+ font-size: 0.1px;
66
+ display: block;
67
+ cursor: col-resize;
68
+ width: 4px;
69
+ right: 0px;
70
+ top: 0;
71
+ height: 100%;
72
+ }
73
+
74
+ .slick-sortable-placeholder {
75
+ background: silver;
76
+ }
77
+
78
+ .grid-canvas {
79
+ position: relative;
80
+ outline: 0;
81
+ }
82
+
83
+ .slick-row.ui-widget-content, .slick-row.ui-state-active {
84
+ position: absolute;
85
+ border: 0px;
86
+ width: 100%;
87
+ }
88
+
89
+ .slick-cell, .slick-headerrow-column {
90
+ position: absolute;
91
+ border: 1px solid transparent;
92
+ border-right: 1px dotted silver;
93
+ border-bottom-color: silver;
94
+ overflow: hidden;
95
+ -o-text-overflow: ellipsis;
96
+ text-overflow: ellipsis;
97
+ vertical-align: middle;
98
+ z-index: 1;
99
+ padding: 1px 2px 2px 1px;
100
+ margin: 0;
101
+ white-space: nowrap;
102
+ cursor: default;
103
+ }
104
+
105
+ .slick-group {
106
+ }
107
+
108
+ .slick-group-toggle {
109
+ display: inline-block;
110
+ }
111
+
112
+ .slick-cell.highlighted {
113
+ background: lightskyblue;
114
+ background: rgba(0, 0, 255, 0.2);
115
+ -webkit-transition: all 0.5s;
116
+ -moz-transition: all 0.5s;
117
+ -o-transition: all 0.5s;
118
+ transition: all 0.5s;
119
+ }
120
+
121
+ .slick-cell.flashing {
122
+ border: 1px solid red !important;
123
+ }
124
+
125
+ .slick-cell.editable {
126
+ z-index: 11;
127
+ overflow: visible;
128
+ background: white;
129
+ border-color: black;
130
+ border-style: solid;
131
+ }
132
+
133
+ .slick-cell:focus {
134
+ outline: none;
135
+ }
136
+
137
+ .slick-reorder-proxy {
138
+ display: inline-block;
139
+ background: blue;
140
+ opacity: 0.15;
141
+ filter: alpha(opacity = 15);
142
+ cursor: move;
143
+ }
144
+
145
+ .slick-reorder-guide {
146
+ display: inline-block;
147
+ height: 2px;
148
+ background: blue;
149
+ opacity: 0.7;
150
+ filter: alpha(opacity = 70);
151
+ }
152
+
153
+ .slick-selection {
154
+ z-index: 10;
155
+ position: absolute;
156
+ border: 2px dashed black;
157
+ }
@@ -7,7 +7,7 @@
7
7
  * Distributed under MIT license.
8
8
  * All rights reserved.
9
9
  *
10
- * SlickGrid v2.0
10
+ * SlickGrid v2.1
11
11
  *
12
12
  * NOTES:
13
13
  * Cell/row DOM manipulations are done directly bypassing jQuery's DOM manipulation methods.
@@ -68,7 +68,7 @@ if (typeof Slick === "undefined") {
68
68
  asyncEditorLoadDelay: 100,
69
69
  forceFitColumns: false,
70
70
  enableAsyncPostRender: false,
71
- asyncPostRenderDelay: 60,
71
+ asyncPostRenderDelay: 50,
72
72
  autoHeight: false,
73
73
  editorLock: Slick.GlobalEditorLock,
74
74
  showHeaderRow: false,
@@ -84,7 +84,8 @@ if (typeof Slick === "undefined") {
84
84
  dataItemColumnValueExtractor: null,
85
85
  fullWidthRows: false,
86
86
  multiColumnSort: false,
87
- defaultFormatter: defaultFormatter
87
+ defaultFormatter: defaultFormatter,
88
+ forceSyncScrolling: false
88
89
  };
89
90
 
90
91
  var columnDefaults = {
@@ -93,7 +94,10 @@ if (typeof Slick === "undefined") {
93
94
  sortable: false,
94
95
  minWidth: 30,
95
96
  rerenderOnResize: false,
96
- headerCssClass: null
97
+ headerCssClass: null,
98
+ defaultSortAsc: true,
99
+ focusable: true,
100
+ selectable: true
97
101
  };
98
102
 
99
103
  // scroller
@@ -105,22 +109,24 @@ if (typeof Slick === "undefined") {
105
109
 
106
110
  var page = 0; // current page
107
111
  var offset = 0; // current page offset
108
- var scrollDir = 1;
112
+ var vScrollDir = 1;
109
113
 
110
114
  // private
111
115
  var initialized = false;
112
116
  var $container;
113
117
  var uid = "slickgrid_" + Math.round(1000000 * Math.random());
114
118
  var self = this;
119
+ var $focusSink, $focusSink2;
115
120
  var $headerScroller;
116
121
  var $headers;
117
- var $headerRow, $headerRowScroller;
122
+ var $headerRow, $headerRowScroller, $headerRowSpacer;
118
123
  var $topPanelScroller;
119
124
  var $topPanel;
120
125
  var $viewport;
121
126
  var $canvas;
122
127
  var $style;
123
- var stylesheet, columnCssRulesL = [], columnCssRulesR = [];
128
+ var $boundAncestors;
129
+ var stylesheet, columnCssRulesL, columnCssRulesR;
124
130
  var viewportH, viewportW;
125
131
  var canvasWidth;
126
132
  var viewportHasHScroll, viewportHasVScroll;
@@ -129,6 +135,7 @@ if (typeof Slick === "undefined") {
129
135
  var absoluteColumnMinWidth;
130
136
  var numberOfRows = 0;
131
137
 
138
+ var tabbingDirection = 1;
132
139
  var activePosX;
133
140
  var activeRow, activeCell;
134
141
  var activeCellNode = null;
@@ -142,8 +149,9 @@ if (typeof Slick === "undefined") {
142
149
  var prevScrollTop = 0;
143
150
  var scrollTop = 0;
144
151
  var lastRenderedScrollTop = 0;
152
+ var lastRenderedScrollLeft = 0;
145
153
  var prevScrollLeft = 0;
146
- var avgRowRenderTime = 10;
154
+ var scrollLeft = 0;
147
155
 
148
156
  var selectionModel;
149
157
  var selectedRows = [];
@@ -153,6 +161,8 @@ if (typeof Slick === "undefined") {
153
161
 
154
162
  var columnsById = {};
155
163
  var sortColumns = [];
164
+ var columnPosLeft = [];
165
+ var columnPosRight = [];
156
166
 
157
167
 
158
168
  // async call handles
@@ -182,8 +192,21 @@ if (typeof Slick === "undefined") {
182
192
  scrollbarDimensions = scrollbarDimensions || measureScrollbar();
183
193
 
184
194
  options = $.extend({}, defaults, options);
195
+ validateAndEnforceOptions();
185
196
  columnDefaults.width = options.defaultColumnWidth;
186
197
 
198
+ columnsById = {};
199
+ for (var i = 0; i < columns.length; i++) {
200
+ var m = columns[i] = $.extend({}, columnDefaults, columns[i]);
201
+ columnsById[m.id] = i;
202
+ if (m.minWidth && m.width < m.minWidth) {
203
+ m.width = m.minWidth;
204
+ }
205
+ if (m.maxWidth && m.width > m.maxWidth) {
206
+ m.width = m.maxWidth;
207
+ }
208
+ }
209
+
187
210
  // validate loaded JavaScript modules against requested options
188
211
  if (options.enableColumnReorder && !$.fn.sortable) {
189
212
  throw new Error("SlickGrid's 'enableColumnReorder = true' option requires jquery-ui.sortable module to be loaded");
@@ -196,8 +219,6 @@ if (typeof Slick === "undefined") {
196
219
 
197
220
  $container
198
221
  .empty()
199
- .attr("tabIndex", 0)
200
- .attr("hideFocus", true)
201
222
  .css("overflow", "hidden")
202
223
  .css("outline", 0)
203
224
  .addClass(uid)
@@ -208,11 +229,17 @@ if (typeof Slick === "undefined") {
208
229
  $container.css("position", "relative");
209
230
  }
210
231
 
232
+ $focusSink = $("<div tabIndex='0' hideFocus style='position:fixed;width:0;height:0;top:0;left:0;outline:0;'></div>").appendTo($container);
233
+
211
234
  $headerScroller = $("<div class='slick-header ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
212
- $headers = $("<div class='slick-header-columns' style='width:10000px; left:-1000px' />").appendTo($headerScroller);
235
+ $headers = $("<div class='slick-header-columns' style='left:-1000px' />").appendTo($headerScroller);
236
+ $headers.width(getHeadersWidth());
213
237
 
214
238
  $headerRowScroller = $("<div class='slick-headerrow ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
215
239
  $headerRow = $("<div class='slick-headerrow-columns' />").appendTo($headerRowScroller);
240
+ $headerRowSpacer = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>")
241
+ .css("width", getCanvasWidth() + scrollbarDimensions.width + "px")
242
+ .appendTo($headerRowScroller);
216
243
 
217
244
  $topPanelScroller = $("<div class='slick-top-panel-scroller ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
218
245
  $topPanel = $("<div class='slick-top-panel' style='width:10000px' />").appendTo($topPanelScroller);
@@ -225,10 +252,12 @@ if (typeof Slick === "undefined") {
225
252
  $headerRowScroller.hide();
226
253
  }
227
254
 
228
- $viewport = $("<div class='slick-viewport' tabIndex='0' hideFocus style='width:100%;overflow:auto;outline:0;position:relative;;'>").appendTo($container);
255
+ $viewport = $("<div class='slick-viewport' style='width:100%;overflow:auto;outline:0;position:relative;;'>").appendTo($container);
229
256
  $viewport.css("overflow-y", options.autoHeight ? "hidden" : "auto");
230
257
 
231
- $canvas = $("<div class='grid-canvas' tabIndex='0' hideFocus />").appendTo($viewport);
258
+ $canvas = $("<div class='grid-canvas' />").appendTo($viewport);
259
+
260
+ $focusSink2 = $focusSink.clone().appendTo($container);
232
261
 
233
262
  if (!options.explicitInitialization) {
234
263
  finishInitialization();
@@ -260,6 +289,7 @@ if (typeof Slick === "undefined") {
260
289
  });
261
290
  }
262
291
 
292
+ updateColumnCaches();
263
293
  createColumnHeaders();
264
294
  setupColumnSort();
265
295
  createCssRules();
@@ -269,17 +299,23 @@ if (typeof Slick === "undefined") {
269
299
  $container
270
300
  .bind("resize.slickgrid", resizeCanvas);
271
301
  $viewport
272
- .bind("scroll.slickgrid", handleScroll);
302
+ .bind("scroll", handleScroll);
273
303
  $headerScroller
274
- .bind("contextmenu.slickgrid", handleHeaderContextMenu)
275
- .bind("click.slickgrid", handleHeaderClick);
304
+ .bind("contextmenu", handleHeaderContextMenu)
305
+ .bind("click", handleHeaderClick)
306
+ .delegate(".slick-header-column", "mouseenter", handleHeaderMouseEnter)
307
+ .delegate(".slick-header-column", "mouseleave", handleHeaderMouseLeave);
308
+ $headerRowScroller
309
+ .bind("scroll", handleHeaderRowScroll);
310
+ $focusSink.add($focusSink2)
311
+ .bind("keydown", handleKeyDown);
276
312
  $canvas
277
- .bind("keydown.slickgrid", handleKeyDown)
278
- .bind("click.slickgrid", handleClick)
279
- .bind("dblclick.slickgrid", handleDblClick)
280
- .bind("contextmenu.slickgrid", handleContextMenu)
313
+ .bind("keydown", handleKeyDown)
314
+ .bind("click", handleClick)
315
+ .bind("dblclick", handleDblClick)
316
+ .bind("contextmenu", handleContextMenu)
281
317
  .bind("draginit", handleDragInit)
282
- .bind("dragstart", handleDragStart)
318
+ .bind("dragstart", {distance: 3}, handleDragStart)
283
319
  .bind("drag", handleDrag)
284
320
  .bind("dragend", handleDragEnd)
285
321
  .delegate(".slick-cell", "mouseenter", handleMouseEnter)
@@ -337,12 +373,22 @@ if (typeof Slick === "undefined") {
337
373
  return dim;
338
374
  }
339
375
 
376
+ function getHeadersWidth() {
377
+ var headersWidth = 0;
378
+ for (var i = 0, ii = columns.length; i < ii; i++) {
379
+ var width = columns[i].width;
380
+ headersWidth += width;
381
+ }
382
+ headersWidth += scrollbarDimensions.width;
383
+ return Math.max(headersWidth, viewportW) + 1000;
384
+ }
385
+
340
386
  function getCanvasWidth() {
341
387
  var availableWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW;
342
388
  var rowWidth = 0;
343
389
  var i = columns.length;
344
390
  while (i--) {
345
- rowWidth += (columns[i].width || columnDefaults.width);
391
+ rowWidth += columns[i].width;
346
392
  }
347
393
  return options.fullWidthRows ? Math.max(rowWidth, availableWidth) : rowWidth;
348
394
  }
@@ -354,9 +400,12 @@ if (typeof Slick === "undefined") {
354
400
  if (canvasWidth != oldCanvasWidth) {
355
401
  $canvas.width(canvasWidth);
356
402
  $headerRow.width(canvasWidth);
403
+ $headers.width(getHeadersWidth());
357
404
  viewportHasHScroll = (canvasWidth > viewportW - scrollbarDimensions.width);
358
405
  }
359
406
 
407
+ $headerRowSpacer.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0));
408
+
360
409
  if (canvasWidth != oldCanvasWidth || forceColumnWidthsUpdate) {
361
410
  applyColumnWidths();
362
411
  }
@@ -374,18 +423,18 @@ if (typeof Slick === "undefined") {
374
423
  }
375
424
 
376
425
  function getMaxSupportedCssHeight() {
377
- var increment = 1000000;
378
- var supportedHeight = increment;
426
+ var supportedHeight = 1000000;
379
427
  // FF reports the height back but still renders blank after ~6M px
380
- var testUpTo = ($.browser.mozilla) ? 5000000 : 1000000000;
428
+ var testUpTo = navigator.userAgent.toLowerCase().match(/firefox/) ? 6000000 : 1000000000;
381
429
  var div = $("<div style='display:none' />").appendTo(document.body);
382
430
 
383
- while (supportedHeight <= testUpTo) {
384
- div.css("height", supportedHeight + increment);
385
- if (div.height() !== supportedHeight + increment) {
431
+ while (true) {
432
+ var test = supportedHeight * 2;
433
+ div.css("height", test);
434
+ if (test > testUpTo || div.height() !== test) {
386
435
  break;
387
436
  } else {
388
- supportedHeight += increment;
437
+ supportedHeight = test;
389
438
  }
390
439
  }
391
440
 
@@ -399,25 +448,55 @@ if (typeof Slick === "undefined") {
399
448
  while ((elem = elem.parentNode) != document.body && elem != null) {
400
449
  // bind to scroll containers only
401
450
  if (elem == $viewport[0] || elem.scrollWidth != elem.clientWidth || elem.scrollHeight != elem.clientHeight) {
402
- $(elem).bind("scroll.slickgrid", handleActiveCellPositionChange);
451
+ var $elem = $(elem);
452
+ if (!$boundAncestors) {
453
+ $boundAncestors = $elem;
454
+ } else {
455
+ $boundAncestors = $boundAncestors.add($elem);
456
+ }
457
+ $elem.bind("scroll." + uid, handleActiveCellPositionChange);
403
458
  }
404
459
  }
405
460
  }
406
461
 
407
462
  function unbindAncestorScrollEvents() {
408
- $canvas.parents().unbind("scroll.slickgrid");
463
+ if (!$boundAncestors) {
464
+ return;
465
+ }
466
+ $boundAncestors.unbind("scroll." + uid);
467
+ $boundAncestors = null;
409
468
  }
410
469
 
411
470
  function updateColumnHeader(columnId, title, toolTip) {
412
471
  if (!initialized) { return; }
413
472
  var idx = getColumnIndex(columnId);
473
+ if (idx == null) {
474
+ return;
475
+ }
476
+
477
+ var columnDef = columns[idx];
414
478
  var $header = $headers.children().eq(idx);
415
479
  if ($header) {
416
- columns[idx].name = title;
417
- columns[idx].toolTip = toolTip;
480
+ if (title !== undefined) {
481
+ columns[idx].name = title;
482
+ }
483
+ if (toolTip !== undefined) {
484
+ columns[idx].toolTip = toolTip;
485
+ }
486
+
487
+ trigger(self.onBeforeHeaderCellDestroy, {
488
+ "node": $header[0],
489
+ "column": columnDef
490
+ });
491
+
418
492
  $header
419
- .attr("title", toolTip || title || "")
493
+ .attr("title", toolTip || "")
420
494
  .children().eq(0).html(title);
495
+
496
+ trigger(self.onHeaderCellRendered, {
497
+ "node": $header[0],
498
+ "column": columnDef
499
+ });
421
500
  }
422
501
  }
423
502
 
@@ -432,48 +511,76 @@ if (typeof Slick === "undefined") {
432
511
  }
433
512
 
434
513
  function createColumnHeaders() {
435
- function hoverBegin() {
514
+ function onMouseEnter() {
436
515
  $(this).addClass("ui-state-hover");
437
516
  }
438
517
 
439
- function hoverEnd() {
518
+ function onMouseLeave() {
440
519
  $(this).removeClass("ui-state-hover");
441
520
  }
442
521
 
522
+ $headers.find(".slick-header-column")
523
+ .each(function() {
524
+ var columnDef = $(this).data("column");
525
+ if (columnDef) {
526
+ trigger(self.onBeforeHeaderCellDestroy, {
527
+ "node": this,
528
+ "column": columnDef
529
+ });
530
+ }
531
+ });
443
532
  $headers.empty();
533
+ $headers.width(getHeadersWidth());
534
+
535
+ $headerRow.find(".slick-headerrow-column")
536
+ .each(function() {
537
+ var columnDef = $(this).data("column");
538
+ if (columnDef) {
539
+ trigger(self.onBeforeHeaderRowCellDestroy, {
540
+ "node": this,
541
+ "column": columnDef
542
+ });
543
+ }
544
+ });
444
545
  $headerRow.empty();
445
- columnsById = {};
446
546
 
447
547
  for (var i = 0; i < columns.length; i++) {
448
- var m = columns[i] = $.extend({}, columnDefaults, columns[i]);
449
- columnsById[m.id] = i;
548
+ var m = columns[i];
450
549
 
451
550
  var header = $("<div class='ui-state-default slick-header-column' id='" + uid + m.id + "' />")
452
551
  .html("<span class='slick-column-name'>" + m.name + "</span>")
453
552
  .width(m.width - headerColumnWidthDiff)
454
- .attr("title", m.toolTip || m.name || "")
455
- .data("fieldId", m.id)
553
+ .attr("title", m.toolTip || "")
554
+ .data("column", m)
456
555
  .addClass(m.headerCssClass || "")
457
556
  .appendTo($headers);
458
557
 
459
558
  if (options.enableColumnReorder || m.sortable) {
460
- header.hover(hoverBegin, hoverEnd);
559
+ header
560
+ .on('mouseenter', onMouseEnter)
561
+ .on('mouseleave', onMouseLeave);
461
562
  }
462
563
 
463
564
  if (m.sortable) {
565
+ header.addClass("slick-header-sortable");
464
566
  header.append("<span class='slick-sort-indicator' />");
465
567
  }
466
568
 
569
+ trigger(self.onHeaderCellRendered, {
570
+ "node": header[0],
571
+ "column": m
572
+ });
573
+
467
574
  if (options.showHeaderRow) {
468
- $("<div class='ui-state-default slick-headerrow-column l" + i + " r" + i + "'></div>")
575
+ var headerRowCell = $("<div class='ui-state-default slick-headerrow-column l" + i + " r" + i + "'></div>")
576
+ .data("column", m)
469
577
  .appendTo($headerRow);
470
- }
471
- }
472
578
 
473
- if (options.showHeaderRow) {
474
- // add a spacer to let the container scroll beyond the header row columns width
475
- $("<div style='display:block;height:1px;width:10000px;position:absolute;top:0;left:0;'></div>")
476
- .appendTo($headerRowScroller);
579
+ trigger(self.onHeaderRowCellRendered, {
580
+ "node": headerRowCell[0],
581
+ "column": m
582
+ });
583
+ }
477
584
  }
478
585
 
479
586
  setSortColumns(sortColumns);
@@ -497,7 +604,7 @@ if (typeof Slick === "undefined") {
497
604
  return;
498
605
  }
499
606
 
500
- var column = columns[getColumnIndex($col.data("fieldId"))];
607
+ var column = $col.data("column");
501
608
  if (column.sortable) {
502
609
  if (!getEditorLock().commitCurrentEdit()) {
503
610
  return;
@@ -524,7 +631,7 @@ if (typeof Slick === "undefined") {
524
631
  }
525
632
 
526
633
  if (!sortOpts) {
527
- sortOpts = { columnId: column.id, sortAsc: true };
634
+ sortOpts = { columnId: column.id, sortAsc: column.defaultSortAsc };
528
635
  sortColumns.push(sortOpts);
529
636
  } else if (sortColumns.length == 0) {
530
637
  sortColumns.push(sortOpts);
@@ -550,8 +657,10 @@ if (typeof Slick === "undefined") {
550
657
  }
551
658
 
552
659
  function setupColumnReorder() {
660
+ $headers.filter(":ui-sortable").sortable("destroy");
553
661
  $headers.sortable({
554
662
  containment: "parent",
663
+ distance: 3,
555
664
  axis: "x",
556
665
  cursor: "default",
557
666
  tolerance: "intersection",
@@ -812,32 +921,48 @@ if (typeof Slick === "undefined") {
812
921
  } else {
813
922
  $style[0].appendChild(document.createTextNode(rules.join(" ")));
814
923
  }
924
+ }
815
925
 
816
- var sheets = document.styleSheets;
817
- for (var i = 0; i < sheets.length; i++) {
818
- if ((sheets[i].ownerNode || sheets[i].owningElement) == $style[0]) {
819
- stylesheet = sheets[i];
820
- break;
926
+ function getColumnCssRules(idx) {
927
+ if (!stylesheet) {
928
+ var sheets = document.styleSheets;
929
+ for (var i = 0; i < sheets.length; i++) {
930
+ if ((sheets[i].ownerNode || sheets[i].owningElement) == $style[0]) {
931
+ stylesheet = sheets[i];
932
+ break;
933
+ }
934
+ }
935
+
936
+ if (!stylesheet) {
937
+ throw new Error("Cannot find stylesheet.");
821
938
  }
822
- }
823
939
 
824
- // find and cache column CSS rules
825
- columnCssRulesL = [], columnCssRulesR = [];
826
- var cssRules = (stylesheet.cssRules || stylesheet.rules);
827
- var matches, columnIdx;
828
- for (var i = 0; i < cssRules.length; i++) {
829
- if (matches = /\.l\d+/.exec(cssRules[i].selectorText)) {
830
- columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10);
831
- columnCssRulesL[columnIdx] = cssRules[i];
832
- } else if (matches = /\.r\d+/.exec(cssRules[i].selectorText)) {
833
- columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10);
834
- columnCssRulesR[columnIdx] = cssRules[i];
940
+ // find and cache column CSS rules
941
+ columnCssRulesL = [];
942
+ columnCssRulesR = [];
943
+ var cssRules = (stylesheet.cssRules || stylesheet.rules);
944
+ var matches, columnIdx;
945
+ for (var i = 0; i < cssRules.length; i++) {
946
+ var selector = cssRules[i].selectorText;
947
+ if (matches = /\.l\d+/.exec(selector)) {
948
+ columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10);
949
+ columnCssRulesL[columnIdx] = cssRules[i];
950
+ } else if (matches = /\.r\d+/.exec(selector)) {
951
+ columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10);
952
+ columnCssRulesR[columnIdx] = cssRules[i];
953
+ }
835
954
  }
836
955
  }
956
+
957
+ return {
958
+ "left": columnCssRulesL[idx],
959
+ "right": columnCssRulesR[idx]
960
+ };
837
961
  }
838
962
 
839
963
  function removeCssRules() {
840
964
  $style.remove();
965
+ stylesheet = null;
841
966
  }
842
967
 
843
968
  function destroy() {
@@ -845,12 +970,13 @@ if (typeof Slick === "undefined") {
845
970
 
846
971
  trigger(self.onBeforeDestroy, {});
847
972
 
848
- for (var i = 0; i < plugins.length; i++) {
973
+ var i = plugins.length;
974
+ while(i--) {
849
975
  unregisterPlugin(plugins[i]);
850
976
  }
851
977
 
852
- if (options.enableColumnReorder && $headers.sortable) {
853
- $headers.sortable("destroy");
978
+ if (options.enableColumnReorder) {
979
+ $headers.filter(":ui-sortable").sortable("destroy");
854
980
  }
855
981
 
856
982
  unbindAncestorScrollEvents();
@@ -968,6 +1094,8 @@ if (typeof Slick === "undefined") {
968
1094
  h.width(columns[i].width - headerColumnWidthDiff);
969
1095
  }
970
1096
  }
1097
+
1098
+ updateColumnCaches();
971
1099
  }
972
1100
 
973
1101
  function applyColumnWidths() {
@@ -975,11 +1103,9 @@ if (typeof Slick === "undefined") {
975
1103
  for (var i = 0; i < columns.length; i++) {
976
1104
  w = columns[i].width;
977
1105
 
978
- rule = columnCssRulesL[i];
979
- rule.style.left = x + "px";
980
-
981
- rule = columnCssRulesR[i];
982
- rule.style.right = (canvasWidth - x - w) + "px";
1106
+ rule = getColumnCssRules(i);
1107
+ rule.left.style.left = x + "px";
1108
+ rule.right.style.right = (canvasWidth - x - w) + "px";
983
1109
 
984
1110
  x += columns[i].width;
985
1111
  }
@@ -1023,8 +1149,8 @@ if (typeof Slick === "undefined") {
1023
1149
  for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
1024
1150
  if (!hash[j]) { // prevent duplicates
1025
1151
  selectedRows.push(j);
1152
+ hash[j] = {};
1026
1153
  }
1027
- hash[j] = {};
1028
1154
  for (var k = ranges[i].fromCell; k <= ranges[i].toCell; k++) {
1029
1155
  if (canCellBeSelected(j, k)) {
1030
1156
  hash[j][columns[k].id] = options.selectedCellCssClass;
@@ -1042,8 +1168,35 @@ if (typeof Slick === "undefined") {
1042
1168
  return columns;
1043
1169
  }
1044
1170
 
1171
+ function updateColumnCaches() {
1172
+ // Pre-calculate cell boundaries.
1173
+ columnPosLeft = [];
1174
+ columnPosRight = [];
1175
+ var x = 0;
1176
+ for (var i = 0, ii = columns.length; i < ii; i++) {
1177
+ columnPosLeft[i] = x;
1178
+ columnPosRight[i] = x + columns[i].width;
1179
+ x += columns[i].width;
1180
+ }
1181
+ }
1182
+
1045
1183
  function setColumns(columnDefinitions) {
1046
1184
  columns = columnDefinitions;
1185
+
1186
+ columnsById = {};
1187
+ for (var i = 0; i < columns.length; i++) {
1188
+ var m = columns[i] = $.extend({}, columnDefaults, columns[i]);
1189
+ columnsById[m.id] = i;
1190
+ if (m.minWidth && m.width < m.minWidth) {
1191
+ m.width = m.minWidth;
1192
+ }
1193
+ if (m.maxWidth && m.width > m.maxWidth) {
1194
+ m.width = m.maxWidth;
1195
+ }
1196
+ }
1197
+
1198
+ updateColumnCaches();
1199
+
1047
1200
  if (initialized) {
1048
1201
  invalidateAllRows();
1049
1202
  createColumnHeaders();
@@ -1071,14 +1224,22 @@ if (typeof Slick === "undefined") {
1071
1224
  }
1072
1225
 
1073
1226
  options = $.extend(options, args);
1227
+ validateAndEnforceOptions();
1074
1228
 
1075
1229
  $viewport.css("overflow-y", options.autoHeight ? "hidden" : "auto");
1076
1230
  render();
1077
1231
  }
1078
1232
 
1233
+ function validateAndEnforceOptions() {
1234
+ if (options.autoHeight) {
1235
+ options.leaveSpaceForNewRows = false;
1236
+ }
1237
+ }
1238
+
1079
1239
  function setData(newData, scrollToTop) {
1080
- invalidateAllRows();
1081
1240
  data = newData;
1241
+ invalidateAllRows();
1242
+ updateRowCount();
1082
1243
  if (scrollToTop) {
1083
1244
  scrollTo(0);
1084
1245
  }
@@ -1108,30 +1269,47 @@ if (typeof Slick === "undefined") {
1108
1269
  return $topPanel[0];
1109
1270
  }
1110
1271
 
1111
- function showTopPanel() {
1112
- options.showTopPanel = true;
1113
- $topPanelScroller.slideDown("fast", resizeCanvas);
1114
- }
1115
-
1116
- function hideTopPanel() {
1117
- options.showTopPanel = false;
1118
- $topPanelScroller.slideUp("fast", resizeCanvas);
1272
+ function setTopPanelVisibility(visible) {
1273
+ if (options.showTopPanel != visible) {
1274
+ options.showTopPanel = visible;
1275
+ if (visible) {
1276
+ $topPanelScroller.slideDown("fast", resizeCanvas);
1277
+ } else {
1278
+ $topPanelScroller.slideUp("fast", resizeCanvas);
1279
+ }
1280
+ }
1119
1281
  }
1120
1282
 
1121
- function showHeaderRowColumns() {
1122
- options.showHeaderRow = true;
1123
- $headerRowScroller.slideDown("fast", resizeCanvas);
1283
+ function setHeaderRowVisibility(visible) {
1284
+ if (options.showHeaderRow != visible) {
1285
+ options.showHeaderRow = visible;
1286
+ if (visible) {
1287
+ $headerRowScroller.slideDown("fast", resizeCanvas);
1288
+ } else {
1289
+ $headerRowScroller.slideUp("fast", resizeCanvas);
1290
+ }
1291
+ }
1124
1292
  }
1125
1293
 
1126
- function hideHeaderRowColumns() {
1127
- options.showHeaderRow = false;
1128
- $headerRowScroller.slideUp("fast", resizeCanvas);
1294
+ function getContainerNode() {
1295
+ return $container.get(0);
1129
1296
  }
1130
1297
 
1131
1298
  //////////////////////////////////////////////////////////////////////////////////////////////
1132
1299
  // Rendering / Scrolling
1133
1300
 
1301
+ function getRowTop(row) {
1302
+ return options.rowHeight * row - offset;
1303
+ }
1304
+
1305
+ function getRowFromPosition(y) {
1306
+ return Math.floor((y + offset) / options.rowHeight);
1307
+ }
1308
+
1134
1309
  function scrollTo(y) {
1310
+ y = Math.max(y, 0);
1311
+ y = Math.min(y, th - viewportH + (viewportHasHScroll ? scrollbarDimensions.height : 0));
1312
+
1135
1313
  var oldOffset = offset;
1136
1314
 
1137
1315
  page = Math.min(n - 1, Math.floor(y / ph));
@@ -1140,12 +1318,12 @@ if (typeof Slick === "undefined") {
1140
1318
 
1141
1319
  if (offset != oldOffset) {
1142
1320
  var range = getVisibleRange(newScrollTop);
1143
- cleanupRows(range.top, range.bottom);
1321
+ cleanupRows(range);
1144
1322
  updateRowPositions();
1145
1323
  }
1146
1324
 
1147
1325
  if (prevScrollTop != newScrollTop) {
1148
- scrollDir = (prevScrollTop + oldOffset < newScrollTop + offset) ? 1 : -1;
1326
+ vScrollDir = (prevScrollTop + oldOffset < newScrollTop + offset) ? 1 : -1;
1149
1327
  $viewport[0].scrollTop = (lastRenderedScrollTop = scrollTop = prevScrollTop = newScrollTop);
1150
1328
 
1151
1329
  trigger(self.onViewportChanged, {});
@@ -1156,7 +1334,7 @@ if (typeof Slick === "undefined") {
1156
1334
  if (value == null) {
1157
1335
  return "";
1158
1336
  } else {
1159
- return value.toString().replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
1337
+ return (value + "").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
1160
1338
  }
1161
1339
  }
1162
1340
 
@@ -1197,12 +1375,12 @@ if (typeof Slick === "undefined") {
1197
1375
  return item[columnDef.field];
1198
1376
  }
1199
1377
 
1200
- function appendRowHtml(stringArray, row) {
1378
+ function appendRowHtml(stringArray, row, range) {
1201
1379
  var d = getDataItem(row);
1202
1380
  var dataLoading = row < getDataLength() && !d;
1203
- var cellCss;
1204
- var rowCss = "slick-row " +
1381
+ var rowCss = "slick-row" +
1205
1382
  (dataLoading ? " loading" : "") +
1383
+ (row === activeRow ? " active" : "") +
1206
1384
  (row % 2 == 1 ? " odd" : " even");
1207
1385
 
1208
1386
  var metadata = data.getItemMetadata && data.getItemMetadata(row);
@@ -1211,41 +1389,69 @@ if (typeof Slick === "undefined") {
1211
1389
  rowCss += " " + metadata.cssClasses;
1212
1390
  }
1213
1391
 
1214
- stringArray.push("<div class='ui-widget-content " + rowCss + "' row='" + row + "' style='top:" + (options.rowHeight * row - offset) + "px'>");
1392
+ stringArray.push("<div class='ui-widget-content " + rowCss + "' style='top:" + getRowTop(row) + "px'>");
1215
1393
 
1216
1394
  var colspan, m;
1217
- for (var i = 0, cols = columns.length; i < cols; i++) {
1395
+ for (var i = 0, ii = columns.length; i < ii; i++) {
1218
1396
  m = columns[i];
1219
- colspan = getColspan(row, i); // TODO: don't calc unless we have to
1220
- cellCss = "slick-cell l" + i + " r" + Math.min(columns.length - 1, i + colspan - 1) + (m.cssClass ? " " + m.cssClass : "");
1221
- if (row === activeRow && i === activeCell) {
1222
- cellCss += (" active");
1397
+ colspan = 1;
1398
+ if (metadata && metadata.columns) {
1399
+ var columnData = metadata.columns[m.id] || metadata.columns[i];
1400
+ colspan = (columnData && columnData.colspan) || 1;
1401
+ if (colspan === "*") {
1402
+ colspan = ii - i;
1403
+ }
1223
1404
  }
1224
1405
 
1225
- // TODO: merge them together in the setter
1226
- for (var key in cellCssClasses) {
1227
- if (cellCssClasses[key][row] && cellCssClasses[key][row][m.id]) {
1228
- cellCss += (" " + cellCssClasses[key][row][m.id]);
1406
+ // Do not render cells outside of the viewport.
1407
+ if (columnPosRight[Math.min(ii - 1, i + colspan - 1)] > range.leftPx) {
1408
+ if (columnPosLeft[i] > range.rightPx) {
1409
+ // All columns to the right are outside the range.
1410
+ break;
1229
1411
  }
1230
- }
1231
1412
 
1232
- stringArray.push("<div class='" + cellCss + "'>");
1413
+ appendCellHtml(stringArray, row, i, colspan);
1414
+ }
1233
1415
 
1234
- // if there is a corresponding row (if not, this is the Add New row or this data hasn't been loaded yet)
1235
- if (d) {
1236
- stringArray.push(getFormatter(row, m)(row, i, getDataItemValueForColumn(d, m), m, d));
1416
+ if (colspan > 1) {
1417
+ i += (colspan - 1);
1237
1418
  }
1419
+ }
1238
1420
 
1239
- stringArray.push("</div>");
1421
+ stringArray.push("</div>");
1422
+ }
1240
1423
 
1241
- if (colspan) {
1242
- i += (colspan - 1);
1424
+ function appendCellHtml(stringArray, row, cell, colspan) {
1425
+ var m = columns[cell];
1426
+ var d = getDataItem(row);
1427
+ var cellCss = "slick-cell l" + cell + " r" + Math.min(columns.length - 1, cell + colspan - 1) +
1428
+ (m.cssClass ? " " + m.cssClass : "");
1429
+ if (row === activeRow && cell === activeCell) {
1430
+ cellCss += (" active");
1431
+ }
1432
+
1433
+ // TODO: merge them together in the setter
1434
+ for (var key in cellCssClasses) {
1435
+ if (cellCssClasses[key][row] && cellCssClasses[key][row][m.id]) {
1436
+ cellCss += (" " + cellCssClasses[key][row][m.id]);
1243
1437
  }
1244
1438
  }
1245
1439
 
1440
+ stringArray.push("<div class='" + cellCss + "'>");
1441
+
1442
+ // if there is a corresponding row (if not, this is the Add New row or this data hasn't been loaded yet)
1443
+ if (d) {
1444
+ var value = getDataItemValueForColumn(d, m);
1445
+ stringArray.push(getFormatter(row, m)(row, cell, value, m, d));
1446
+ }
1447
+
1246
1448
  stringArray.push("</div>");
1449
+
1450
+ rowsCache[row].cellRenderQueue.push(cell);
1451
+ rowsCache[row].cellColSpans[cell] = colspan;
1247
1452
  }
1248
1453
 
1454
+
1249
1455
  function cleanupRows(rangeToKeep) {
1250
1456
  for (var i in rowsCache) {
1251
1457
  if (((i = parseInt(i, 10)) !== activeRow) && (i < rangeToKeep.top || i > rangeToKeep.bottom)) {
@@ -1270,11 +1476,11 @@ if (typeof Slick === "undefined") {
1270
1476
  }
1271
1477
 
1272
1478
  function removeRowFromCache(row) {
1273
- var node = rowsCache[row];
1274
- if (!node) {
1479
+ var cacheEntry = rowsCache[row];
1480
+ if (!cacheEntry) {
1275
1481
  return;
1276
1482
  }
1277
- $canvas[0].removeChild(node);
1483
+ $canvas[0].removeChild(cacheEntry.rowNode);
1278
1484
  delete rowsCache[row];
1279
1485
  delete postProcessedRows[row];
1280
1486
  renderedRows--;
@@ -1286,7 +1492,7 @@ if (typeof Slick === "undefined") {
1286
1492
  if (!rows || !rows.length) {
1287
1493
  return;
1288
1494
  }
1289
- scrollDir = 0;
1495
+ vScrollDir = 0;
1290
1496
  for (i = 0, rl = rows.length; i < rl; i++) {
1291
1497
  if (currentEditor && activeRow === rows[i]) {
1292
1498
  makeActiveCellNormal();
@@ -1317,26 +1523,39 @@ if (typeof Slick === "undefined") {
1317
1523
  }
1318
1524
 
1319
1525
  function updateRow(row) {
1320
- if (!rowsCache[row]) {
1526
+ var cacheEntry = rowsCache[row];
1527
+ if (!cacheEntry) {
1321
1528
  return;
1322
1529
  }
1323
1530
 
1324
- $(rowsCache[row]).children().each(function (i) {
1325
- var m = columns[i], d = getDataItem(row);
1326
- if (row === activeRow && i === activeCell && currentEditor) {
1327
- currentEditor.loadValue(getDataItem(activeRow));
1531
+ ensureCellNodesInRowsCache(row);
1532
+
1533
+ for (var columnIdx in cacheEntry.cellNodesByColumnIdx) {
1534
+ if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) {
1535
+ continue;
1536
+ }
1537
+
1538
+ columnIdx = columnIdx | 0;
1539
+ var m = columns[columnIdx],
1540
+ d = getDataItem(row),
1541
+ node = cacheEntry.cellNodesByColumnIdx[columnIdx];
1542
+
1543
+ if (row === activeRow && columnIdx === activeCell && currentEditor) {
1544
+ currentEditor.loadValue(d);
1328
1545
  } else if (d) {
1329
- this.innerHTML = getFormatter(row, m)(row, i, getDataItemValueForColumn(d, m), m, getDataItem(row));
1546
+ node.innerHTML = getFormatter(row, m)(row, columnIdx, getDataItemValueForColumn(d, m), m, d);
1330
1547
  } else {
1331
- this.innerHTML = "";
1548
+ node.innerHTML = "";
1332
1549
  }
1333
- });
1550
+ }
1334
1551
 
1335
1552
  invalidatePostProcessingResults(row);
1336
1553
  }
1337
1554
 
1338
1555
  function getViewportHeight() {
1339
1556
  return parseFloat($.css($container[0], "height", true)) -
1557
+ parseFloat($.css($container[0], "paddingTop", true)) -
1558
+ parseFloat($.css($container[0], "paddingBottom", true)) -
1340
1559
  parseFloat($.css($headerScroller[0], "height")) - getVBoxDelta($headerScroller) -
1341
1560
  (options.showTopPanel ? options.topPanelHeight + getVBoxDelta($topPanelScroller) : 0) -
1342
1561
  (options.showHeaderRow ? options.headerRowHeight + getVBoxDelta($headerRowScroller) : 0);
@@ -1345,20 +1564,23 @@ if (typeof Slick === "undefined") {
1345
1564
  function resizeCanvas() {
1346
1565
  if (!initialized) { return; }
1347
1566
  if (options.autoHeight) {
1348
- viewportH = options.rowHeight * (getDataLength() + (options.enableAddRow ? 1 : 0) + (options.leaveSpaceForNewRows ? numVisibleRows - 1 : 0));
1567
+ viewportH = options.rowHeight * (getDataLength() + (options.enableAddRow ? 1 : 0));
1349
1568
  } else {
1350
1569
  viewportH = getViewportHeight();
1351
1570
  }
1352
1571
 
1353
1572
  numVisibleRows = Math.ceil(viewportH / options.rowHeight);
1354
1573
  viewportW = parseFloat($.css($container[0], "width", true));
1355
- $viewport.height(viewportH);
1574
+ if (!options.autoHeight) {
1575
+ $viewport.height(viewportH);
1576
+ }
1356
1577
 
1357
1578
  if (options.forceFitColumns) {
1358
1579
  autosizeColumns();
1359
1580
  }
1360
1581
 
1361
1582
  updateRowCount();
1583
+ handleScroll();
1362
1584
  render();
1363
1585
  }
1364
1586
 
@@ -1381,6 +1603,10 @@ if (typeof Slick === "undefined") {
1381
1603
  }
1382
1604
  }
1383
1605
 
1606
+ if (activeCellNode && activeRow > l) {
1607
+ resetActiveCell();
1608
+ }
1609
+
1384
1610
  var oldH = h;
1385
1611
  th = Math.max(options.rowHeight * numberOfRows, viewportH - scrollbarDimensions.height);
1386
1612
  if (th < maxSupportedCssHeight) {
@@ -1423,26 +1649,31 @@ if (typeof Slick === "undefined") {
1423
1649
  updateCanvasWidth(false);
1424
1650
  }
1425
1651
 
1426
- function getVisibleRange(viewportTop) {
1652
+ function getVisibleRange(viewportTop, viewportLeft) {
1427
1653
  if (viewportTop == null) {
1428
1654
  viewportTop = scrollTop;
1429
1655
  }
1656
+ if (viewportLeft == null) {
1657
+ viewportLeft = scrollLeft;
1658
+ }
1430
1659
 
1431
1660
  return {
1432
- top: Math.floor((viewportTop + offset) / options.rowHeight),
1433
- bottom: Math.ceil((viewportTop + offset + viewportH) / options.rowHeight)
1661
+ top: getRowFromPosition(viewportTop),
1662
+ bottom: getRowFromPosition(viewportTop + viewportH) + 1,
1663
+ leftPx: viewportLeft,
1664
+ rightPx: viewportLeft + viewportW
1434
1665
  };
1435
1666
  }
1436
1667
 
1437
- function getRenderedRange(viewportTop) {
1438
- var range = getVisibleRange(viewportTop);
1668
+ function getRenderedRange(viewportTop, viewportLeft) {
1669
+ var range = getVisibleRange(viewportTop, viewportLeft);
1439
1670
  var buffer = Math.round(viewportH / options.rowHeight);
1440
1671
  var minBuffer = 3;
1441
1672
 
1442
- if (scrollDir == -1) {
1673
+ if (vScrollDir == -1) {
1443
1674
  range.top -= buffer;
1444
1675
  range.bottom += minBuffer;
1445
- } else if (scrollDir == 1) {
1676
+ } else if (vScrollDir == 1) {
1446
1677
  range.top -= minBuffer;
1447
1678
  range.bottom += buffer;
1448
1679
  } else {
@@ -1453,25 +1684,178 @@ if (typeof Slick === "undefined") {
1453
1684
  range.top = Math.max(0, range.top);
1454
1685
  range.bottom = Math.min(options.enableAddRow ? getDataLength() : getDataLength() - 1, range.bottom);
1455
1686
 
1687
+ range.leftPx -= viewportW;
1688
+ range.rightPx += viewportW;
1689
+
1690
+ range.leftPx = Math.max(0, range.leftPx);
1691
+ range.rightPx = Math.min(canvasWidth, range.rightPx);
1692
+
1456
1693
  return range;
1457
1694
  }
1458
1695
 
1696
+ function ensureCellNodesInRowsCache(row) {
1697
+ var cacheEntry = rowsCache[row];
1698
+ if (cacheEntry) {
1699
+ if (cacheEntry.cellRenderQueue.length) {
1700
+ var lastChild = cacheEntry.rowNode.lastChild;
1701
+ while (cacheEntry.cellRenderQueue.length) {
1702
+ var columnIdx = cacheEntry.cellRenderQueue.pop();
1703
+ cacheEntry.cellNodesByColumnIdx[columnIdx] = lastChild;
1704
+ lastChild = lastChild.previousSibling;
1705
+ }
1706
+ }
1707
+ }
1708
+ }
1709
+
1710
+ function cleanUpCells(range, row) {
1711
+ var totalCellsRemoved = 0;
1712
+ var cacheEntry = rowsCache[row];
1713
+
1714
+ // Remove cells outside the range.
1715
+ var cellsToRemove = [];
1716
+ for (var i in cacheEntry.cellNodesByColumnIdx) {
1717
+ // I really hate it when people mess with Array.prototype.
1718
+ if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(i)) {
1719
+ continue;
1720
+ }
1721
+
1722
+ // This is a string, so it needs to be cast back to a number.
1723
+ i = i | 0;
1724
+
1725
+ var colspan = cacheEntry.cellColSpans[i];
1726
+ if (columnPosLeft[i] > range.rightPx ||
1727
+ columnPosRight[Math.min(columns.length - 1, i + colspan - 1)] < range.leftPx) {
1728
+ if (!(row == activeRow && i == activeCell)) {
1729
+ cellsToRemove.push(i);
1730
+ }
1731
+ }
1732
+ }
1733
+
1734
+ var cellToRemove;
1735
+ while ((cellToRemove = cellsToRemove.pop()) != null) {
1736
+ cacheEntry.rowNode.removeChild(cacheEntry.cellNodesByColumnIdx[cellToRemove]);
1737
+ delete cacheEntry.cellColSpans[cellToRemove];
1738
+ delete cacheEntry.cellNodesByColumnIdx[cellToRemove];
1739
+ if (postProcessedRows[row]) {
1740
+ delete postProcessedRows[row][cellToRemove];
1741
+ }
1742
+ totalCellsRemoved++;
1743
+ }
1744
+ }
1745
+
1746
+ function cleanUpAndRenderCells(range) {
1747
+ var cacheEntry;
1748
+ var stringArray = [];
1749
+ var processedRows = [];
1750
+ var cellsAdded;
1751
+ var totalCellsAdded = 0;
1752
+ var colspan;
1753
+
1754
+ for (var row = range.top; row <= range.bottom; row++) {
1755
+ cacheEntry = rowsCache[row];
1756
+ if (!cacheEntry) {
1757
+ continue;
1758
+ }
1759
+
1760
+ // cellRenderQueue populated in renderRows() needs to be cleared first
1761
+ ensureCellNodesInRowsCache(row);
1762
+
1763
+ cleanUpCells(range, row);
1764
+
1765
+ // Render missing cells.
1766
+ cellsAdded = 0;
1767
+
1768
+ var metadata = data.getItemMetadata && data.getItemMetadata(row);
1769
+ metadata = metadata && metadata.columns;
1770
+
1771
+ // TODO: shorten this loop (index? heuristics? binary search?)
1772
+ for (var i = 0, ii = columns.length; i < ii; i++) {
1773
+ // Cells to the right are outside the range.
1774
+ if (columnPosLeft[i] > range.rightPx) {
1775
+ break;
1776
+ }
1777
+
1778
+ // Already rendered.
1779
+ if ((colspan = cacheEntry.cellColSpans[i]) != null) {
1780
+ i += (colspan > 1 ? colspan - 1 : 0);
1781
+ continue;
1782
+ }
1783
+
1784
+ colspan = 1;
1785
+ if (metadata) {
1786
+ var columnData = metadata[columns[i].id] || metadata[i];
1787
+ colspan = (columnData && columnData.colspan) || 1;
1788
+ if (colspan === "*") {
1789
+ colspan = ii - i;
1790
+ }
1791
+ }
1792
+
1793
+ if (columnPosRight[Math.min(ii - 1, i + colspan - 1)] > range.leftPx) {
1794
+ appendCellHtml(stringArray, row, i, colspan);
1795
+ cellsAdded++;
1796
+ }
1797
+
1798
+ i += (colspan > 1 ? colspan - 1 : 0);
1799
+ }
1800
+
1801
+ if (cellsAdded) {
1802
+ totalCellsAdded += cellsAdded;
1803
+ processedRows.push(row);
1804
+ }
1805
+ }
1806
+
1807
+ if (!stringArray.length) {
1808
+ return;
1809
+ }
1810
+
1811
+ var x = document.createElement("div");
1812
+ x.innerHTML = stringArray.join("");
1813
+
1814
+ var processedRow;
1815
+ var node;
1816
+ while ((processedRow = processedRows.pop()) != null) {
1817
+ cacheEntry = rowsCache[processedRow];
1818
+ var columnIdx;
1819
+ while ((columnIdx = cacheEntry.cellRenderQueue.pop()) != null) {
1820
+ node = x.lastChild;
1821
+ cacheEntry.rowNode.appendChild(node);
1822
+ cacheEntry.cellNodesByColumnIdx[columnIdx] = node;
1823
+ }
1824
+ }
1825
+ }
1826
+
1459
1827
  function renderRows(range) {
1460
- var i, l,
1461
- parentNode = $canvas[0],
1462
- rowsBefore = renderedRows,
1828
+ var parentNode = $canvas[0],
1463
1829
  stringArray = [],
1464
1830
  rows = [],
1465
- startTimestamp = new Date(),
1466
1831
  needToReselectCell = false;
1467
1832
 
1468
- for (i = range.top; i <= range.bottom; i++) {
1833
+ for (var i = range.top; i <= range.bottom; i++) {
1469
1834
  if (rowsCache[i]) {
1470
1835
  continue;
1471
1836
  }
1472
1837
  renderedRows++;
1473
1838
  rows.push(i);
1474
- appendRowHtml(stringArray, i);
1839
+
1840
+ // Create an entry right away so that appendRowHtml() can
1841
+ // start populatating it.
1842
+ rowsCache[i] = {
1843
+ "rowNode": null,
1844
+
1845
+ // ColSpans of rendered cells (by column idx).
1846
+ // Can also be used for checking whether a cell has been rendered.
1847
+ "cellColSpans": [],
1848
+
1849
+ // Cell nodes (by column idx). Lazy-populated by ensureCellNodesInRowsCache().
1850
+ "cellNodesByColumnIdx": [],
1851
+
1852
+ // Column indices of cell nodes that have been rendered, but not yet indexed in
1853
+ // cellNodesByColumnIdx. These are in the same order as cell nodes added at the
1854
+ // end of the row.
1855
+ "cellRenderQueue": []
1856
+ };
1857
+
1858
+ appendRowHtml(stringArray, i, range);
1475
1859
  if (activeCellNode && activeRow === i) {
1476
1860
  needToReselectCell = true;
1477
1861
  }
@@ -1483,17 +1867,13 @@ if (typeof Slick === "undefined") {
1483
1867
  var x = document.createElement("div");
1484
1868
  x.innerHTML = stringArray.join("");
1485
1869
 
1486
- for (i = 0, l = x.childNodes.length; i < l; i++) {
1487
- rowsCache[rows[i]] = parentNode.appendChild(x.firstChild);
1870
+ for (var i = 0, ii = rows.length; i < ii; i++) {
1871
+ rowsCache[rows[i]].rowNode = parentNode.appendChild(x.firstChild);
1488
1872
  }
1489
1873
 
1490
1874
  if (needToReselectCell) {
1491
1875
  activeCellNode = getCellNode(activeRow, activeCell);
1492
1876
  }
1493
-
1494
- if (renderedRows - rowsBefore > 5) {
1495
- avgRowRenderTime = (new Date() - startTimestamp) / (renderedRows - rowsBefore);
1496
- }
1497
1877
  }
1498
1878
 
1499
1879
  function startPostProcessing() {
@@ -1513,7 +1893,7 @@ if (typeof Slick === "undefined") {
1513
1893
 
1514
1894
  function updateRowPositions() {
1515
1895
  for (var row in rowsCache) {
1516
- rowsCache[row].style.top = (row * options.rowHeight - offset) + "px";
1896
+ rowsCache[row].rowNode.style.top = getRowTop(row) + "px";
1517
1897
  }
1518
1898
  }
1519
1899
 
@@ -1525,7 +1905,12 @@ if (typeof Slick === "undefined") {
1525
1905
  // remove rows no longer in the viewport
1526
1906
  cleanupRows(rendered);
1527
1907
 
1528
- // add new rows
1908
+ // add new rows & missing cells in existing rows
1909
+ if (lastRenderedScrollLeft != scrollLeft) {
1910
+ cleanUpAndRenderCells(rendered);
1911
+ }
1912
+
1913
+ // render missing rows
1529
1914
  renderRows(rendered);
1530
1915
 
1531
1916
  postProcessFromRow = visible.top;
@@ -1533,48 +1918,68 @@ if (typeof Slick === "undefined") {
1533
1918
  startPostProcessing();
1534
1919
 
1535
1920
  lastRenderedScrollTop = scrollTop;
1921
+ lastRenderedScrollLeft = scrollLeft;
1536
1922
  h_render = null;
1537
1923
  }
1538
1924
 
1925
+ function handleHeaderRowScroll() {
1926
+ var scrollLeft = $headerRowScroller[0].scrollLeft;
1927
+ if (scrollLeft != $viewport[0].scrollLeft) {
1928
+ $viewport[0].scrollLeft = scrollLeft;
1929
+ }
1930
+ }
1931
+
1539
1932
  function handleScroll() {
1540
1933
  scrollTop = $viewport[0].scrollTop;
1541
- var scrollLeft = $viewport[0].scrollLeft;
1542
- var scrollDist = Math.abs(scrollTop - prevScrollTop);
1934
+ scrollLeft = $viewport[0].scrollLeft;
1935
+ var vScrollDist = Math.abs(scrollTop - prevScrollTop);
1936
+ var hScrollDist = Math.abs(scrollLeft - prevScrollLeft);
1543
1937
 
1544
- if (scrollLeft !== prevScrollLeft) {
1938
+ if (hScrollDist) {
1545
1939
  prevScrollLeft = scrollLeft;
1546
1940
  $headerScroller[0].scrollLeft = scrollLeft;
1547
1941
  $topPanelScroller[0].scrollLeft = scrollLeft;
1548
1942
  $headerRowScroller[0].scrollLeft = scrollLeft;
1549
1943
  }
1550
1944
 
1551
- if (scrollDist) {
1552
- scrollDir = prevScrollTop < scrollTop ? 1 : -1;
1945
+ if (vScrollDist) {
1946
+ vScrollDir = prevScrollTop < scrollTop ? 1 : -1;
1553
1947
  prevScrollTop = scrollTop;
1554
1948
 
1555
1949
  // switch virtual pages if needed
1556
- if (scrollDist < viewportH) {
1950
+ if (vScrollDist < viewportH) {
1557
1951
  scrollTo(scrollTop + offset);
1558
1952
  } else {
1559
1953
  var oldOffset = offset;
1560
- page = Math.min(n - 1, Math.floor(scrollTop * ((th - viewportH) / (h - viewportH)) * (1 / ph)));
1954
+ if (h == viewportH) {
1955
+ page = 0;
1956
+ } else {
1957
+ page = Math.min(n - 1, Math.floor(scrollTop * ((th - viewportH) / (h - viewportH)) * (1 / ph)));
1958
+ }
1561
1959
  offset = Math.round(page * cj);
1562
1960
  if (oldOffset != offset) {
1563
1961
  invalidateAllRows();
1564
1962
  }
1565
1963
  }
1964
+ }
1566
1965
 
1966
+ if (hScrollDist || vScrollDist) {
1567
1967
  if (h_render) {
1568
1968
  clearTimeout(h_render);
1569
1969
  }
1570
1970
 
1571
- if (Math.abs(lastRenderedScrollTop - scrollTop) < viewportH) {
1572
- render();
1573
- } else {
1574
- h_render = setTimeout(render, 50);
1575
- }
1971
+ if (Math.abs(lastRenderedScrollTop - scrollTop) > 20 ||
1972
+ Math.abs(lastRenderedScrollLeft - scrollLeft) > 20) {
1973
+ if (options.forceSyncScrolling || (
1974
+ Math.abs(lastRenderedScrollTop - scrollTop) < viewportH &&
1975
+ Math.abs(lastRenderedScrollLeft - scrollLeft) < viewportW)) {
1976
+ render();
1977
+ } else {
1978
+ h_render = setTimeout(render, 50);
1979
+ }
1576
1980
 
1577
- trigger(self.onViewportChanged, {});
1981
+ trigger(self.onViewportChanged, {});
1982
+ }
1578
1983
  }
1579
1984
 
1580
1985
  trigger(self.onScroll, {scrollLeft: scrollLeft, scrollTop: scrollTop});
@@ -1582,22 +1987,34 @@ if (typeof Slick === "undefined") {
1582
1987
 
1583
1988
  function asyncPostProcessRows() {
1584
1989
  while (postProcessFromRow <= postProcessToRow) {
1585
- var row = (scrollDir >= 0) ? postProcessFromRow++ : postProcessToRow--;
1586
- var rowNode = rowsCache[row];
1587
- if (!rowNode || postProcessedRows[row] || row >= getDataLength()) {
1990
+ var row = (vScrollDir >= 0) ? postProcessFromRow++ : postProcessToRow--;
1991
+ var cacheEntry = rowsCache[row];
1992
+ if (!cacheEntry || row >= getDataLength()) {
1588
1993
  continue;
1589
1994
  }
1590
1995
 
1591
- var d = getDataItem(row), cellNodes = rowNode.childNodes;
1592
- for (var i = 0, j = 0, l = columns.length; i < l; ++i) {
1593
- var m = columns[i];
1594
- if (m.asyncPostRender) {
1595
- m.asyncPostRender(cellNodes[j], postProcessFromRow, d, m);
1996
+ if (!postProcessedRows[row]) {
1997
+ postProcessedRows[row] = {};
1998
+ }
1999
+
2000
+ ensureCellNodesInRowsCache(row);
2001
+ for (var columnIdx in cacheEntry.cellNodesByColumnIdx) {
2002
+ if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) {
2003
+ continue;
2004
+ }
2005
+
2006
+ columnIdx = columnIdx | 0;
2007
+
2008
+ var m = columns[columnIdx];
2009
+ if (m.asyncPostRender && !postProcessedRows[row][columnIdx]) {
2010
+ var node = cacheEntry.cellNodesByColumnIdx[columnIdx];
2011
+ if (node) {
2012
+ m.asyncPostRender(node, row, getDataItem(row), m);
2013
+ }
2014
+ postProcessedRows[row][columnIdx] = true;
1596
2015
  }
1597
- ++j;
1598
2016
  }
1599
2017
 
1600
- postProcessedRows[row] = true;
1601
2018
  h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay);
1602
2019
  return;
1603
2020
  }
@@ -1699,7 +2116,7 @@ if (typeof Slick === "undefined") {
1699
2116
  return false;
1700
2117
  }
1701
2118
 
1702
- retval = trigger(self.onDragInit, dd, e);
2119
+ var retval = trigger(self.onDragInit, dd, e);
1703
2120
  if (e.isImmediatePropagationStopped()) {
1704
2121
  return retval;
1705
2122
  }
@@ -1743,23 +2160,22 @@ if (typeof Slick === "undefined") {
1743
2160
  }
1744
2161
  cancelEditAndSetFocus();
1745
2162
  } else if (e.which == 37) {
1746
- navigateLeft();
2163
+ handled = navigateLeft();
1747
2164
  } else if (e.which == 39) {
1748
- navigateRight();
2165
+ handled = navigateRight();
1749
2166
  } else if (e.which == 38) {
1750
- navigateUp();
2167
+ handled = navigateUp();
1751
2168
  } else if (e.which == 40) {
1752
- navigateDown();
2169
+ handled = navigateDown();
1753
2170
  } else if (e.which == 9) {
1754
- navigateNext();
2171
+ handled = navigateNext();
1755
2172
  } else if (e.which == 13) {
1756
2173
  if (options.editable) {
1757
2174
  if (currentEditor) {
1758
2175
  // adding new row
1759
2176
  if (activeRow === getDataLength()) {
1760
2177
  navigateDown();
1761
- }
1762
- else {
2178
+ } else {
1763
2179
  commitEditAndSetFocus();
1764
2180
  }
1765
2181
  } else {
@@ -1768,29 +2184,36 @@ if (typeof Slick === "undefined") {
1768
2184
  }
1769
2185
  }
1770
2186
  }
1771
- } else {
1772
- return;
2187
+ handled = true;
1773
2188
  }
1774
2189
  } else if (e.which == 9 && e.shiftKey && !e.ctrlKey && !e.altKey) {
1775
- navigatePrev();
1776
- } else {
1777
- return;
2190
+ handled = navigatePrev();
1778
2191
  }
1779
2192
  }
1780
2193
 
1781
- // the event has been handled so don't let parent element (bubbling/propagation) or browser (default) handle it
1782
- e.stopPropagation();
1783
- e.preventDefault();
1784
- try {
1785
- e.originalEvent.keyCode = 0; // prevent default behaviour for special keys in IE browsers (F3, F5, etc.)
1786
- }
2194
+ if (handled) {
2195
+ // the event has been handled so don't let parent element (bubbling/propagation) or browser (default) handle it
2196
+ e.stopPropagation();
2197
+ e.preventDefault();
2198
+ try {
2199
+ e.originalEvent.keyCode = 0; // prevent default behaviour for special keys in IE browsers (F3, F5, etc.)
2200
+ }
1787
2201
  // ignore exceptions - setting the original event's keycode throws access denied exception for "Ctrl"
1788
2202
  // (hitting control key only, nothing else), "Shift" (maybe others)
1789
- catch (error) {
2203
+ catch (error) {
2204
+ }
1790
2205
  }
1791
2206
  }
1792
2207
 
1793
2208
  function handleClick(e) {
2209
+ if (!currentEditor) {
2210
+ // if this click resulted in some cell child node getting focus,
2211
+ // don't steal it back - keyboard events will still bubble up
2212
+ if (e.target != document.activeElement) {
2213
+ setFocus();
2214
+ }
2215
+ }
2216
+
1794
2217
  var cell = getCellFromEvent(e);
1795
2218
  if (!cell || (currentEditor !== null && activeRow == cell.row && activeCell == cell.cell)) {
1796
2219
  return;
@@ -1801,7 +2224,7 @@ if (typeof Slick === "undefined") {
1801
2224
  return;
1802
2225
  }
1803
2226
 
1804
- if (canCellBeActive(cell.row, cell.cell)) {
2227
+ if ((activeCell != cell.cell || activeRow != cell.row) && canCellBeActive(cell.row, cell.cell)) {
1805
2228
  if (!getEditorLock().isActive() || getEditorLock().commitCurrentEdit()) {
1806
2229
  scrollRowIntoView(cell.row, false);
1807
2230
  setActiveCellInternal(getCellNode(cell.row, cell.cell), (cell.row === getDataLength()) || options.autoEdit);
@@ -1839,16 +2262,30 @@ if (typeof Slick === "undefined") {
1839
2262
  }
1840
2263
  }
1841
2264
 
2265
+ function handleHeaderMouseEnter(e) {
2266
+ trigger(self.onHeaderMouseEnter, {
2267
+ "column": $(this).data("column")
2268
+ }, e);
2269
+ }
2270
+
2271
+ function handleHeaderMouseLeave(e) {
2272
+ trigger(self.onHeaderMouseLeave, {
2273
+ "column": $(this).data("column")
2274
+ }, e);
2275
+ }
2276
+
1842
2277
  function handleHeaderContextMenu(e) {
1843
2278
  var $header = $(e.target).closest(".slick-header-column", ".slick-header-columns");
1844
- var column = $header && columns[self.getColumnIndex($header.data("fieldId"))];
2279
+ var column = $header && $header.data("column");
1845
2280
  trigger(self.onHeaderContextMenu, {column: column}, e);
1846
2281
  }
1847
2282
 
1848
2283
  function handleHeaderClick(e) {
1849
2284
  var $header = $(e.target).closest(".slick-header-column", ".slick-header-columns");
1850
- var column = $header && columns[self.getColumnIndex($header.data("fieldId"))];
1851
- trigger(self.onHeaderClick, {column: column}, e);
2285
+ var column = $header && $header.data("column");
2286
+ if (column) {
2287
+ trigger(self.onHeaderClick, {column: column}, e);
2288
+ }
1852
2289
  }
1853
2290
 
1854
2291
  function handleMouseEnter(e) {
@@ -1864,7 +2301,7 @@ if (typeof Slick === "undefined") {
1864
2301
  }
1865
2302
 
1866
2303
  function getCellFromPoint(x, y) {
1867
- var row = Math.floor((y + offset) / options.rowHeight);
2304
+ var row = getRowFromPosition(y);
1868
2305
  var cell = 0;
1869
2306
 
1870
2307
  var w = 0;
@@ -1880,25 +2317,42 @@ if (typeof Slick === "undefined") {
1880
2317
  return {row: row, cell: cell - 1};
1881
2318
  }
1882
2319
 
1883
- function getCellFromNode(node) {
1884
- // read column number from .l1 or .c1 CSS classes
1885
- var cls = /l\d+/.exec(node.className) || /c\d+/.exec(node.className);
2320
+ function getCellFromNode(cellNode) {
2321
+ // read column number from .l<columnNumber> CSS class
2322
+ var cls = /l\d+/.exec(cellNode.className);
1886
2323
  if (!cls) {
1887
- throw "getCellFromNode: cannot get cell - " + node.className;
2324
+ throw "getCellFromNode: cannot get cell - " + cellNode.className;
1888
2325
  }
1889
2326
  return parseInt(cls[0].substr(1, cls[0].length - 1), 10);
1890
2327
  }
1891
2328
 
2329
+ function getRowFromNode(rowNode) {
2330
+ for (var row in rowsCache) {
2331
+ if (rowsCache[row].rowNode === rowNode) {
2332
+ return row | 0;
2333
+ }
2334
+ }
2335
+
2336
+ return null;
2337
+ }
2338
+
1892
2339
  function getCellFromEvent(e) {
1893
2340
  var $cell = $(e.target).closest(".slick-cell", $canvas);
1894
2341
  if (!$cell.length) {
1895
2342
  return null;
1896
2343
  }
1897
2344
 
1898
- return {
1899
- row: $cell.parent().attr("row") | 0,
1900
- cell: getCellFromNode($cell[0])
1901
- };
2345
+ var row = getRowFromNode($cell[0].parentNode);
2346
+ var cell = getCellFromNode($cell[0]);
2347
+
2348
+ if (row == null || cell == null) {
2349
+ return null;
2350
+ } else {
2351
+ return {
2352
+ "row": row,
2353
+ "cell": cell
2354
+ };
2355
+ }
1902
2356
  }
1903
2357
 
1904
2358
  function getCellNodeBox(row, cell) {
@@ -1906,7 +2360,7 @@ if (typeof Slick === "undefined") {
1906
2360
  return null;
1907
2361
  }
1908
2362
 
1909
- var y1 = row * options.rowHeight - offset;
2363
+ var y1 = getRowTop(row);
1910
2364
  var y2 = y1 + options.rowHeight - 1;
1911
2365
  var x1 = 0;
1912
2366
  for (var i = 0; i < cell; i++) {
@@ -1930,27 +2384,29 @@ if (typeof Slick === "undefined") {
1930
2384
  }
1931
2385
 
1932
2386
  function setFocus() {
1933
- // IE tries to scroll the viewport so that the item being focused is aligned to the left border
1934
- // IE-specific .setActive() sets the focus, but doesn't scroll
1935
- if ($.browser.msie) {
1936
- $canvas[0].setActive();
2387
+ if (tabbingDirection == -1) {
2388
+ $focusSink[0].focus();
1937
2389
  } else {
1938
- $canvas[0].focus();
2390
+ $focusSink2[0].focus();
1939
2391
  }
1940
2392
  }
1941
2393
 
1942
- function scrollActiveCellIntoView() {
1943
- if (activeCellNode) {
1944
- var left = $(activeCellNode).position().left,
1945
- right = left + $(activeCellNode).outerWidth(),
1946
- scrollLeft = $viewport.scrollLeft(),
1947
- scrollRight = scrollLeft + $viewport.width();
2394
+ function scrollCellIntoView(row, cell, doPaging) {
2395
+ scrollRowIntoView(row, doPaging);
1948
2396
 
1949
- if (left < scrollLeft) {
1950
- $viewport.scrollLeft(left);
1951
- } else if (right > scrollRight) {
1952
- $viewport.scrollLeft(Math.min(left, right - $viewport[0].clientWidth));
1953
- }
2397
+ var colspan = getColspan(row, cell);
2398
+ var left = columnPosLeft[cell],
2399
+ right = columnPosRight[cell + (colspan > 1 ? colspan - 1 : 0)],
2400
+ scrollRight = scrollLeft + viewportW;
2401
+
2402
+ if (left < scrollLeft) {
2403
+ $viewport.scrollLeft(left);
2404
+ handleScroll();
2405
+ render();
2406
+ } else if (right > scrollRight) {
2407
+ $viewport.scrollLeft(Math.min(left, right - $viewport[0].clientWidth));
2408
+ handleScroll();
2409
+ render();
1954
2410
  }
1955
2411
  }
1956
2412
 
@@ -1958,16 +2414,20 @@ if (typeof Slick === "undefined") {
1958
2414
  if (activeCellNode !== null) {
1959
2415
  makeActiveCellNormal();
1960
2416
  $(activeCellNode).removeClass("active");
2417
+ if (rowsCache[activeRow]) {
2418
+ $(rowsCache[activeRow].rowNode).removeClass("active");
2419
+ }
1961
2420
  }
1962
2421
 
1963
2422
  var activeCellChanged = (activeCellNode !== newCell);
1964
2423
  activeCellNode = newCell;
1965
2424
 
1966
2425
  if (activeCellNode != null) {
1967
- activeRow = parseInt($(activeCellNode).parent().attr("row"));
2426
+ activeRow = getRowFromNode(activeCellNode.parentNode);
1968
2427
  activeCell = activePosX = getCellFromNode(activeCellNode);
1969
2428
 
1970
2429
  $(activeCellNode).addClass("active");
2430
+ $(rowsCache[activeRow].rowNode).addClass("active");
1971
2431
 
1972
2432
  if (options.editable && editMode && isCellPotentiallyEditable(activeRow, activeCell)) {
1973
2433
  clearTimeout(h_editorLoader);
@@ -1979,15 +2439,12 @@ if (typeof Slick === "undefined") {
1979
2439
  } else {
1980
2440
  makeActiveCellEditable();
1981
2441
  }
1982
- } else {
1983
- setFocus();
1984
2442
  }
1985
2443
  } else {
1986
2444
  activeRow = activeCell = null;
1987
2445
  }
1988
2446
 
1989
2447
  if (activeCellChanged) {
1990
- scrollActiveCellIntoView();
1991
2448
  trigger(self.onActiveCellChanged, getActiveCell());
1992
2449
  }
1993
2450
  }
@@ -2043,7 +2500,7 @@ if (typeof Slick === "undefined") {
2043
2500
 
2044
2501
  // if there previously was text selected on a page (such as selected text in the edit cell just removed),
2045
2502
  // IE can't set focus to anything else correctly
2046
- if ($.browser.msie) {
2503
+ if (navigator.userAgent.toLowerCase().match(/msie/)) {
2047
2504
  clearTextSelection();
2048
2505
  }
2049
2506
 
@@ -2222,6 +2679,11 @@ if (typeof Slick === "undefined") {
2222
2679
  }
2223
2680
  }
2224
2681
 
2682
+ function scrollRowToTop(row) {
2683
+ scrollTo(row * options.rowHeight);
2684
+ render();
2685
+ }
2686
+
2225
2687
  function getColspan(row, cell) {
2226
2688
  var metadata = data.getItemMetadata && data.getItemMetadata(row);
2227
2689
  if (!metadata || !metadata.columns) {
@@ -2232,8 +2694,11 @@ if (typeof Slick === "undefined") {
2232
2694
  var colspan = (columnData && columnData.colspan);
2233
2695
  if (colspan === "*") {
2234
2696
  colspan = columns.length - cell;
2697
+ } else {
2698
+ colspan = colspan || 1;
2235
2699
  }
2236
- return (colspan || 1);
2700
+
2701
+ return colspan;
2237
2702
  }
2238
2703
 
2239
2704
  function findFirstFocusableCell(row) {
@@ -2354,6 +2819,17 @@ if (typeof Slick === "undefined") {
2354
2819
  }
2355
2820
 
2356
2821
  function gotoNext(row, cell, posX) {
2822
+ if (row == null && cell == null) {
2823
+ row = cell = posX = 0;
2824
+ if (canCellBeActive(row, cell)) {
2825
+ return {
2826
+ "row": row,
2827
+ "cell": cell,
2828
+ "posX": cell
2829
+ };
2830
+ }
2831
+ }
2832
+
2357
2833
  var pos = gotoRight(row, cell, posX);
2358
2834
  if (pos) {
2359
2835
  return pos;
@@ -2374,6 +2850,18 @@ if (typeof Slick === "undefined") {
2374
2850
  }
2375
2851
 
2376
2852
  function gotoPrev(row, cell, posX) {
2853
+ if (row == null && cell == null) {
2854
+ row = getDataLength() + (options.enableAddRow ? 1 : 0) - 1;
2855
+ cell = posX = columns.length - 1;
2856
+ if (canCellBeActive(row, cell)) {
2857
+ return {
2858
+ "row": row,
2859
+ "cell": cell,
2860
+ "posX": cell
2861
+ };
2862
+ }
2863
+ }
2864
+
2377
2865
  var pos;
2378
2866
  var lastSelectableCell;
2379
2867
  while (!pos) {
@@ -2399,36 +2887,56 @@ if (typeof Slick === "undefined") {
2399
2887
  }
2400
2888
 
2401
2889
  function navigateRight() {
2402
- navigate("right");
2890
+ return navigate("right");
2403
2891
  }
2404
2892
 
2405
2893
  function navigateLeft() {
2406
- navigate("left");
2894
+ return navigate("left");
2407
2895
  }
2408
2896
 
2409
2897
  function navigateDown() {
2410
- navigate("down");
2898
+ return navigate("down");
2411
2899
  }
2412
2900
 
2413
2901
  function navigateUp() {
2414
- navigate("up");
2902
+ return navigate("up");
2415
2903
  }
2416
2904
 
2417
2905
  function navigateNext() {
2418
- navigate("next");
2906
+ return navigate("next");
2419
2907
  }
2420
2908
 
2421
2909
  function navigatePrev() {
2422
- navigate("prev");
2910
+ return navigate("prev");
2423
2911
  }
2424
2912
 
2913
+ /**
2914
+ * @param {string} dir Navigation direction.
2915
+ * @return {boolean} Whether navigation resulted in a change of active cell.
2916
+ */
2425
2917
  function navigate(dir) {
2426
- if (!activeCellNode || !options.enableCellNavigation) {
2427
- return;
2918
+ if (!options.enableCellNavigation) {
2919
+ return false;
2428
2920
  }
2921
+
2922
+ if (!activeCellNode && dir != "prev" && dir != "next") {
2923
+ return false;
2924
+ }
2925
+
2429
2926
  if (!getEditorLock().commitCurrentEdit()) {
2430
- return;
2927
+ return true;
2431
2928
  }
2929
+ setFocus();
2930
+
2931
+ var tabbingDirections = {
2932
+ "up": -1,
2933
+ "down": 1,
2934
+ "left": -1,
2935
+ "right": 1,
2936
+ "prev": -1,
2937
+ "next": 1
2938
+ };
2939
+ tabbingDirection = tabbingDirections[dir];
2432
2940
 
2433
2941
  var stepFunctions = {
2434
2942
  "up": gotoUp,
@@ -2442,27 +2950,20 @@ if (typeof Slick === "undefined") {
2442
2950
  var pos = stepFn(activeRow, activeCell, activePosX);
2443
2951
  if (pos) {
2444
2952
  var isAddNewRow = (pos.row == getDataLength());
2445
- scrollRowIntoView(pos.row, !isAddNewRow);
2953
+ scrollCellIntoView(pos.row, pos.cell, !isAddNewRow);
2446
2954
  setActiveCellInternal(getCellNode(pos.row, pos.cell), isAddNewRow || options.autoEdit);
2447
2955
  activePosX = pos.posX;
2956
+ return true;
2448
2957
  } else {
2449
2958
  setActiveCellInternal(getCellNode(activeRow, activeCell), (activeRow == getDataLength()) || options.autoEdit);
2959
+ return false;
2450
2960
  }
2451
2961
  }
2452
2962
 
2453
2963
  function getCellNode(row, cell) {
2454
2964
  if (rowsCache[row]) {
2455
- var cells = $(rowsCache[row]).children();
2456
- var nodeCell;
2457
- for (var i = 0; i < cells.length; i++) {
2458
- nodeCell = getCellFromNode(cells[i]);
2459
- if (nodeCell === cell) {
2460
- return cells[i];
2461
- } else if (nodeCell > cell) {
2462
- return null;
2463
- }
2464
-
2465
- }
2965
+ ensureCellNodesInRowsCache(row);
2966
+ return rowsCache[row].cellNodesByColumnIdx[cell];
2466
2967
  }
2467
2968
  return null;
2468
2969
  }
@@ -2477,7 +2978,7 @@ if (typeof Slick === "undefined") {
2477
2978
  return;
2478
2979
  }
2479
2980
 
2480
- scrollRowIntoView(row, false);
2981
+ scrollCellIntoView(row, cell, false);
2481
2982
  setActiveCellInternal(getCellNode(row, cell), false);
2482
2983
  }
2483
2984
 
@@ -2500,11 +3001,7 @@ if (typeof Slick === "undefined") {
2500
3001
  return columnMetadata[cell].focusable;
2501
3002
  }
2502
3003
 
2503
- if (typeof columns[cell].focusable === "boolean") {
2504
- return columns[cell].focusable;
2505
- }
2506
-
2507
- return true;
3004
+ return columns[cell].focusable;
2508
3005
  }
2509
3006
 
2510
3007
  function canCellBeSelected(row, cell) {
@@ -2522,11 +3019,7 @@ if (typeof Slick === "undefined") {
2522
3019
  return columnMetadata.selectable;
2523
3020
  }
2524
3021
 
2525
- if (typeof columns[cell].selectable === "boolean") {
2526
- return columns[cell].selectable;
2527
- }
2528
-
2529
- return true;
3022
+ return columns[cell].selectable;
2530
3023
  }
2531
3024
 
2532
3025
  function gotoCell(row, cell, forceEdit) {
@@ -2539,7 +3032,7 @@ if (typeof Slick === "undefined") {
2539
3032
  return;
2540
3033
  }
2541
3034
 
2542
- scrollRowIntoView(row, false);
3035
+ scrollCellIntoView(row, cell, false);
2543
3036
 
2544
3037
  var newCell = getCellNode(row, cell);
2545
3038
 
@@ -2671,7 +3164,7 @@ if (typeof Slick === "undefined") {
2671
3164
  s += ("\n" + "n(umber of pages): " + n);
2672
3165
  s += ("\n" + "(current) page: " + page);
2673
3166
  s += ("\n" + "page height (ph): " + ph);
2674
- s += ("\n" + "scrollDir: " + scrollDir);
3167
+ s += ("\n" + "vScrollDir: " + vScrollDir);
2675
3168
 
2676
3169
  alert(s);
2677
3170
  };
@@ -2685,13 +3178,19 @@ if (typeof Slick === "undefined") {
2685
3178
  // Public API
2686
3179
 
2687
3180
  $.extend(this, {
2688
- "slickGridVersion": "2.0",
3181
+ "slickGridVersion": "2.1",
2689
3182
 
2690
3183
  // Events
2691
3184
  "onScroll": new Slick.Event(),
2692
3185
  "onSort": new Slick.Event(),
3186
+ "onHeaderMouseEnter": new Slick.Event(),
3187
+ "onHeaderMouseLeave": new Slick.Event(),
2693
3188
  "onHeaderContextMenu": new Slick.Event(),
2694
3189
  "onHeaderClick": new Slick.Event(),
3190
+ "onHeaderCellRendered": new Slick.Event(),
3191
+ "onBeforeHeaderCellDestroy": new Slick.Event(),
3192
+ "onHeaderRowCellRendered": new Slick.Event(),
3193
+ "onBeforeHeaderRowCellDestroy": new Slick.Event(),
2695
3194
  "onMouseEnter": new Slick.Event(),
2696
3195
  "onMouseLeave": new Slick.Event(),
2697
3196
  "onClick": new Slick.Event(),
@@ -2737,6 +3236,7 @@ if (typeof Slick === "undefined") {
2737
3236
  "setSelectionModel": setSelectionModel,
2738
3237
  "getSelectedRows": getSelectedRows,
2739
3238
  "setSelectedRows": setSelectedRows,
3239
+ "getContainerNode": getContainerNode,
2740
3240
 
2741
3241
  "render": render,
2742
3242
  "invalidate": invalidate,
@@ -2750,7 +3250,10 @@ if (typeof Slick === "undefined") {
2750
3250
  "resizeCanvas": resizeCanvas,
2751
3251
  "updateRowCount": updateRowCount,
2752
3252
  "scrollRowIntoView": scrollRowIntoView,
3253
+ "scrollRowToTop": scrollRowToTop,
3254
+ "scrollCellIntoView": scrollCellIntoView,
2753
3255
  "getCanvasNode": getCanvasNode,
3256
+ "focus": setFocus,
2754
3257
 
2755
3258
  "getCellFromPoint": getCellFromPoint,
2756
3259
  "getCellFromEvent": getCellFromEvent,
@@ -2773,10 +3276,8 @@ if (typeof Slick === "undefined") {
2773
3276
  "navigateRight": navigateRight,
2774
3277
  "gotoCell": gotoCell,
2775
3278
  "getTopPanel": getTopPanel,
2776
- "showTopPanel": showTopPanel,
2777
- "hideTopPanel": hideTopPanel,
2778
- "showHeaderRowColumns": showHeaderRowColumns,
2779
- "hideHeaderRowColumns": hideHeaderRowColumns,
3279
+ "setTopPanelVisibility": setTopPanelVisibility,
3280
+ "setHeaderRowVisibility": setHeaderRowVisibility,
2780
3281
  "getHeaderRow": getHeaderRow,
2781
3282
  "getHeaderRowColumn": getHeaderRowColumn,
2782
3283
  "getGridPosition": getGridPosition,