slickgrid-rails 0.1.0

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