slickgrid-rails 0.2.0 → 0.3.0

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