dataclips 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +34 -0
  5. data/app/assets/fonts/bootstrap/glyphicons-halflings-regular.eot +0 -0
  6. data/app/assets/fonts/bootstrap/glyphicons-halflings-regular.svg +288 -0
  7. data/app/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf +0 -0
  8. data/app/assets/fonts/bootstrap/glyphicons-halflings-regular.woff +0 -0
  9. data/app/assets/fonts/bootstrap/glyphicons-halflings-regular.woff2 +0 -0
  10. data/app/assets/images/dataclips/slickgrid/sort-asc.png +0 -0
  11. data/app/assets/images/dataclips/slickgrid/sort-desc.png +0 -0
  12. data/app/assets/javascripts/dataclips/application.js +26 -0
  13. data/app/assets/javascripts/dataclips/backbone.js +1608 -0
  14. data/app/assets/javascripts/dataclips/dataclips.js.coffee +168 -0
  15. data/app/assets/javascripts/dataclips/formatters.js.coffee +14 -0
  16. data/app/assets/javascripts/dataclips/namespace.js.coffee +1 -0
  17. data/app/assets/javascripts/dataclips/record.js.coffee +39 -0
  18. data/app/assets/javascripts/dataclips/slickgrid.js +5 -0
  19. data/app/assets/javascripts/dataclips/slickgrid/jquery.event.drag-2.2.js +402 -0
  20. data/app/assets/javascripts/dataclips/slickgrid/plugins/slick.rowselectionmodel.js +187 -0
  21. data/app/assets/javascripts/dataclips/slickgrid/slick.core.js +458 -0
  22. data/app/assets/javascripts/dataclips/slickgrid/slick.dataview.js +1063 -0
  23. data/app/assets/javascripts/dataclips/slickgrid/slick.grid.js +3309 -0
  24. data/app/assets/javascripts/dataclips/underscore.js +1416 -0
  25. data/app/assets/stylesheets/bootstrap/_alerts.scss +68 -0
  26. data/app/assets/stylesheets/bootstrap/_badges.scss +63 -0
  27. data/app/assets/stylesheets/bootstrap/_breadcrumbs.scss +26 -0
  28. data/app/assets/stylesheets/bootstrap/_button-groups.scss +243 -0
  29. data/app/assets/stylesheets/bootstrap/_buttons.scss +160 -0
  30. data/app/assets/stylesheets/bootstrap/_carousel.scss +267 -0
  31. data/app/assets/stylesheets/bootstrap/_close.scss +35 -0
  32. data/app/assets/stylesheets/bootstrap/_code.scss +69 -0
  33. data/app/assets/stylesheets/bootstrap/_component-animations.scss +38 -0
  34. data/app/assets/stylesheets/bootstrap/_dropdowns.scss +213 -0
  35. data/app/assets/stylesheets/bootstrap/_forms.scss +548 -0
  36. data/app/assets/stylesheets/bootstrap/_glyphicons.scss +234 -0
  37. data/app/assets/stylesheets/bootstrap/_grid.scss +84 -0
  38. data/app/assets/stylesheets/bootstrap/_input-groups.scss +166 -0
  39. data/app/assets/stylesheets/bootstrap/_jumbotron.scss +49 -0
  40. data/app/assets/stylesheets/bootstrap/_labels.scss +66 -0
  41. data/app/assets/stylesheets/bootstrap/_list-group.scss +124 -0
  42. data/app/assets/stylesheets/bootstrap/_media.scss +47 -0
  43. data/app/assets/stylesheets/bootstrap/_mixins.scss +39 -0
  44. data/app/assets/stylesheets/bootstrap/_modals.scss +148 -0
  45. data/app/assets/stylesheets/bootstrap/_navbar.scss +662 -0
  46. data/app/assets/stylesheets/bootstrap/_navs.scss +244 -0
  47. data/app/assets/stylesheets/bootstrap/_normalize.scss +427 -0
  48. data/app/assets/stylesheets/bootstrap/_pager.scss +54 -0
  49. data/app/assets/stylesheets/bootstrap/_pagination.scss +88 -0
  50. data/app/assets/stylesheets/bootstrap/_panels.scss +261 -0
  51. data/app/assets/stylesheets/bootstrap/_popovers.scss +135 -0
  52. data/app/assets/stylesheets/bootstrap/_print.scss +107 -0
  53. data/app/assets/stylesheets/bootstrap/_progress-bars.scss +87 -0
  54. data/app/assets/stylesheets/bootstrap/_responsive-embed.scss +35 -0
  55. data/app/assets/stylesheets/bootstrap/_responsive-utilities.scss +174 -0
  56. data/app/assets/stylesheets/bootstrap/_scaffolding.scss +150 -0
  57. data/app/assets/stylesheets/bootstrap/_tables.scss +234 -0
  58. data/app/assets/stylesheets/bootstrap/_theme.scss +272 -0
  59. data/app/assets/stylesheets/bootstrap/_thumbnails.scss +38 -0
  60. data/app/assets/stylesheets/bootstrap/_tooltip.scss +103 -0
  61. data/app/assets/stylesheets/bootstrap/_type.scss +298 -0
  62. data/app/assets/stylesheets/bootstrap/_utilities.scss +56 -0
  63. data/app/assets/stylesheets/bootstrap/_variables.scss +864 -0
  64. data/app/assets/stylesheets/bootstrap/_wells.scss +29 -0
  65. data/app/assets/stylesheets/bootstrap/mixins/_alerts.scss +14 -0
  66. data/app/assets/stylesheets/bootstrap/mixins/_background-variant.scss +11 -0
  67. data/app/assets/stylesheets/bootstrap/mixins/_border-radius.scss +18 -0
  68. data/app/assets/stylesheets/bootstrap/mixins/_buttons.scss +52 -0
  69. data/app/assets/stylesheets/bootstrap/mixins/_center-block.scss +7 -0
  70. data/app/assets/stylesheets/bootstrap/mixins/_clearfix.scss +22 -0
  71. data/app/assets/stylesheets/bootstrap/mixins/_forms.scss +88 -0
  72. data/app/assets/stylesheets/bootstrap/mixins/_gradients.scss +58 -0
  73. data/app/assets/stylesheets/bootstrap/mixins/_grid-framework.scss +81 -0
  74. data/app/assets/stylesheets/bootstrap/mixins/_grid.scss +122 -0
  75. data/app/assets/stylesheets/bootstrap/mixins/_hide-text.scss +21 -0
  76. data/app/assets/stylesheets/bootstrap/mixins/_image.scss +33 -0
  77. data/app/assets/stylesheets/bootstrap/mixins/_labels.scss +12 -0
  78. data/app/assets/stylesheets/bootstrap/mixins/_list-group.scss +31 -0
  79. data/app/assets/stylesheets/bootstrap/mixins/_nav-divider.scss +10 -0
  80. data/app/assets/stylesheets/bootstrap/mixins/_nav-vertical-align.scss +9 -0
  81. data/app/assets/stylesheets/bootstrap/mixins/_opacity.scss +8 -0
  82. data/app/assets/stylesheets/bootstrap/mixins/_pagination.scss +23 -0
  83. data/app/assets/stylesheets/bootstrap/mixins/_panels.scss +24 -0
  84. data/app/assets/stylesheets/bootstrap/mixins/_progress-bar.scss +10 -0
  85. data/app/assets/stylesheets/bootstrap/mixins/_reset-filter.scss +8 -0
  86. data/app/assets/stylesheets/bootstrap/mixins/_resize.scss +6 -0
  87. data/app/assets/stylesheets/bootstrap/mixins/_responsive-visibility.scss +21 -0
  88. data/app/assets/stylesheets/bootstrap/mixins/_size.scss +10 -0
  89. data/app/assets/stylesheets/bootstrap/mixins/_tab-focus.scss +9 -0
  90. data/app/assets/stylesheets/bootstrap/mixins/_table-row.scss +28 -0
  91. data/app/assets/stylesheets/bootstrap/mixins/_text-emphasis.scss +11 -0
  92. data/app/assets/stylesheets/bootstrap/mixins/_text-overflow.scss +8 -0
  93. data/app/assets/stylesheets/bootstrap/mixins/_vendor-prefixes.scss +222 -0
  94. data/app/assets/stylesheets/dataclips/_bootstrap.scss +53 -0
  95. data/app/assets/stylesheets/dataclips/application.css +18 -0
  96. data/app/assets/stylesheets/dataclips/slickgrid/slickgrid.sass +341 -0
  97. data/app/controllers/dataclips/application_controller.rb +31 -0
  98. data/app/controllers/dataclips/clips_controller.rb +46 -0
  99. data/app/controllers/dataclips/insights_controller.rb +45 -0
  100. data/app/helpers/dataclips/application_helper.rb +4 -0
  101. data/app/models/dataclips/clip.rb +79 -0
  102. data/app/models/dataclips/insight.rb +18 -0
  103. data/app/models/dataclips/sql_query.rb +36 -0
  104. data/app/views/dataclips/application/_boolean.html.erb +1 -0
  105. data/app/views/dataclips/application/_date.html.erb +22 -0
  106. data/app/views/dataclips/application/_datetime.html.erb +25 -0
  107. data/app/views/dataclips/application/_float.html.erb +7 -0
  108. data/app/views/dataclips/application/_integer.html.erb +7 -0
  109. data/app/views/dataclips/application/_text.html.erb +10 -0
  110. data/app/views/dataclips/application/_time.html.erb +23 -0
  111. data/app/views/dataclips/clips/edit.html.erb +14 -0
  112. data/app/views/dataclips/clips/show.html.erb +109 -0
  113. data/app/views/dataclips/insights/show.html.erb +99 -0
  114. data/app/views/layouts/dataclips/application.html.erb +14 -0
  115. data/config/initializers/assets.rb +1 -0
  116. data/config/routes.rb +9 -0
  117. data/db/migrate/20150101143530_create_dataclips_insights.rb +12 -0
  118. data/lib/dataclips.rb +47 -0
  119. data/lib/dataclips/engine.rb +29 -0
  120. data/lib/dataclips/version.rb +3 -0
  121. data/lib/tasks/dataclips_tasks.rake +4 -0
  122. data/test/dataclips_test.rb +7 -0
  123. data/test/dummy/README.rdoc +28 -0
  124. data/test/dummy/Rakefile +6 -0
  125. data/test/dummy/app/assets/javascripts/application.js +13 -0
  126. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  127. data/test/dummy/app/controllers/application_controller.rb +5 -0
  128. data/test/dummy/app/helpers/application_helper.rb +2 -0
  129. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  130. data/test/dummy/bin/bundle +3 -0
  131. data/test/dummy/bin/rails +4 -0
  132. data/test/dummy/bin/rake +4 -0
  133. data/test/dummy/config.ru +4 -0
  134. data/test/dummy/config/application.rb +23 -0
  135. data/test/dummy/config/boot.rb +5 -0
  136. data/test/dummy/config/database.yml +25 -0
  137. data/test/dummy/config/environment.rb +5 -0
  138. data/test/dummy/config/environments/development.rb +37 -0
  139. data/test/dummy/config/environments/production.rb +78 -0
  140. data/test/dummy/config/environments/test.rb +39 -0
  141. data/test/dummy/config/initializers/assets.rb +8 -0
  142. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  143. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  144. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  145. data/test/dummy/config/initializers/inflections.rb +16 -0
  146. data/test/dummy/config/initializers/mime_types.rb +4 -0
  147. data/test/dummy/config/initializers/session_store.rb +3 -0
  148. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  149. data/test/dummy/config/locales/en.yml +23 -0
  150. data/test/dummy/config/routes.rb +4 -0
  151. data/test/dummy/config/secrets.yml +22 -0
  152. data/test/dummy/public/404.html +67 -0
  153. data/test/dummy/public/422.html +67 -0
  154. data/test/dummy/public/500.html +66 -0
  155. data/test/dummy/public/favicon.ico +0 -0
  156. data/test/integration/navigation_test.rb +10 -0
  157. data/test/test_helper.rb +17 -0
  158. metadata +348 -0
@@ -0,0 +1,3309 @@
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.1
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: 50,
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
+ forceSyncScrolling: false
89
+ };
90
+
91
+ var columnDefaults = {
92
+ name: "",
93
+ resizable: true,
94
+ sortable: false,
95
+ minWidth: 30,
96
+ rerenderOnResize: false,
97
+ headerCssClass: null,
98
+ defaultSortAsc: true,
99
+ focusable: true,
100
+ selectable: true
101
+ };
102
+
103
+ // scroller
104
+ var th; // virtual height
105
+ var h; // real scrollable height
106
+ var ph; // page height
107
+ var n; // number of pages
108
+ var cj; // "jumpiness" coefficient
109
+
110
+ var page = 0; // current page
111
+ var offset = 0; // current page offset
112
+ var vScrollDir = 1;
113
+
114
+ // private
115
+ var initialized = false;
116
+ var $container;
117
+ var uid = "slickgrid_" + Math.round(1000000 * Math.random());
118
+ var self = this;
119
+ var $focusSink, $focusSink2;
120
+ var $headerScroller;
121
+ var $headers;
122
+ var $headerRow, $headerRowScroller, $headerRowSpacer;
123
+ var $topPanelScroller;
124
+ var $topPanel;
125
+ var $viewport;
126
+ var $canvas;
127
+ var $style;
128
+ var $boundAncestors;
129
+ var stylesheet, columnCssRulesL, columnCssRulesR;
130
+ var viewportH, viewportW;
131
+ var canvasWidth;
132
+ var viewportHasHScroll, viewportHasVScroll;
133
+ var headerColumnWidthDiff = 0, headerColumnHeightDiff = 0, // border+padding
134
+ cellWidthDiff = 0, cellHeightDiff = 0;
135
+ var absoluteColumnMinWidth;
136
+ var numberOfRows = 0;
137
+
138
+ var tabbingDirection = 1;
139
+ var activePosX;
140
+ var activeRow, activeCell;
141
+ var activeCellNode = null;
142
+ var currentEditor = null;
143
+ var serializedEditorValue;
144
+ var editController;
145
+
146
+ var rowsCache = {};
147
+ var renderedRows = 0;
148
+ var numVisibleRows;
149
+ var prevScrollTop = 0;
150
+ var scrollTop = 0;
151
+ var lastRenderedScrollTop = 0;
152
+ var lastRenderedScrollLeft = 0;
153
+ var prevScrollLeft = 0;
154
+ var scrollLeft = 0;
155
+
156
+ var selectionModel;
157
+ var selectedRows = [];
158
+
159
+ var plugins = [];
160
+ var cellCssClasses = {};
161
+
162
+ var columnsById = {};
163
+ var sortColumns = [];
164
+ var columnPosLeft = [];
165
+ var columnPosRight = [];
166
+
167
+
168
+ // async call handles
169
+ var h_editorLoader = null;
170
+ var h_render = null;
171
+ var h_postrender = null;
172
+ var postProcessedRows = {};
173
+ var postProcessToRow = null;
174
+ var postProcessFromRow = null;
175
+
176
+ // perf counters
177
+ var counter_rows_rendered = 0;
178
+ var counter_rows_removed = 0;
179
+
180
+
181
+ //////////////////////////////////////////////////////////////////////////////////////////////
182
+ // Initialization
183
+
184
+ function init() {
185
+ $container = $(container);
186
+ if ($container.length < 1) {
187
+ throw new Error("SlickGrid requires a valid container, " + container + " does not exist in the DOM.");
188
+ }
189
+
190
+ // calculate these only once and share between grid instances
191
+ maxSupportedCssHeight = maxSupportedCssHeight || getMaxSupportedCssHeight();
192
+ scrollbarDimensions = scrollbarDimensions || measureScrollbar();
193
+
194
+ options = $.extend({}, defaults, options);
195
+ validateAndEnforceOptions();
196
+ columnDefaults.width = options.defaultColumnWidth;
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
+
210
+ // validate loaded JavaScript modules against requested options
211
+ if (options.enableColumnReorder && !$.fn.sortable) {
212
+ throw new Error("SlickGrid's 'enableColumnReorder = true' option requires jquery-ui.sortable module to be loaded");
213
+ }
214
+
215
+ editController = {
216
+ "commitCurrentEdit": commitCurrentEdit,
217
+ "cancelCurrentEdit": cancelCurrentEdit
218
+ };
219
+
220
+ $container
221
+ .empty()
222
+ .css("overflow", "hidden")
223
+ .css("outline", 0)
224
+ .addClass(uid)
225
+ .addClass("ui-widget");
226
+
227
+ // set up a positioning container if needed
228
+ if (!/relative|absolute|fixed/.test($container.css("position"))) {
229
+ $container.css("position", "relative");
230
+ }
231
+
232
+ $focusSink = $("<div tabIndex='0' hideFocus style='position:fixed;width:0;height:0;top:0;left:0;outline:0;'></div>").appendTo($container);
233
+
234
+ $headerScroller = $("<div class='slick-header ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
235
+ $headers = $("<div class='slick-header-columns' style='left:-1000px' />").appendTo($headerScroller);
236
+ $headers.width(getHeadersWidth());
237
+
238
+ $headerRowScroller = $("<div class='slick-headerrow ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
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);
243
+
244
+ $topPanelScroller = $("<div class='slick-top-panel-scroller ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
245
+ $topPanel = $("<div class='slick-top-panel' style='width:10000px' />").appendTo($topPanelScroller);
246
+
247
+ if (!options.showTopPanel) {
248
+ $topPanelScroller.hide();
249
+ }
250
+
251
+ if (!options.showHeaderRow) {
252
+ $headerRowScroller.hide();
253
+ }
254
+
255
+ $viewport = $("<div class='slick-viewport' style='width:100%;overflow:auto;outline:0;position:relative;;'>").appendTo($container);
256
+ $viewport.css("overflow-y", options.autoHeight ? "hidden" : "auto");
257
+
258
+ $canvas = $("<div class='grid-canvas' />").appendTo($viewport);
259
+
260
+ $focusSink2 = $focusSink.clone().appendTo($container);
261
+
262
+ if (!options.explicitInitialization) {
263
+ finishInitialization();
264
+ }
265
+ }
266
+
267
+ function finishInitialization() {
268
+ if (!initialized) {
269
+ initialized = true;
270
+
271
+ viewportW = parseFloat($.css($container[0], "width", true));
272
+
273
+ // header columns and cells may have different padding/border skewing width calculations (box-sizing, hello?)
274
+ // calculate the diff so we can set consistent sizes
275
+ measureCellPaddingAndBorder();
276
+
277
+ // for usability reasons, all text selection in SlickGrid is disabled
278
+ // with the exception of input and textarea elements (selection must
279
+ // be enabled there so that editors work as expected); note that
280
+ // selection in grid cells (grid body) is already unavailable in
281
+ // all browsers except IE
282
+ disableSelection($headers); // disable all text selection in header (including input and textarea)
283
+
284
+ if (!options.enableTextSelectionOnCells) {
285
+ // disable text selection in grid cells except in input and textarea elements
286
+ // (this is IE-specific, because selectstart event will only fire in IE)
287
+ $viewport.bind("selectstart.ui", function (event) {
288
+ return $(event.target).is("input,textarea");
289
+ });
290
+ }
291
+
292
+ updateColumnCaches();
293
+ createColumnHeaders();
294
+ setupColumnSort();
295
+ createCssRules();
296
+ resizeCanvas();
297
+ bindAncestorScrollEvents();
298
+
299
+ $container
300
+ .bind("resize.slickgrid", resizeCanvas);
301
+ $viewport
302
+ .bind("scroll", handleScroll);
303
+ $headerScroller
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);
312
+ $canvas
313
+ .bind("keydown", handleKeyDown)
314
+ .bind("click", handleClick)
315
+ .bind("dblclick", handleDblClick)
316
+ .bind("contextmenu", handleContextMenu)
317
+ .bind("draginit", handleDragInit)
318
+ .bind("dragstart", {distance: 3}, handleDragStart)
319
+ .bind("drag", handleDrag)
320
+ .bind("dragend", handleDragEnd)
321
+ .delegate(".slick-cell", "mouseenter", handleMouseEnter)
322
+ .delegate(".slick-cell", "mouseleave", handleMouseLeave);
323
+ }
324
+ }
325
+
326
+ function registerPlugin(plugin) {
327
+ plugins.unshift(plugin);
328
+ plugin.init(self);
329
+ }
330
+
331
+ function unregisterPlugin(plugin) {
332
+ for (var i = plugins.length; i >= 0; i--) {
333
+ if (plugins[i] === plugin) {
334
+ if (plugins[i].destroy) {
335
+ plugins[i].destroy();
336
+ }
337
+ plugins.splice(i, 1);
338
+ break;
339
+ }
340
+ }
341
+ }
342
+
343
+ function setSelectionModel(model) {
344
+ if (selectionModel) {
345
+ selectionModel.onSelectedRangesChanged.unsubscribe(handleSelectedRangesChanged);
346
+ if (selectionModel.destroy) {
347
+ selectionModel.destroy();
348
+ }
349
+ }
350
+
351
+ selectionModel = model;
352
+ if (selectionModel) {
353
+ selectionModel.init(self);
354
+ selectionModel.onSelectedRangesChanged.subscribe(handleSelectedRangesChanged);
355
+ }
356
+ }
357
+
358
+ function getSelectionModel() {
359
+ return selectionModel;
360
+ }
361
+
362
+ function getCanvasNode() {
363
+ return $canvas[0];
364
+ }
365
+
366
+ function measureScrollbar() {
367
+ var $c = $("<div style='position:absolute; top:-10000px; left:-10000px; width:100px; height:100px; overflow:scroll;'></div>").appendTo("body");
368
+ var dim = {
369
+ width: $c.width() - $c[0].clientWidth,
370
+ height: $c.height() - $c[0].clientHeight
371
+ };
372
+ $c.remove();
373
+ return dim;
374
+ }
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
+
386
+ function getCanvasWidth() {
387
+ var availableWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW;
388
+ var rowWidth = 0;
389
+ var i = columns.length;
390
+ while (i--) {
391
+ rowWidth += columns[i].width;
392
+ }
393
+ return options.fullWidthRows ? Math.max(rowWidth, availableWidth) : rowWidth;
394
+ }
395
+
396
+ function updateCanvasWidth(forceColumnWidthsUpdate) {
397
+ var oldCanvasWidth = canvasWidth;
398
+ canvasWidth = getCanvasWidth();
399
+
400
+ if (canvasWidth != oldCanvasWidth) {
401
+ $canvas.width(canvasWidth);
402
+ $headerRow.width(canvasWidth);
403
+ $headers.width(getHeadersWidth());
404
+ viewportHasHScroll = (canvasWidth > viewportW - scrollbarDimensions.width);
405
+ }
406
+
407
+ $headerRowSpacer.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0));
408
+
409
+ if (canvasWidth != oldCanvasWidth || forceColumnWidthsUpdate) {
410
+ applyColumnWidths();
411
+ }
412
+ }
413
+
414
+ function disableSelection($target) {
415
+ if ($target && $target.jquery) {
416
+ $target
417
+ .attr("unselectable", "on")
418
+ .css("MozUserSelect", "none")
419
+ .bind("selectstart.ui", function () {
420
+ return false;
421
+ }); // from jquery:ui.core.js 1.7.2
422
+ }
423
+ }
424
+
425
+ function getMaxSupportedCssHeight() {
426
+ var supportedHeight = 1000000;
427
+ // FF reports the height back but still renders blank after ~6M px
428
+ var testUpTo = navigator.userAgent.toLowerCase().match(/firefox/) ? 6000000 : 1000000000;
429
+ var div = $("<div style='display:none' />").appendTo(document.body);
430
+
431
+ while (true) {
432
+ var test = supportedHeight * 2;
433
+ div.css("height", test);
434
+ if (test > testUpTo || div.height() !== test) {
435
+ break;
436
+ } else {
437
+ supportedHeight = test;
438
+ }
439
+ }
440
+
441
+ div.remove();
442
+ return supportedHeight;
443
+ }
444
+
445
+ // TODO: this is static. need to handle page mutation.
446
+ function bindAncestorScrollEvents() {
447
+ var elem = $canvas[0];
448
+ while ((elem = elem.parentNode) != document.body && elem != null) {
449
+ // bind to scroll containers only
450
+ if (elem == $viewport[0] || elem.scrollWidth != elem.clientWidth || elem.scrollHeight != elem.clientHeight) {
451
+ var $elem = $(elem);
452
+ if (!$boundAncestors) {
453
+ $boundAncestors = $elem;
454
+ } else {
455
+ $boundAncestors = $boundAncestors.add($elem);
456
+ }
457
+ $elem.bind("scroll." + uid, handleActiveCellPositionChange);
458
+ }
459
+ }
460
+ }
461
+
462
+ function unbindAncestorScrollEvents() {
463
+ if (!$boundAncestors) {
464
+ return;
465
+ }
466
+ $boundAncestors.unbind("scroll." + uid);
467
+ $boundAncestors = null;
468
+ }
469
+
470
+ function updateColumnHeader(columnId, title, toolTip) {
471
+ if (!initialized) { return; }
472
+ var idx = getColumnIndex(columnId);
473
+ if (idx == null) {
474
+ return;
475
+ }
476
+
477
+ var columnDef = columns[idx];
478
+ var $header = $headers.children().eq(idx);
479
+ if ($header) {
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
+
492
+ $header
493
+ .attr("title", toolTip || "")
494
+ .children().eq(0).html(title);
495
+
496
+ trigger(self.onHeaderCellRendered, {
497
+ "node": $header[0],
498
+ "column": columnDef
499
+ });
500
+ }
501
+ }
502
+
503
+ function getHeaderRow() {
504
+ return $headerRow[0];
505
+ }
506
+
507
+ function getHeaderRowColumn(columnId) {
508
+ var idx = getColumnIndex(columnId);
509
+ var $header = $headerRow.children().eq(idx);
510
+ return $header && $header[0];
511
+ }
512
+
513
+ function createColumnHeaders() {
514
+ function onMouseEnter() {
515
+ $(this).addClass("ui-state-hover");
516
+ }
517
+
518
+ function onMouseLeave() {
519
+ $(this).removeClass("ui-state-hover");
520
+ }
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
+ });
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
+ });
545
+ $headerRow.empty();
546
+
547
+ for (var i = 0; i < columns.length; i++) {
548
+ var m = columns[i];
549
+
550
+ var header = $("<div class='ui-state-default slick-header-column' />")
551
+ .html("<span class='slick-column-name'>" + m.name + "</span>")
552
+ .width(m.width - headerColumnWidthDiff)
553
+ .attr("id", "" + uid + m.id)
554
+ .attr("title", m.toolTip || "")
555
+ .data("column", m)
556
+ .addClass(m.headerCssClass || "")
557
+ .appendTo($headers);
558
+
559
+ if (options.enableColumnReorder || m.sortable) {
560
+ header
561
+ .on('mouseenter', onMouseEnter)
562
+ .on('mouseleave', onMouseLeave);
563
+ }
564
+
565
+ if (m.sortable) {
566
+ header.addClass("slick-header-sortable");
567
+ header.append("<span class='slick-sort-indicator' />");
568
+ }
569
+
570
+ trigger(self.onHeaderCellRendered, {
571
+ "node": header[0],
572
+ "column": m
573
+ });
574
+
575
+ if (options.showHeaderRow) {
576
+ var headerRowCell = $("<div class='ui-state-default slick-headerrow-column l" + i + " r" + i + "'></div>")
577
+ .data("column", m)
578
+ .appendTo($headerRow);
579
+
580
+ trigger(self.onHeaderRowCellRendered, {
581
+ "node": headerRowCell[0],
582
+ "column": m
583
+ });
584
+ }
585
+ }
586
+
587
+ setSortColumns(sortColumns);
588
+ setupColumnResize();
589
+ if (options.enableColumnReorder) {
590
+ setupColumnReorder();
591
+ }
592
+ }
593
+
594
+ function setupColumnSort() {
595
+ $headers.click(function (e) {
596
+ // temporary workaround for a bug in jQuery 1.7.1 (http://bugs.jquery.com/ticket/11328)
597
+ e.metaKey = e.metaKey || e.ctrlKey;
598
+
599
+ if ($(e.target).hasClass("slick-resizable-handle")) {
600
+ return;
601
+ }
602
+
603
+ var $col = $(e.target).closest(".slick-header-column");
604
+ if (!$col.length) {
605
+ return;
606
+ }
607
+
608
+ var column = $col.data("column");
609
+ if (column.sortable) {
610
+ if (!getEditorLock().commitCurrentEdit()) {
611
+ return;
612
+ }
613
+
614
+ var sortOpts = null;
615
+ var i = 0;
616
+ for (; i < sortColumns.length; i++) {
617
+ if (sortColumns[i].columnId == column.id) {
618
+ sortOpts = sortColumns[i];
619
+ sortOpts.sortAsc = !sortOpts.sortAsc;
620
+ break;
621
+ }
622
+ }
623
+
624
+ if (e.metaKey && options.multiColumnSort) {
625
+ if (sortOpts) {
626
+ sortColumns.splice(i, 1);
627
+ }
628
+ }
629
+ else {
630
+ if ((!e.shiftKey && !e.metaKey) || !options.multiColumnSort) {
631
+ sortColumns = [];
632
+ }
633
+
634
+ if (!sortOpts) {
635
+ sortOpts = { columnId: column.id, sortAsc: column.defaultSortAsc };
636
+ sortColumns.push(sortOpts);
637
+ } else if (sortColumns.length == 0) {
638
+ sortColumns.push(sortOpts);
639
+ }
640
+ }
641
+
642
+ setSortColumns(sortColumns);
643
+
644
+ if (!options.multiColumnSort) {
645
+ trigger(self.onSort, {
646
+ multiColumnSort: false,
647
+ sortCol: column,
648
+ sortAsc: sortOpts.sortAsc}, e);
649
+ } else {
650
+ trigger(self.onSort, {
651
+ multiColumnSort: true,
652
+ sortCols: $.map(sortColumns, function(col) {
653
+ return {sortCol: columns[getColumnIndex(col.columnId)], sortAsc: col.sortAsc };
654
+ })}, e);
655
+ }
656
+ }
657
+ });
658
+ }
659
+
660
+ function setupColumnReorder() {
661
+ $headers.filter(":ui-sortable").sortable("destroy");
662
+ $headers.sortable({
663
+ containment: "parent",
664
+ distance: 3,
665
+ axis: "x",
666
+ cursor: "default",
667
+ tolerance: "intersection",
668
+ helper: "clone",
669
+ placeholder: "slick-sortable-placeholder ui-state-default slick-header-column",
670
+ forcePlaceholderSize: true,
671
+ start: function (e, ui) {
672
+ $(ui.helper).addClass("slick-header-column-active");
673
+ },
674
+ beforeStop: function (e, ui) {
675
+ $(ui.helper).removeClass("slick-header-column-active");
676
+ },
677
+ stop: function (e) {
678
+ if (!getEditorLock().commitCurrentEdit()) {
679
+ $(this).sortable("cancel");
680
+ return;
681
+ }
682
+
683
+ var reorderedIds = $headers.sortable("toArray");
684
+ var reorderedColumns = [];
685
+ for (var i = 0; i < reorderedIds.length; i++) {
686
+ reorderedColumns.push(columns[getColumnIndex(reorderedIds[i].replace(uid, ""))]);
687
+ }
688
+ setColumns(reorderedColumns);
689
+
690
+ trigger(self.onColumnsReordered, {});
691
+ e.stopPropagation();
692
+ setupColumnResize();
693
+ }
694
+ });
695
+ }
696
+
697
+ function setupColumnResize() {
698
+ var $col, j, c, pageX, columnElements, minPageX, maxPageX, firstResizable, lastResizable;
699
+ columnElements = $headers.children();
700
+ columnElements.find(".slick-resizable-handle").remove();
701
+ columnElements.each(function (i, e) {
702
+ if (columns[i].resizable) {
703
+ if (firstResizable === undefined) {
704
+ firstResizable = i;
705
+ }
706
+ lastResizable = i;
707
+ }
708
+ });
709
+ if (firstResizable === undefined) {
710
+ return;
711
+ }
712
+ columnElements.each(function (i, e) {
713
+ if (i < firstResizable || (options.forceFitColumns && i >= lastResizable)) {
714
+ return;
715
+ }
716
+ $col = $(e);
717
+ $("<div class='slick-resizable-handle' />")
718
+ .appendTo(e)
719
+ .bind("dragstart", function (e, dd) {
720
+ if (!getEditorLock().commitCurrentEdit()) {
721
+ return false;
722
+ }
723
+ pageX = e.pageX;
724
+ $(this).parent().addClass("slick-header-column-active");
725
+ var shrinkLeewayOnRight = null, stretchLeewayOnRight = null;
726
+ // lock each column's width option to current width
727
+ columnElements.each(function (i, e) {
728
+ columns[i].previousWidth = $(e).outerWidth();
729
+ });
730
+ if (options.forceFitColumns) {
731
+ shrinkLeewayOnRight = 0;
732
+ stretchLeewayOnRight = 0;
733
+ // colums on right affect maxPageX/minPageX
734
+ for (j = i + 1; j < columnElements.length; j++) {
735
+ c = columns[j];
736
+ if (c.resizable) {
737
+ if (stretchLeewayOnRight !== null) {
738
+ if (c.maxWidth) {
739
+ stretchLeewayOnRight += c.maxWidth - c.previousWidth;
740
+ } else {
741
+ stretchLeewayOnRight = null;
742
+ }
743
+ }
744
+ shrinkLeewayOnRight += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth);
745
+ }
746
+ }
747
+ }
748
+ var shrinkLeewayOnLeft = 0, stretchLeewayOnLeft = 0;
749
+ for (j = 0; j <= i; j++) {
750
+ // columns on left only affect minPageX
751
+ c = columns[j];
752
+ if (c.resizable) {
753
+ if (stretchLeewayOnLeft !== null) {
754
+ if (c.maxWidth) {
755
+ stretchLeewayOnLeft += c.maxWidth - c.previousWidth;
756
+ } else {
757
+ stretchLeewayOnLeft = null;
758
+ }
759
+ }
760
+ shrinkLeewayOnLeft += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth);
761
+ }
762
+ }
763
+ if (shrinkLeewayOnRight === null) {
764
+ shrinkLeewayOnRight = 100000;
765
+ }
766
+ if (shrinkLeewayOnLeft === null) {
767
+ shrinkLeewayOnLeft = 100000;
768
+ }
769
+ if (stretchLeewayOnRight === null) {
770
+ stretchLeewayOnRight = 100000;
771
+ }
772
+ if (stretchLeewayOnLeft === null) {
773
+ stretchLeewayOnLeft = 100000;
774
+ }
775
+ maxPageX = pageX + Math.min(shrinkLeewayOnRight, stretchLeewayOnLeft);
776
+ minPageX = pageX - Math.min(shrinkLeewayOnLeft, stretchLeewayOnRight);
777
+ })
778
+ .bind("drag", function (e, dd) {
779
+ var actualMinWidth, d = Math.min(maxPageX, Math.max(minPageX, e.pageX)) - pageX, x;
780
+ if (d < 0) { // shrink column
781
+ x = d;
782
+ for (j = i; j >= 0; j--) {
783
+ c = columns[j];
784
+ if (c.resizable) {
785
+ actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth);
786
+ if (x && c.previousWidth + x < actualMinWidth) {
787
+ x += c.previousWidth - actualMinWidth;
788
+ c.width = actualMinWidth;
789
+ } else {
790
+ c.width = c.previousWidth + x;
791
+ x = 0;
792
+ }
793
+ }
794
+ }
795
+
796
+ if (options.forceFitColumns) {
797
+ x = -d;
798
+ for (j = i + 1; j < columnElements.length; j++) {
799
+ c = columns[j];
800
+ if (c.resizable) {
801
+ if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {
802
+ x -= c.maxWidth - c.previousWidth;
803
+ c.width = c.maxWidth;
804
+ } else {
805
+ c.width = c.previousWidth + x;
806
+ x = 0;
807
+ }
808
+ }
809
+ }
810
+ }
811
+ } else { // stretch column
812
+ x = d;
813
+ for (j = i; j >= 0; j--) {
814
+ c = columns[j];
815
+ if (c.resizable) {
816
+ if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {
817
+ x -= c.maxWidth - c.previousWidth;
818
+ c.width = c.maxWidth;
819
+ } else {
820
+ c.width = c.previousWidth + x;
821
+ x = 0;
822
+ }
823
+ }
824
+ }
825
+
826
+ if (options.forceFitColumns) {
827
+ x = -d;
828
+ for (j = i + 1; j < columnElements.length; j++) {
829
+ c = columns[j];
830
+ if (c.resizable) {
831
+ actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth);
832
+ if (x && c.previousWidth + x < actualMinWidth) {
833
+ x += c.previousWidth - actualMinWidth;
834
+ c.width = actualMinWidth;
835
+ } else {
836
+ c.width = c.previousWidth + x;
837
+ x = 0;
838
+ }
839
+ }
840
+ }
841
+ }
842
+ }
843
+ applyColumnHeaderWidths();
844
+ if (options.syncColumnCellResize) {
845
+ applyColumnWidths();
846
+ }
847
+ })
848
+ .bind("dragend", function (e, dd) {
849
+ var newWidth;
850
+ $(this).parent().removeClass("slick-header-column-active");
851
+ for (j = 0; j < columnElements.length; j++) {
852
+ c = columns[j];
853
+ newWidth = $(columnElements[j]).outerWidth();
854
+
855
+ if (c.previousWidth !== newWidth && c.rerenderOnResize) {
856
+ invalidateAllRows();
857
+ }
858
+ }
859
+ updateCanvasWidth(true);
860
+ render();
861
+ trigger(self.onColumnsResized, {});
862
+ });
863
+ });
864
+ }
865
+
866
+ function getVBoxDelta($el) {
867
+ var p = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"];
868
+ var delta = 0;
869
+ $.each(p, function (n, val) {
870
+ delta += parseFloat($el.css(val)) || 0;
871
+ });
872
+ return delta;
873
+ }
874
+
875
+ function measureCellPaddingAndBorder() {
876
+ var el;
877
+ var h = ["borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight"];
878
+ var v = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"];
879
+
880
+ el = $("<div class='ui-state-default slick-header-column' style='visibility:hidden'>-</div>").appendTo($headers);
881
+ headerColumnWidthDiff = headerColumnHeightDiff = 0;
882
+ $.each(h, function (n, val) {
883
+ headerColumnWidthDiff += parseFloat(el.css(val)) || 0;
884
+ });
885
+ $.each(v, function (n, val) {
886
+ headerColumnHeightDiff += parseFloat(el.css(val)) || 0;
887
+ });
888
+ el.remove();
889
+
890
+ var r = $("<div class='slick-row' />").appendTo($canvas);
891
+ el = $("<div class='slick-cell' id='' style='visibility:hidden'>-</div>").appendTo(r);
892
+ cellWidthDiff = cellHeightDiff = 0;
893
+ $.each(h, function (n, val) {
894
+ cellWidthDiff += parseFloat(el.css(val)) || 0;
895
+ });
896
+ $.each(v, function (n, val) {
897
+ cellHeightDiff += parseFloat(el.css(val)) || 0;
898
+ });
899
+ r.remove();
900
+
901
+ absoluteColumnMinWidth = Math.max(headerColumnWidthDiff, cellWidthDiff);
902
+ }
903
+
904
+ function createCssRules() {
905
+ $style = $("<style type='text/css' rel='stylesheet' />").appendTo($("head"));
906
+ var rowHeight = (options.rowHeight - cellHeightDiff);
907
+ var rules = [
908
+ "." + uid + " .slick-header-column { left: 1000px; }",
909
+ "." + uid + " .slick-top-panel { height:" + options.topPanelHeight + "px; }",
910
+ "." + uid + " .slick-headerrow-columns { height:" + options.headerRowHeight + "px; }",
911
+ "." + uid + " .slick-cell { height:" + rowHeight + "px; }",
912
+ "." + uid + " .slick-row { height:" + options.rowHeight + "px; }"
913
+ ];
914
+
915
+ for (var i = 0; i < columns.length; i++) {
916
+ rules.push("." + uid + " .l" + i + " { }");
917
+ rules.push("." + uid + " .r" + i + " { }");
918
+ }
919
+
920
+ if ($style[0].styleSheet) { // IE
921
+ $style[0].styleSheet.cssText = rules.join(" ");
922
+ } else {
923
+ $style[0].appendChild(document.createTextNode(rules.join(" ")));
924
+ }
925
+ }
926
+
927
+ function getColumnCssRules(idx) {
928
+ if (!stylesheet) {
929
+ var sheets = document.styleSheets;
930
+ for (var i = 0; i < sheets.length; i++) {
931
+ if ((sheets[i].ownerNode || sheets[i].owningElement) == $style[0]) {
932
+ stylesheet = sheets[i];
933
+ break;
934
+ }
935
+ }
936
+
937
+ if (!stylesheet) {
938
+ throw new Error("Cannot find stylesheet.");
939
+ }
940
+
941
+ // find and cache column CSS rules
942
+ columnCssRulesL = [];
943
+ columnCssRulesR = [];
944
+ var cssRules = (stylesheet.cssRules || stylesheet.rules);
945
+ var matches, columnIdx;
946
+ for (var i = 0; i < cssRules.length; i++) {
947
+ var selector = cssRules[i].selectorText;
948
+ if (matches = /\.l\d+/.exec(selector)) {
949
+ columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10);
950
+ columnCssRulesL[columnIdx] = cssRules[i];
951
+ } else if (matches = /\.r\d+/.exec(selector)) {
952
+ columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10);
953
+ columnCssRulesR[columnIdx] = cssRules[i];
954
+ }
955
+ }
956
+ }
957
+
958
+ return {
959
+ "left": columnCssRulesL[idx],
960
+ "right": columnCssRulesR[idx]
961
+ };
962
+ }
963
+
964
+ function removeCssRules() {
965
+ $style.remove();
966
+ stylesheet = null;
967
+ }
968
+
969
+ function destroy() {
970
+ getEditorLock().cancelCurrentEdit();
971
+
972
+ trigger(self.onBeforeDestroy, {});
973
+
974
+ var i = plugins.length;
975
+ while(i--) {
976
+ unregisterPlugin(plugins[i]);
977
+ }
978
+
979
+ if (options.enableColumnReorder) {
980
+ $headers.filter(":ui-sortable").sortable("destroy");
981
+ }
982
+
983
+ unbindAncestorScrollEvents();
984
+ $container.unbind(".slickgrid");
985
+ removeCssRules();
986
+
987
+ $canvas.unbind("draginit dragstart dragend drag");
988
+ $container.empty().removeClass(uid);
989
+ }
990
+
991
+
992
+ //////////////////////////////////////////////////////////////////////////////////////////////
993
+ // General
994
+
995
+ function trigger(evt, args, e) {
996
+ e = e || new Slick.EventData();
997
+ args = args || {};
998
+ args.grid = self;
999
+ return evt.notify(args, e, self);
1000
+ }
1001
+
1002
+ function getEditorLock() {
1003
+ return options.editorLock;
1004
+ }
1005
+
1006
+ function getEditController() {
1007
+ return editController;
1008
+ }
1009
+
1010
+ function getColumnIndex(id) {
1011
+ return columnsById[id];
1012
+ }
1013
+
1014
+ function autosizeColumns() {
1015
+ var i, c,
1016
+ widths = [],
1017
+ shrinkLeeway = 0,
1018
+ total = 0,
1019
+ prevTotal,
1020
+ availWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW;
1021
+
1022
+ for (i = 0; i < columns.length; i++) {
1023
+ c = columns[i];
1024
+ widths.push(c.width);
1025
+ total += c.width;
1026
+ if (c.resizable) {
1027
+ shrinkLeeway += c.width - Math.max(c.minWidth, absoluteColumnMinWidth);
1028
+ }
1029
+ }
1030
+
1031
+ // shrink
1032
+ prevTotal = total;
1033
+ while (total > availWidth && shrinkLeeway) {
1034
+ var shrinkProportion = (total - availWidth) / shrinkLeeway;
1035
+ for (i = 0; i < columns.length && total > availWidth; i++) {
1036
+ c = columns[i];
1037
+ var width = widths[i];
1038
+ if (!c.resizable || width <= c.minWidth || width <= absoluteColumnMinWidth) {
1039
+ continue;
1040
+ }
1041
+ var absMinWidth = Math.max(c.minWidth, absoluteColumnMinWidth);
1042
+ var shrinkSize = Math.floor(shrinkProportion * (width - absMinWidth)) || 1;
1043
+ shrinkSize = Math.min(shrinkSize, width - absMinWidth);
1044
+ total -= shrinkSize;
1045
+ shrinkLeeway -= shrinkSize;
1046
+ widths[i] -= shrinkSize;
1047
+ }
1048
+ if (prevTotal == total) { // avoid infinite loop
1049
+ break;
1050
+ }
1051
+ prevTotal = total;
1052
+ }
1053
+
1054
+ // grow
1055
+ prevTotal = total;
1056
+ while (total < availWidth) {
1057
+ var growProportion = availWidth / total;
1058
+ for (i = 0; i < columns.length && total < availWidth; i++) {
1059
+ c = columns[i];
1060
+ if (!c.resizable || c.maxWidth <= c.width) {
1061
+ continue;
1062
+ }
1063
+ var growSize = Math.min(Math.floor(growProportion * c.width) - c.width, (c.maxWidth - c.width) || 1000000) || 1;
1064
+ total += growSize;
1065
+ widths[i] += growSize;
1066
+ }
1067
+ if (prevTotal == total) { // avoid infinite loop
1068
+ break;
1069
+ }
1070
+ prevTotal = total;
1071
+ }
1072
+
1073
+ var reRender = false;
1074
+ for (i = 0; i < columns.length; i++) {
1075
+ if (columns[i].rerenderOnResize && columns[i].width != widths[i]) {
1076
+ reRender = true;
1077
+ }
1078
+ columns[i].width = widths[i];
1079
+ }
1080
+
1081
+ applyColumnHeaderWidths();
1082
+ updateCanvasWidth(true);
1083
+ if (reRender) {
1084
+ invalidateAllRows();
1085
+ render();
1086
+ }
1087
+ }
1088
+
1089
+ function applyColumnHeaderWidths() {
1090
+ if (!initialized) { return; }
1091
+ var h;
1092
+ for (var i = 0, headers = $headers.children(), ii = headers.length; i < ii; i++) {
1093
+ h = $(headers[i]);
1094
+ if (h.width() !== columns[i].width - headerColumnWidthDiff) {
1095
+ h.width(columns[i].width - headerColumnWidthDiff);
1096
+ }
1097
+ }
1098
+
1099
+ updateColumnCaches();
1100
+ }
1101
+
1102
+ function applyColumnWidths() {
1103
+ var x = 0, w, rule;
1104
+ for (var i = 0; i < columns.length; i++) {
1105
+ w = columns[i].width;
1106
+
1107
+ rule = getColumnCssRules(i);
1108
+ rule.left.style.left = x + "px";
1109
+ rule.right.style.right = (canvasWidth - x - w) + "px";
1110
+
1111
+ x += columns[i].width;
1112
+ }
1113
+ }
1114
+
1115
+ function setSortColumn(columnId, ascending) {
1116
+ setSortColumns([{ columnId: columnId, sortAsc: ascending}]);
1117
+ }
1118
+
1119
+ function setSortColumns(cols) {
1120
+ sortColumns = cols;
1121
+
1122
+ var headerColumnEls = $headers.children();
1123
+ headerColumnEls
1124
+ .removeClass("slick-header-column-sorted")
1125
+ .find(".slick-sort-indicator")
1126
+ .removeClass("slick-sort-indicator-asc slick-sort-indicator-desc");
1127
+
1128
+ $.each(sortColumns, function(i, col) {
1129
+ if (col.sortAsc == null) {
1130
+ col.sortAsc = true;
1131
+ }
1132
+ var columnIndex = getColumnIndex(col.columnId);
1133
+ if (columnIndex != null) {
1134
+ headerColumnEls.eq(columnIndex)
1135
+ .addClass("slick-header-column-sorted")
1136
+ .find(".slick-sort-indicator")
1137
+ .addClass(col.sortAsc ? "slick-sort-indicator-asc" : "slick-sort-indicator-desc");
1138
+ }
1139
+ });
1140
+ }
1141
+
1142
+ function getSortColumns() {
1143
+ return sortColumns;
1144
+ }
1145
+
1146
+ function handleSelectedRangesChanged(e, ranges) {
1147
+ selectedRows = [];
1148
+ var hash = {};
1149
+ for (var i = 0; i < ranges.length; i++) {
1150
+ for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
1151
+ if (!hash[j]) { // prevent duplicates
1152
+ selectedRows.push(j);
1153
+ hash[j] = {};
1154
+ }
1155
+ for (var k = ranges[i].fromCell; k <= ranges[i].toCell; k++) {
1156
+ if (canCellBeSelected(j, k)) {
1157
+ hash[j][columns[k].id] = options.selectedCellCssClass;
1158
+ }
1159
+ }
1160
+ }
1161
+ }
1162
+
1163
+ setCellCssStyles(options.selectedCellCssClass, hash);
1164
+
1165
+ trigger(self.onSelectedRowsChanged, {rows: getSelectedRows()}, e);
1166
+ }
1167
+
1168
+ function getColumns() {
1169
+ return columns;
1170
+ }
1171
+
1172
+ function updateColumnCaches() {
1173
+ // Pre-calculate cell boundaries.
1174
+ columnPosLeft = [];
1175
+ columnPosRight = [];
1176
+ var x = 0;
1177
+ for (var i = 0, ii = columns.length; i < ii; i++) {
1178
+ columnPosLeft[i] = x;
1179
+ columnPosRight[i] = x + columns[i].width;
1180
+ x += columns[i].width;
1181
+ }
1182
+ }
1183
+
1184
+ function setColumns(columnDefinitions) {
1185
+ columns = columnDefinitions;
1186
+
1187
+ columnsById = {};
1188
+ for (var i = 0; i < columns.length; i++) {
1189
+ var m = columns[i] = $.extend({}, columnDefaults, columns[i]);
1190
+ columnsById[m.id] = i;
1191
+ if (m.minWidth && m.width < m.minWidth) {
1192
+ m.width = m.minWidth;
1193
+ }
1194
+ if (m.maxWidth && m.width > m.maxWidth) {
1195
+ m.width = m.maxWidth;
1196
+ }
1197
+ }
1198
+
1199
+ updateColumnCaches();
1200
+
1201
+ if (initialized) {
1202
+ invalidateAllRows();
1203
+ createColumnHeaders();
1204
+ removeCssRules();
1205
+ createCssRules();
1206
+ resizeCanvas();
1207
+ applyColumnWidths();
1208
+ handleScroll();
1209
+ }
1210
+ }
1211
+
1212
+ function getOptions() {
1213
+ return options;
1214
+ }
1215
+
1216
+ function setOptions(args) {
1217
+ if (!getEditorLock().commitCurrentEdit()) {
1218
+ return;
1219
+ }
1220
+
1221
+ makeActiveCellNormal();
1222
+
1223
+ if (options.enableAddRow !== args.enableAddRow) {
1224
+ invalidateRow(getDataLength());
1225
+ }
1226
+
1227
+ options = $.extend(options, args);
1228
+ validateAndEnforceOptions();
1229
+
1230
+ $viewport.css("overflow-y", options.autoHeight ? "hidden" : "auto");
1231
+ render();
1232
+ }
1233
+
1234
+ function validateAndEnforceOptions() {
1235
+ if (options.autoHeight) {
1236
+ options.leaveSpaceForNewRows = false;
1237
+ }
1238
+ }
1239
+
1240
+ function setData(newData, scrollToTop) {
1241
+ data = newData;
1242
+ invalidateAllRows();
1243
+ updateRowCount();
1244
+ if (scrollToTop) {
1245
+ scrollTo(0);
1246
+ }
1247
+ }
1248
+
1249
+ function getData() {
1250
+ return data;
1251
+ }
1252
+
1253
+ function getDataLength() {
1254
+ if (data.getLength) {
1255
+ return data.getLength();
1256
+ } else {
1257
+ return data.length;
1258
+ }
1259
+ }
1260
+
1261
+ function getDataItem(i) {
1262
+ if (data.getItem) {
1263
+ return data.getItem(i);
1264
+ } else {
1265
+ return data[i];
1266
+ }
1267
+ }
1268
+
1269
+ function getTopPanel() {
1270
+ return $topPanel[0];
1271
+ }
1272
+
1273
+ function setTopPanelVisibility(visible) {
1274
+ if (options.showTopPanel != visible) {
1275
+ options.showTopPanel = visible;
1276
+ if (visible) {
1277
+ $topPanelScroller.slideDown("fast", resizeCanvas);
1278
+ } else {
1279
+ $topPanelScroller.slideUp("fast", resizeCanvas);
1280
+ }
1281
+ }
1282
+ }
1283
+
1284
+ function setHeaderRowVisibility(visible) {
1285
+ if (options.showHeaderRow != visible) {
1286
+ options.showHeaderRow = visible;
1287
+ if (visible) {
1288
+ $headerRowScroller.slideDown("fast", resizeCanvas);
1289
+ } else {
1290
+ $headerRowScroller.slideUp("fast", resizeCanvas);
1291
+ }
1292
+ }
1293
+ }
1294
+
1295
+ function getContainerNode() {
1296
+ return $container.get(0);
1297
+ }
1298
+
1299
+ //////////////////////////////////////////////////////////////////////////////////////////////
1300
+ // Rendering / Scrolling
1301
+
1302
+ function getRowTop(row) {
1303
+ return options.rowHeight * row - offset;
1304
+ }
1305
+
1306
+ function getRowFromPosition(y) {
1307
+ return Math.floor((y + offset) / options.rowHeight);
1308
+ }
1309
+
1310
+ function scrollTo(y) {
1311
+ y = Math.max(y, 0);
1312
+ y = Math.min(y, th - viewportH + (viewportHasHScroll ? scrollbarDimensions.height : 0));
1313
+
1314
+ var oldOffset = offset;
1315
+
1316
+ page = Math.min(n - 1, Math.floor(y / ph));
1317
+ offset = Math.round(page * cj);
1318
+ var newScrollTop = y - offset;
1319
+
1320
+ if (offset != oldOffset) {
1321
+ var range = getVisibleRange(newScrollTop);
1322
+ cleanupRows(range);
1323
+ updateRowPositions();
1324
+ }
1325
+
1326
+ if (prevScrollTop != newScrollTop) {
1327
+ vScrollDir = (prevScrollTop + oldOffset < newScrollTop + offset) ? 1 : -1;
1328
+ $viewport[0].scrollTop = (lastRenderedScrollTop = scrollTop = prevScrollTop = newScrollTop);
1329
+
1330
+ trigger(self.onViewportChanged, {});
1331
+ }
1332
+ }
1333
+
1334
+ function defaultFormatter(row, cell, value, columnDef, dataContext) {
1335
+ if (value == null) {
1336
+ return "";
1337
+ } else {
1338
+ return (value + "").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
1339
+ }
1340
+ }
1341
+
1342
+ function getFormatter(row, column) {
1343
+ var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
1344
+
1345
+ // look up by id, then index
1346
+ var columnOverrides = rowMetadata &&
1347
+ rowMetadata.columns &&
1348
+ (rowMetadata.columns[column.id] || rowMetadata.columns[getColumnIndex(column.id)]);
1349
+
1350
+ return (columnOverrides && columnOverrides.formatter) ||
1351
+ (rowMetadata && rowMetadata.formatter) ||
1352
+ column.formatter ||
1353
+ (options.formatterFactory && options.formatterFactory.getFormatter(column)) ||
1354
+ options.defaultFormatter;
1355
+ }
1356
+
1357
+ function getEditor(row, cell) {
1358
+ var column = columns[cell];
1359
+ var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
1360
+ var columnMetadata = rowMetadata && rowMetadata.columns;
1361
+
1362
+ if (columnMetadata && columnMetadata[column.id] && columnMetadata[column.id].editor !== undefined) {
1363
+ return columnMetadata[column.id].editor;
1364
+ }
1365
+ if (columnMetadata && columnMetadata[cell] && columnMetadata[cell].editor !== undefined) {
1366
+ return columnMetadata[cell].editor;
1367
+ }
1368
+
1369
+ return column.editor || (options.editorFactory && options.editorFactory.getEditor(column));
1370
+ }
1371
+
1372
+ function getDataItemValueForColumn(item, columnDef) {
1373
+ if (options.dataItemColumnValueExtractor) {
1374
+ return options.dataItemColumnValueExtractor(item, columnDef);
1375
+ }
1376
+ return item[columnDef.field];
1377
+ }
1378
+
1379
+ function appendRowHtml(stringArray, row, range, dataLength) {
1380
+ var d = getDataItem(row);
1381
+ var dataLoading = row < dataLength && !d;
1382
+ var rowCss = "slick-row" +
1383
+ (dataLoading ? " loading" : "") +
1384
+ (row === activeRow ? " active" : "") +
1385
+ (row % 2 == 1 ? " odd" : " even");
1386
+
1387
+ var metadata = data.getItemMetadata && data.getItemMetadata(row);
1388
+
1389
+ if (metadata && metadata.cssClasses) {
1390
+ rowCss += " " + metadata.cssClasses;
1391
+ }
1392
+
1393
+ stringArray.push("<div class='ui-widget-content " + rowCss + "' style='top:" + getRowTop(row) + "px'>");
1394
+
1395
+ var colspan, m;
1396
+ for (var i = 0, ii = columns.length; i < ii; i++) {
1397
+ m = columns[i];
1398
+ colspan = 1;
1399
+ if (metadata && metadata.columns) {
1400
+ var columnData = metadata.columns[m.id] || metadata.columns[i];
1401
+ colspan = (columnData && columnData.colspan) || 1;
1402
+ if (colspan === "*") {
1403
+ colspan = ii - i;
1404
+ }
1405
+ }
1406
+
1407
+ // Do not render cells outside of the viewport.
1408
+ if (columnPosRight[Math.min(ii - 1, i + colspan - 1)] > range.leftPx) {
1409
+ if (columnPosLeft[i] > range.rightPx) {
1410
+ // All columns to the right are outside the range.
1411
+ break;
1412
+ }
1413
+
1414
+ appendCellHtml(stringArray, row, i, colspan, d);
1415
+ }
1416
+
1417
+ if (colspan > 1) {
1418
+ i += (colspan - 1);
1419
+ }
1420
+ }
1421
+
1422
+ stringArray.push("</div>");
1423
+ }
1424
+
1425
+ function appendCellHtml(stringArray, row, cell, colspan, item) {
1426
+ var m = columns[cell];
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]);
1437
+ }
1438
+ }
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 (item) {
1444
+ var value = getDataItemValueForColumn(item, m);
1445
+ stringArray.push(getFormatter(row, m)(row, cell, value, m, item));
1446
+ }
1447
+
1448
+ stringArray.push("</div>");
1449
+
1450
+ rowsCache[row].cellRenderQueue.push(cell);
1451
+ rowsCache[row].cellColSpans[cell] = colspan;
1452
+ }
1453
+
1454
+
1455
+ function cleanupRows(rangeToKeep) {
1456
+ for (var i in rowsCache) {
1457
+ if (((i = parseInt(i, 10)) !== activeRow) && (i < rangeToKeep.top || i > rangeToKeep.bottom)) {
1458
+ removeRowFromCache(i);
1459
+ }
1460
+ }
1461
+ }
1462
+
1463
+ function invalidate() {
1464
+ updateRowCount();
1465
+ invalidateAllRows();
1466
+ render();
1467
+ }
1468
+
1469
+ function invalidateAllRows() {
1470
+ if (currentEditor) {
1471
+ makeActiveCellNormal();
1472
+ }
1473
+ for (var row in rowsCache) {
1474
+ removeRowFromCache(row);
1475
+ }
1476
+ }
1477
+
1478
+ function removeRowFromCache(row) {
1479
+ var cacheEntry = rowsCache[row];
1480
+ if (!cacheEntry) {
1481
+ return;
1482
+ }
1483
+ $canvas[0].removeChild(cacheEntry.rowNode);
1484
+ delete rowsCache[row];
1485
+ delete postProcessedRows[row];
1486
+ renderedRows--;
1487
+ counter_rows_removed++;
1488
+ }
1489
+
1490
+ function invalidateRows(rows) {
1491
+ var i, rl;
1492
+ if (!rows || !rows.length) {
1493
+ return;
1494
+ }
1495
+ vScrollDir = 0;
1496
+ for (i = 0, rl = rows.length; i < rl; i++) {
1497
+ if (currentEditor && activeRow === rows[i]) {
1498
+ makeActiveCellNormal();
1499
+ }
1500
+ if (rowsCache[rows[i]]) {
1501
+ removeRowFromCache(rows[i]);
1502
+ }
1503
+ }
1504
+ }
1505
+
1506
+ function invalidateRow(row) {
1507
+ invalidateRows([row]);
1508
+ }
1509
+
1510
+ function updateCell(row, cell) {
1511
+ var cellNode = getCellNode(row, cell);
1512
+ if (!cellNode) {
1513
+ return;
1514
+ }
1515
+
1516
+ var m = columns[cell], d = getDataItem(row);
1517
+ if (currentEditor && activeRow === row && activeCell === cell) {
1518
+ currentEditor.loadValue(d);
1519
+ } else {
1520
+ cellNode.innerHTML = d ? getFormatter(row, m)(row, cell, getDataItemValueForColumn(d, m), m, d) : "";
1521
+ invalidatePostProcessingResults(row);
1522
+ }
1523
+ }
1524
+
1525
+ function updateRow(row) {
1526
+ var cacheEntry = rowsCache[row];
1527
+ if (!cacheEntry) {
1528
+ return;
1529
+ }
1530
+
1531
+ ensureCellNodesInRowsCache(row);
1532
+
1533
+ var d = getDataItem(row);
1534
+
1535
+ for (var columnIdx in cacheEntry.cellNodesByColumnIdx) {
1536
+ if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) {
1537
+ continue;
1538
+ }
1539
+
1540
+ columnIdx = columnIdx | 0;
1541
+ var m = columns[columnIdx],
1542
+ node = cacheEntry.cellNodesByColumnIdx[columnIdx];
1543
+
1544
+ if (row === activeRow && columnIdx === activeCell && currentEditor) {
1545
+ currentEditor.loadValue(d);
1546
+ } else if (d) {
1547
+ node.innerHTML = getFormatter(row, m)(row, columnIdx, getDataItemValueForColumn(d, m), m, d);
1548
+ } else {
1549
+ node.innerHTML = "";
1550
+ }
1551
+ }
1552
+
1553
+ invalidatePostProcessingResults(row);
1554
+ }
1555
+
1556
+ function getViewportHeight() {
1557
+ return parseFloat($.css($container[0], "height", true)) -
1558
+ parseFloat($.css($container[0], "paddingTop", true)) -
1559
+ parseFloat($.css($container[0], "paddingBottom", true)) -
1560
+ parseFloat($.css($headerScroller[0], "height")) - getVBoxDelta($headerScroller) -
1561
+ (options.showTopPanel ? options.topPanelHeight + getVBoxDelta($topPanelScroller) : 0) -
1562
+ (options.showHeaderRow ? options.headerRowHeight + getVBoxDelta($headerRowScroller) : 0);
1563
+ }
1564
+
1565
+ function resizeCanvas() {
1566
+ if (!initialized) { return; }
1567
+ if (options.autoHeight) {
1568
+ viewportH = options.rowHeight * (getDataLength() + (options.enableAddRow ? 1 : 0));
1569
+ } else {
1570
+ viewportH = getViewportHeight();
1571
+ }
1572
+
1573
+ numVisibleRows = Math.ceil(viewportH / options.rowHeight);
1574
+ viewportW = parseFloat($.css($container[0], "width", true));
1575
+ if (!options.autoHeight) {
1576
+ $viewport.height(viewportH);
1577
+ }
1578
+
1579
+ if (options.forceFitColumns) {
1580
+ autosizeColumns();
1581
+ }
1582
+
1583
+ updateRowCount();
1584
+ handleScroll();
1585
+ render();
1586
+ }
1587
+
1588
+ function updateRowCount() {
1589
+ var dataLength = getDataLength();
1590
+ if (!initialized) { return; }
1591
+ numberOfRows = dataLength +
1592
+ (options.enableAddRow ? 1 : 0) +
1593
+ (options.leaveSpaceForNewRows ? numVisibleRows - 1 : 0);
1594
+
1595
+ var oldViewportHasVScroll = viewportHasVScroll;
1596
+ // with autoHeight, we do not need to accommodate the vertical scroll bar
1597
+ viewportHasVScroll = !options.autoHeight && (numberOfRows * options.rowHeight > viewportH);
1598
+
1599
+ // remove the rows that are now outside of the data range
1600
+ // this helps avoid redundant calls to .removeRow() when the size of the data decreased by thousands of rows
1601
+ var l = options.enableAddRow ? dataLength : dataLength - 1;
1602
+ for (var i in rowsCache) {
1603
+ if (i >= l) {
1604
+ removeRowFromCache(i);
1605
+ }
1606
+ }
1607
+
1608
+ if (activeCellNode && activeRow > l) {
1609
+ resetActiveCell();
1610
+ }
1611
+
1612
+ var oldH = h;
1613
+ th = Math.max(options.rowHeight * numberOfRows, viewportH - scrollbarDimensions.height);
1614
+ if (th < maxSupportedCssHeight) {
1615
+ // just one page
1616
+ h = ph = th;
1617
+ n = 1;
1618
+ cj = 0;
1619
+ } else {
1620
+ // break into pages
1621
+ h = maxSupportedCssHeight;
1622
+ ph = h / 100;
1623
+ n = Math.floor(th / ph);
1624
+ cj = (th - h) / (n - 1);
1625
+ }
1626
+
1627
+ if (h !== oldH) {
1628
+ $canvas.css("height", h);
1629
+ scrollTop = $viewport[0].scrollTop;
1630
+ }
1631
+
1632
+ var oldScrollTopInRange = (scrollTop + offset <= th - viewportH);
1633
+
1634
+ if (th == 0 || scrollTop == 0) {
1635
+ page = offset = 0;
1636
+ } else if (oldScrollTopInRange) {
1637
+ // maintain virtual position
1638
+ scrollTo(scrollTop + offset);
1639
+ } else {
1640
+ // scroll to bottom
1641
+ scrollTo(th - viewportH);
1642
+ }
1643
+
1644
+ if (h != oldH && options.autoHeight) {
1645
+ resizeCanvas();
1646
+ }
1647
+
1648
+ if (options.forceFitColumns && oldViewportHasVScroll != viewportHasVScroll) {
1649
+ autosizeColumns();
1650
+ }
1651
+ updateCanvasWidth(false);
1652
+ }
1653
+
1654
+ function getVisibleRange(viewportTop, viewportLeft) {
1655
+ if (viewportTop == null) {
1656
+ viewportTop = scrollTop;
1657
+ }
1658
+ if (viewportLeft == null) {
1659
+ viewportLeft = scrollLeft;
1660
+ }
1661
+
1662
+ return {
1663
+ top: getRowFromPosition(viewportTop),
1664
+ bottom: getRowFromPosition(viewportTop + viewportH) + 1,
1665
+ leftPx: viewportLeft,
1666
+ rightPx: viewportLeft + viewportW
1667
+ };
1668
+ }
1669
+
1670
+ function getRenderedRange(viewportTop, viewportLeft) {
1671
+ var range = getVisibleRange(viewportTop, viewportLeft);
1672
+ var buffer = Math.round(viewportH / options.rowHeight);
1673
+ var minBuffer = 3;
1674
+
1675
+ if (vScrollDir == -1) {
1676
+ range.top -= buffer;
1677
+ range.bottom += minBuffer;
1678
+ } else if (vScrollDir == 1) {
1679
+ range.top -= minBuffer;
1680
+ range.bottom += buffer;
1681
+ } else {
1682
+ range.top -= minBuffer;
1683
+ range.bottom += minBuffer;
1684
+ }
1685
+
1686
+ range.top = Math.max(0, range.top);
1687
+ range.bottom = Math.min(options.enableAddRow ? getDataLength() : getDataLength() - 1, range.bottom);
1688
+
1689
+ range.leftPx -= viewportW;
1690
+ range.rightPx += viewportW;
1691
+
1692
+ range.leftPx = Math.max(0, range.leftPx);
1693
+ range.rightPx = Math.min(canvasWidth, range.rightPx);
1694
+
1695
+ return range;
1696
+ }
1697
+
1698
+ function ensureCellNodesInRowsCache(row) {
1699
+ var cacheEntry = rowsCache[row];
1700
+ if (cacheEntry) {
1701
+ if (cacheEntry.cellRenderQueue.length) {
1702
+ var lastChild = cacheEntry.rowNode.lastChild;
1703
+ while (cacheEntry.cellRenderQueue.length) {
1704
+ var columnIdx = cacheEntry.cellRenderQueue.pop();
1705
+ cacheEntry.cellNodesByColumnIdx[columnIdx] = lastChild;
1706
+ lastChild = lastChild.previousSibling;
1707
+ }
1708
+ }
1709
+ }
1710
+ }
1711
+
1712
+ function cleanUpCells(range, row) {
1713
+ var totalCellsRemoved = 0;
1714
+ var cacheEntry = rowsCache[row];
1715
+
1716
+ // Remove cells outside the range.
1717
+ var cellsToRemove = [];
1718
+ for (var i in cacheEntry.cellNodesByColumnIdx) {
1719
+ // I really hate it when people mess with Array.prototype.
1720
+ if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(i)) {
1721
+ continue;
1722
+ }
1723
+
1724
+ // This is a string, so it needs to be cast back to a number.
1725
+ i = i | 0;
1726
+
1727
+ var colspan = cacheEntry.cellColSpans[i];
1728
+ if (columnPosLeft[i] > range.rightPx ||
1729
+ columnPosRight[Math.min(columns.length - 1, i + colspan - 1)] < range.leftPx) {
1730
+ if (!(row == activeRow && i == activeCell)) {
1731
+ cellsToRemove.push(i);
1732
+ }
1733
+ }
1734
+ }
1735
+
1736
+ var cellToRemove;
1737
+ while ((cellToRemove = cellsToRemove.pop()) != null) {
1738
+ cacheEntry.rowNode.removeChild(cacheEntry.cellNodesByColumnIdx[cellToRemove]);
1739
+ delete cacheEntry.cellColSpans[cellToRemove];
1740
+ delete cacheEntry.cellNodesByColumnIdx[cellToRemove];
1741
+ if (postProcessedRows[row]) {
1742
+ delete postProcessedRows[row][cellToRemove];
1743
+ }
1744
+ totalCellsRemoved++;
1745
+ }
1746
+ }
1747
+
1748
+ function cleanUpAndRenderCells(range) {
1749
+ var cacheEntry;
1750
+ var stringArray = [];
1751
+ var processedRows = [];
1752
+ var cellsAdded;
1753
+ var totalCellsAdded = 0;
1754
+ var colspan;
1755
+
1756
+ for (var row = range.top, btm = range.bottom; row <= btm; row++) {
1757
+ cacheEntry = rowsCache[row];
1758
+ if (!cacheEntry) {
1759
+ continue;
1760
+ }
1761
+
1762
+ // cellRenderQueue populated in renderRows() needs to be cleared first
1763
+ ensureCellNodesInRowsCache(row);
1764
+
1765
+ cleanUpCells(range, row);
1766
+
1767
+ // Render missing cells.
1768
+ cellsAdded = 0;
1769
+
1770
+ var metadata = data.getItemMetadata && data.getItemMetadata(row);
1771
+ metadata = metadata && metadata.columns;
1772
+
1773
+ var d = getDataItem(row);
1774
+
1775
+ // TODO: shorten this loop (index? heuristics? binary search?)
1776
+ for (var i = 0, ii = columns.length; i < ii; i++) {
1777
+ // Cells to the right are outside the range.
1778
+ if (columnPosLeft[i] > range.rightPx) {
1779
+ break;
1780
+ }
1781
+
1782
+ // Already rendered.
1783
+ if ((colspan = cacheEntry.cellColSpans[i]) != null) {
1784
+ i += (colspan > 1 ? colspan - 1 : 0);
1785
+ continue;
1786
+ }
1787
+
1788
+ colspan = 1;
1789
+ if (metadata) {
1790
+ var columnData = metadata[columns[i].id] || metadata[i];
1791
+ colspan = (columnData && columnData.colspan) || 1;
1792
+ if (colspan === "*") {
1793
+ colspan = ii - i;
1794
+ }
1795
+ }
1796
+
1797
+ if (columnPosRight[Math.min(ii - 1, i + colspan - 1)] > range.leftPx) {
1798
+ appendCellHtml(stringArray, row, i, colspan, d);
1799
+ cellsAdded++;
1800
+ }
1801
+
1802
+ i += (colspan > 1 ? colspan - 1 : 0);
1803
+ }
1804
+
1805
+ if (cellsAdded) {
1806
+ totalCellsAdded += cellsAdded;
1807
+ processedRows.push(row);
1808
+ }
1809
+ }
1810
+
1811
+ if (!stringArray.length) {
1812
+ return;
1813
+ }
1814
+
1815
+ var x = document.createElement("div");
1816
+ x.innerHTML = stringArray.join("");
1817
+
1818
+ var processedRow;
1819
+ var node;
1820
+ while ((processedRow = processedRows.pop()) != null) {
1821
+ cacheEntry = rowsCache[processedRow];
1822
+ var columnIdx;
1823
+ while ((columnIdx = cacheEntry.cellRenderQueue.pop()) != null) {
1824
+ node = x.lastChild;
1825
+ cacheEntry.rowNode.appendChild(node);
1826
+ cacheEntry.cellNodesByColumnIdx[columnIdx] = node;
1827
+ }
1828
+ }
1829
+ }
1830
+
1831
+ function renderRows(range) {
1832
+ var parentNode = $canvas[0],
1833
+ stringArray = [],
1834
+ rows = [],
1835
+ needToReselectCell = false,
1836
+ dataLength = getDataLength();
1837
+
1838
+ for (var i = range.top, ii = range.bottom; i <= ii; i++) {
1839
+ if (rowsCache[i]) {
1840
+ continue;
1841
+ }
1842
+ renderedRows++;
1843
+ rows.push(i);
1844
+
1845
+ // Create an entry right away so that appendRowHtml() can
1846
+ // start populatating it.
1847
+ rowsCache[i] = {
1848
+ "rowNode": null,
1849
+
1850
+ // ColSpans of rendered cells (by column idx).
1851
+ // Can also be used for checking whether a cell has been rendered.
1852
+ "cellColSpans": [],
1853
+
1854
+ // Cell nodes (by column idx). Lazy-populated by ensureCellNodesInRowsCache().
1855
+ "cellNodesByColumnIdx": [],
1856
+
1857
+ // Column indices of cell nodes that have been rendered, but not yet indexed in
1858
+ // cellNodesByColumnIdx. These are in the same order as cell nodes added at the
1859
+ // end of the row.
1860
+ "cellRenderQueue": []
1861
+ };
1862
+
1863
+ appendRowHtml(stringArray, i, range, dataLength);
1864
+ if (activeCellNode && activeRow === i) {
1865
+ needToReselectCell = true;
1866
+ }
1867
+ counter_rows_rendered++;
1868
+ }
1869
+
1870
+ if (!rows.length) { return; }
1871
+
1872
+ var x = document.createElement("div");
1873
+ x.innerHTML = stringArray.join("");
1874
+
1875
+ for (var i = 0, ii = rows.length; i < ii; i++) {
1876
+ rowsCache[rows[i]].rowNode = parentNode.appendChild(x.firstChild);
1877
+ }
1878
+
1879
+ if (needToReselectCell) {
1880
+ activeCellNode = getCellNode(activeRow, activeCell);
1881
+ }
1882
+ }
1883
+
1884
+ function startPostProcessing() {
1885
+ if (!options.enableAsyncPostRender) {
1886
+ return;
1887
+ }
1888
+ clearTimeout(h_postrender);
1889
+ h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay);
1890
+ }
1891
+
1892
+ function invalidatePostProcessingResults(row) {
1893
+ delete postProcessedRows[row];
1894
+ postProcessFromRow = Math.min(postProcessFromRow, row);
1895
+ postProcessToRow = Math.max(postProcessToRow, row);
1896
+ startPostProcessing();
1897
+ }
1898
+
1899
+ function updateRowPositions() {
1900
+ for (var row in rowsCache) {
1901
+ rowsCache[row].rowNode.style.top = getRowTop(row) + "px";
1902
+ }
1903
+ }
1904
+
1905
+ function render() {
1906
+ if (!initialized) { return; }
1907
+ var visible = getVisibleRange();
1908
+ var rendered = getRenderedRange();
1909
+
1910
+ // remove rows no longer in the viewport
1911
+ cleanupRows(rendered);
1912
+
1913
+ // add new rows & missing cells in existing rows
1914
+ if (lastRenderedScrollLeft != scrollLeft) {
1915
+ cleanUpAndRenderCells(rendered);
1916
+ }
1917
+
1918
+ // render missing rows
1919
+ renderRows(rendered);
1920
+
1921
+ postProcessFromRow = visible.top;
1922
+ postProcessToRow = Math.min(options.enableAddRow ? getDataLength() : getDataLength() - 1, visible.bottom);
1923
+ startPostProcessing();
1924
+
1925
+ lastRenderedScrollTop = scrollTop;
1926
+ lastRenderedScrollLeft = scrollLeft;
1927
+ h_render = null;
1928
+ }
1929
+
1930
+ function handleHeaderRowScroll() {
1931
+ var scrollLeft = $headerRowScroller[0].scrollLeft;
1932
+ if (scrollLeft != $viewport[0].scrollLeft) {
1933
+ $viewport[0].scrollLeft = scrollLeft;
1934
+ }
1935
+ }
1936
+
1937
+ function handleScroll() {
1938
+ scrollTop = $viewport[0].scrollTop;
1939
+ scrollLeft = $viewport[0].scrollLeft;
1940
+ var vScrollDist = Math.abs(scrollTop - prevScrollTop);
1941
+ var hScrollDist = Math.abs(scrollLeft - prevScrollLeft);
1942
+
1943
+ if (hScrollDist) {
1944
+ prevScrollLeft = scrollLeft;
1945
+ $headerScroller[0].scrollLeft = scrollLeft;
1946
+ $topPanelScroller[0].scrollLeft = scrollLeft;
1947
+ $headerRowScroller[0].scrollLeft = scrollLeft;
1948
+ }
1949
+
1950
+ if (vScrollDist) {
1951
+ vScrollDir = prevScrollTop < scrollTop ? 1 : -1;
1952
+ prevScrollTop = scrollTop;
1953
+
1954
+ // switch virtual pages if needed
1955
+ if (vScrollDist < viewportH) {
1956
+ scrollTo(scrollTop + offset);
1957
+ } else {
1958
+ var oldOffset = offset;
1959
+ if (h == viewportH) {
1960
+ page = 0;
1961
+ } else {
1962
+ page = Math.min(n - 1, Math.floor(scrollTop * ((th - viewportH) / (h - viewportH)) * (1 / ph)));
1963
+ }
1964
+ offset = Math.round(page * cj);
1965
+ if (oldOffset != offset) {
1966
+ invalidateAllRows();
1967
+ }
1968
+ }
1969
+ }
1970
+
1971
+ if (hScrollDist || vScrollDist) {
1972
+ if (h_render) {
1973
+ clearTimeout(h_render);
1974
+ }
1975
+
1976
+ if (Math.abs(lastRenderedScrollTop - scrollTop) > 20 ||
1977
+ Math.abs(lastRenderedScrollLeft - scrollLeft) > 20) {
1978
+ if (options.forceSyncScrolling || (
1979
+ Math.abs(lastRenderedScrollTop - scrollTop) < viewportH &&
1980
+ Math.abs(lastRenderedScrollLeft - scrollLeft) < viewportW)) {
1981
+ render();
1982
+ } else {
1983
+ h_render = setTimeout(render, 50);
1984
+ }
1985
+
1986
+ trigger(self.onViewportChanged, {});
1987
+ }
1988
+ }
1989
+
1990
+ trigger(self.onScroll, {scrollLeft: scrollLeft, scrollTop: scrollTop});
1991
+ }
1992
+
1993
+ function asyncPostProcessRows() {
1994
+ while (postProcessFromRow <= postProcessToRow) {
1995
+ var row = (vScrollDir >= 0) ? postProcessFromRow++ : postProcessToRow--;
1996
+ var cacheEntry = rowsCache[row];
1997
+ if (!cacheEntry || row >= getDataLength()) {
1998
+ continue;
1999
+ }
2000
+
2001
+ if (!postProcessedRows[row]) {
2002
+ postProcessedRows[row] = {};
2003
+ }
2004
+
2005
+ ensureCellNodesInRowsCache(row);
2006
+ for (var columnIdx in cacheEntry.cellNodesByColumnIdx) {
2007
+ if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) {
2008
+ continue;
2009
+ }
2010
+
2011
+ columnIdx = columnIdx | 0;
2012
+
2013
+ var m = columns[columnIdx];
2014
+ if (m.asyncPostRender && !postProcessedRows[row][columnIdx]) {
2015
+ var node = cacheEntry.cellNodesByColumnIdx[columnIdx];
2016
+ if (node) {
2017
+ m.asyncPostRender(node, row, getDataItem(row), m);
2018
+ }
2019
+ postProcessedRows[row][columnIdx] = true;
2020
+ }
2021
+ }
2022
+
2023
+ h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay);
2024
+ return;
2025
+ }
2026
+ }
2027
+
2028
+ function updateCellCssStylesOnRenderedRows(addedHash, removedHash) {
2029
+ var node, columnId, addedRowHash, removedRowHash;
2030
+ for (var row in rowsCache) {
2031
+ removedRowHash = removedHash && removedHash[row];
2032
+ addedRowHash = addedHash && addedHash[row];
2033
+
2034
+ if (removedRowHash) {
2035
+ for (columnId in removedRowHash) {
2036
+ if (!addedRowHash || removedRowHash[columnId] != addedRowHash[columnId]) {
2037
+ node = getCellNode(row, getColumnIndex(columnId));
2038
+ if (node) {
2039
+ $(node).removeClass(removedRowHash[columnId]);
2040
+ }
2041
+ }
2042
+ }
2043
+ }
2044
+
2045
+ if (addedRowHash) {
2046
+ for (columnId in addedRowHash) {
2047
+ if (!removedRowHash || removedRowHash[columnId] != addedRowHash[columnId]) {
2048
+ node = getCellNode(row, getColumnIndex(columnId));
2049
+ if (node) {
2050
+ $(node).addClass(addedRowHash[columnId]);
2051
+ }
2052
+ }
2053
+ }
2054
+ }
2055
+ }
2056
+ }
2057
+
2058
+ function addCellCssStyles(key, hash) {
2059
+ if (cellCssClasses[key]) {
2060
+ throw "addCellCssStyles: cell CSS hash with key '" + key + "' already exists.";
2061
+ }
2062
+
2063
+ cellCssClasses[key] = hash;
2064
+ updateCellCssStylesOnRenderedRows(hash, null);
2065
+
2066
+ trigger(self.onCellCssStylesChanged, { "key": key, "hash": hash });
2067
+ }
2068
+
2069
+ function removeCellCssStyles(key) {
2070
+ if (!cellCssClasses[key]) {
2071
+ return;
2072
+ }
2073
+
2074
+ updateCellCssStylesOnRenderedRows(null, cellCssClasses[key]);
2075
+ delete cellCssClasses[key];
2076
+
2077
+ trigger(self.onCellCssStylesChanged, { "key": key, "hash": null });
2078
+ }
2079
+
2080
+ function setCellCssStyles(key, hash) {
2081
+ var prevHash = cellCssClasses[key];
2082
+
2083
+ cellCssClasses[key] = hash;
2084
+ updateCellCssStylesOnRenderedRows(hash, prevHash);
2085
+
2086
+ trigger(self.onCellCssStylesChanged, { "key": key, "hash": hash });
2087
+ }
2088
+
2089
+ function getCellCssStyles(key) {
2090
+ return cellCssClasses[key];
2091
+ }
2092
+
2093
+ function flashCell(row, cell, speed) {
2094
+ speed = speed || 100;
2095
+ if (rowsCache[row]) {
2096
+ var $cell = $(getCellNode(row, cell));
2097
+
2098
+ function toggleCellClass(times) {
2099
+ if (!times) {
2100
+ return;
2101
+ }
2102
+ setTimeout(function () {
2103
+ $cell.queue(function () {
2104
+ $cell.toggleClass(options.cellFlashingCssClass).dequeue();
2105
+ toggleCellClass(times - 1);
2106
+ });
2107
+ },
2108
+ speed);
2109
+ }
2110
+
2111
+ toggleCellClass(4);
2112
+ }
2113
+ }
2114
+
2115
+ //////////////////////////////////////////////////////////////////////////////////////////////
2116
+ // Interactivity
2117
+
2118
+ function handleDragInit(e, dd) {
2119
+ var cell = getCellFromEvent(e);
2120
+ if (!cell || !cellExists(cell.row, cell.cell)) {
2121
+ return false;
2122
+ }
2123
+
2124
+ var retval = trigger(self.onDragInit, dd, e);
2125
+ if (e.isImmediatePropagationStopped()) {
2126
+ return retval;
2127
+ }
2128
+
2129
+ // if nobody claims to be handling drag'n'drop by stopping immediate propagation,
2130
+ // cancel out of it
2131
+ return false;
2132
+ }
2133
+
2134
+ function handleDragStart(e, dd) {
2135
+ var cell = getCellFromEvent(e);
2136
+ if (!cell || !cellExists(cell.row, cell.cell)) {
2137
+ return false;
2138
+ }
2139
+
2140
+ var retval = trigger(self.onDragStart, dd, e);
2141
+ if (e.isImmediatePropagationStopped()) {
2142
+ return retval;
2143
+ }
2144
+
2145
+ return false;
2146
+ }
2147
+
2148
+ function handleDrag(e, dd) {
2149
+ return trigger(self.onDrag, dd, e);
2150
+ }
2151
+
2152
+ function handleDragEnd(e, dd) {
2153
+ trigger(self.onDragEnd, dd, e);
2154
+ }
2155
+
2156
+ function handleKeyDown(e) {
2157
+ trigger(self.onKeyDown, {row: activeRow, cell: activeCell}, e);
2158
+ var handled = e.isImmediatePropagationStopped();
2159
+
2160
+ if (!handled) {
2161
+ if (!e.shiftKey && !e.altKey && !e.ctrlKey) {
2162
+ if (e.which == 27) {
2163
+ if (!getEditorLock().isActive()) {
2164
+ return; // no editing mode to cancel, allow bubbling and default processing (exit without cancelling the event)
2165
+ }
2166
+ cancelEditAndSetFocus();
2167
+ } else if (e.which == 37) {
2168
+ handled = navigateLeft();
2169
+ } else if (e.which == 39) {
2170
+ handled = navigateRight();
2171
+ } else if (e.which == 38) {
2172
+ handled = navigateUp();
2173
+ } else if (e.which == 40) {
2174
+ handled = navigateDown();
2175
+ } else if (e.which == 9) {
2176
+ handled = navigateNext();
2177
+ } else if (e.which == 13) {
2178
+ if (options.editable) {
2179
+ if (currentEditor) {
2180
+ // adding new row
2181
+ if (activeRow === getDataLength()) {
2182
+ navigateDown();
2183
+ } else {
2184
+ commitEditAndSetFocus();
2185
+ }
2186
+ } else {
2187
+ if (getEditorLock().commitCurrentEdit()) {
2188
+ makeActiveCellEditable();
2189
+ }
2190
+ }
2191
+ }
2192
+ handled = true;
2193
+ }
2194
+ } else if (e.which == 9 && e.shiftKey && !e.ctrlKey && !e.altKey) {
2195
+ handled = navigatePrev();
2196
+ }
2197
+ }
2198
+
2199
+ if (handled) {
2200
+ // the event has been handled so don't let parent element (bubbling/propagation) or browser (default) handle it
2201
+ e.stopPropagation();
2202
+ e.preventDefault();
2203
+ try {
2204
+ e.originalEvent.keyCode = 0; // prevent default behaviour for special keys in IE browsers (F3, F5, etc.)
2205
+ }
2206
+ // ignore exceptions - setting the original event's keycode throws access denied exception for "Ctrl"
2207
+ // (hitting control key only, nothing else), "Shift" (maybe others)
2208
+ catch (error) {
2209
+ }
2210
+ }
2211
+ }
2212
+
2213
+ function handleClick(e) {
2214
+ if (!currentEditor) {
2215
+ // if this click resulted in some cell child node getting focus,
2216
+ // don't steal it back - keyboard events will still bubble up
2217
+ if (e.target != document.activeElement) {
2218
+ setFocus();
2219
+ }
2220
+ }
2221
+
2222
+ var cell = getCellFromEvent(e);
2223
+ if (!cell || (currentEditor !== null && activeRow == cell.row && activeCell == cell.cell)) {
2224
+ return;
2225
+ }
2226
+
2227
+ trigger(self.onClick, {row: cell.row, cell: cell.cell}, e);
2228
+ if (e.isImmediatePropagationStopped()) {
2229
+ return;
2230
+ }
2231
+
2232
+ if ((activeCell != cell.cell || activeRow != cell.row) && canCellBeActive(cell.row, cell.cell)) {
2233
+ if (!getEditorLock().isActive() || getEditorLock().commitCurrentEdit()) {
2234
+ scrollRowIntoView(cell.row, false);
2235
+ setActiveCellInternal(getCellNode(cell.row, cell.cell), (cell.row === getDataLength()) || options.autoEdit);
2236
+ }
2237
+ }
2238
+ }
2239
+
2240
+ function handleContextMenu(e) {
2241
+ var $cell = $(e.target).closest(".slick-cell", $canvas);
2242
+ if ($cell.length === 0) {
2243
+ return;
2244
+ }
2245
+
2246
+ // are we editing this cell?
2247
+ if (activeCellNode === $cell[0] && currentEditor !== null) {
2248
+ return;
2249
+ }
2250
+
2251
+ trigger(self.onContextMenu, {}, e);
2252
+ }
2253
+
2254
+ function handleDblClick(e) {
2255
+ var cell = getCellFromEvent(e);
2256
+ if (!cell || (currentEditor !== null && activeRow == cell.row && activeCell == cell.cell)) {
2257
+ return;
2258
+ }
2259
+
2260
+ trigger(self.onDblClick, {row: cell.row, cell: cell.cell}, e);
2261
+ if (e.isImmediatePropagationStopped()) {
2262
+ return;
2263
+ }
2264
+
2265
+ if (options.editable) {
2266
+ gotoCell(cell.row, cell.cell, true);
2267
+ }
2268
+ }
2269
+
2270
+ function handleHeaderMouseEnter(e) {
2271
+ trigger(self.onHeaderMouseEnter, {
2272
+ "column": $(this).data("column")
2273
+ }, e);
2274
+ }
2275
+
2276
+ function handleHeaderMouseLeave(e) {
2277
+ trigger(self.onHeaderMouseLeave, {
2278
+ "column": $(this).data("column")
2279
+ }, e);
2280
+ }
2281
+
2282
+ function handleHeaderContextMenu(e) {
2283
+ var $header = $(e.target).closest(".slick-header-column", ".slick-header-columns");
2284
+ var column = $header && $header.data("column");
2285
+ trigger(self.onHeaderContextMenu, {column: column}, e);
2286
+ }
2287
+
2288
+ function handleHeaderClick(e) {
2289
+ var $header = $(e.target).closest(".slick-header-column", ".slick-header-columns");
2290
+ var column = $header && $header.data("column");
2291
+ if (column) {
2292
+ trigger(self.onHeaderClick, {column: column}, e);
2293
+ }
2294
+ }
2295
+
2296
+ function handleMouseEnter(e) {
2297
+ trigger(self.onMouseEnter, {}, e);
2298
+ }
2299
+
2300
+ function handleMouseLeave(e) {
2301
+ trigger(self.onMouseLeave, {}, e);
2302
+ }
2303
+
2304
+ function cellExists(row, cell) {
2305
+ return !(row < 0 || row >= getDataLength() || cell < 0 || cell >= columns.length);
2306
+ }
2307
+
2308
+ function getCellFromPoint(x, y) {
2309
+ var row = getRowFromPosition(y);
2310
+ var cell = 0;
2311
+
2312
+ var w = 0;
2313
+ for (var i = 0; i < columns.length && w < x; i++) {
2314
+ w += columns[i].width;
2315
+ cell++;
2316
+ }
2317
+
2318
+ if (cell < 0) {
2319
+ cell = 0;
2320
+ }
2321
+
2322
+ return {row: row, cell: cell - 1};
2323
+ }
2324
+
2325
+ function getCellFromNode(cellNode) {
2326
+ // read column number from .l<columnNumber> CSS class
2327
+ var cls = /l\d+/.exec(cellNode.className);
2328
+ if (!cls) {
2329
+ throw "getCellFromNode: cannot get cell - " + cellNode.className;
2330
+ }
2331
+ return parseInt(cls[0].substr(1, cls[0].length - 1), 10);
2332
+ }
2333
+
2334
+ function getRowFromNode(rowNode) {
2335
+ for (var row in rowsCache) {
2336
+ if (rowsCache[row].rowNode === rowNode) {
2337
+ return row | 0;
2338
+ }
2339
+ }
2340
+
2341
+ return null;
2342
+ }
2343
+
2344
+ function getCellFromEvent(e) {
2345
+ var $cell = $(e.target).closest(".slick-cell", $canvas);
2346
+ if (!$cell.length) {
2347
+ return null;
2348
+ }
2349
+
2350
+ var row = getRowFromNode($cell[0].parentNode);
2351
+ var cell = getCellFromNode($cell[0]);
2352
+
2353
+ if (row == null || cell == null) {
2354
+ return null;
2355
+ } else {
2356
+ return {
2357
+ "row": row,
2358
+ "cell": cell
2359
+ };
2360
+ }
2361
+ }
2362
+
2363
+ function getCellNodeBox(row, cell) {
2364
+ if (!cellExists(row, cell)) {
2365
+ return null;
2366
+ }
2367
+
2368
+ var y1 = getRowTop(row);
2369
+ var y2 = y1 + options.rowHeight - 1;
2370
+ var x1 = 0;
2371
+ for (var i = 0; i < cell; i++) {
2372
+ x1 += columns[i].width;
2373
+ }
2374
+ var x2 = x1 + columns[cell].width;
2375
+
2376
+ return {
2377
+ top: y1,
2378
+ left: x1,
2379
+ bottom: y2,
2380
+ right: x2
2381
+ };
2382
+ }
2383
+
2384
+ //////////////////////////////////////////////////////////////////////////////////////////////
2385
+ // Cell switching
2386
+
2387
+ function resetActiveCell() {
2388
+ setActiveCellInternal(null, false);
2389
+ }
2390
+
2391
+ function setFocus() {
2392
+ if (tabbingDirection == -1) {
2393
+ $focusSink[0].focus();
2394
+ } else {
2395
+ $focusSink2[0].focus();
2396
+ }
2397
+ }
2398
+
2399
+ function scrollCellIntoView(row, cell, doPaging) {
2400
+ scrollRowIntoView(row, doPaging);
2401
+
2402
+ var colspan = getColspan(row, cell);
2403
+ var left = columnPosLeft[cell],
2404
+ right = columnPosRight[cell + (colspan > 1 ? colspan - 1 : 0)],
2405
+ scrollRight = scrollLeft + viewportW;
2406
+
2407
+ if (left < scrollLeft) {
2408
+ $viewport.scrollLeft(left);
2409
+ handleScroll();
2410
+ render();
2411
+ } else if (right > scrollRight) {
2412
+ $viewport.scrollLeft(Math.min(left, right - $viewport[0].clientWidth));
2413
+ handleScroll();
2414
+ render();
2415
+ }
2416
+ }
2417
+
2418
+ function setActiveCellInternal(newCell, editMode) {
2419
+ if (activeCellNode !== null) {
2420
+ makeActiveCellNormal();
2421
+ $(activeCellNode).removeClass("active");
2422
+ if (rowsCache[activeRow]) {
2423
+ $(rowsCache[activeRow].rowNode).removeClass("active");
2424
+ }
2425
+ }
2426
+
2427
+ var activeCellChanged = (activeCellNode !== newCell);
2428
+ activeCellNode = newCell;
2429
+
2430
+ if (activeCellNode != null) {
2431
+ activeRow = getRowFromNode(activeCellNode.parentNode);
2432
+ activeCell = activePosX = getCellFromNode(activeCellNode);
2433
+
2434
+ $(activeCellNode).addClass("active");
2435
+ $(rowsCache[activeRow].rowNode).addClass("active");
2436
+
2437
+ if (options.editable && editMode && isCellPotentiallyEditable(activeRow, activeCell)) {
2438
+ clearTimeout(h_editorLoader);
2439
+
2440
+ if (options.asyncEditorLoading) {
2441
+ h_editorLoader = setTimeout(function () {
2442
+ makeActiveCellEditable();
2443
+ }, options.asyncEditorLoadDelay);
2444
+ } else {
2445
+ makeActiveCellEditable();
2446
+ }
2447
+ }
2448
+ } else {
2449
+ activeRow = activeCell = null;
2450
+ }
2451
+
2452
+ if (activeCellChanged) {
2453
+ trigger(self.onActiveCellChanged, getActiveCell());
2454
+ }
2455
+ }
2456
+
2457
+ function clearTextSelection() {
2458
+ if (document.selection && document.selection.empty) {
2459
+ try {
2460
+ //IE fails here if selected element is not in dom
2461
+ document.selection.empty();
2462
+ } catch (e) { }
2463
+ } else if (window.getSelection) {
2464
+ var sel = window.getSelection();
2465
+ if (sel && sel.removeAllRanges) {
2466
+ sel.removeAllRanges();
2467
+ }
2468
+ }
2469
+ }
2470
+
2471
+ function isCellPotentiallyEditable(row, cell) {
2472
+ // is the data for this row loaded?
2473
+ if (row < getDataLength() && !getDataItem(row)) {
2474
+ return false;
2475
+ }
2476
+
2477
+ // are we in the Add New row? can we create new from this cell?
2478
+ if (columns[cell].cannotTriggerInsert && row >= getDataLength()) {
2479
+ return false;
2480
+ }
2481
+
2482
+ // does this cell have an editor?
2483
+ if (!getEditor(row, cell)) {
2484
+ return false;
2485
+ }
2486
+
2487
+ return true;
2488
+ }
2489
+
2490
+ function makeActiveCellNormal() {
2491
+ if (!currentEditor) {
2492
+ return;
2493
+ }
2494
+ trigger(self.onBeforeCellEditorDestroy, {editor: currentEditor});
2495
+ currentEditor.destroy();
2496
+ currentEditor = null;
2497
+
2498
+ if (activeCellNode) {
2499
+ var d = getDataItem(activeRow);
2500
+ $(activeCellNode).removeClass("editable invalid");
2501
+ if (d) {
2502
+ var column = columns[activeCell];
2503
+ var formatter = getFormatter(activeRow, column);
2504
+ activeCellNode.innerHTML = formatter(activeRow, activeCell, getDataItemValueForColumn(d, column), column, d);
2505
+ invalidatePostProcessingResults(activeRow);
2506
+ }
2507
+ }
2508
+
2509
+ // if there previously was text selected on a page (such as selected text in the edit cell just removed),
2510
+ // IE can't set focus to anything else correctly
2511
+ if (navigator.userAgent.toLowerCase().match(/msie/)) {
2512
+ clearTextSelection();
2513
+ }
2514
+
2515
+ getEditorLock().deactivate(editController);
2516
+ }
2517
+
2518
+ function makeActiveCellEditable(editor) {
2519
+ if (!activeCellNode) {
2520
+ return;
2521
+ }
2522
+ if (!options.editable) {
2523
+ throw "Grid : makeActiveCellEditable : should never get called when options.editable is false";
2524
+ }
2525
+
2526
+ // cancel pending async call if there is one
2527
+ clearTimeout(h_editorLoader);
2528
+
2529
+ if (!isCellPotentiallyEditable(activeRow, activeCell)) {
2530
+ return;
2531
+ }
2532
+
2533
+ var columnDef = columns[activeCell];
2534
+ var item = getDataItem(activeRow);
2535
+
2536
+ if (trigger(self.onBeforeEditCell, {row: activeRow, cell: activeCell, item: item, column: columnDef}) === false) {
2537
+ setFocus();
2538
+ return;
2539
+ }
2540
+
2541
+ getEditorLock().activate(editController);
2542
+ $(activeCellNode).addClass("editable");
2543
+
2544
+ // don't clear the cell if a custom editor is passed through
2545
+ if (!editor) {
2546
+ activeCellNode.innerHTML = "";
2547
+ }
2548
+
2549
+ currentEditor = new (editor || getEditor(activeRow, activeCell))({
2550
+ grid: self,
2551
+ gridPosition: absBox($container[0]),
2552
+ position: absBox(activeCellNode),
2553
+ container: activeCellNode,
2554
+ column: columnDef,
2555
+ item: item || {},
2556
+ commitChanges: commitEditAndSetFocus,
2557
+ cancelChanges: cancelEditAndSetFocus
2558
+ });
2559
+
2560
+ if (item) {
2561
+ currentEditor.loadValue(item);
2562
+ }
2563
+
2564
+ serializedEditorValue = currentEditor.serializeValue();
2565
+
2566
+ if (currentEditor.position) {
2567
+ handleActiveCellPositionChange();
2568
+ }
2569
+ }
2570
+
2571
+ function commitEditAndSetFocus() {
2572
+ // if the commit fails, it would do so due to a validation error
2573
+ // if so, do not steal the focus from the editor
2574
+ if (getEditorLock().commitCurrentEdit()) {
2575
+ setFocus();
2576
+ if (options.autoEdit) {
2577
+ navigateDown();
2578
+ }
2579
+ }
2580
+ }
2581
+
2582
+ function cancelEditAndSetFocus() {
2583
+ if (getEditorLock().cancelCurrentEdit()) {
2584
+ setFocus();
2585
+ }
2586
+ }
2587
+
2588
+ function absBox(elem) {
2589
+ var box = {
2590
+ top: elem.offsetTop,
2591
+ left: elem.offsetLeft,
2592
+ bottom: 0,
2593
+ right: 0,
2594
+ width: $(elem).outerWidth(),
2595
+ height: $(elem).outerHeight(),
2596
+ visible: true};
2597
+ box.bottom = box.top + box.height;
2598
+ box.right = box.left + box.width;
2599
+
2600
+ // walk up the tree
2601
+ var offsetParent = elem.offsetParent;
2602
+ while ((elem = elem.parentNode) != document.body) {
2603
+ if (box.visible && elem.scrollHeight != elem.offsetHeight && $(elem).css("overflowY") != "visible") {
2604
+ box.visible = box.bottom > elem.scrollTop && box.top < elem.scrollTop + elem.clientHeight;
2605
+ }
2606
+
2607
+ if (box.visible && elem.scrollWidth != elem.offsetWidth && $(elem).css("overflowX") != "visible") {
2608
+ box.visible = box.right > elem.scrollLeft && box.left < elem.scrollLeft + elem.clientWidth;
2609
+ }
2610
+
2611
+ box.left -= elem.scrollLeft;
2612
+ box.top -= elem.scrollTop;
2613
+
2614
+ if (elem === offsetParent) {
2615
+ box.left += elem.offsetLeft;
2616
+ box.top += elem.offsetTop;
2617
+ offsetParent = elem.offsetParent;
2618
+ }
2619
+
2620
+ box.bottom = box.top + box.height;
2621
+ box.right = box.left + box.width;
2622
+ }
2623
+
2624
+ return box;
2625
+ }
2626
+
2627
+ function getActiveCellPosition() {
2628
+ return absBox(activeCellNode);
2629
+ }
2630
+
2631
+ function getGridPosition() {
2632
+ return absBox($container[0])
2633
+ }
2634
+
2635
+ function handleActiveCellPositionChange() {
2636
+ if (!activeCellNode) {
2637
+ return;
2638
+ }
2639
+
2640
+ trigger(self.onActiveCellPositionChanged, {});
2641
+
2642
+ if (currentEditor) {
2643
+ var cellBox = getActiveCellPosition();
2644
+ if (currentEditor.show && currentEditor.hide) {
2645
+ if (!cellBox.visible) {
2646
+ currentEditor.hide();
2647
+ } else {
2648
+ currentEditor.show();
2649
+ }
2650
+ }
2651
+
2652
+ if (currentEditor.position) {
2653
+ currentEditor.position(cellBox);
2654
+ }
2655
+ }
2656
+ }
2657
+
2658
+ function getCellEditor() {
2659
+ return currentEditor;
2660
+ }
2661
+
2662
+ function getActiveCell() {
2663
+ if (!activeCellNode) {
2664
+ return null;
2665
+ } else {
2666
+ return {row: activeRow, cell: activeCell};
2667
+ }
2668
+ }
2669
+
2670
+ function getActiveCellNode() {
2671
+ return activeCellNode;
2672
+ }
2673
+
2674
+ function scrollRowIntoView(row, doPaging) {
2675
+ var rowAtTop = row * options.rowHeight;
2676
+ var rowAtBottom = (row + 1) * options.rowHeight - viewportH + (viewportHasHScroll ? scrollbarDimensions.height : 0);
2677
+
2678
+ // need to page down?
2679
+ if ((row + 1) * options.rowHeight > scrollTop + viewportH + offset) {
2680
+ scrollTo(doPaging ? rowAtTop : rowAtBottom);
2681
+ render();
2682
+ }
2683
+ // or page up?
2684
+ else if (row * options.rowHeight < scrollTop + offset) {
2685
+ scrollTo(doPaging ? rowAtBottom : rowAtTop);
2686
+ render();
2687
+ }
2688
+ }
2689
+
2690
+ function scrollRowToTop(row) {
2691
+ scrollTo(row * options.rowHeight);
2692
+ render();
2693
+ }
2694
+
2695
+ function getColspan(row, cell) {
2696
+ var metadata = data.getItemMetadata && data.getItemMetadata(row);
2697
+ if (!metadata || !metadata.columns) {
2698
+ return 1;
2699
+ }
2700
+
2701
+ var columnData = metadata.columns[columns[cell].id] || metadata.columns[cell];
2702
+ var colspan = (columnData && columnData.colspan);
2703
+ if (colspan === "*") {
2704
+ colspan = columns.length - cell;
2705
+ } else {
2706
+ colspan = colspan || 1;
2707
+ }
2708
+
2709
+ return colspan;
2710
+ }
2711
+
2712
+ function findFirstFocusableCell(row) {
2713
+ var cell = 0;
2714
+ while (cell < columns.length) {
2715
+ if (canCellBeActive(row, cell)) {
2716
+ return cell;
2717
+ }
2718
+ cell += getColspan(row, cell);
2719
+ }
2720
+ return null;
2721
+ }
2722
+
2723
+ function findLastFocusableCell(row) {
2724
+ var cell = 0;
2725
+ var lastFocusableCell = null;
2726
+ while (cell < columns.length) {
2727
+ if (canCellBeActive(row, cell)) {
2728
+ lastFocusableCell = cell;
2729
+ }
2730
+ cell += getColspan(row, cell);
2731
+ }
2732
+ return lastFocusableCell;
2733
+ }
2734
+
2735
+ function gotoRight(row, cell, posX) {
2736
+ if (cell >= columns.length) {
2737
+ return null;
2738
+ }
2739
+
2740
+ do {
2741
+ cell += getColspan(row, cell);
2742
+ }
2743
+ while (cell < columns.length && !canCellBeActive(row, cell));
2744
+
2745
+ if (cell < columns.length) {
2746
+ return {
2747
+ "row": row,
2748
+ "cell": cell,
2749
+ "posX": cell
2750
+ };
2751
+ }
2752
+ return null;
2753
+ }
2754
+
2755
+ function gotoLeft(row, cell, posX) {
2756
+ if (cell <= 0) {
2757
+ return null;
2758
+ }
2759
+
2760
+ var firstFocusableCell = findFirstFocusableCell(row);
2761
+ if (firstFocusableCell === null || firstFocusableCell >= cell) {
2762
+ return null;
2763
+ }
2764
+
2765
+ var prev = {
2766
+ "row": row,
2767
+ "cell": firstFocusableCell,
2768
+ "posX": firstFocusableCell
2769
+ };
2770
+ var pos;
2771
+ while (true) {
2772
+ pos = gotoRight(prev.row, prev.cell, prev.posX);
2773
+ if (!pos) {
2774
+ return null;
2775
+ }
2776
+ if (pos.cell >= cell) {
2777
+ return prev;
2778
+ }
2779
+ prev = pos;
2780
+ }
2781
+ }
2782
+
2783
+ function gotoDown(row, cell, posX) {
2784
+ var prevCell;
2785
+ while (true) {
2786
+ if (++row >= getDataLength() + (options.enableAddRow ? 1 : 0)) {
2787
+ return null;
2788
+ }
2789
+
2790
+ prevCell = cell = 0;
2791
+ while (cell <= posX) {
2792
+ prevCell = cell;
2793
+ cell += getColspan(row, cell);
2794
+ }
2795
+
2796
+ if (canCellBeActive(row, prevCell)) {
2797
+ return {
2798
+ "row": row,
2799
+ "cell": prevCell,
2800
+ "posX": posX
2801
+ };
2802
+ }
2803
+ }
2804
+ }
2805
+
2806
+ function gotoUp(row, cell, posX) {
2807
+ var prevCell;
2808
+ while (true) {
2809
+ if (--row < 0) {
2810
+ return null;
2811
+ }
2812
+
2813
+ prevCell = cell = 0;
2814
+ while (cell <= posX) {
2815
+ prevCell = cell;
2816
+ cell += getColspan(row, cell);
2817
+ }
2818
+
2819
+ if (canCellBeActive(row, prevCell)) {
2820
+ return {
2821
+ "row": row,
2822
+ "cell": prevCell,
2823
+ "posX": posX
2824
+ };
2825
+ }
2826
+ }
2827
+ }
2828
+
2829
+ function gotoNext(row, cell, posX) {
2830
+ if (row == null && cell == null) {
2831
+ row = cell = posX = 0;
2832
+ if (canCellBeActive(row, cell)) {
2833
+ return {
2834
+ "row": row,
2835
+ "cell": cell,
2836
+ "posX": cell
2837
+ };
2838
+ }
2839
+ }
2840
+
2841
+ var pos = gotoRight(row, cell, posX);
2842
+ if (pos) {
2843
+ return pos;
2844
+ }
2845
+
2846
+ var firstFocusableCell = null;
2847
+ while (++row < getDataLength() + (options.enableAddRow ? 1 : 0)) {
2848
+ firstFocusableCell = findFirstFocusableCell(row);
2849
+ if (firstFocusableCell !== null) {
2850
+ return {
2851
+ "row": row,
2852
+ "cell": firstFocusableCell,
2853
+ "posX": firstFocusableCell
2854
+ };
2855
+ }
2856
+ }
2857
+ return null;
2858
+ }
2859
+
2860
+ function gotoPrev(row, cell, posX) {
2861
+ if (row == null && cell == null) {
2862
+ row = getDataLength() + (options.enableAddRow ? 1 : 0) - 1;
2863
+ cell = posX = columns.length - 1;
2864
+ if (canCellBeActive(row, cell)) {
2865
+ return {
2866
+ "row": row,
2867
+ "cell": cell,
2868
+ "posX": cell
2869
+ };
2870
+ }
2871
+ }
2872
+
2873
+ var pos;
2874
+ var lastSelectableCell;
2875
+ while (!pos) {
2876
+ pos = gotoLeft(row, cell, posX);
2877
+ if (pos) {
2878
+ break;
2879
+ }
2880
+ if (--row < 0) {
2881
+ return null;
2882
+ }
2883
+
2884
+ cell = 0;
2885
+ lastSelectableCell = findLastFocusableCell(row);
2886
+ if (lastSelectableCell !== null) {
2887
+ pos = {
2888
+ "row": row,
2889
+ "cell": lastSelectableCell,
2890
+ "posX": lastSelectableCell
2891
+ };
2892
+ }
2893
+ }
2894
+ return pos;
2895
+ }
2896
+
2897
+ function navigateRight() {
2898
+ return navigate("right");
2899
+ }
2900
+
2901
+ function navigateLeft() {
2902
+ return navigate("left");
2903
+ }
2904
+
2905
+ function navigateDown() {
2906
+ return navigate("down");
2907
+ }
2908
+
2909
+ function navigateUp() {
2910
+ return navigate("up");
2911
+ }
2912
+
2913
+ function navigateNext() {
2914
+ return navigate("next");
2915
+ }
2916
+
2917
+ function navigatePrev() {
2918
+ return navigate("prev");
2919
+ }
2920
+
2921
+ /**
2922
+ * @param {string} dir Navigation direction.
2923
+ * @return {boolean} Whether navigation resulted in a change of active cell.
2924
+ */
2925
+ function navigate(dir) {
2926
+ if (!options.enableCellNavigation) {
2927
+ return false;
2928
+ }
2929
+
2930
+ if (!activeCellNode && dir != "prev" && dir != "next") {
2931
+ return false;
2932
+ }
2933
+
2934
+ if (!getEditorLock().commitCurrentEdit()) {
2935
+ return true;
2936
+ }
2937
+ setFocus();
2938
+
2939
+ var tabbingDirections = {
2940
+ "up": -1,
2941
+ "down": 1,
2942
+ "left": -1,
2943
+ "right": 1,
2944
+ "prev": -1,
2945
+ "next": 1
2946
+ };
2947
+ tabbingDirection = tabbingDirections[dir];
2948
+
2949
+ var stepFunctions = {
2950
+ "up": gotoUp,
2951
+ "down": gotoDown,
2952
+ "left": gotoLeft,
2953
+ "right": gotoRight,
2954
+ "prev": gotoPrev,
2955
+ "next": gotoNext
2956
+ };
2957
+ var stepFn = stepFunctions[dir];
2958
+ var pos = stepFn(activeRow, activeCell, activePosX);
2959
+ if (pos) {
2960
+ var isAddNewRow = (pos.row == getDataLength());
2961
+ scrollCellIntoView(pos.row, pos.cell, !isAddNewRow);
2962
+ setActiveCellInternal(getCellNode(pos.row, pos.cell), isAddNewRow || options.autoEdit);
2963
+ activePosX = pos.posX;
2964
+ return true;
2965
+ } else {
2966
+ setActiveCellInternal(getCellNode(activeRow, activeCell), (activeRow == getDataLength()) || options.autoEdit);
2967
+ return false;
2968
+ }
2969
+ }
2970
+
2971
+ function getCellNode(row, cell) {
2972
+ if (rowsCache[row]) {
2973
+ ensureCellNodesInRowsCache(row);
2974
+ return rowsCache[row].cellNodesByColumnIdx[cell];
2975
+ }
2976
+ return null;
2977
+ }
2978
+
2979
+ function setActiveCell(row, cell) {
2980
+ if (!initialized) { return; }
2981
+ if (row > getDataLength() || row < 0 || cell >= columns.length || cell < 0) {
2982
+ return;
2983
+ }
2984
+
2985
+ if (!options.enableCellNavigation) {
2986
+ return;
2987
+ }
2988
+
2989
+ scrollCellIntoView(row, cell, false);
2990
+ setActiveCellInternal(getCellNode(row, cell), false);
2991
+ }
2992
+
2993
+ function canCellBeActive(row, cell) {
2994
+ if (!options.enableCellNavigation || row >= getDataLength() + (options.enableAddRow ? 1 : 0) ||
2995
+ row < 0 || cell >= columns.length || cell < 0) {
2996
+ return false;
2997
+ }
2998
+
2999
+ var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
3000
+ if (rowMetadata && typeof rowMetadata.focusable === "boolean") {
3001
+ return rowMetadata.focusable;
3002
+ }
3003
+
3004
+ var columnMetadata = rowMetadata && rowMetadata.columns;
3005
+ if (columnMetadata && columnMetadata[columns[cell].id] && typeof columnMetadata[columns[cell].id].focusable === "boolean") {
3006
+ return columnMetadata[columns[cell].id].focusable;
3007
+ }
3008
+ if (columnMetadata && columnMetadata[cell] && typeof columnMetadata[cell].focusable === "boolean") {
3009
+ return columnMetadata[cell].focusable;
3010
+ }
3011
+
3012
+ return columns[cell].focusable;
3013
+ }
3014
+
3015
+ function canCellBeSelected(row, cell) {
3016
+ if (row >= getDataLength() || row < 0 || cell >= columns.length || cell < 0) {
3017
+ return false;
3018
+ }
3019
+
3020
+ var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
3021
+ if (rowMetadata && typeof rowMetadata.selectable === "boolean") {
3022
+ return rowMetadata.selectable;
3023
+ }
3024
+
3025
+ var columnMetadata = rowMetadata && rowMetadata.columns && (rowMetadata.columns[columns[cell].id] || rowMetadata.columns[cell]);
3026
+ if (columnMetadata && typeof columnMetadata.selectable === "boolean") {
3027
+ return columnMetadata.selectable;
3028
+ }
3029
+
3030
+ return columns[cell].selectable;
3031
+ }
3032
+
3033
+ function gotoCell(row, cell, forceEdit) {
3034
+ if (!initialized) { return; }
3035
+ if (!canCellBeActive(row, cell)) {
3036
+ return;
3037
+ }
3038
+
3039
+ if (!getEditorLock().commitCurrentEdit()) {
3040
+ return;
3041
+ }
3042
+
3043
+ scrollCellIntoView(row, cell, false);
3044
+
3045
+ var newCell = getCellNode(row, cell);
3046
+
3047
+ // if selecting the 'add new' row, start editing right away
3048
+ setActiveCellInternal(newCell, forceEdit || (row === getDataLength()) || options.autoEdit);
3049
+
3050
+ // if no editor was created, set the focus back on the grid
3051
+ if (!currentEditor) {
3052
+ setFocus();
3053
+ }
3054
+ }
3055
+
3056
+
3057
+ //////////////////////////////////////////////////////////////////////////////////////////////
3058
+ // IEditor implementation for the editor lock
3059
+
3060
+ function commitCurrentEdit() {
3061
+ var item = getDataItem(activeRow);
3062
+ var column = columns[activeCell];
3063
+
3064
+ if (currentEditor) {
3065
+ if (currentEditor.isValueChanged()) {
3066
+ var validationResults = currentEditor.validate();
3067
+
3068
+ if (validationResults.valid) {
3069
+ if (activeRow < getDataLength()) {
3070
+ var editCommand = {
3071
+ row: activeRow,
3072
+ cell: activeCell,
3073
+ editor: currentEditor,
3074
+ serializedValue: currentEditor.serializeValue(),
3075
+ prevSerializedValue: serializedEditorValue,
3076
+ execute: function () {
3077
+ this.editor.applyValue(item, this.serializedValue);
3078
+ updateRow(this.row);
3079
+ },
3080
+ undo: function () {
3081
+ this.editor.applyValue(item, this.prevSerializedValue);
3082
+ updateRow(this.row);
3083
+ }
3084
+ };
3085
+
3086
+ if (options.editCommandHandler) {
3087
+ makeActiveCellNormal();
3088
+ options.editCommandHandler(item, column, editCommand);
3089
+ } else {
3090
+ editCommand.execute();
3091
+ makeActiveCellNormal();
3092
+ }
3093
+
3094
+ trigger(self.onCellChange, {
3095
+ row: activeRow,
3096
+ cell: activeCell,
3097
+ item: item
3098
+ });
3099
+ } else {
3100
+ var newItem = {};
3101
+ currentEditor.applyValue(newItem, currentEditor.serializeValue());
3102
+ makeActiveCellNormal();
3103
+ trigger(self.onAddNewRow, {item: newItem, column: column});
3104
+ }
3105
+
3106
+ // check whether the lock has been re-acquired by event handlers
3107
+ return !getEditorLock().isActive();
3108
+ } else {
3109
+ // Re-add the CSS class to trigger transitions, if any.
3110
+ $(activeCellNode).removeClass("invalid");
3111
+ $(activeCellNode).width(); // force layout
3112
+ $(activeCellNode).addClass("invalid");
3113
+
3114
+ trigger(self.onValidationError, {
3115
+ editor: currentEditor,
3116
+ cellNode: activeCellNode,
3117
+ validationResults: validationResults,
3118
+ row: activeRow,
3119
+ cell: activeCell,
3120
+ column: column
3121
+ });
3122
+
3123
+ currentEditor.focus();
3124
+ return false;
3125
+ }
3126
+ }
3127
+
3128
+ makeActiveCellNormal();
3129
+ }
3130
+ return true;
3131
+ }
3132
+
3133
+ function cancelCurrentEdit() {
3134
+ makeActiveCellNormal();
3135
+ return true;
3136
+ }
3137
+
3138
+ function rowsToRanges(rows) {
3139
+ var ranges = [];
3140
+ var lastCell = columns.length - 1;
3141
+ for (var i = 0; i < rows.length; i++) {
3142
+ ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));
3143
+ }
3144
+ return ranges;
3145
+ }
3146
+
3147
+ function getSelectedRows() {
3148
+ if (!selectionModel) {
3149
+ throw "Selection model is not set";
3150
+ }
3151
+ return selectedRows;
3152
+ }
3153
+
3154
+ function setSelectedRows(rows) {
3155
+ if (!selectionModel) {
3156
+ throw "Selection model is not set";
3157
+ }
3158
+ selectionModel.setSelectedRanges(rowsToRanges(rows));
3159
+ }
3160
+
3161
+
3162
+ //////////////////////////////////////////////////////////////////////////////////////////////
3163
+ // Debug
3164
+
3165
+ this.debug = function () {
3166
+ var s = "";
3167
+
3168
+ s += ("\n" + "counter_rows_rendered: " + counter_rows_rendered);
3169
+ s += ("\n" + "counter_rows_removed: " + counter_rows_removed);
3170
+ s += ("\n" + "renderedRows: " + renderedRows);
3171
+ s += ("\n" + "numVisibleRows: " + numVisibleRows);
3172
+ s += ("\n" + "maxSupportedCssHeight: " + maxSupportedCssHeight);
3173
+ s += ("\n" + "n(umber of pages): " + n);
3174
+ s += ("\n" + "(current) page: " + page);
3175
+ s += ("\n" + "page height (ph): " + ph);
3176
+ s += ("\n" + "vScrollDir: " + vScrollDir);
3177
+
3178
+ alert(s);
3179
+ };
3180
+
3181
+ // a debug helper to be able to access private members
3182
+ this.eval = function (expr) {
3183
+ return eval(expr);
3184
+ };
3185
+
3186
+ //////////////////////////////////////////////////////////////////////////////////////////////
3187
+ // Public API
3188
+
3189
+ $.extend(this, {
3190
+ "slickGridVersion": "2.1",
3191
+
3192
+ // Events
3193
+ "onScroll": new Slick.Event(),
3194
+ "onSort": new Slick.Event(),
3195
+ "onHeaderMouseEnter": new Slick.Event(),
3196
+ "onHeaderMouseLeave": new Slick.Event(),
3197
+ "onHeaderContextMenu": new Slick.Event(),
3198
+ "onHeaderClick": new Slick.Event(),
3199
+ "onHeaderCellRendered": new Slick.Event(),
3200
+ "onBeforeHeaderCellDestroy": new Slick.Event(),
3201
+ "onHeaderRowCellRendered": new Slick.Event(),
3202
+ "onBeforeHeaderRowCellDestroy": new Slick.Event(),
3203
+ "onMouseEnter": new Slick.Event(),
3204
+ "onMouseLeave": new Slick.Event(),
3205
+ "onClick": new Slick.Event(),
3206
+ "onDblClick": new Slick.Event(),
3207
+ "onContextMenu": new Slick.Event(),
3208
+ "onKeyDown": new Slick.Event(),
3209
+ "onAddNewRow": new Slick.Event(),
3210
+ "onValidationError": new Slick.Event(),
3211
+ "onViewportChanged": new Slick.Event(),
3212
+ "onColumnsReordered": new Slick.Event(),
3213
+ "onColumnsResized": new Slick.Event(),
3214
+ "onCellChange": new Slick.Event(),
3215
+ "onBeforeEditCell": new Slick.Event(),
3216
+ "onBeforeCellEditorDestroy": new Slick.Event(),
3217
+ "onBeforeDestroy": new Slick.Event(),
3218
+ "onActiveCellChanged": new Slick.Event(),
3219
+ "onActiveCellPositionChanged": new Slick.Event(),
3220
+ "onDragInit": new Slick.Event(),
3221
+ "onDragStart": new Slick.Event(),
3222
+ "onDrag": new Slick.Event(),
3223
+ "onDragEnd": new Slick.Event(),
3224
+ "onSelectedRowsChanged": new Slick.Event(),
3225
+ "onCellCssStylesChanged": new Slick.Event(),
3226
+
3227
+ // Methods
3228
+ "registerPlugin": registerPlugin,
3229
+ "unregisterPlugin": unregisterPlugin,
3230
+ "getColumns": getColumns,
3231
+ "setColumns": setColumns,
3232
+ "getColumnIndex": getColumnIndex,
3233
+ "updateColumnHeader": updateColumnHeader,
3234
+ "setSortColumn": setSortColumn,
3235
+ "setSortColumns": setSortColumns,
3236
+ "getSortColumns": getSortColumns,
3237
+ "autosizeColumns": autosizeColumns,
3238
+ "getOptions": getOptions,
3239
+ "setOptions": setOptions,
3240
+ "getData": getData,
3241
+ "getDataLength": getDataLength,
3242
+ "getDataItem": getDataItem,
3243
+ "setData": setData,
3244
+ "getSelectionModel": getSelectionModel,
3245
+ "setSelectionModel": setSelectionModel,
3246
+ "getSelectedRows": getSelectedRows,
3247
+ "setSelectedRows": setSelectedRows,
3248
+ "getContainerNode": getContainerNode,
3249
+
3250
+ "render": render,
3251
+ "invalidate": invalidate,
3252
+ "invalidateRow": invalidateRow,
3253
+ "invalidateRows": invalidateRows,
3254
+ "invalidateAllRows": invalidateAllRows,
3255
+ "updateCell": updateCell,
3256
+ "updateRow": updateRow,
3257
+ "getViewport": getVisibleRange,
3258
+ "getRenderedRange": getRenderedRange,
3259
+ "resizeCanvas": resizeCanvas,
3260
+ "updateRowCount": updateRowCount,
3261
+ "scrollRowIntoView": scrollRowIntoView,
3262
+ "scrollRowToTop": scrollRowToTop,
3263
+ "scrollCellIntoView": scrollCellIntoView,
3264
+ "getCanvasNode": getCanvasNode,
3265
+ "focus": setFocus,
3266
+
3267
+ "getCellFromPoint": getCellFromPoint,
3268
+ "getCellFromEvent": getCellFromEvent,
3269
+ "getActiveCell": getActiveCell,
3270
+ "setActiveCell": setActiveCell,
3271
+ "getActiveCellNode": getActiveCellNode,
3272
+ "getActiveCellPosition": getActiveCellPosition,
3273
+ "resetActiveCell": resetActiveCell,
3274
+ "editActiveCell": makeActiveCellEditable,
3275
+ "getCellEditor": getCellEditor,
3276
+ "getCellNode": getCellNode,
3277
+ "getCellNodeBox": getCellNodeBox,
3278
+ "canCellBeSelected": canCellBeSelected,
3279
+ "canCellBeActive": canCellBeActive,
3280
+ "navigatePrev": navigatePrev,
3281
+ "navigateNext": navigateNext,
3282
+ "navigateUp": navigateUp,
3283
+ "navigateDown": navigateDown,
3284
+ "navigateLeft": navigateLeft,
3285
+ "navigateRight": navigateRight,
3286
+ "gotoCell": gotoCell,
3287
+ "getTopPanel": getTopPanel,
3288
+ "setTopPanelVisibility": setTopPanelVisibility,
3289
+ "setHeaderRowVisibility": setHeaderRowVisibility,
3290
+ "getHeaderRow": getHeaderRow,
3291
+ "getHeaderRowColumn": getHeaderRowColumn,
3292
+ "getGridPosition": getGridPosition,
3293
+ "flashCell": flashCell,
3294
+ "addCellCssStyles": addCellCssStyles,
3295
+ "setCellCssStyles": setCellCssStyles,
3296
+ "removeCellCssStyles": removeCellCssStyles,
3297
+ "getCellCssStyles": getCellCssStyles,
3298
+
3299
+ "init": finishInitialization,
3300
+ "destroy": destroy,
3301
+
3302
+ // IEditor implementation
3303
+ "getEditorLock": getEditorLock,
3304
+ "getEditController": getEditController
3305
+ });
3306
+
3307
+ init();
3308
+ }
3309
+ }(jQuery));