dataclips 0.0.1

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