UNPKG

211 kBJavaScriptView Raw
1/**
2 * @license
3 * (c) 2009-2016 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.4
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
20if (typeof jQuery === "undefined") {
21 throw new Error("SlickGrid requires jquery module to be loaded");
22}
23if (!jQuery.fn.drag) {
24 throw new Error("SlickGrid requires jquery.event.drag module to be loaded");
25}
26if (typeof Slick === "undefined") {
27 throw new Error("slick.core.js not loaded");
28}
29
30
31(function ($) {
32 "use strict";
33 // shared across all grids on the page
34 var scrollbarDimensions;
35 var maxSupportedCssHeight; // browser's breaking point
36
37 //////////////////////////////////////////////////////////////////////////////////////////////
38 // SlickGrid class implementation (available as Slick.Grid)
39
40 /**
41 * Creates a new instance of the grid.
42 * @class SlickGrid
43 * @constructor
44 * @param {Node} container Container node to create the grid in.
45 * @param {Array,Object} data An array of objects for databinding.
46 * @param {Array} columns An array of column definitions.
47 * @param {Object} options Grid options.
48 **/
49 function SlickGrid(container, data, columns, options) {
50 // settings
51 var defaults = {
52 alwaysShowVerticalScroll: false,
53 alwaysAllowHorizontalScroll: false,
54 explicitInitialization: false,
55 rowHeight: 25,
56 defaultColumnWidth: 80,
57 enableAddRow: false,
58 leaveSpaceForNewRows: false,
59 editable: false,
60 autoEdit: true,
61 suppressActiveCellChangeOnEdit: false,
62 enableCellNavigation: true,
63 enableColumnReorder: true,
64 asyncEditorLoading: false,
65 asyncEditorLoadDelay: 100,
66 forceFitColumns: false,
67 enableAsyncPostRender: false,
68 asyncPostRenderDelay: 50,
69 enableAsyncPostRenderCleanup: false,
70 asyncPostRenderCleanupDelay: 40,
71 autoHeight: false,
72 editorLock: Slick.GlobalEditorLock,
73 showColumnHeader: true,
74 showHeaderRow: false,
75 headerRowHeight: 25,
76 createFooterRow: false,
77 showFooterRow: false,
78 footerRowHeight: 25,
79 createPreHeaderPanel: false,
80 showPreHeaderPanel: false,
81 preHeaderPanelHeight: 25,
82 showTopPanel: false,
83 topPanelHeight: 25,
84 formatterFactory: null,
85 editorFactory: null,
86 cellFlashingCssClass: "flashing",
87 selectedCellCssClass: "selected",
88 multiSelect: true,
89 enableTextSelectionOnCells: false,
90 dataItemColumnValueExtractor: null,
91 frozenBottom: false,
92 frozenColumn: -1,
93 frozenRow: -1,
94 frozenRightViewportMinWidth: 100,
95 fullWidthRows: false,
96 multiColumnSort: false,
97 numberedMultiColumnSort: false,
98 tristateMultiColumnSort: false,
99 sortColNumberInSeparateSpan: false,
100 defaultFormatter: defaultFormatter,
101 forceSyncScrolling: false,
102 addNewRowCssClass: "new-row",
103 preserveCopiedSelectionOnPaste: false,
104 showCellSelection: true,
105 viewportClass: null,
106 minRowBuffer: 3,
107 emulatePagingWhenScrolling: true, // when scrolling off bottom of viewport, place new row at top of viewport
108 editorCellNavOnLRKeys: false,
109 enableMouseWheelScrollHandler: true,
110 doPaging: true,
111 autosizeColsMode: Slick.GridAutosizeColsMode.LegacyOff,
112 autosizeColPaddingPx: 4,
113 autosizeTextAvgToMWidthRatio: 0.75,
114 viewportSwitchToScrollModeWidthPercent: undefined,
115 viewportMinWidthPx: undefined,
116 viewportMaxWidthPx: undefined,
117 suppressCssChangesOnHiddenInit: false
118 };
119
120 var columnDefaults = {
121 name: "",
122 resizable: true,
123 sortable: false,
124 minWidth: 30,
125 maxWidth: undefined,
126 rerenderOnResize: false,
127 headerCssClass: null,
128 defaultSortAsc: true,
129 focusable: true,
130 selectable: true,
131 };
132
133 var columnAutosizeDefaults = {
134 ignoreHeaderText: false,
135 colValueArray: undefined,
136 allowAddlPercent: undefined,
137 formatterOverride: undefined,
138 autosizeMode: Slick.ColAutosizeMode.ContentIntelligent,
139 rowSelectionModeOnInit: undefined,
140 rowSelectionMode: Slick.RowSelectionMode.FirstNRows,
141 rowSelectionCount: 100,
142 valueFilterMode: Slick.ValueFilterMode.None,
143 widthEvalMode: Slick.WidthEvalMode.CanvasTextSize,
144 sizeToRemaining: undefined,
145 widthPx: undefined,
146 colDataTypeOf: undefined
147 };
148
149 // scroller
150 var th; // virtual height
151 var h; // real scrollable height
152 var ph; // page height
153 var n; // number of pages
154 var cj; // "jumpiness" coefficient
155
156 var page = 0; // current page
157 var offset = 0; // current page offset
158 var vScrollDir = 1;
159
160 // private
161 var initialized = false;
162 var $container;
163 var uid = "slickgrid_" + Math.round(1000000 * Math.random());
164 var self = this;
165 var $focusSink, $focusSink2;
166 var $groupHeaders = $();
167 var $headerScroller;
168 var $headers;
169 var $headerRow, $headerRowScroller, $headerRowSpacerL, $headerRowSpacerR;
170 var $footerRow, $footerRowScroller, $footerRowSpacerL, $footerRowSpacerR;
171 var $preHeaderPanel, $preHeaderPanelScroller, $preHeaderPanelSpacer;
172 var $preHeaderPanelR, $preHeaderPanelScrollerR, $preHeaderPanelSpacerR;
173 var $topPanelScroller;
174 var $topPanel;
175 var $viewport;
176 var $canvas;
177 var $style;
178 var $boundAncestors;
179 var treeColumns;
180 var stylesheet, columnCssRulesL, columnCssRulesR;
181 var viewportH, viewportW;
182 var canvasWidth, canvasWidthL, canvasWidthR;
183 var headersWidth, headersWidthL, headersWidthR;
184 var viewportHasHScroll, viewportHasVScroll;
185 var headerColumnWidthDiff = 0, headerColumnHeightDiff = 0, // border+padding
186 cellWidthDiff = 0, cellHeightDiff = 0, jQueryNewWidthBehaviour = false;
187 var absoluteColumnMinWidth;
188 var hasFrozenRows = false;
189 var frozenRowsHeight = 0;
190 var actualFrozenRow = -1;
191 var paneTopH = 0;
192 var paneBottomH = 0;
193 var viewportTopH = 0;
194 var viewportBottomH = 0;
195 var topPanelH = 0;
196 var headerRowH = 0;
197 var footerRowH = 0;
198
199 var tabbingDirection = 1;
200 var $activeCanvasNode;
201 var $activeViewportNode;
202 var activePosX;
203 var activeRow, activeCell;
204 var activeCellNode = null;
205 var currentEditor = null;
206 var serializedEditorValue;
207 var editController;
208
209 var rowsCache = {};
210 var renderedRows = 0;
211 var numVisibleRows = 0;
212 var prevScrollTop = 0;
213 var scrollTop = 0;
214 var lastRenderedScrollTop = 0;
215 var lastRenderedScrollLeft = 0;
216 var prevScrollLeft = 0;
217 var scrollLeft = 0;
218
219 var selectionModel;
220 var selectedRows = [];
221
222 var plugins = [];
223 var cellCssClasses = {};
224
225 var columnsById = {};
226 var sortColumns = [];
227 var columnPosLeft = [];
228 var columnPosRight = [];
229
230 var pagingActive = false;
231 var pagingIsLastPage = false;
232
233 var scrollThrottle = ActionThrottle(render, 50);
234
235 // async call handles
236 var h_editorLoader = null;
237 var h_render = null;
238 var h_postrender = null;
239 var h_postrenderCleanup = null;
240 var postProcessedRows = {};
241 var postProcessToRow = null;
242 var postProcessFromRow = null;
243 var postProcessedCleanupQueue = [];
244 var postProcessgroupId = 0;
245
246 // perf counters
247 var counter_rows_rendered = 0;
248 var counter_rows_removed = 0;
249
250 var $paneHeaderL;
251 var $paneHeaderR;
252 var $paneTopL;
253 var $paneTopR;
254 var $paneBottomL;
255 var $paneBottomR;
256
257 var $headerScrollerL;
258 var $headerScrollerR;
259
260 var $headerL;
261 var $headerR;
262
263 var $groupHeadersL;
264 var $groupHeadersR;
265
266 var $headerRowScrollerL;
267 var $headerRowScrollerR;
268
269 var $footerRowScrollerL;
270 var $footerRowScrollerR;
271
272 var $headerRowL;
273 var $headerRowR;
274
275 var $footerRowL;
276 var $footerRowR;
277
278 var $topPanelScrollerL;
279 var $topPanelScrollerR;
280
281 var $topPanelL;
282 var $topPanelR;
283
284 var $viewportTopL;
285 var $viewportTopR;
286 var $viewportBottomL;
287 var $viewportBottomR;
288
289 var $canvasTopL;
290 var $canvasTopR;
291 var $canvasBottomL;
292 var $canvasBottomR;
293
294 var $viewportScrollContainerX;
295 var $viewportScrollContainerY;
296 var $headerScrollContainer;
297 var $headerRowScrollContainer;
298 var $footerRowScrollContainer;
299
300 // store css attributes if display:none is active in container or parent
301 var cssShow = { position: 'absolute', visibility: 'hidden', display: 'block' };
302 var $hiddenParents;
303 var oldProps = [];
304 var columnResizeDragging = false;
305
306 //////////////////////////////////////////////////////////////////////////////////////////////
307 // Initialization
308
309 function init() {
310 if (container instanceof jQuery) {
311 $container = container;
312 } else {
313 $container = $(container);
314 }
315 if ($container.length < 1) {
316 throw new Error("SlickGrid requires a valid container, " + container + " does not exist in the DOM.");
317 }
318
319 // calculate these only once and share between grid instances
320 maxSupportedCssHeight = maxSupportedCssHeight || getMaxSupportedCssHeight();
321
322 options = $.extend({}, defaults, options);
323 validateAndEnforceOptions();
324 columnDefaults.width = options.defaultColumnWidth;
325
326 if (!options.suppressCssChangesOnHiddenInit) { cacheCssForHiddenInit(); }
327
328 treeColumns = new Slick.TreeColumns(columns);
329 columns = treeColumns.extractColumns();
330
331 updateColumnProps();
332
333 // validate loaded JavaScript modules against requested options
334 if (options.enableColumnReorder && !$.fn.sortable) {
335 throw new Error("SlickGrid's 'enableColumnReorder = true' option requires jquery-ui.sortable module to be loaded");
336 }
337
338 editController = {
339 "commitCurrentEdit": commitCurrentEdit,
340 "cancelCurrentEdit": cancelCurrentEdit
341 };
342
343 $container
344 .empty()
345 .css("overflow", "hidden")
346 .css("outline", 0)
347 .addClass(uid)
348 .addClass("ui-widget");
349
350 // set up a positioning container if needed
351 if (!(/relative|absolute|fixed/).test($container.css("position"))) {
352 $container.css("position", "relative");
353 }
354
355 $focusSink = $("<div tabIndex='0' hideFocus style='position:fixed;width:0;height:0;top:0;left:0;outline:0;'></div>").appendTo($container);
356
357 // Containers used for scrolling frozen columns and rows
358 $paneHeaderL = $("<div class='slick-pane slick-pane-header slick-pane-left' tabIndex='0' />").appendTo($container);
359 $paneHeaderR = $("<div class='slick-pane slick-pane-header slick-pane-right' tabIndex='0' />").appendTo($container);
360 $paneTopL = $("<div class='slick-pane slick-pane-top slick-pane-left' tabIndex='0' />").appendTo($container);
361 $paneTopR = $("<div class='slick-pane slick-pane-top slick-pane-right' tabIndex='0' />").appendTo($container);
362 $paneBottomL = $("<div class='slick-pane slick-pane-bottom slick-pane-left' tabIndex='0' />").appendTo($container);
363 $paneBottomR = $("<div class='slick-pane slick-pane-bottom slick-pane-right' tabIndex='0' />").appendTo($container);
364
365 if (options.createPreHeaderPanel) {
366 $preHeaderPanelScroller = $("<div class='slick-preheader-panel ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($paneHeaderL);
367 $preHeaderPanel = $("<div />").appendTo($preHeaderPanelScroller);
368 $preHeaderPanelSpacer = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>")
369 .appendTo($preHeaderPanelScroller);
370
371 $preHeaderPanelScrollerR = $("<div class='slick-preheader-panel ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($paneHeaderR);
372 $preHeaderPanelR = $("<div />").appendTo($preHeaderPanelScrollerR);
373 $preHeaderPanelSpacerR = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>")
374 .appendTo($preHeaderPanelScrollerR);
375
376 if (!options.showPreHeaderPanel) {
377 $preHeaderPanelScroller.hide();
378 $preHeaderPanelScrollerR.hide();
379 }
380 }
381
382 // Append the header scroller containers
383 $headerScrollerL = $("<div class='slick-header ui-state-default slick-header-left' />").appendTo($paneHeaderL);
384 $headerScrollerR = $("<div class='slick-header ui-state-default slick-header-right' />").appendTo($paneHeaderR);
385
386 // Cache the header scroller containers
387 $headerScroller = $().add($headerScrollerL).add($headerScrollerR);
388
389 if (treeColumns.hasDepth()) {
390 $groupHeadersL = [];
391 $groupHeadersR = [];
392 for (var index = 0; index < treeColumns.getDepth() - 1; index++) {
393 $groupHeadersL[index] = $("<div class='slick-group-header-columns slick-group-header-columns-left' style='left:-1000px' />").appendTo($headerScrollerL);
394 $groupHeadersR[index] = $("<div class='slick-group-header-columns slick-group-header-columns-right' style='left:-1000px' />").appendTo($headerScrollerR);
395 }
396
397 $groupHeaders = $().add($groupHeadersL).add($groupHeadersR);
398 }
399
400 // Append the columnn containers to the headers
401 $headerL = $("<div class='slick-header-columns slick-header-columns-left' style='left:-1000px' />").appendTo($headerScrollerL);
402 $headerR = $("<div class='slick-header-columns slick-header-columns-right' style='left:-1000px' />").appendTo($headerScrollerR);
403
404 // Cache the header columns
405 $headers = $().add($headerL).add($headerR);
406
407 $headerRowScrollerL = $("<div class='slick-headerrow ui-state-default' />").appendTo($paneTopL);
408 $headerRowScrollerR = $("<div class='slick-headerrow ui-state-default' />").appendTo($paneTopR);
409
410 $headerRowScroller = $().add($headerRowScrollerL).add($headerRowScrollerR);
411
412 $headerRowSpacerL = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>")
413 .appendTo($headerRowScrollerL);
414 $headerRowSpacerR = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>")
415 .appendTo($headerRowScrollerR);
416
417
418 $headerRowL = $("<div class='slick-headerrow-columns slick-headerrow-columns-left' />").appendTo($headerRowScrollerL);
419 $headerRowR = $("<div class='slick-headerrow-columns slick-headerrow-columns-right' />").appendTo($headerRowScrollerR);
420
421 $headerRow = $().add($headerRowL).add($headerRowR);
422
423 // Append the top panel scroller
424 $topPanelScrollerL = $("<div class='slick-top-panel-scroller ui-state-default' />").appendTo($paneTopL);
425 $topPanelScrollerR = $("<div class='slick-top-panel-scroller ui-state-default' />").appendTo($paneTopR);
426
427 $topPanelScroller = $().add($topPanelScrollerL).add($topPanelScrollerR);
428
429 // Append the top panel
430 $topPanelL = $("<div class='slick-top-panel' style='width:10000px' />").appendTo($topPanelScrollerL);
431 $topPanelR = $("<div class='slick-top-panel' style='width:10000px' />").appendTo($topPanelScrollerR);
432
433 $topPanel = $().add($topPanelL).add($topPanelR);
434
435 if (!options.showColumnHeader) {
436 $headerScroller.hide();
437 }
438
439 if (!options.showTopPanel) {
440 $topPanelScroller.hide();
441 }
442
443 if (!options.showHeaderRow) {
444 $headerRowScroller.hide();
445 }
446
447 // Append the viewport containers
448 $viewportTopL = $("<div class='slick-viewport slick-viewport-top slick-viewport-left' tabIndex='0' hideFocus />").appendTo($paneTopL);
449 $viewportTopR = $("<div class='slick-viewport slick-viewport-top slick-viewport-right' tabIndex='0' hideFocus />").appendTo($paneTopR);
450 $viewportBottomL = $("<div class='slick-viewport slick-viewport-bottom slick-viewport-left' tabIndex='0' hideFocus />").appendTo($paneBottomL);
451 $viewportBottomR = $("<div class='slick-viewport slick-viewport-bottom slick-viewport-right' tabIndex='0' hideFocus />").appendTo($paneBottomR);
452
453 // Cache the viewports
454 $viewport = $().add($viewportTopL).add($viewportTopR).add($viewportBottomL).add($viewportBottomR);
455
456
457 // Default the active viewport to the top left
458 $activeViewportNode = $viewportTopL;
459
460 // Append the canvas containers
461 $canvasTopL = $("<div class='grid-canvas grid-canvas-top grid-canvas-left' tabIndex='0' hideFocus />").appendTo($viewportTopL);
462 $canvasTopR = $("<div class='grid-canvas grid-canvas-top grid-canvas-right' tabIndex='0' hideFocus />").appendTo($viewportTopR);
463 $canvasBottomL = $("<div class='grid-canvas grid-canvas-bottom grid-canvas-left' tabIndex='0' hideFocus />").appendTo($viewportBottomL);
464 $canvasBottomR = $("<div class='grid-canvas grid-canvas-bottom grid-canvas-right' tabIndex='0' hideFocus />").appendTo($viewportBottomR);
465 if (options.viewportClass) $viewport.toggleClass(options.viewportClass, true);
466
467 // Cache the canvases
468 $canvas = $().add($canvasTopL).add($canvasTopR).add($canvasBottomL).add($canvasBottomR);
469
470 scrollbarDimensions = scrollbarDimensions || measureScrollbar();
471
472 // Default the active canvas to the top left
473 $activeCanvasNode = $canvasTopL;
474
475 // pre-header
476 if ($preHeaderPanelSpacer) $preHeaderPanelSpacer.css("width", getCanvasWidth() + scrollbarDimensions.width + "px");
477 $headers.width(getHeadersWidth());
478 $headerRowSpacerL.css("width", getCanvasWidth() + scrollbarDimensions.width + "px");
479 $headerRowSpacerR.css("width", getCanvasWidth() + scrollbarDimensions.width + "px");
480
481 // footer Row
482 if (options.createFooterRow) {
483 $footerRowScrollerR = $("<div class='slick-footerrow ui-state-default' />").appendTo($paneTopR);
484 $footerRowScrollerL = $("<div class='slick-footerrow ui-state-default' />").appendTo($paneTopL);
485
486 $footerRowScroller = $().add($footerRowScrollerL).add($footerRowScrollerR);
487
488 $footerRowSpacerL = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>")
489 .css("width", getCanvasWidth() + scrollbarDimensions.width + "px")
490 .appendTo($footerRowScrollerL);
491 $footerRowSpacerR = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>")
492 .css("width", getCanvasWidth() + scrollbarDimensions.width + "px")
493 .appendTo($footerRowScrollerR);
494
495
496 $footerRowL = $("<div class='slick-footerrow-columns slick-footerrow-columns-left' />").appendTo($footerRowScrollerL);
497 $footerRowR = $("<div class='slick-footerrow-columns slick-footerrow-columns-right' />").appendTo($footerRowScrollerR);
498
499 $footerRow = $().add($footerRowL).add($footerRowR);
500
501 if (!options.showFooterRow) {
502 $footerRowScroller.hide();
503 }
504 }
505
506 $focusSink2 = $focusSink.clone().appendTo($container);
507
508 if (!options.explicitInitialization) {
509 finishInitialization();
510 }
511 }
512
513 function finishInitialization() {
514 if (!initialized) {
515 initialized = true;
516
517 getViewportWidth();
518 getViewportHeight();
519
520 // header columns and cells may have different padding/border skewing width calculations (box-sizing, hello?)
521 // calculate the diff so we can set consistent sizes
522 measureCellPaddingAndBorder();
523
524 // for usability reasons, all text selection in SlickGrid is disabled
525 // with the exception of input and textarea elements (selection must
526 // be enabled there so that editors work as expected); note that
527 // selection in grid cells (grid body) is already unavailable in
528 // all browsers except IE
529 disableSelection($headers); // disable all text selection in header (including input and textarea)
530
531 if (!options.enableTextSelectionOnCells) {
532 // disable text selection in grid cells except in input and textarea elements
533 // (this is IE-specific, because selectstart event will only fire in IE)
534 $viewport.on("selectstart.ui", function (event) {
535 return $(event.target).is("input,textarea");
536 });
537 }
538
539 setFrozenOptions();
540 setPaneVisibility();
541 setScroller();
542 setOverflow();
543
544 updateColumnCaches();
545 createColumnHeaders();
546 createColumnGroupHeaders();
547 createColumnFooter();
548 setupColumnSort();
549 createCssRules();
550 resizeCanvas();
551 bindAncestorScrollEvents();
552
553 $container
554 .on("resize.slickgrid", resizeCanvas);
555 $viewport
556 .on("scroll", handleScroll);
557
558 if (jQuery.fn.mousewheel && options.enableMouseWheelScrollHandler) {
559 $viewport.on("mousewheel", handleMouseWheel);
560 }
561
562 $headerScroller
563 //.on("scroll", handleHeaderScroll)
564 .on("contextmenu", handleHeaderContextMenu)
565 .on("click", handleHeaderClick)
566 .on("mouseenter", ".slick-header-column", handleHeaderMouseEnter)
567 .on("mouseleave", ".slick-header-column", handleHeaderMouseLeave);
568 $headerRowScroller
569 .on("scroll", handleHeaderRowScroll);
570
571 if (options.showHeaderRow) {
572 $headerRow
573 .on("mouseenter", ".slick-headerrow-column", handleHeaderRowMouseEnter)
574 .on("mouseleave", ".slick-headerrow-column", handleHeaderRowMouseLeave);
575 }
576
577 if (options.createFooterRow) {
578 $footerRow
579 .on("contextmenu", handleFooterContextMenu)
580 .on("click", handleFooterClick);
581
582 $footerRowScroller
583 .on("scroll", handleFooterRowScroll);
584 }
585
586 if (options.createPreHeaderPanel) {
587 $preHeaderPanelScroller
588 .on("scroll", handlePreHeaderPanelScroll);
589 }
590
591 $focusSink.add($focusSink2)
592 .on("keydown", handleKeyDown);
593 $canvas
594 .on("keydown", handleKeyDown)
595 .on("click", handleClick)
596 .on("dblclick", handleDblClick)
597 .on("contextmenu", handleContextMenu)
598 .on("draginit", handleDragInit)
599 .on("dragstart", {distance: 3}, handleDragStart)
600 .on("drag", handleDrag)
601 .on("dragend", handleDragEnd)
602 .on("mouseenter", ".slick-cell", handleMouseEnter)
603 .on("mouseleave", ".slick-cell", handleMouseLeave);
604
605 if (!options.suppressCssChangesOnHiddenInit) { restoreCssFromHiddenInit(); }
606 }
607 }
608
609 function cacheCssForHiddenInit() {
610 // handle display:none on container or container parents
611 $hiddenParents = $container.parents().addBack().not(':visible');
612 $hiddenParents.each(function() {
613 var old = {};
614 for ( var name in cssShow ) {
615 old[ name ] = this.style[ name ];
616 this.style[ name ] = cssShow[ name ];
617 }
618 oldProps.push(old);
619 });
620 }
621
622 function restoreCssFromHiddenInit() {
623 // finish handle display:none on container or container parents
624 // - put values back the way they were
625 $hiddenParents.each(function(i) {
626 var old = oldProps[i];
627 for ( var name in cssShow ) {
628 this.style[ name ] = old[ name ];
629 }
630 });
631 }
632
633 function hasFrozenColumns() {
634 return options.frozenColumn > -1;
635 }
636
637 function registerPlugin(plugin) {
638 plugins.unshift(plugin);
639 plugin.init(self);
640 }
641
642 function unregisterPlugin(plugin) {
643 for (var i = plugins.length; i >= 0; i--) {
644 if (plugins[i] === plugin) {
645 if (plugins[i].destroy) {
646 plugins[i].destroy();
647 }
648 plugins.splice(i, 1);
649 break;
650 }
651 }
652 }
653
654 function getPluginByName(name) {
655 for (var i = plugins.length-1; i >= 0; i--) {
656 if (plugins[i].pluginName === name) {
657 return plugins[i];
658 }
659 }
660 return undefined;
661 }
662
663 function setSelectionModel(model) {
664 if (selectionModel) {
665 selectionModel.onSelectedRangesChanged.unsubscribe(handleSelectedRangesChanged);
666 if (selectionModel.destroy) {
667 selectionModel.destroy();
668 }
669 }
670
671 selectionModel = model;
672 if (selectionModel) {
673 selectionModel.init(self);
674 selectionModel.onSelectedRangesChanged.subscribe(handleSelectedRangesChanged);
675 }
676 }
677
678 function getSelectionModel() {
679 return selectionModel;
680 }
681
682 function getCanvasNode(columnIdOrIdx, rowIndex) {
683 return _getContainerElement(getCanvases(), columnIdOrIdx, rowIndex);
684 }
685
686 function getActiveCanvasNode(element) {
687 setActiveCanvasNode(element);
688
689 return $activeCanvasNode[0];
690 }
691
692 function getCanvases() {
693 return $canvas;
694 }
695
696 function setActiveCanvasNode(element) {
697 if (element) {
698 $activeCanvasNode = $(element.target).closest('.grid-canvas');
699 }
700 }
701
702 function getViewportNode(columnIdOrIdx, rowIndex) {
703 return _getContainerElement(getViewports(), columnIdOrIdx, rowIndex);
704 }
705
706 function getViewports() {
707 return $viewport;
708 }
709
710 function getActiveViewportNode(element) {
711 setActiveViewportNode(element);
712
713 return $activeViewportNode[0];
714 }
715
716 function setActiveViewportNode(element) {
717 if (element) {
718 $activeViewportNode = $(element.target).closest('.slick-viewport');
719 }
720 }
721
722 function _getContainerElement($targetContainers, columnIdOrIdx, rowIndex) {
723 if (!$targetContainers) { return }
724 if (!columnIdOrIdx) { columnIdOrIdx = 0; }
725 if (!rowIndex) { rowIndex = 0; }
726
727 var idx = (typeof columnIdOrIdx === "number" ? columnIdOrIdx : getColumnIndex(columnIdOrIdx));
728
729 var isBottomSide = hasFrozenRows && rowIndex >= actualFrozenRow + (options.frozenBottom ? 0 : 1);
730 var isRightSide = hasFrozenColumns() && idx > options.frozenColumn;
731
732 return $targetContainers[(isBottomSide ? 2 : 0) + (isRightSide ? 1 : 0)];
733 }
734
735 function measureScrollbar() {
736 var $outerdiv = $('<div class="' + $viewport.className + '" style="position:absolute; top:-10000px; left:-10000px; overflow:auto; width:100px; height:100px;"></div>').appendTo('body');
737 var $innerdiv = $('<div style="width:200px; height:200px; overflow:auto;"></div>').appendTo($outerdiv);
738 var dim = {
739 width: $outerdiv[0].offsetWidth - $outerdiv[0].clientWidth,
740 height: $outerdiv[0].offsetHeight - $outerdiv[0].clientHeight
741 };
742 $innerdiv.remove();
743 $outerdiv.remove();
744 return dim;
745 }
746
747 function getHeadersWidth() {
748 headersWidth = headersWidthL = headersWidthR = 0;
749 var includeScrollbar = !options.autoHeight;
750
751 for (var i = 0, ii = columns.length; i < ii; i++) {
752 var width = columns[ i ].width;
753
754 if (( options.frozenColumn ) > -1 && ( i > options.frozenColumn )) {
755 headersWidthR += width;
756 } else {
757 headersWidthL += width;
758 }
759 }
760
761 if (includeScrollbar) {
762 if (( options.frozenColumn ) > -1 && ( i > options.frozenColumn )) {
763 headersWidthR += scrollbarDimensions.width;
764 } else {
765 headersWidthL += scrollbarDimensions.width;
766 }
767 }
768
769 if (hasFrozenColumns()) {
770 headersWidthL = headersWidthL + 1000;
771
772 headersWidthR = Math.max(headersWidthR, viewportW) + headersWidthL;
773 headersWidthR += scrollbarDimensions.width;
774 } else {
775 headersWidthL += scrollbarDimensions.width;
776 headersWidthL = Math.max(headersWidthL, viewportW) + 1000;
777 }
778
779 headersWidth = headersWidthL + headersWidthR;
780 return Math.max(headersWidth, viewportW) + 1000;
781 }
782
783 function getHeadersWidthL() {
784 headersWidthL =0;
785
786 columns.forEach(function(column, i) {
787 if (!(( options.frozenColumn ) > -1 && ( i > options.frozenColumn )))
788 headersWidthL += column.width;
789 });
790
791 if (hasFrozenColumns()) {
792 headersWidthL += 1000;
793 } else {
794 headersWidthL += scrollbarDimensions.width;
795 headersWidthL = Math.max(headersWidthL, viewportW) + 1000;
796 }
797
798 return headersWidthL;
799 }
800
801 function getHeadersWidthR() {
802 headersWidthR =0;
803
804 columns.forEach(function(column, i) {
805 if (( options.frozenColumn ) > -1 && ( i > options.frozenColumn ))
806 headersWidthR += column.width;
807 });
808
809 if (hasFrozenColumns()) {
810 headersWidthR = Math.max(headersWidthR, viewportW) + getHeadersWidthL();
811 headersWidthR += scrollbarDimensions.width;
812 }
813
814 return headersWidthR;
815 }
816
817 function getCanvasWidth() {
818 var availableWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW;
819 var i = columns.length;
820
821 canvasWidthL = canvasWidthR = 0;
822
823 while (i--) {
824 if (hasFrozenColumns() && (i > options.frozenColumn)) {
825 canvasWidthR += columns[i].width;
826 } else {
827 canvasWidthL += columns[i].width;
828 }
829 }
830 var totalRowWidth = canvasWidthL + canvasWidthR;
831 return options.fullWidthRows ? Math.max(totalRowWidth, availableWidth) : totalRowWidth;
832 }
833
834 function updateCanvasWidth(forceColumnWidthsUpdate) {
835 var oldCanvasWidth = canvasWidth;
836 var oldCanvasWidthL = canvasWidthL;
837 var oldCanvasWidthR = canvasWidthR;
838 var widthChanged;
839 canvasWidth = getCanvasWidth();
840
841 widthChanged = canvasWidth !== oldCanvasWidth || canvasWidthL !== oldCanvasWidthL || canvasWidthR !== oldCanvasWidthR;
842
843 if (widthChanged || hasFrozenColumns() || hasFrozenRows) {
844 $canvasTopL.width(canvasWidthL);
845
846 getHeadersWidth();
847
848 $headerL.width(headersWidthL);
849 $headerR.width(headersWidthR);
850
851 if (hasFrozenColumns()) {
852 $canvasTopR.width(canvasWidthR);
853
854 $paneHeaderL.width(canvasWidthL);
855 $paneHeaderR.css('left', canvasWidthL);
856 $paneHeaderR.css('width', viewportW - canvasWidthL);
857
858 $paneTopL.width(canvasWidthL);
859 $paneTopR.css('left', canvasWidthL);
860 $paneTopR.css('width', viewportW - canvasWidthL);
861
862 $headerRowScrollerL.width(canvasWidthL);
863 $headerRowScrollerR.width(viewportW - canvasWidthL);
864
865 $headerRowL.width(canvasWidthL);
866 $headerRowR.width(canvasWidthR);
867
868 if (options.createFooterRow) {
869 $footerRowScrollerL.width(canvasWidthL);
870 $footerRowScrollerR.width(viewportW - canvasWidthL);
871
872 $footerRowL.width(canvasWidthL);
873 $footerRowR.width(canvasWidthR);
874 }
875 if (options.createPreHeaderPanel) {
876 $preHeaderPanel.width(canvasWidth);
877 }
878 $viewportTopL.width(canvasWidthL);
879 $viewportTopR.width(viewportW - canvasWidthL);
880
881 if (hasFrozenRows) {
882 $paneBottomL.width(canvasWidthL);
883 $paneBottomR.css('left', canvasWidthL);
884
885 $viewportBottomL.width(canvasWidthL);
886 $viewportBottomR.width(viewportW - canvasWidthL);
887
888 $canvasBottomL.width(canvasWidthL);
889 $canvasBottomR.width(canvasWidthR);
890 }
891 } else {
892 $paneHeaderL.width('100%');
893 $paneTopL.width('100%');
894 $headerRowScrollerL.width('100%');
895 $headerRowL.width(canvasWidth);
896
897 if (options.createFooterRow) {
898 $footerRowScrollerL.width('100%');
899 $footerRowL.width(canvasWidth);
900 }
901
902 if (options.createPreHeaderPanel) {
903 $preHeaderPanel.width('100%');
904 $preHeaderPanel.width(canvasWidth);
905 }
906 $viewportTopL.width('100%');
907
908 if (hasFrozenRows) {
909 $viewportBottomL.width('100%');
910 $canvasBottomL.width(canvasWidthL);
911 }
912 }
913
914 viewportHasHScroll = (canvasWidth >= viewportW - scrollbarDimensions.width);
915 }
916
917 $headerRowSpacerL.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0));
918 $headerRowSpacerR.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0));
919
920 if (options.createFooterRow) {
921 $footerRowSpacerL.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0));
922 $footerRowSpacerR.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0));
923 }
924
925 if (widthChanged || forceColumnWidthsUpdate) {
926 applyColumnWidths();
927 }
928 }
929
930 function disableSelection($target) {
931 if ($target && $target.jquery) {
932 $target
933 .attr("unselectable", "on")
934 .css("MozUserSelect", "none")
935 .on("selectstart.ui", function () {
936 return false;
937 }); // from jquery:ui.core.js 1.7.2
938 }
939 }
940
941 function getMaxSupportedCssHeight() {
942 var supportedHeight = 1000000;
943 // FF reports the height back but still renders blank after ~6M px
944 var testUpTo = navigator.userAgent.toLowerCase().match(/firefox/) ? 6000000 : 1000000000;
945 var div = $("<div style='display:none' />").appendTo(document.body);
946
947 while (true) {
948 var test = supportedHeight * 2;
949 div.css("height", test);
950 if (test > testUpTo || div.height() !== test) {
951 break;
952 } else {
953 supportedHeight = test;
954 }
955 }
956
957 div.remove();
958 return supportedHeight;
959 }
960
961 function getUID() {
962 return uid;
963 }
964
965 function getHeaderColumnWidthDiff() {
966 return headerColumnWidthDiff;
967 }
968
969 function getScrollbarDimensions() {
970 return scrollbarDimensions;
971 }
972
973 // TODO: this is static. need to handle page mutation.
974 function bindAncestorScrollEvents() {
975 var elem = (hasFrozenRows && !options.frozenBottom) ? $canvasBottomL[0] : $canvasTopL[0];
976 while ((elem = elem.parentNode) != document.body && elem != null) {
977 // bind to scroll containers only
978 if (elem == $viewportTopL[0] || elem.scrollWidth != elem.clientWidth || elem.scrollHeight != elem.clientHeight) {
979 var $elem = $(elem);
980 if (!$boundAncestors) {
981 $boundAncestors = $elem;
982 } else {
983 $boundAncestors = $boundAncestors.add($elem);
984 }
985 $elem.on("scroll." + uid, handleActiveCellPositionChange);
986 }
987 }
988 }
989
990 function unbindAncestorScrollEvents() {
991 if (!$boundAncestors) {
992 return;
993 }
994 $boundAncestors.off("scroll." + uid);
995 $boundAncestors = null;
996 }
997
998 function updateColumnHeader(columnId, title, toolTip) {
999 if (!initialized) { return; }
1000 var idx = getColumnIndex(columnId);
1001 if (idx == null) {
1002 return;
1003 }
1004
1005 var columnDef = columns[idx];
1006 var $header = $headers.children().eq(idx);
1007 if ($header) {
1008 if (title !== undefined) {
1009 columns[idx].name = title;
1010 }
1011 if (toolTip !== undefined) {
1012 columns[idx].toolTip = toolTip;
1013 }
1014
1015 trigger(self.onBeforeHeaderCellDestroy, {
1016 "node": $header[0],
1017 "column": columnDef,
1018 "grid": self
1019 });
1020
1021 $header
1022 .attr("title", toolTip || "")
1023 .children().eq(0).html(title);
1024
1025 trigger(self.onHeaderCellRendered, {
1026 "node": $header[0],
1027 "column": columnDef,
1028 "grid": self
1029 });
1030 }
1031 }
1032
1033 function getHeader(columnDef) {
1034 if (!columnDef) {
1035 return hasFrozenColumns() ? $headers : $headerL;
1036 }
1037 var idx = getColumnIndex(columnDef.id);
1038 return hasFrozenColumns() ? ((idx <= options.frozenColumn) ? $headerL : $headerR) : $headerL;
1039 }
1040
1041 function getHeaderColumn(columnIdOrIdx) {
1042 var idx = (typeof columnIdOrIdx === "number" ? columnIdOrIdx : getColumnIndex(columnIdOrIdx));
1043 var targetHeader = hasFrozenColumns() ? ((idx <= options.frozenColumn) ? $headerL : $headerR) : $headerL;
1044 var targetIndex = hasFrozenColumns() ? ((idx <= options.frozenColumn) ? idx : idx - options.frozenColumn - 1) : idx;
1045 var $rtn = targetHeader.children().eq(targetIndex);
1046 return $rtn && $rtn[0];
1047 }
1048
1049 function getHeaderRow() {
1050 return hasFrozenColumns() ? $headerRow : $headerRow[0];
1051 }
1052
1053 function getFooterRow() {
1054 return hasFrozenColumns() ? $footerRow : $footerRow[0];
1055 }
1056
1057 function getPreHeaderPanel() {
1058 return $preHeaderPanel[0];
1059 }
1060
1061 function getPreHeaderPanelRight() {
1062 return $preHeaderPanelR[0];
1063 }
1064
1065 function getHeaderRowColumn(columnIdOrIdx) {
1066 var idx = (typeof columnIdOrIdx === "number" ? columnIdOrIdx : getColumnIndex(columnIdOrIdx));
1067
1068 var $headerRowTarget;
1069
1070 if (hasFrozenColumns()) {
1071 if (idx <= options.frozenColumn) {
1072 $headerRowTarget = $headerRowL;
1073 } else {
1074 $headerRowTarget = $headerRowR;
1075 idx -= options.frozenColumn + 1;
1076 }
1077 } else {
1078 $headerRowTarget = $headerRowL;
1079 }
1080
1081 var $header = $headerRowTarget.children().eq(idx);
1082 return $header && $header[0];
1083 }
1084
1085 function getFooterRowColumn(columnIdOrIdx) {
1086 var idx = (typeof columnIdOrIdx === "number" ? columnIdOrIdx : getColumnIndex(columnIdOrIdx));
1087
1088 var $footerRowTarget;
1089
1090 if (hasFrozenColumns()) {
1091 if (idx <= options.frozenColumn) {
1092 $footerRowTarget = $footerRowL;
1093 } else {
1094 $footerRowTarget = $footerRowR;
1095
1096 idx -= options.frozenColumn + 1;
1097 }
1098 } else {
1099 $footerRowTarget = $footerRowL;
1100 }
1101
1102 var $footer = $footerRowTarget && $footerRowTarget.children().eq(idx);
1103 return $footer && $footer[0];
1104 }
1105
1106 function createColumnFooter() {
1107 if (options.createFooterRow) {
1108 $footerRow.find(".slick-footerrow-column")
1109 .each(function () {
1110 var columnDef = $(this).data("column");
1111 if (columnDef) {
1112 trigger(self.onBeforeFooterRowCellDestroy, {
1113 "node": this,
1114 "column": columnDef,
1115 "grid": self
1116 });
1117 }
1118 });
1119
1120 $footerRowL.empty();
1121 $footerRowR.empty();
1122
1123 for (var i = 0; i < columns.length; i++) {
1124 var m = columns[i];
1125
1126 var footerRowCell = $("<div class='ui-state-default slick-footerrow-column l" + i + " r" + i + "'></div>")
1127 .data("column", m)
1128 .addClass(hasFrozenColumns() && i <= options.frozenColumn? 'frozen': '')
1129 .appendTo(hasFrozenColumns() && (i > options.frozenColumn)? $footerRowR: $footerRowL);
1130
1131 trigger(self.onFooterRowCellRendered, {
1132 "node": footerRowCell[0],
1133 "column": m,
1134 "grid": self
1135 });
1136 }
1137 }
1138 }
1139
1140 function createColumnGroupHeaders() {
1141 var columnsLength = 0;
1142 var frozenColumnsValid = false;
1143
1144 if (!treeColumns.hasDepth())
1145 return;
1146
1147 for (var index = 0; index < $groupHeadersL.length; index++) {
1148
1149 $groupHeadersL[index].empty();
1150 $groupHeadersR[index].empty();
1151
1152 var groupColumns = treeColumns.getColumnsInDepth(index);
1153
1154 for (var indexGroup in groupColumns) {
1155 var m = groupColumns[indexGroup];
1156
1157 columnsLength += m.extractColumns().length;
1158
1159 if (hasFrozenColumns() && index === 0 && (columnsLength-1) === options.frozenColumn)
1160 frozenColumnsValid = true;
1161
1162 $("<div class='ui-state-default slick-group-header-column' />")
1163 .html("<span class='slick-column-name'>" + m.name + "</span>")
1164 .attr("id", "" + uid + m.id)
1165 .attr("title", m.toolTip || "")
1166 .data("column", m)
1167 .addClass(m.headerCssClass || "")
1168 .addClass(hasFrozenColumns() && (columnsLength - 1) > options.frozenColumn? 'frozen': '')
1169 .appendTo(hasFrozenColumns() && (columnsLength - 1) > options.frozenColumn? $groupHeadersR[index]: $groupHeadersL[index]);
1170 }
1171
1172 if (hasFrozenColumns() && index === 0 && !frozenColumnsValid) {
1173 $groupHeadersL[index].empty();
1174 $groupHeadersR[index].empty();
1175 alert("All columns of group should to be grouped!");
1176 break;
1177 }
1178 }
1179
1180 applyColumnGroupHeaderWidths();
1181 }
1182
1183 function createColumnHeaders() {
1184 function onMouseEnter() {
1185 $(this).addClass("ui-state-hover");
1186 }
1187
1188 function onMouseLeave() {
1189 $(this).removeClass("ui-state-hover");
1190 }
1191
1192 $headers.find(".slick-header-column")
1193 .each(function() {
1194 var columnDef = $(this).data("column");
1195 if (columnDef) {
1196 trigger(self.onBeforeHeaderCellDestroy, {
1197 "node": this,
1198 "column": columnDef,
1199 "grid": self
1200 });
1201 }
1202 });
1203
1204 $headerL.empty();
1205 $headerR.empty();
1206
1207 getHeadersWidth();
1208
1209 $headerL.width(headersWidthL);
1210 $headerR.width(headersWidthR);
1211
1212 $headerRow.find(".slick-headerrow-column")
1213 .each(function() {
1214 var columnDef = $(this).data("column");
1215 if (columnDef) {
1216 trigger(self.onBeforeHeaderRowCellDestroy, {
1217 "node": this,
1218 "column": columnDef,
1219 "grid": self
1220 });
1221 }
1222 });
1223
1224 $headerRowL.empty();
1225 $headerRowR.empty();
1226
1227 if (options.createFooterRow) {
1228 $footerRowL.find(".slick-footerrow-column")
1229 .each(function() {
1230 var columnDef = $(this).data("column");
1231 if (columnDef) {
1232 trigger(self.onBeforeFooterRowCellDestroy, {
1233 "node": this,
1234 "column": columnDef,
1235 "grid": self
1236 });
1237 }
1238 });
1239 $footerRowL.empty();
1240
1241 if (hasFrozenColumns()) {
1242 $footerRowR.find(".slick-footerrow-column")
1243 .each(function() {
1244 var columnDef = $(this).data("column");
1245 if (columnDef) {
1246 trigger(self.onBeforeFooterRowCellDestroy, {
1247 "node": this,
1248 "column": columnDef,
1249 "grid": self
1250 });
1251 }
1252 });
1253 $footerRowR.empty();
1254 }
1255 }
1256
1257 for (var i = 0; i < columns.length; i++) {
1258 var m = columns[i];
1259
1260 var $headerTarget = hasFrozenColumns() ? ((i <= options.frozenColumn) ? $headerL : $headerR) : $headerL;
1261 var $headerRowTarget = hasFrozenColumns() ? ((i <= options.frozenColumn) ? $headerRowL : $headerRowR) : $headerRowL;
1262
1263 var header = $("<div class='ui-state-default slick-header-column' />")
1264 .html("<span class='slick-column-name'>" + m.name + "</span>")
1265 .width(m.width - headerColumnWidthDiff)
1266 .attr("id", "" + uid + m.id)
1267 .attr("title", m.toolTip || "")
1268 .data("column", m)
1269 .addClass(m.headerCssClass || "")
1270 .addClass(hasFrozenColumns() && i <= options.frozenColumn? 'frozen': '')
1271 .appendTo($headerTarget);
1272
1273 if (options.enableColumnReorder || m.sortable) {
1274 header
1275 .on('mouseenter', onMouseEnter)
1276 .on('mouseleave', onMouseLeave);
1277 }
1278
1279 if(m.hasOwnProperty('headerCellAttrs') && m.headerCellAttrs instanceof Object) {
1280 for (var key in m.headerCellAttrs) {
1281 if (m.headerCellAttrs.hasOwnProperty(key)) {
1282 header.attr(key, m.headerCellAttrs[key]);
1283 }
1284 }
1285 }
1286
1287 if (m.sortable) {
1288 header.addClass("slick-header-sortable");
1289 header.append("<span class='slick-sort-indicator"
1290 + (options.numberedMultiColumnSort && !options.sortColNumberInSeparateSpan ? " slick-sort-indicator-numbered" : "" ) + "' />");
1291 if (options.numberedMultiColumnSort && options.sortColNumberInSeparateSpan) { header.append("<span class='slick-sort-indicator-numbered' />"); }
1292 }
1293
1294 trigger(self.onHeaderCellRendered, {
1295 "node": header[0],
1296 "column": m,
1297 "grid": self
1298 });
1299
1300 if (options.showHeaderRow) {
1301 var headerRowCell = $("<div class='ui-state-default slick-headerrow-column l" + i + " r" + i + "'></div>")
1302 .data("column", m)
1303 .addClass(hasFrozenColumns() && i <= options.frozenColumn? 'frozen': '')
1304 .appendTo($headerRowTarget);
1305
1306 trigger(self.onHeaderRowCellRendered, {
1307 "node": headerRowCell[0],
1308 "column": m,
1309 "grid": self
1310 });
1311 }
1312 if (options.createFooterRow && options.showFooterRow) {
1313 var footerRowCell = $("<div class='ui-state-default slick-footerrow-column l" + i + " r" + i + "'></div>")
1314 .data("column", m)
1315 .appendTo($footerRow);
1316
1317 trigger(self.onFooterRowCellRendered, {
1318 "node": footerRowCell[0],
1319 "column": m,
1320 "grid": self
1321 });
1322 }
1323 }
1324
1325 setSortColumns(sortColumns);
1326 setupColumnResize();
1327 if (options.enableColumnReorder) {
1328 if (typeof options.enableColumnReorder == 'function') {
1329 options.enableColumnReorder(self, $headers, headerColumnWidthDiff, setColumns, setupColumnResize, columns, getColumnIndex, uid, trigger);
1330 } else {
1331 setupColumnReorder();
1332 }
1333 }
1334 }
1335
1336 function setupColumnSort() {
1337 $headers.click(function (e) {
1338 if (columnResizeDragging) return;
1339 // temporary workaround for a bug in jQuery 1.7.1 (http://bugs.jquery.com/ticket/11328)
1340 e.metaKey = e.metaKey || e.ctrlKey;
1341
1342 if ($(e.target).hasClass("slick-resizable-handle")) {
1343 return;
1344 }
1345
1346 var $col = $(e.target).closest(".slick-header-column");
1347 if (!$col.length) {
1348 return;
1349 }
1350
1351 var column = $col.data("column");
1352 if (column.sortable) {
1353 if (!getEditorLock().commitCurrentEdit()) {
1354 return;
1355 }
1356
1357 var previousSortColumns = $.extend(true, [], sortColumns);
1358 var sortColumn = null;
1359 var i = 0;
1360 for (; i < sortColumns.length; i++) {
1361 if (sortColumns[i].columnId == column.id) {
1362 sortColumn = sortColumns[i];
1363 sortColumn.sortAsc = !sortColumn.sortAsc;
1364 break;
1365 }
1366 }
1367 var hadSortCol = !!sortColumn;
1368
1369 if (options.tristateMultiColumnSort) {
1370 if (!sortColumn) {
1371 sortColumn = { columnId: column.id, sortAsc: column.defaultSortAsc };
1372 }
1373 if (hadSortCol && sortColumn.sortAsc) {
1374 // three state: remove sort rather than go back to ASC
1375 sortColumns.splice(i, 1);
1376 sortColumn = null;
1377 }
1378 if (!options.multiColumnSort) { sortColumns = []; }
1379 if (sortColumn && (!hadSortCol || !options.multiColumnSort)) {
1380 sortColumns.push(sortColumn);
1381 }
1382 } else {
1383 // legacy behaviour
1384 if (e.metaKey && options.multiColumnSort) {
1385 if (sortColumn) {
1386 sortColumns.splice(i, 1);
1387 }
1388 }
1389 else {
1390 if ((!e.shiftKey && !e.metaKey) || !options.multiColumnSort) {
1391 sortColumns = [];
1392 }
1393
1394 if (!sortColumn) {
1395 sortColumn = { columnId: column.id, sortAsc: column.defaultSortAsc };
1396 sortColumns.push(sortColumn);
1397 } else if (sortColumns.length === 0) {
1398 sortColumns.push(sortColumn);
1399 }
1400 }
1401 }
1402
1403 var onSortArgs;
1404 if (!options.multiColumnSort) {
1405 onSortArgs = {
1406 multiColumnSort: false,
1407 previousSortColumns: previousSortColumns,
1408 columnId: (sortColumns.length > 0 ? column.id : null),
1409 sortCol: (sortColumns.length > 0 ? column : null),
1410 sortAsc: (sortColumns.length > 0 ? sortColumns[0].sortAsc : true)
1411 };
1412 } else {
1413 onSortArgs = {
1414 multiColumnSort: true,
1415 previousSortColumns: previousSortColumns,
1416 sortCols: $.map(sortColumns, function(col) {
1417 return {columnId: columns[getColumnIndex(col.columnId)].id, sortCol: columns[getColumnIndex(col.columnId)], sortAsc: col.sortAsc };
1418 })
1419 };
1420 }
1421
1422 if (trigger(self.onBeforeSort, onSortArgs, e) !== false) {
1423 setSortColumns(sortColumns);
1424 trigger(self.onSort, onSortArgs, e);
1425 }
1426 }
1427 });
1428 }
1429
1430 function currentPositionInHeader(id) {
1431 var currentPosition = 0;
1432 $headers.find('.slick-header-column').each(function (i) {
1433 if (this.id == id) {
1434 currentPosition = i;
1435 return false;
1436 }
1437 });
1438
1439 return currentPosition;
1440 }
1441
1442 function limitPositionInGroup(idColumn) {
1443 var groupColumnOfPreviousPosition,
1444 startLimit = 0,
1445 endLimit = 0;
1446
1447 treeColumns
1448 .getColumnsInDepth($groupHeadersL.length - 1)
1449 .some(function (groupColumn) {
1450 startLimit = endLimit;
1451 endLimit += groupColumn.columns.length;
1452
1453 groupColumn.columns.some(function (column) {
1454
1455 if (column.id === idColumn)
1456 groupColumnOfPreviousPosition = groupColumn;
1457
1458 return groupColumnOfPreviousPosition;
1459 });
1460
1461 return groupColumnOfPreviousPosition;
1462 });
1463
1464 endLimit--;
1465
1466 return {
1467 start: startLimit,
1468 end: endLimit,
1469 group: groupColumnOfPreviousPosition
1470 };
1471 }
1472
1473 function remove(arr, elem) {
1474 var index = arr.lastIndexOf(elem);
1475 if(index > -1) {
1476 arr.splice(index, 1);
1477 remove(arr, elem);
1478 }
1479 }
1480
1481 function columnPositionValidInGroup($item) {
1482 var currentPosition = currentPositionInHeader($item[0].id);
1483 var limit = limitPositionInGroup($item.data('column').id);
1484 var positionValid = limit.start <= currentPosition && currentPosition <= limit.end;
1485
1486 return {
1487 limit: limit,
1488 valid: positionValid,
1489 message: positionValid? '': 'Column "'.concat($item.text(), '" can be reordered only within the "', limit.group.name, '" group!')
1490 };
1491 }
1492
1493 function setupColumnReorder() {
1494 $headers.filter(":ui-sortable").sortable("destroy");
1495 var columnScrollTimer = null;
1496
1497 function scrollColumnsRight() {
1498 $viewportScrollContainerX[0].scrollLeft = $viewportScrollContainerX[0].scrollLeft + 10;
1499 }
1500
1501 function scrollColumnsLeft() {
1502 $viewportScrollContainerX[0].scrollLeft = $viewportScrollContainerX[0].scrollLeft - 10;
1503 }
1504
1505 var canDragScroll;
1506 $headers.sortable({
1507 containment: "parent",
1508 distance: 3,
1509 axis: "x",
1510 cursor: "default",
1511 tolerance: "intersection",
1512 helper: "clone",
1513 placeholder: "slick-sortable-placeholder ui-state-default slick-header-column",
1514 start: function (e, ui) {
1515 ui.placeholder.width(ui.helper.outerWidth() - headerColumnWidthDiff);
1516 canDragScroll = !hasFrozenColumns() ||
1517 (ui.placeholder.offset().left + ui.placeholder.width()) > $viewportScrollContainerX.offset().left;
1518 $(ui.helper).addClass("slick-header-column-active");
1519 },
1520 beforeStop: function (e, ui) {
1521 $(ui.helper).removeClass("slick-header-column-active");
1522 },
1523 sort: function (e, ui) {
1524 if (canDragScroll && e.originalEvent.pageX > $container[0].clientWidth) {
1525 if (!(columnScrollTimer)) {
1526 columnScrollTimer = setInterval(
1527 scrollColumnsRight, 100);
1528 }
1529 } else if (canDragScroll && e.originalEvent.pageX < $viewportScrollContainerX.offset().left) {
1530 if (!(columnScrollTimer)) {
1531 columnScrollTimer = setInterval(
1532 scrollColumnsLeft, 100);
1533 }
1534 } else {
1535 clearInterval(columnScrollTimer);
1536 columnScrollTimer = null;
1537 }
1538 },
1539 stop: function (e, ui) {
1540 var cancel = false;
1541 clearInterval(columnScrollTimer);
1542 columnScrollTimer = null;
1543 var limit = null;
1544
1545 if (treeColumns.hasDepth()) {
1546 var validPositionInGroup = columnPositionValidInGroup(ui.item);
1547 limit = validPositionInGroup.limit;
1548
1549 cancel = !validPositionInGroup.valid;
1550
1551 if (cancel)
1552 alert(validPositionInGroup.message);
1553 }
1554
1555 if (cancel || !getEditorLock().commitCurrentEdit()) {
1556 $(this).sortable("cancel");
1557 return;
1558 }
1559
1560 var reorderedIds = $headerL.sortable("toArray");
1561 reorderedIds = reorderedIds.concat($headerR.sortable("toArray"));
1562
1563 var reorderedColumns = [];
1564 for (var i = 0; i < reorderedIds.length; i++) {
1565 reorderedColumns.push(columns[getColumnIndex(reorderedIds[i].replace(uid, ""))]);
1566 }
1567 setColumns(reorderedColumns);
1568
1569 trigger(self.onColumnsReordered, { impactedColumns : getImpactedColumns( limit ) });
1570 e.stopPropagation();
1571 setupColumnResize();
1572 }
1573 });
1574 }
1575
1576 function getImpactedColumns( limit ) {
1577 var impactedColumns = [];
1578
1579 if( limit ) {
1580
1581 for( var i = limit.start; i <= limit.end; i++ ) {
1582 impactedColumns.push( columns[i] );
1583 }
1584 }
1585 else {
1586
1587 impactedColumns = columns;
1588 }
1589
1590 return impactedColumns;
1591 }
1592
1593 function setupColumnResize() {
1594 var $col, j, k, c, pageX, columnElements, minPageX, maxPageX, firstResizable, lastResizable;
1595 var frozenLeftColMaxWidth = 0;
1596 columnElements = $headers.children();
1597 columnElements.find(".slick-resizable-handle").remove();
1598 columnElements.each(function (i, e) {
1599 if (i >= columns.length) { return; }
1600 if (columns[i].resizable) {
1601 if (firstResizable === undefined) {
1602 firstResizable = i;
1603 }
1604 lastResizable = i;
1605 }
1606 });
1607 if (firstResizable === undefined) {
1608 return;
1609 }
1610 columnElements.each(function (i, e) {
1611 if (i >= columns.length) { return; }
1612 if (i < firstResizable || (options.forceFitColumns && i >= lastResizable)) {
1613 return;
1614 }
1615 $col = $(e);
1616 $("<div class='slick-resizable-handle' />")
1617 .appendTo(e)
1618 .on("dragstart", function (e, dd) {
1619 if (!getEditorLock().commitCurrentEdit()) {
1620 return false;
1621 }
1622 pageX = e.pageX;
1623 frozenLeftColMaxWidth = 0;
1624 $(this).parent().addClass("slick-header-column-active");
1625 var shrinkLeewayOnRight = null, stretchLeewayOnRight = null;
1626 // lock each column's width option to current width
1627 columnElements.each(function (i, e) {
1628 if (i >= columns.length) { return; }
1629 columns[i].previousWidth = $(e).outerWidth();
1630 });
1631 if (options.forceFitColumns) {
1632 shrinkLeewayOnRight = 0;
1633 stretchLeewayOnRight = 0;
1634 // colums on right affect maxPageX/minPageX
1635 for (j = i + 1; j < columns.length; j++) {
1636 c = columns[j];
1637 if (c.resizable) {
1638 if (stretchLeewayOnRight !== null) {
1639 if (c.maxWidth) {
1640 stretchLeewayOnRight += c.maxWidth - c.previousWidth;
1641 } else {
1642 stretchLeewayOnRight = null;
1643 }
1644 }
1645 shrinkLeewayOnRight += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth);
1646 }
1647 }
1648 }
1649 var shrinkLeewayOnLeft = 0, stretchLeewayOnLeft = 0;
1650 for (j = 0; j <= i; j++) {
1651 // columns on left only affect minPageX
1652 c = columns[j];
1653 if (c.resizable) {
1654 if (stretchLeewayOnLeft !== null) {
1655 if (c.maxWidth) {
1656 stretchLeewayOnLeft += c.maxWidth - c.previousWidth;
1657 } else {
1658 stretchLeewayOnLeft = null;
1659 }
1660 }
1661 shrinkLeewayOnLeft += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth);
1662 }
1663 }
1664 if (shrinkLeewayOnRight === null) {
1665 shrinkLeewayOnRight = 100000;
1666 }
1667 if (shrinkLeewayOnLeft === null) {
1668 shrinkLeewayOnLeft = 100000;
1669 }
1670 if (stretchLeewayOnRight === null) {
1671 stretchLeewayOnRight = 100000;
1672 }
1673 if (stretchLeewayOnLeft === null) {
1674 stretchLeewayOnLeft = 100000;
1675 }
1676 maxPageX = pageX + Math.min(shrinkLeewayOnRight, stretchLeewayOnLeft);
1677 minPageX = pageX - Math.min(shrinkLeewayOnLeft, stretchLeewayOnRight);
1678 })
1679 .on("drag", function (e, dd) {
1680 columnResizeDragging = true;
1681 var actualMinWidth, d = Math.min(maxPageX, Math.max(minPageX, e.pageX)) - pageX, x;
1682 var newCanvasWidthL = 0, newCanvasWidthR = 0;
1683 var viewportWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW;
1684
1685 if (d < 0) { // shrink column
1686 x = d;
1687
1688 for (j = i; j >= 0; j--) {
1689 c = columns[j];
1690 if (c.resizable) {
1691 actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth);
1692 if (x && c.previousWidth + x < actualMinWidth) {
1693 x += c.previousWidth - actualMinWidth;
1694 c.width = actualMinWidth;
1695 } else {
1696 c.width = c.previousWidth + x;
1697 x = 0;
1698 }
1699 }
1700 }
1701
1702 for (k = 0; k <= i; k++) {
1703 c = columns[k];
1704
1705 if (hasFrozenColumns() && (k > options.frozenColumn)) {
1706 newCanvasWidthR += c.width;
1707 } else {
1708 newCanvasWidthL += c.width;
1709 }
1710 }
1711
1712 if (options.forceFitColumns) {
1713 x = -d;
1714 for (j = i + 1; j < columns.length; j++) {
1715 c = columns[j];
1716 if (c.resizable) {
1717 if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {
1718 x -= c.maxWidth - c.previousWidth;
1719 c.width = c.maxWidth;
1720 } else {
1721 c.width = c.previousWidth + x;
1722 x = 0;
1723 }
1724
1725 if (hasFrozenColumns() && (j > options.frozenColumn)) {
1726 newCanvasWidthR += c.width;
1727 } else {
1728 newCanvasWidthL += c.width;
1729 }
1730 }
1731 }
1732 } else {
1733 for (j = i + 1; j < columns.length; j++) {
1734 c = columns[j];
1735
1736 if (hasFrozenColumns() && (j > options.frozenColumn)) {
1737 newCanvasWidthR += c.width;
1738 } else {
1739 newCanvasWidthL += c.width;
1740 }
1741 }
1742 }
1743
1744 if (options.forceFitColumns) {
1745 x = -d;
1746 for (j = i + 1; j < columns.length; j++) {
1747 c = columns[j];
1748 if (c.resizable) {
1749 if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {
1750 x -= c.maxWidth - c.previousWidth;
1751 c.width = c.maxWidth;
1752 } else {
1753 c.width = c.previousWidth + x;
1754 x = 0;
1755 }
1756 }
1757 }
1758 }
1759 } else { // stretch column
1760 x = d;
1761
1762 newCanvasWidthL = 0;
1763 newCanvasWidthR = 0;
1764
1765 for (j = i; j >= 0; j--) {
1766 c = columns[j];
1767 if (c.resizable) {
1768 if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {
1769 x -= c.maxWidth - c.previousWidth;
1770 c.width = c.maxWidth;
1771 } else {
1772 var newWidth = c.previousWidth + x;
1773 var resizedCanvasWidthL = canvasWidthL + x;
1774
1775 if (hasFrozenColumns() && (j <= options.frozenColumn)) {
1776 // if we're on the left frozen side, we need to make sure that our left section width never goes over the total viewport width
1777 if (newWidth > frozenLeftColMaxWidth && resizedCanvasWidthL < (viewportWidth - options.frozenRightViewportMinWidth)) {
1778 frozenLeftColMaxWidth = newWidth; // keep max column width ref, if we go over the limit this number will stop increasing
1779 }
1780 c.width = ((resizedCanvasWidthL + options.frozenRightViewportMinWidth) > viewportWidth) ? frozenLeftColMaxWidth : newWidth;
1781 } else {
1782 c.width = newWidth;
1783 }
1784 x = 0;
1785 }
1786 }
1787 }
1788
1789 for (k = 0; k <= i; k++) {
1790 c = columns[k];
1791
1792 if (hasFrozenColumns() && (k > options.frozenColumn)) {
1793 newCanvasWidthR += c.width;
1794 } else {
1795 newCanvasWidthL += c.width;
1796 }
1797 }
1798
1799 if (options.forceFitColumns) {
1800 x = -d;
1801 for (j = i + 1; j < columns.length; j++) {
1802 c = columns[j];
1803 if (c.resizable) {
1804 actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth);
1805 if (x && c.previousWidth + x < actualMinWidth) {
1806 x += c.previousWidth - actualMinWidth;
1807 c.width = actualMinWidth;
1808 } else {
1809 c.width = c.previousWidth + x;
1810 x = 0;
1811 }
1812
1813 if (hasFrozenColumns() && (j > options.frozenColumn)) {
1814 newCanvasWidthR += c.width;
1815 } else {
1816 newCanvasWidthL += c.width;
1817 }
1818 }
1819 }
1820 } else {
1821 for (j = i + 1; j < columns.length; j++) {
1822 c = columns[j];
1823
1824 if (hasFrozenColumns() && (j > options.frozenColumn)) {
1825 newCanvasWidthR += c.width;
1826 } else {
1827 newCanvasWidthL += c.width;
1828 }
1829 }
1830 }
1831 }
1832
1833 if (hasFrozenColumns() && newCanvasWidthL != canvasWidthL) {
1834 $headerL.width(newCanvasWidthL + 1000);
1835 $paneHeaderR.css('left', newCanvasWidthL);
1836 }
1837
1838 applyColumnHeaderWidths();
1839 applyColumnGroupHeaderWidths();
1840 if (options.syncColumnCellResize) {
1841 applyColumnWidths();
1842 }
1843 trigger(self.onColumnsDrag, {
1844 triggeredByColumn: $(this).parent().attr("id").replace(uid, ""),
1845 resizeHandle: $(this)
1846 });
1847 })
1848 .on("dragend", function (e, dd) {
1849 $(this).parent().removeClass("slick-header-column-active");
1850
1851 var triggeredByColumn = $(this).parent().attr("id").replace(uid, "");
1852 if (trigger(self.onBeforeColumnsResize, { triggeredByColumn: triggeredByColumn }) === true) {
1853 applyColumnHeaderWidths();
1854 applyColumnGroupHeaderWidths();
1855 }
1856 var newWidth;
1857 for (j = 0; j < columns.length; j++) {
1858 c = columns[j];
1859 newWidth = $(columnElements[j]).outerWidth();
1860
1861 if (c.previousWidth !== newWidth && c.rerenderOnResize) {
1862 invalidateAllRows();
1863 }
1864 }
1865 updateCanvasWidth(true);
1866 render();
1867 trigger(self.onColumnsResized, { triggeredByColumn: triggeredByColumn });
1868 setTimeout(function () { columnResizeDragging = false; }, 300);
1869 })
1870 .on("dblclick", function () {
1871 var triggeredByColumn = $(this).parent().attr("id").replace(uid, "");
1872 trigger(self.onColumnsResizeDblClick, { triggeredByColumn: triggeredByColumn });
1873 });
1874 });
1875 }
1876
1877 function getVBoxDelta($el) {
1878 var p = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"];
1879 var delta = 0;
1880 if ($el && typeof $el.css === 'function') {
1881 $.each(p, function (n, val) {
1882 delta += parseFloat($el.css(val)) || 0;
1883 });
1884 }
1885 return delta;
1886 }
1887
1888 function setFrozenOptions() {
1889 options.frozenColumn = (options.frozenColumn >= 0 && options.frozenColumn < columns.length)
1890 ? parseInt(options.frozenColumn)
1891 : -1;
1892
1893 if (options.frozenRow > -1) {
1894 hasFrozenRows = true;
1895 frozenRowsHeight = ( options.frozenRow ) * options.rowHeight;
1896
1897 var dataLength = getDataLength();
1898
1899 actualFrozenRow = ( options.frozenBottom )
1900 ? ( dataLength - options.frozenRow )
1901 : options.frozenRow;
1902 } else {
1903 hasFrozenRows = false;
1904 }
1905 }
1906
1907 function setPaneVisibility() {
1908 if (hasFrozenColumns()) {
1909 $paneHeaderR.show();
1910 $paneTopR.show();
1911
1912 if (hasFrozenRows) {
1913 $paneBottomL.show();
1914 $paneBottomR.show();
1915 } else {
1916 $paneBottomR.hide();
1917 $paneBottomL.hide();
1918 }
1919 } else {
1920 $paneHeaderR.hide();
1921 $paneTopR.hide();
1922 $paneBottomR.hide();
1923
1924 if (hasFrozenRows) {
1925 $paneBottomL.show();
1926 } else {
1927 $paneBottomR.hide();
1928 $paneBottomL.hide();
1929 }
1930 }
1931 }
1932
1933 function setOverflow() {
1934 $viewportTopL.css({
1935 'overflow-x': ( hasFrozenColumns() ) ? ( hasFrozenRows && !options.alwaysAllowHorizontalScroll ? 'hidden' : 'scroll' ) : ( hasFrozenRows && !options.alwaysAllowHorizontalScroll ? 'hidden' : 'auto' ),
1936 'overflow-y': (!hasFrozenColumns() && options.alwaysShowVerticalScroll) ? "scroll" : (( hasFrozenColumns() ) ? ( hasFrozenRows ? 'hidden' : 'hidden' ) : ( hasFrozenRows ? 'scroll' : 'auto' ))
1937 });
1938
1939 $viewportTopR.css({
1940 'overflow-x': ( hasFrozenColumns() ) ? ( hasFrozenRows && !options.alwaysAllowHorizontalScroll ? 'hidden' : 'scroll' ) : ( hasFrozenRows && !options.alwaysAllowHorizontalScroll ? 'hidden' : 'auto' ),
1941 'overflow-y': options.alwaysShowVerticalScroll ? "scroll" : (( hasFrozenColumns() ) ? ( hasFrozenRows ? 'scroll' : 'auto' ) : ( hasFrozenRows ? 'scroll' : 'auto' ))
1942 });
1943
1944 $viewportBottomL.css({
1945 'overflow-x': ( hasFrozenColumns() ) ? ( hasFrozenRows && !options.alwaysAllowHorizontalScroll ? 'scroll' : 'auto' ): ( hasFrozenRows && !options.alwaysAllowHorizontalScroll ? 'auto' : 'auto' ),
1946 'overflow-y': (!hasFrozenColumns() && options.alwaysShowVerticalScroll) ? "scroll" : (( hasFrozenColumns() ) ? ( hasFrozenRows ? 'hidden' : 'hidden' ): ( hasFrozenRows ? 'scroll' : 'auto' ))
1947 });
1948
1949 $viewportBottomR.css({
1950 'overflow-x': ( hasFrozenColumns() ) ? ( hasFrozenRows && !options.alwaysAllowHorizontalScroll ? 'scroll' : 'auto' ) : ( hasFrozenRows && !options.alwaysAllowHorizontalScroll ? 'auto' : 'auto' ),
1951 'overflow-y': options.alwaysShowVerticalScroll ? "scroll" : (( hasFrozenColumns() ) ? ( hasFrozenRows ? 'auto' : 'auto' ) : ( hasFrozenRows ? 'auto' : 'auto' ))
1952 });
1953 if (options.viewportClass) {
1954 $viewportTopL.toggleClass(options.viewportClass, true);
1955 $viewportTopR.toggleClass(options.viewportClass, true);
1956 $viewportBottomL.toggleClass(options.viewportClass, true);
1957 $viewportBottomR.toggleClass(options.viewportClass, true);
1958 }
1959 }
1960
1961 function setScroller() {
1962 if (hasFrozenColumns()) {
1963 $headerScrollContainer = $headerScrollerR;
1964 $headerRowScrollContainer = $headerRowScrollerR;
1965 $footerRowScrollContainer = $footerRowScrollerR;
1966
1967 if (hasFrozenRows) {
1968 if (options.frozenBottom) {
1969 $viewportScrollContainerX = $viewportBottomR;
1970 $viewportScrollContainerY = $viewportTopR;
1971 } else {
1972 $viewportScrollContainerX = $viewportScrollContainerY = $viewportBottomR;
1973 }
1974 } else {
1975 $viewportScrollContainerX = $viewportScrollContainerY = $viewportTopR;
1976 }
1977 } else {
1978 $headerScrollContainer = $headerScrollerL;
1979 $headerRowScrollContainer = $headerRowScrollerL;
1980 $footerRowScrollContainer = $footerRowScrollerL;
1981
1982 if (hasFrozenRows) {
1983 if (options.frozenBottom) {
1984 $viewportScrollContainerX = $viewportBottomL;
1985 $viewportScrollContainerY = $viewportTopL;
1986 } else {
1987 $viewportScrollContainerX = $viewportScrollContainerY = $viewportBottomL;
1988 }
1989 } else {
1990 $viewportScrollContainerX = $viewportScrollContainerY = $viewportTopL;
1991 }
1992 }
1993 }
1994
1995 function measureCellPaddingAndBorder() {
1996 var el;
1997 var h = ["borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight"];
1998 var v = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"];
1999
2000 // jquery prior to version 1.8 handles .width setter/getter as a direct css write/read
2001 // jquery 1.8 changed .width to read the true inner element width if box-sizing is set to border-box, and introduced a setter for .outerWidth
2002 // so for equivalent functionality, prior to 1.8 use .width, and after use .outerWidth
2003 var verArray = $.fn.jquery.split('.');
2004 jQueryNewWidthBehaviour = (verArray[0]==1 && verArray[1]>=8) || verArray[0] >=2;
2005
2006 el = $("<div class='ui-state-default slick-header-column' style='visibility:hidden'>-</div>").appendTo($headers);
2007 headerColumnWidthDiff = headerColumnHeightDiff = 0;
2008 if (el.css("box-sizing") != "border-box" && el.css("-moz-box-sizing") != "border-box" && el.css("-webkit-box-sizing") != "border-box") {
2009 $.each(h, function (n, val) {
2010 headerColumnWidthDiff += parseFloat(el.css(val)) || 0;
2011 });
2012 $.each(v, function (n, val) {
2013 headerColumnHeightDiff += parseFloat(el.css(val)) || 0;
2014 });
2015 }
2016 el.remove();
2017
2018 var r = $("<div class='slick-row' />").appendTo($canvas);
2019 el = $("<div class='slick-cell' id='' style='visibility:hidden'>-</div>").appendTo(r);
2020 cellWidthDiff = cellHeightDiff = 0;
2021 if (el.css("box-sizing") != "border-box" && el.css("-moz-box-sizing") != "border-box" && el.css("-webkit-box-sizing") != "border-box") {
2022 $.each(h, function (n, val) {
2023 cellWidthDiff += parseFloat(el.css(val)) || 0;
2024 });
2025 $.each(v, function (n, val) {
2026 cellHeightDiff += parseFloat(el.css(val)) || 0;
2027 });
2028 }
2029 r.remove();
2030
2031 absoluteColumnMinWidth = Math.max(headerColumnWidthDiff, cellWidthDiff);
2032 }
2033
2034 function createCssRules() {
2035 $style = $("<style type='text/css' rel='stylesheet' />").appendTo($("head"));
2036 var rowHeight = (options.rowHeight - cellHeightDiff);
2037 var rules = [
2038 "." + uid + " .slick-group-header-column { left: 1000px; }",
2039 "." + uid + " .slick-header-column { left: 1000px; }",
2040 "." + uid + " .slick-top-panel { height:" + options.topPanelHeight + "px; }",
2041 "." + uid + " .slick-preheader-panel { height:" + options.preHeaderPanelHeight + "px; }",
2042 "." + uid + " .slick-headerrow-columns { height:" + options.headerRowHeight + "px; }",
2043 "." + uid + " .slick-footerrow-columns { height:" + options.footerRowHeight + "px; }",
2044 "." + uid + " .slick-cell { height:" + rowHeight + "px; }",
2045 "." + uid + " .slick-row { height:" + options.rowHeight + "px; }"
2046 ];
2047
2048 for (var i = 0; i < columns.length; i++) {
2049 rules.push("." + uid + " .l" + i + " { }");
2050 rules.push("." + uid + " .r" + i + " { }");
2051 }
2052
2053 if ($style[0].styleSheet) { // IE
2054 $style[0].styleSheet.cssText = rules.join(" ");
2055 } else {
2056 $style[0].appendChild(document.createTextNode(rules.join(" ")));
2057 }
2058 }
2059
2060 function getColumnCssRules(idx) {
2061 var i;
2062 if (!stylesheet) {
2063 var sheets = document.styleSheets;
2064 for (i = 0; i < sheets.length; i++) {
2065 if ((sheets[i].ownerNode || sheets[i].owningElement) == $style[0]) {
2066 stylesheet = sheets[i];
2067 break;
2068 }
2069 }
2070
2071 if (!stylesheet) {
2072 throw new Error("SlickGrid Cannot find stylesheet.");
2073 }
2074
2075 // find and cache column CSS rules
2076 columnCssRulesL = [];
2077 columnCssRulesR = [];
2078 var cssRules = (stylesheet.cssRules || stylesheet.rules);
2079 var matches, columnIdx;
2080 for (i = 0; i < cssRules.length; i++) {
2081 var selector = cssRules[i].selectorText;
2082 if (matches = /\.l\d+/.exec(selector)) {
2083 columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10);
2084 columnCssRulesL[columnIdx] = cssRules[i];
2085 } else if (matches = /\.r\d+/.exec(selector)) {
2086 columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10);
2087 columnCssRulesR[columnIdx] = cssRules[i];
2088 }
2089 }
2090 }
2091
2092 return {
2093 "left": columnCssRulesL[idx],
2094 "right": columnCssRulesR[idx]
2095 };
2096 }
2097
2098 function removeCssRules() {
2099 $style.remove();
2100 stylesheet = null;
2101 }
2102
2103 function destroy(shouldDestroyAllElements) {
2104 getEditorLock().cancelCurrentEdit();
2105
2106 trigger(self.onBeforeDestroy, {});
2107
2108 var i = plugins.length;
2109 while(i--) {
2110 unregisterPlugin(plugins[i]);
2111 }
2112
2113 if (options.enableColumnReorder) {
2114 $headers.filter(":ui-sortable").sortable("destroy");
2115 }
2116
2117 unbindAncestorScrollEvents();
2118 $container.off(".slickgrid");
2119 removeCssRules();
2120
2121 $canvas.off();
2122 $viewport.off();
2123 $headerScroller.off();
2124 $headerRowScroller.off();
2125 if ($footerRow) {
2126 $footerRow.off();
2127 }
2128 if ($footerRowScroller) {
2129 $footerRowScroller.off();
2130 }
2131 if ($preHeaderPanelScroller) {
2132 $preHeaderPanelScroller.off();
2133 }
2134 $focusSink.off();
2135 $(".slick-resizable-handle").off();
2136 $(".slick-header-column").off();
2137 $container.empty().removeClass(uid);
2138 if (shouldDestroyAllElements) {
2139 destroyAllElements();
2140 }
2141 }
2142
2143 function destroyAllElements() {
2144 $activeCanvasNode = null;
2145 $activeViewportNode = null;
2146 $boundAncestors = null;
2147 $canvas = null;
2148 $canvasTopL = null;
2149 $canvasTopR = null;
2150 $canvasBottomL = null;
2151 $canvasBottomR = null;
2152 $container = null;
2153 $focusSink = null;
2154 $focusSink2 = null;
2155 $groupHeaders = null;
2156 $groupHeadersL = null;
2157 $groupHeadersR = null;
2158 $headerL = null;
2159 $headerR = null;
2160 $headers = null;
2161 $headerRow = null;
2162 $headerRowL = null;
2163 $headerRowR = null;
2164 $headerRowSpacerL = null;
2165 $headerRowSpacerR = null;
2166 $headerRowScrollContainer = null;
2167 $headerRowScroller = null;
2168 $headerRowScrollerL = null;
2169 $headerRowScrollerR = null;
2170 $headerScrollContainer = null;
2171 $headerScroller = null;
2172 $headerScrollerL = null;
2173 $headerScrollerR = null;
2174 $hiddenParents = null;
2175 $footerRow = null;
2176 $footerRowL = null;
2177 $footerRowR = null;
2178 $footerRowSpacerL = null;
2179 $footerRowSpacerR = null;
2180 $footerRowScroller = null;
2181 $footerRowScrollerL = null;
2182 $footerRowScrollerR = null;
2183 $footerRowScrollContainer = null;
2184 $preHeaderPanel = null;
2185 $preHeaderPanelR = null;
2186 $preHeaderPanelScroller = null;
2187 $preHeaderPanelScrollerR = null;
2188 $preHeaderPanelSpacer = null;
2189 $preHeaderPanelSpacerR = null;
2190 $topPanel = null;
2191 $topPanelScroller = null;
2192 $style = null;
2193 $topPanelScrollerL = null;
2194 $topPanelScrollerR = null;
2195 $topPanelL = null;
2196 $topPanelR = null;
2197 $paneHeaderL = null;
2198 $paneHeaderR = null;
2199 $paneTopL = null;
2200 $paneTopR = null;
2201 $paneBottomL = null;
2202 $paneBottomR = null;
2203 $viewport = null;
2204 $viewportTopL = null;
2205 $viewportTopR = null;
2206 $viewportBottomL = null;
2207 $viewportBottomR = null;
2208 $viewportScrollContainerX = null;
2209 $viewportScrollContainerY = null;
2210 }
2211
2212 //////////////////////////////////////////////////////////////////////////////////////////////
2213 // Column Autosizing
2214 //////////////////////////////////////////////////////////////////////////////////////////////
2215
2216 var canvas = null;
2217 var canvas_context = null;
2218
2219 function autosizeColumn(columnOrIndexOrId, isInit) {
2220 var c = columnOrIndexOrId;
2221 if (typeof columnOrIndexOrId === 'number') {
2222 c = columns[columnOrIndexOrId];
2223 }
2224 else if (typeof columnOrIndexOrId === 'string') {
2225 for (var i = 0; i < columns.length; i++) {
2226 if (columns[i].Id === columnOrIndexOrId) { c = columns[i]; }
2227 }
2228 }
2229 var $gridCanvas = $(getCanvasNode(0, 0));
2230 getColAutosizeWidth(c, $gridCanvas, isInit);
2231 }
2232
2233 function autosizeColumns(autosizeMode, isInit) {
2234 //LogColWidths();
2235
2236 autosizeMode = autosizeMode || options.autosizeColsMode;
2237 if (autosizeMode === Slick.GridAutosizeColsMode.LegacyForceFit
2238 || autosizeMode === Slick.GridAutosizeColsMode.LegacyOff) {
2239 legacyAutosizeColumns();
2240 return;
2241 }
2242
2243 if (autosizeMode === Slick.GridAutosizeColsMode.None) {
2244 return;
2245 }
2246
2247 // test for brower canvas support, canvas_context!=null if supported
2248 canvas = document.createElement("canvas");
2249 if (canvas && canvas.getContext) { canvas_context = canvas.getContext("2d"); }
2250
2251 // pass in the grid canvas
2252 var $gridCanvas = $(getCanvasNode(0, 0));
2253 var viewportWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW;
2254
2255 // iterate columns to get autosizes
2256 var i, c, colWidth, reRender, totalWidth = 0, totalWidthLessSTR = 0, strColsMinWidth = 0, totalMinWidth = 0, totalLockedColWidth = 0;
2257 for (i = 0; i < columns.length; i++) {
2258 c = columns[i];
2259 getColAutosizeWidth(c, $gridCanvas, isInit);
2260 totalLockedColWidth += (c.autoSize.autosizeMode === Slick.ColAutosizeMode.Locked ? c.width : 0);
2261 totalMinWidth += (c.autoSize.autosizeMode === Slick.ColAutosizeMode.Locked ? c.width : c.minWidth);
2262 totalWidth += c.autoSize.widthPx;
2263 totalWidthLessSTR += (c.autoSize.sizeToRemaining ? 0 : c.autoSize.widthPx);
2264 strColsMinWidth += (c.autoSize.sizeToRemaining ? c.minWidth || 0 : 0);
2265 }
2266 var strColTotalGuideWidth = totalWidth - totalWidthLessSTR;
2267
2268 if (autosizeMode === Slick.GridAutosizeColsMode.FitViewportToCols) {
2269 // - if viewport with is outside MinViewportWidthPx and MaxViewportWidthPx, then the viewport is set to
2270 // MinViewportWidthPx or MaxViewportWidthPx and the FitColsToViewport algorithm is used
2271 // - viewport is resized to fit columns
2272 var setWidth = totalWidth + scrollbarDimensions.width;
2273 autosizeMode = Slick.GridAutosizeColsMode.IgnoreViewport;
2274
2275 if (options.viewportMaxWidthPx && setWidth > options.viewportMaxWidthPx) {
2276 setWidth = options.viewportMaxWidthPx;
2277 autosizeMode = Slick.GridAutosizeColsMode.FitColsToViewport;
2278 } else if (options.viewportMinWidthPx && setWidth < options.viewportMinWidthPx) {
2279 setWidth = options.viewportMinWidthPx;
2280 autosizeMode = Slick.GridAutosizeColsMode.FitColsToViewport;
2281 } else {
2282 // falling back to IgnoreViewport will size the columns as-is, with render checking
2283 //for (i = 0; i < columns.length; i++) { columns[i].width = columns[i].autoSize.widthPx; }
2284 }
2285 $container.width(setWidth);
2286 }
2287
2288 if (autosizeMode === Slick.GridAutosizeColsMode.FitColsToViewport) {
2289 if (strColTotalGuideWidth > 0 && totalWidthLessSTR < viewportWidth - strColsMinWidth) {
2290 // if addl space remains in the viewport and there are SizeToRemaining cols, just the SizeToRemaining cols expand proportionally to fill viewport
2291 for (i = 0; i < columns.length; i++) {
2292 c = columns[i];
2293 var totalSTRViewportWidth = viewportWidth - totalWidthLessSTR;
2294 if (c.autoSize.sizeToRemaining) {
2295 colWidth = totalSTRViewportWidth * c.autoSize.widthPx / strColTotalGuideWidth;
2296 } else {
2297 colWidth = c.autoSize.widthPx;
2298 }
2299 if (c.rerenderOnResize && c.width != colWidth) { reRender = true; }
2300 c.width = colWidth;
2301 }
2302 } else if ((options.viewportSwitchToScrollModeWidthPercent && totalWidthLessSTR + strColsMinWidth > viewportWidth * options.viewportSwitchToScrollModeWidthPercent / 100)
2303 || (totalMinWidth > viewportWidth)) {
2304 // if the total columns width is wider than the viewport by switchToScrollModeWidthPercent, switch to IgnoreViewport mode
2305 autosizeMode = Slick.GridAutosizeColsMode.IgnoreViewport;
2306 } else {
2307 // otherwise (ie. no SizeToRemaining cols or viewport smaller than columns) all cols other than 'Locked' scale in proportion to fill viewport
2308 // and SizeToRemaining get minWidth
2309 var unallocatedColWidth = totalWidthLessSTR - totalLockedColWidth;
2310 var unallocatedViewportWidth = viewportWidth - totalLockedColWidth - strColsMinWidth;
2311 for (i = 0; i < columns.length; i++) {
2312 c = columns[i];
2313 colWidth = c.width;
2314 if (c.autoSize.autosizeMode !== Slick.ColAutosizeMode.Locked) {
2315 if (c.autoSize.sizeToRemaining) {
2316 colWidth = c.minWidth;
2317 } else {
2318 // size width proportionally to free space (we know we have enough room due to the earlier calculations)
2319 colWidth = unallocatedViewportWidth / unallocatedColWidth * c.autoSize.widthPx;
2320 if (colWidth < c.minWidth) { colWidth = c.minWidth; }
2321
2322 // remove the just allocated widths from the allocation pool
2323 unallocatedColWidth -= c.autoSize.widthPx;
2324 unallocatedViewportWidth -= colWidth;
2325 }
2326 }
2327 if (c.rerenderOnResize && c.width != colWidth) { reRender = true; }
2328 c.width = colWidth;
2329 }
2330 }
2331 }
2332
2333 if (autosizeMode === Slick.GridAutosizeColsMode.IgnoreViewport) {
2334 // just size columns as-is
2335 for (i = 0; i < columns.length; i++) {
2336 colWidth = columns[i].autoSize.widthPx;
2337 if (columns[i].rerenderOnResize && columns[i].width != colWidth) {
2338 reRender = true;
2339 }
2340 columns[i].width = colWidth;
2341 }
2342 }
2343
2344 //LogColWidths();
2345 reRenderColumns(reRender);
2346 }
2347
2348 function LogColWidths () {
2349 var s = "Col Widths:";
2350 for (var i = 0; i < columns.length; i++) { s += ' ' + columns[i].width; }
2351 console.log(s);
2352 }
2353
2354 function getColAutosizeWidth(columnDef, $gridCanvas, isInit) {
2355 var autoSize = columnDef.autoSize;
2356
2357 // set to width as default
2358 autoSize.widthPx = columnDef.width;
2359 if (autoSize.autosizeMode === Slick.ColAutosizeMode.Locked
2360 || autoSize.autosizeMode === Slick.ColAutosizeMode.Guide) {
2361 return;
2362 }
2363
2364 var dl = getDataLength(); //getDataItem();
2365
2366 // ContentIntelligent takes settings from column data type
2367 if (autoSize.autosizeMode === Slick.ColAutosizeMode.ContentIntelligent) {
2368 // default to column colDataTypeOf (can be used if initially there are no data rows)
2369 var colDataTypeOf = autoSize.colDataTypeOf;
2370 var colDataItem;
2371 if (dl > 0) {
2372 var tempRow = getDataItem(0);
2373 if (tempRow) {
2374 colDataItem = tempRow[columnDef.field];
2375 colDataTypeOf = typeof colDataItem;
2376 if (colDataTypeOf === 'object') {
2377 if (colDataItem instanceof Date) { colDataTypeOf = "date"; }
2378 if (typeof moment!=='undefined' && colDataItem instanceof moment) { colDataTypeOf = "moment"; }
2379 }
2380 }
2381 }
2382 if (colDataTypeOf === 'boolean') {
2383 autoSize.colValueArray = [ true, false ];
2384 }
2385 if (colDataTypeOf === 'number') {
2386 autoSize.valueFilterMode = Slick.ValueFilterMode.GetGreatestAndSub;
2387 autoSize.rowSelectionMode = Slick.RowSelectionMode.AllRows;
2388 }
2389 if (colDataTypeOf === 'string') {
2390 autoSize.valueFilterMode = Slick.ValueFilterMode.GetLongestText;
2391 autoSize.rowSelectionMode = Slick.RowSelectionMode.AllRows;
2392 autoSize.allowAddlPercent = 5;
2393 }
2394 if (colDataTypeOf === 'date') {
2395 autoSize.colValueArray = [ new Date(2009, 8, 30, 12, 20, 20) ]; // Sep 30th 2009, 12:20:20 AM
2396 }
2397 if (colDataTypeOf === 'moment' && typeof moment!=='undefined') {
2398 autoSize.colValueArray = [ moment([2009, 8, 30, 12, 20, 20]) ]; // Sep 30th 2009, 12:20:20 AM
2399 }
2400 }
2401
2402 // at this point, the autosizeMode is effectively 'Content', so proceed to get size
2403 var colWidth = getColContentSize(columnDef, $gridCanvas, isInit);
2404
2405 var addlPercentMultiplier = (autoSize.allowAddlPercent ? (1 + autoSize.allowAddlPercent/100) : 1);
2406 colWidth = colWidth * addlPercentMultiplier + options.autosizeColPaddingPx;
2407 if (columnDef.minWidth && colWidth < columnDef.minWidth) { colWidth = columnDef.minWidth; }
2408 if (columnDef.maxWidth && colWidth > columnDef.maxWidth) { colWidth = columnDef.maxWidth; }
2409
2410 autoSize.widthPx = colWidth;
2411 }
2412
2413 function getColContentSize(columnDef, $gridCanvas, isInit) {
2414 var autoSize = columnDef.autoSize;
2415 var widthAdjustRatio = 1;
2416
2417 // at this point, the autosizeMode is effectively 'Content', so proceed to get size
2418
2419 // get header width, if we are taking notice of it
2420 var i, ii;
2421 var maxColWidth = 0;
2422 var headerWidth = 0;
2423 if (!autoSize.ignoreHeaderText) {
2424 headerWidth = getColHeaderWidth(columnDef);
2425 }
2426 if (headerWidth === 0) {
2427 headerWidth = (columnDef.width ? columnDef.width
2428 : (columnDef.maxWidth ? columnDef.maxWidth
2429 : (columnDef.minWidth ? columnDef.minWidth : 20)
2430 )
2431 );
2432 }
2433
2434 if (autoSize.colValueArray) {
2435 // if an array of values are specified, just pass them in instead of data
2436 maxColWidth = getColWidth(columnDef, $gridCanvas, autoSize.colValueArray);
2437 return Math.max(headerWidth, maxColWidth);
2438 }
2439
2440 // select rows to evaluate using rowSelectionMode and rowSelectionCount
2441 var rows = getData();
2442 if (rows.getItems) { rows = rows.getItems(); }
2443
2444 if (rows.length === 0) { return headerWidth; }
2445
2446 var rowSelectionMode = (isInit ? autoSize.rowSelectionModeOnInit : undefined) || autoSize.rowSelectionMode;
2447
2448 if (rowSelectionMode === Slick.RowSelectionMode.FirstRow) { rows = rows.slice(0,1); }
2449 if (rowSelectionMode === Slick.RowSelectionMode.LastRow) { rows = rows.slice(rows.length -1, rows.length); }
2450 if (rowSelectionMode === Slick.RowSelectionMode.FirstNRows) { rows = rows.slice(0, autoSize.rowSelectionCount); }
2451
2452 // now use valueFilterMode to further filter selected rows
2453 if (autoSize.valueFilterMode === Slick.ValueFilterMode.DeDuplicate) {
2454 var rowsDict = {};
2455 for (i = 0, ii = rows.length; i < ii; i++) {
2456 rowsDict[rows[i][columnDef.field]] = true;
2457 }
2458 if (Object.keys) {
2459 rows = Object.keys(rowsDict);
2460 } else {
2461 rows = [];
2462 for (var i in rowsDict) rows.push(i);
2463 }
2464 }
2465
2466 if (autoSize.valueFilterMode === Slick.ValueFilterMode.GetGreatestAndSub) {
2467 // get greatest abs value in data
2468 var tempVal, maxVal = 0, maxAbsVal = 0;
2469 for (i = 0, ii = rows.length; i < ii; i++) {
2470 tempVal = rows[i][columnDef.field];
2471 if (Math.abs(tempVal) > maxAbsVal) { maxVal = tempVal; maxAbsVal = Math.abs(tempVal); }
2472 }
2473 // now substitute a '9' for all characters (to get widest width) and convert back to a number
2474 maxVal = '' + maxVal;
2475 maxVal = Array(maxVal.length + 1).join("9");
2476 maxVal = +maxVal;
2477
2478 rows = [ maxVal ];
2479 }
2480
2481 if (autoSize.valueFilterMode === Slick.ValueFilterMode.GetLongestTextAndSub) {
2482 // get greatest abs value in data
2483 var tempVal, maxLen = 0;
2484 for (i = 0, ii = rows.length; i < ii; i++) {
2485 tempVal = rows[i][columnDef.field];
2486 if ((tempVal || '').length > maxLen) { maxLen = tempVal.length; }
2487 }
2488 // now substitute a 'c' for all characters
2489 tempVal = Array(maxLen + 1).join("m");
2490 widthAdjustRatio = options.autosizeTextAvgToMWidthRatio;
2491
2492 rows = [ tempVal ];
2493 }
2494
2495 if (autoSize.valueFilterMode === Slick.ValueFilterMode.GetLongestText) {
2496 // get greatest abs value in data
2497 var tempVal = '', maxLen = 0, maxIndex = 0;
2498 for (i = 0, ii = rows.length; i < ii; i++) {
2499 tempVal = rows[i][columnDef.field];
2500 if ((tempVal || '').length > maxLen) { maxLen = tempVal.length; maxIndex = i; }
2501 }
2502 // now substitute a 'c' for all characters
2503 tempVal = rows[maxIndex][columnDef.field];
2504
2505 rows = [ tempVal ];
2506 }
2507
2508 maxColWidth = getColWidth(columnDef, $gridCanvas, rows) * widthAdjustRatio;
2509 return Math.max(headerWidth, maxColWidth);
2510 }
2511
2512 function getColWidth(columnDef, $gridCanvas, data) {
2513 var colIndex = getColumnIndex(columnDef.id);
2514
2515 var $rowEl = $('<div class="slick-row ui-widget-content"></div>');
2516 var $cellEl = $('<div class="slick-cell"></div>');
2517 $cellEl.css({
2518 "position": "absolute",
2519 "visibility": "hidden",
2520 "text-overflow": "initial",
2521 "white-space": "nowrap"
2522 });
2523 $rowEl.append($cellEl);
2524
2525 $gridCanvas.append($rowEl);
2526
2527 var len, max = 0, text, maxText, formatterResult, maxWidth = 0, val;
2528
2529 // use canvas - very fast, but text-only
2530 if (canvas_context && columnDef.autoSize.widthEvalMode === Slick.WidthEvalMode.CanvasTextSize) {
2531 canvas_context.font = $cellEl.css("font-size") + " " + $cellEl.css("font-family");
2532 $(data).each(function (index, row) {
2533 // row is either an array or values or a single value
2534 val = (Array.isArray(row) ? row[columnDef.field] : row);
2535 text = '' + val;
2536 len = text ? canvas_context.measureText(text).width : 0;
2537 if (len > max) { max = len; maxText = text; }
2538 });
2539
2540 $cellEl.html(maxText);
2541 len = $cellEl.outerWidth();
2542
2543 $rowEl.remove();
2544 $cellEl = null;
2545 return len;
2546 }
2547
2548 $(data).each(function (index, row) {
2549 val = (Array.isArray(row) ? row[columnDef.field] : row);
2550 if (columnDef.formatterOverride) {
2551 // use formatterOverride as first preference
2552 formatterResult = columnDef.formatterOverride(index, colIndex, val, columnDef, row, self);
2553 } else if (columnDef.formatter) {
2554 // otherwise, use formatter
2555 formatterResult = columnDef.formatter(index, colIndex, val, columnDef, row, self);
2556 } else {
2557 // otherwise, use plain text
2558 formatterResult = '' + val;
2559 }
2560 applyFormatResultToCellNode(formatterResult, $cellEl[0]);
2561 len = $cellEl.outerWidth();
2562 if (len > max) { max = len; }
2563 });
2564
2565 $rowEl.remove();
2566 $cellEl = null;
2567 return max;
2568 }
2569
2570 function getColHeaderWidth(columnDef) {
2571 var width = 0;
2572 //if (columnDef && (!columnDef.resizable || columnDef._autoCalcWidth === true)) return;
2573 var headerColElId = getUID() + columnDef.id;
2574 var headerColEl = document.getElementById(headerColElId);
2575 var dummyHeaderColElId = headerColElId + "_";
2576 if (headerColEl) {
2577 // headers have been created, use clone technique
2578 var clone = headerColEl.cloneNode(true);
2579 clone.id = dummyHeaderColElId;
2580 clone.style.cssText = 'position: absolute; visibility: hidden;right: auto;text-overflow: initial;white-space: nowrap;';
2581 headerColEl.parentNode.insertBefore(clone, headerColEl);
2582 width = clone.offsetWidth;
2583 clone.parentNode.removeChild(clone);
2584 } else {
2585 // headers have not yet been created, create a new node
2586 var header = getHeader(columnDef);
2587 headerColEl = $("<div class='ui-state-default slick-header-column' />")
2588 .html("<span class='slick-column-name'>" + columnDef.name + "</span>")
2589 .attr("id", dummyHeaderColElId)
2590 .css({ "position": "absolute", "visibility": "hidden", "right": "auto", "text-overflow:": "initial", "white-space": "nowrap" })
2591 .addClass(columnDef.headerCssClass || "")
2592 .appendTo(header);
2593 width = headerColEl[0].offsetWidth;
2594 header[0].removeChild(headerColEl[0]);
2595 }
2596 return width;
2597 }
2598
2599 function legacyAutosizeColumns() {
2600 var i, c,
2601 widths = [],
2602 shrinkLeeway = 0,
2603 total = 0,
2604 prevTotal,
2605 availWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW;
2606
2607 for (i = 0; i < columns.length; i++) {
2608 c = columns[i];
2609 widths.push(c.width);
2610 total += c.width;
2611 if (c.resizable) {
2612 shrinkLeeway += c.width - Math.max(c.minWidth, absoluteColumnMinWidth);
2613 }
2614 }
2615
2616 // shrink
2617 prevTotal = total;
2618 while (total > availWidth && shrinkLeeway) {
2619 var shrinkProportion = (total - availWidth) / shrinkLeeway;
2620 for (i = 0; i < columns.length && total > availWidth; i++) {
2621 c = columns[i];
2622 var width = widths[i];
2623 if (!c.resizable || width <= c.minWidth || width <= absoluteColumnMinWidth) {
2624 continue;
2625 }
2626 var absMinWidth = Math.max(c.minWidth, absoluteColumnMinWidth);
2627 var shrinkSize = Math.floor(shrinkProportion * (width - absMinWidth)) || 1;
2628 shrinkSize = Math.min(shrinkSize, width - absMinWidth);
2629 total -= shrinkSize;
2630 shrinkLeeway -= shrinkSize;
2631 widths[i] -= shrinkSize;
2632 }
2633 if (prevTotal <= total) { // avoid infinite loop
2634 break;
2635 }
2636 prevTotal = total;
2637 }
2638
2639 // grow
2640 prevTotal = total;
2641 while (total < availWidth) {
2642 var growProportion = availWidth / total;
2643 for (i = 0; i < columns.length && total < availWidth; i++) {
2644 c = columns[i];
2645 var currentWidth = widths[i];
2646 var growSize;
2647
2648 if (!c.resizable || c.maxWidth <= currentWidth) {
2649 growSize = 0;
2650 } else {
2651 growSize = Math.min(Math.floor(growProportion * currentWidth) - currentWidth, (c.maxWidth - currentWidth) || 1000000) || 1;
2652 }
2653 total += growSize;
2654 widths[i] += (total <= availWidth ? growSize : 0);
2655 }
2656 if (prevTotal >= total) { // avoid infinite loop
2657 break;
2658 }
2659 prevTotal = total;
2660 }
2661
2662 var reRender = false;
2663 for (i = 0; i < columns.length; i++) {
2664 if (columns[i].rerenderOnResize && columns[i].width != widths[i]) {
2665 reRender = true;
2666 }
2667 columns[i].width = widths[i];
2668 }
2669
2670 reRenderColumns(reRender);
2671 }
2672
2673 function reRenderColumns(reRender) {
2674 applyColumnHeaderWidths();
2675 applyColumnGroupHeaderWidths();
2676 updateCanvasWidth(true);
2677
2678 trigger(self.onAutosizeColumns, { "columns": columns});
2679
2680 if (reRender) {
2681 invalidateAllRows();
2682 render();
2683 }
2684 }
2685
2686 //////////////////////////////////////////////////////////////////////////////////////////////
2687 // General
2688 //////////////////////////////////////////////////////////////////////////////////////////////
2689
2690 function trigger(evt, args, e) {
2691 e = e || new Slick.EventData();
2692 args = args || {};
2693 args.grid = self;
2694 return evt.notify(args, e, self);
2695 }
2696
2697 function getEditorLock() {
2698 return options.editorLock;
2699 }
2700
2701 function getEditController() {
2702 return editController;
2703 }
2704
2705 function getColumnIndex(id) {
2706 return columnsById[id];
2707 }
2708
2709 function applyColumnGroupHeaderWidths() {
2710 if (!treeColumns.hasDepth())
2711 return;
2712
2713 for (var depth = $groupHeadersL.length - 1; depth >= 0; depth--) {
2714
2715 var groupColumns = treeColumns.getColumnsInDepth(depth);
2716
2717 $().add($groupHeadersL[depth]).add($groupHeadersR[depth]).each(function(i) {
2718 var $groupHeader = $(this),
2719 currentColumnIndex = 0;
2720
2721 $groupHeader.width(i === 0? getHeadersWidthL(): getHeadersWidthR());
2722
2723 $groupHeader.children().each(function() {
2724 var $groupHeaderColumn = $(this);
2725
2726 var m = $(this).data('column');
2727
2728 m.width = 0;
2729
2730 m.columns.forEach(function() {
2731 var $headerColumn = $groupHeader.next().children(':eq(' + (currentColumnIndex++) + ')');
2732 m.width += $headerColumn.outerWidth();
2733 });
2734
2735 $groupHeaderColumn.width(m.width - headerColumnWidthDiff);
2736
2737 });
2738
2739 });
2740
2741 }
2742 }
2743
2744 function applyColumnHeaderWidths() {
2745 if (!initialized) { return; }
2746 var h;
2747
2748 for (var i = 0, headers = $headers.children(), ii = columns.length; i < ii; i++) {
2749 h = $(headers[i]);
2750 if (jQueryNewWidthBehaviour) {
2751 if (h.outerWidth() !== columns[i].width) {
2752 h.outerWidth(columns[i].width);
2753 }
2754 } else {
2755 if (h.width() !== columns[i].width - headerColumnWidthDiff) {
2756 h.width(columns[i].width - headerColumnWidthDiff);
2757 }
2758 }
2759 }
2760
2761 updateColumnCaches();
2762 }
2763
2764 function applyColumnWidths() {
2765 var x = 0, w, rule;
2766 for (var i = 0; i < columns.length; i++) {
2767 w = columns[i].width;
2768
2769 rule = getColumnCssRules(i);
2770 rule.left.style.left = x + "px";
2771 rule.right.style.right = (((options.frozenColumn != -1 && i > options.frozenColumn) ? canvasWidthR : canvasWidthL) - x - w) + "px";
2772
2773 // If this column is frozen, reset the css left value since the
2774 // column starts in a new viewport.
2775 if (options.frozenColumn == i) {
2776 x = 0;
2777 } else {
2778 x += columns[i].width;
2779 }
2780 }
2781 }
2782
2783 function setSortColumn(columnId, ascending) {
2784 setSortColumns([{ columnId: columnId, sortAsc: ascending}]);
2785 }
2786
2787 function setSortColumns(cols) {
2788 sortColumns = cols;
2789 var numberCols = options.numberedMultiColumnSort && sortColumns.length > 1;
2790 var headerColumnEls = $headers.children();
2791 headerColumnEls
2792 .removeClass("slick-header-column-sorted")
2793 .find(".slick-sort-indicator")
2794 .removeClass("slick-sort-indicator-asc slick-sort-indicator-desc");
2795 headerColumnEls
2796 .find(".slick-sort-indicator-numbered")
2797 .text('');
2798
2799 $.each(sortColumns, function(i, col) {
2800 if (col.sortAsc == null) {
2801 col.sortAsc = true;
2802 }
2803 var columnIndex = getColumnIndex(col.columnId);
2804 if (columnIndex != null) {
2805 headerColumnEls.eq(columnIndex)
2806 .addClass("slick-header-column-sorted")
2807 .find(".slick-sort-indicator")
2808 .addClass(col.sortAsc ? "slick-sort-indicator-asc" : "slick-sort-indicator-desc");
2809 if (numberCols) {
2810 headerColumnEls.eq(columnIndex)
2811 .find(".slick-sort-indicator-numbered")
2812 .text(i+1);
2813 }
2814 }
2815 });
2816 }
2817
2818 function getSortColumns() {
2819 return sortColumns;
2820 }
2821
2822 function handleSelectedRangesChanged(e, ranges) {
2823 var previousSelectedRows = selectedRows.slice(0); // shallow copy previously selected rows for later comparison
2824 selectedRows = [];
2825 var hash = {};
2826 for (var i = 0; i < ranges.length; i++) {
2827 for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
2828 if (!hash[j]) { // prevent duplicates
2829 selectedRows.push(j);
2830 hash[j] = {};
2831 }
2832 for (var k = ranges[i].fromCell; k <= ranges[i].toCell; k++) {
2833 if (canCellBeSelected(j, k)) {
2834 hash[j][columns[k].id] = options.selectedCellCssClass;
2835 }
2836 }
2837 }
2838 }
2839
2840 setCellCssStyles(options.selectedCellCssClass, hash);
2841
2842 if (simpleArrayEquals(previousSelectedRows, selectedRows)) {
2843 trigger(self.onSelectedRowsChanged, {rows: getSelectedRows(), previousSelectedRows: previousSelectedRows}, e);
2844 }
2845 }
2846
2847 // compare 2 simple arrays (integers or strings only, do not use to compare object arrays)
2848 function simpleArrayEquals(arr1, arr2) {
2849 return Array.isArray(arr1) && Array.isArray(arr2) && arr2.sort().toString() !== arr1.sort().toString();
2850 }
2851
2852 function getColumns() {
2853 return columns;
2854 }
2855
2856 function updateColumnCaches() {
2857 // Pre-calculate cell boundaries.
2858 columnPosLeft = [];
2859 columnPosRight = [];
2860 var x = 0;
2861 for (var i = 0, ii = columns.length; i < ii; i++) {
2862 columnPosLeft[i] = x;
2863 columnPosRight[i] = x + columns[i].width;
2864
2865 if (options.frozenColumn == i) {
2866 x = 0;
2867 } else {
2868 x += columns[i].width;
2869 }
2870 }
2871 }
2872
2873 function updateColumnProps() {
2874 columnsById = {};
2875 for (var i = 0; i < columns.length; i++) {
2876 if (columns[i].width) { columns[i].widthRequest = columns[i].width; }
2877
2878 var m = columns[i] = $.extend({}, columnDefaults, columns[i]);
2879 m.autoSize = $.extend({}, columnAutosizeDefaults, m.autoSize);
2880
2881 columnsById[m.id] = i;
2882 if (m.minWidth && m.width < m.minWidth) {
2883 m.width = m.minWidth;
2884 }
2885 if (m.maxWidth && m.width > m.maxWidth) {
2886 m.width = m.maxWidth;
2887 }
2888 if (!m.resizable) {
2889 // there is difference between user resizable and autoWidth resizable
2890 //m.autoSize.autosizeMode = Slick.ColAutosizeMode.Locked;
2891 }
2892 }
2893 }
2894
2895 function setColumns(columnDefinitions) {
2896 trigger(self.onBeforeSetColumns, { previousColumns: columns, newColumns: columnDefinitions, grid: self });
2897
2898 var _treeColumns = new Slick.TreeColumns(columnDefinitions);
2899 if (_treeColumns.hasDepth()) {
2900 treeColumns = _treeColumns;
2901 columns = treeColumns.extractColumns();
2902 } else {
2903 columns = columnDefinitions;
2904 }
2905
2906 updateColumnProps();
2907 updateColumnCaches();
2908
2909 if (initialized) {
2910 setPaneVisibility();
2911 setOverflow();
2912
2913 invalidateAllRows();
2914 createColumnHeaders();
2915 createColumnGroupHeaders();
2916 createColumnFooter();
2917 removeCssRules();
2918 createCssRules();
2919 resizeCanvas();
2920 updateCanvasWidth();
2921 applyColumnHeaderWidths();
2922 applyColumnWidths();
2923 handleScroll();
2924 }
2925 }
2926
2927 function getOptions() {
2928 return options;
2929 }
2930
2931 function setOptions(args, suppressRender, suppressColumnSet, suppressSetOverflow) {
2932 if (!getEditorLock().commitCurrentEdit()) {
2933 return;
2934 }
2935
2936 makeActiveCellNormal();
2937
2938 if (args.showColumnHeader !== undefined) {
2939 setColumnHeaderVisibility(args.showColumnHeader);
2940 }
2941
2942 if (options.enableAddRow !== args.enableAddRow) {
2943 invalidateRow(getDataLength());
2944 }
2945
2946 var originalOptions = $.extend(true, {}, options);
2947 options = $.extend(options, args);
2948 trigger(self.onSetOptions, { "optionsBefore": originalOptions, "optionsAfter": options });
2949
2950 validateAndEnforceOptions();
2951
2952 $viewport.css("overflow-y", options.autoHeight ? "hidden" : "auto");
2953 if (!suppressRender) {
2954 render();
2955 }
2956
2957 setFrozenOptions();
2958 setScroller();
2959 if (!suppressSetOverflow) {
2960 setOverflow();
2961 }
2962
2963 if (!suppressColumnSet) {
2964 setColumns(treeColumns.extractColumns());
2965 }
2966
2967 if (options.enableMouseWheelScrollHandler && $viewport && jQuery.fn.mousewheel) {
2968 var viewportEvents = $._data($viewport[0], "events");
2969 if (!viewportEvents || !viewportEvents.mousewheel) {
2970 $viewport.on("mousewheel", handleMouseWheel);
2971 }
2972 } else if (options.enableMouseWheelScrollHandler === false) {
2973 $viewport.off("mousewheel"); // remove scroll handler when option is disable
2974 }
2975 }
2976
2977 function validateAndEnforceOptions() {
2978 if (options.autoHeight) {
2979 options.leaveSpaceForNewRows = false;
2980 }
2981 if (options.forceFitColumns) {
2982 options.autosizeColsMode = Slick.GridAutosizeColsMode.LegacyForceFit;
2983 console.log("forceFitColumns option is deprecated - use autosizeColsMode");
2984 }
2985 }
2986
2987 function setData(newData, scrollToTop) {
2988 data = newData;
2989 invalidateAllRows();
2990 updateRowCount();
2991 if (scrollToTop) {
2992 scrollTo(0);
2993 }
2994 }
2995
2996 function getData() {
2997 return data;
2998 }
2999
3000 function getDataLength() {
3001 if (data.getLength) {
3002 return data.getLength();
3003 } else {
3004 return data && data.length || 0;
3005 }
3006 }
3007
3008 function getDataLengthIncludingAddNew() {
3009 return getDataLength() + (!options.enableAddRow ? 0
3010 : (!pagingActive || pagingIsLastPage ? 1 : 0)
3011 );
3012 }
3013
3014 function getDataItem(i) {
3015 if (data.getItem) {
3016 return data.getItem(i);
3017 } else {
3018 return data[i];
3019 }
3020 }
3021
3022 function getTopPanel() {
3023 return $topPanel[0];
3024 }
3025
3026 function setTopPanelVisibility(visible, animate) {
3027 var animated = (animate === false) ? false : true;
3028
3029 if (options.showTopPanel != visible) {
3030 options.showTopPanel = visible;
3031 if (visible) {
3032 if (animated) {
3033 $topPanelScroller.slideDown("fast", resizeCanvas);
3034 } else {
3035 $topPanelScroller.show();
3036 resizeCanvas();
3037 }
3038 } else {
3039 if (animated) {
3040 $topPanelScroller.slideUp("fast", resizeCanvas);
3041 } else {
3042 $topPanelScroller.hide();
3043 resizeCanvas();
3044 }
3045 }
3046 }
3047 }
3048
3049 function setHeaderRowVisibility(visible, animate) {
3050 var animated = (animate === false) ? false : true;
3051
3052 if (options.showHeaderRow != visible) {
3053 options.showHeaderRow = visible;
3054 if (visible) {
3055 if (animated) {
3056 $headerRowScroller.slideDown("fast", resizeCanvas);
3057 } else {
3058 $headerRowScroller.show();
3059 resizeCanvas();
3060 }
3061 } else {
3062 if (animated) {
3063 $headerRowScroller.slideUp("fast", resizeCanvas);
3064 } else {
3065 $headerRowScroller.hide();
3066 resizeCanvas();
3067 }
3068 }
3069 }
3070 }
3071
3072 function setColumnHeaderVisibility(visible, animate) {
3073 if (options.showColumnHeader != visible) {
3074 options.showColumnHeader = visible;
3075 if (visible) {
3076 if (animate) {
3077 $headerScroller.slideDown("fast", resizeCanvas);
3078 } else {
3079 $headerScroller.show();
3080 resizeCanvas();
3081 }
3082 } else {
3083 if (animate) {
3084 $headerScroller.slideUp("fast", resizeCanvas);
3085 } else {
3086 $headerScroller.hide();
3087 resizeCanvas();
3088 }
3089 }
3090 }
3091 }
3092
3093 function setFooterRowVisibility(visible, animate) {
3094 var animated = (animate === false) ? false : true;
3095
3096 if (options.showFooterRow != visible) {
3097 options.showFooterRow = visible;
3098 if (visible) {
3099 if (animated) {
3100 $footerRowScroller.slideDown("fast", resizeCanvas);
3101 } else {
3102 $footerRowScroller.show();
3103 resizeCanvas();
3104 }
3105 } else {
3106 if (animated) {
3107 $footerRowScroller.slideUp("fast", resizeCanvas);
3108 } else {
3109 $footerRowScroller.hide();
3110 resizeCanvas();
3111 }
3112 }
3113 }
3114 }
3115
3116 function setPreHeaderPanelVisibility(visible, animate) {
3117 var animated = (animate === false) ? false : true;
3118
3119 if (options.showPreHeaderPanel != visible) {
3120 options.showPreHeaderPanel = visible;
3121 if (visible) {
3122 if (animated) {
3123 $preHeaderPanelScroller.slideDown("fast", resizeCanvas);
3124 $preHeaderPanelScrollerR.slideDown("fast", resizeCanvas);
3125 } else {
3126 $preHeaderPanelScroller.show();
3127 $preHeaderPanelScrollerR.show();
3128 resizeCanvas();
3129 }
3130 } else {
3131 if (animated) {
3132 $preHeaderPanelScroller.slideUp("fast", resizeCanvas);
3133 $preHeaderPanelScrollerR.slideUp("fast", resizeCanvas);
3134 } else {
3135 $preHeaderPanelScroller.hide();
3136 $preHeaderPanelScrollerR.hide();
3137 resizeCanvas();
3138 }
3139 }
3140 }
3141 }
3142
3143 function getContainerNode() {
3144 return $container.get(0);
3145 }
3146
3147 //////////////////////////////////////////////////////////////////////////////////////////////
3148 // Rendering / Scrolling
3149
3150 function getRowTop(row) {
3151 return options.rowHeight * row - offset;
3152 }
3153
3154 function getRowFromPosition(y) {
3155 return Math.floor((y + offset) / options.rowHeight);
3156 }
3157
3158 function scrollTo(y) {
3159 y = Math.max(y, 0);
3160 y = Math.min(y, th - $viewportScrollContainerY.height() + ((viewportHasHScroll || hasFrozenColumns()) ? scrollbarDimensions.height : 0));
3161
3162 var oldOffset = offset;
3163
3164 page = Math.min(n - 1, Math.floor(y / ph));
3165 offset = Math.round(page * cj);
3166 var newScrollTop = y - offset;
3167
3168 if (offset != oldOffset) {
3169 var range = getVisibleRange(newScrollTop);
3170 cleanupRows(range);
3171 updateRowPositions();
3172 }
3173
3174 if (prevScrollTop != newScrollTop) {
3175 vScrollDir = (prevScrollTop + oldOffset < newScrollTop + offset) ? 1 : -1;
3176 lastRenderedScrollTop = ( scrollTop = prevScrollTop = newScrollTop );
3177
3178 if (hasFrozenColumns()) {
3179 $viewportTopL[0].scrollTop = newScrollTop;
3180 }
3181
3182 if (hasFrozenRows) {
3183 $viewportBottomL[0].scrollTop = $viewportBottomR[0].scrollTop = newScrollTop;
3184 }
3185
3186 $viewportScrollContainerY[0].scrollTop = newScrollTop;
3187
3188 trigger(self.onViewportChanged, {});
3189 }
3190 }
3191
3192 function defaultFormatter(row, cell, value, columnDef, dataContext, grid) {
3193 if (value == null) {
3194 return "";
3195 } else {
3196 return (value + "").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
3197 }
3198 }
3199
3200 function getFormatter(row, column) {
3201 var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
3202
3203 // look up by id, then index
3204 var columnOverrides = rowMetadata &&
3205 rowMetadata.columns &&
3206 (rowMetadata.columns[column.id] || rowMetadata.columns[getColumnIndex(column.id)]);
3207
3208 return (columnOverrides && columnOverrides.formatter) ||
3209 (rowMetadata && rowMetadata.formatter) ||
3210 column.formatter ||
3211 (options.formatterFactory && options.formatterFactory.getFormatter(column)) ||
3212 options.defaultFormatter;
3213 }
3214
3215 function getEditor(row, cell) {
3216 var column = columns[cell];
3217 var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
3218 var columnMetadata = rowMetadata && rowMetadata.columns;
3219
3220 if (columnMetadata && columnMetadata[column.id] && columnMetadata[column.id].editor !== undefined) {
3221 return columnMetadata[column.id].editor;
3222 }
3223 if (columnMetadata && columnMetadata[cell] && columnMetadata[cell].editor !== undefined) {
3224 return columnMetadata[cell].editor;
3225 }
3226
3227 return column.editor || (options.editorFactory && options.editorFactory.getEditor(column));
3228 }
3229
3230 function getDataItemValueForColumn(item, columnDef) {
3231 if (options.dataItemColumnValueExtractor) {
3232 return options.dataItemColumnValueExtractor(item, columnDef);
3233 }
3234 return item[columnDef.field];
3235 }
3236
3237 function appendRowHtml(stringArrayL, stringArrayR, row, range, dataLength) {
3238 var d = getDataItem(row);
3239 var dataLoading = row < dataLength && !d;
3240 var rowCss = "slick-row" +
3241 (hasFrozenRows && row <= options.frozenRow? ' frozen': '') +
3242 (dataLoading ? " loading" : "") +
3243 (row === activeRow && options.showCellSelection ? " active" : "") +
3244 (row % 2 == 1 ? " odd" : " even");
3245
3246 if (!d) {
3247 rowCss += " " + options.addNewRowCssClass;
3248 }
3249
3250 var metadata = data.getItemMetadata && data.getItemMetadata(row);
3251
3252 if (metadata && metadata.cssClasses) {
3253 rowCss += " " + metadata.cssClasses;
3254 }
3255
3256 var frozenRowOffset = getFrozenRowOffset(row);
3257
3258 var rowHtml = "<div class='ui-widget-content " + rowCss + "' style='top:"
3259 + (getRowTop(row) - frozenRowOffset )
3260 + "px'>";
3261
3262 stringArrayL.push(rowHtml);
3263
3264 if (hasFrozenColumns()) {
3265 stringArrayR.push(rowHtml);
3266 }
3267
3268 var colspan, m;
3269 for (var i = 0, ii = columns.length; i < ii; i++) {
3270 m = columns[i];
3271 colspan = 1;
3272 if (metadata && metadata.columns) {
3273 var columnData = metadata.columns[m.id] || metadata.columns[i];
3274 colspan = (columnData && columnData.colspan) || 1;
3275 if (colspan === "*") {
3276 colspan = ii - i;
3277 }
3278 }
3279
3280 // Do not render cells outside of the viewport.
3281 if (columnPosRight[Math.min(ii - 1, i + colspan - 1)] > range.leftPx) {
3282 if (!m.alwaysRenderColumn && columnPosLeft[i] > range.rightPx) {
3283 // All columns to the right are outside the range.
3284 break;
3285 }
3286
3287 if (hasFrozenColumns() && ( i > options.frozenColumn )) {
3288 appendCellHtml(stringArrayR, row, i, colspan, d);
3289 } else {
3290 appendCellHtml(stringArrayL, row, i, colspan, d);
3291 }
3292 } else if (m.alwaysRenderColumn || (hasFrozenColumns() && i <= options.frozenColumn)) {
3293 appendCellHtml(stringArrayL, row, i, colspan, d);
3294 }
3295
3296 if (colspan > 1) {
3297 i += (colspan - 1);
3298 }
3299 }
3300
3301 stringArrayL.push("</div>");
3302
3303 if (hasFrozenColumns()) {
3304 stringArrayR.push("</div>");
3305 }
3306 }
3307
3308 function appendCellHtml(stringArray, row, cell, colspan, item) {
3309 // stringArray: stringBuilder containing the HTML parts
3310 // row, cell: row and column index
3311 // colspan: HTML colspan
3312 // item: grid data for row
3313
3314 var m = columns[cell];
3315 var cellCss = "slick-cell l" + cell + " r" + Math.min(columns.length - 1, cell + colspan - 1) +
3316 (m.cssClass ? " " + m.cssClass : "");
3317
3318 if (hasFrozenColumns() && cell <= options.frozenColumn) {
3319 cellCss += (" frozen");
3320 }
3321
3322 if (row === activeRow && cell === activeCell && options.showCellSelection) {
3323 cellCss += (" active");
3324 }
3325
3326 // TODO: merge them together in the setter
3327 for (var key in cellCssClasses) {
3328 if (cellCssClasses[key][row] && cellCssClasses[key][row][m.id]) {
3329 cellCss += (" " + cellCssClasses[key][row][m.id]);
3330 }
3331 }
3332
3333 var value = null, formatterResult = '';
3334 if (item) {
3335 value = getDataItemValueForColumn(item, m);
3336 formatterResult = getFormatter(row, m)(row, cell, value, m, item, self);
3337 if (formatterResult === null || formatterResult === undefined) { formatterResult = ''; }
3338 }
3339
3340 // get addl css class names from object type formatter return and from string type return of onBeforeAppendCell
3341 var addlCssClasses = trigger(self.onBeforeAppendCell, { row: row, cell: cell, value: value, dataContext: item }) || '';
3342 addlCssClasses += (formatterResult && formatterResult.addClasses ? (addlCssClasses ? ' ' : '') + formatterResult.addClasses : '');
3343 var toolTip = formatterResult && formatterResult.toolTip ? "title='" + formatterResult.toolTip + "'" : '';
3344
3345 var customAttrStr = '';
3346 if(m.hasOwnProperty('cellAttrs') && m.cellAttrs instanceof Object) {
3347 for (var key in m.cellAttrs) {
3348 if (m.cellAttrs.hasOwnProperty(key)) {
3349 customAttrStr += ' ' + key + '="' + m.cellAttrs[key] + '" ';
3350 }
3351 }
3352 }
3353
3354 stringArray.push("<div class='" + cellCss + (addlCssClasses ? ' ' + addlCssClasses : '') + "' " + toolTip + customAttrStr + ">");
3355
3356 // if there is a corresponding row (if not, this is the Add New row or this data hasn't been loaded yet)
3357 if (item) {
3358 stringArray.push(Object.prototype.toString.call(formatterResult) !== '[object Object]' ? formatterResult : formatterResult.text);
3359 }
3360
3361 stringArray.push("</div>");
3362
3363 rowsCache[row].cellRenderQueue.push(cell);
3364 rowsCache[row].cellColSpans[cell] = colspan;
3365 }
3366
3367
3368 function cleanupRows(rangeToKeep) {
3369 for (var i in rowsCache) {
3370 var removeFrozenRow = true;
3371
3372 if (hasFrozenRows
3373 && ( ( options.frozenBottom && i >= actualFrozenRow ) // Frozen bottom rows
3374 || ( !options.frozenBottom && i <= actualFrozenRow ) // Frozen top rows
3375 )
3376 ) {
3377 removeFrozenRow = false;
3378 }
3379
3380 if (( ( i = parseInt(i, 10)) !== activeRow )
3381 && ( i < rangeToKeep.top || i > rangeToKeep.bottom )
3382 && ( removeFrozenRow )
3383 ) {
3384 removeRowFromCache(i);
3385 }
3386 }
3387 if (options.enableAsyncPostRenderCleanup) { startPostProcessingCleanup(); }
3388 }
3389
3390 function invalidate() {
3391 updateRowCount();
3392 invalidateAllRows();
3393 render();
3394 }
3395
3396 function invalidateAllRows() {
3397 if (currentEditor) {
3398 makeActiveCellNormal();
3399 }
3400 for (var row in rowsCache) {
3401 removeRowFromCache(row);
3402 }
3403 if (options.enableAsyncPostRenderCleanup) { startPostProcessingCleanup(); }
3404 }
3405
3406 function queuePostProcessedRowForCleanup(cacheEntry, postProcessedRow, rowIdx) {
3407 postProcessgroupId++;
3408
3409 // store and detach node for later async cleanup
3410 for (var columnIdx in postProcessedRow) {
3411 if (postProcessedRow.hasOwnProperty(columnIdx)) {
3412 postProcessedCleanupQueue.push({
3413 actionType: 'C',
3414 groupId: postProcessgroupId,
3415 node: cacheEntry.cellNodesByColumnIdx[ columnIdx | 0],
3416 columnIdx: columnIdx | 0,
3417 rowIdx: rowIdx
3418 });
3419 }
3420 }
3421 postProcessedCleanupQueue.push({
3422 actionType: 'R',
3423 groupId: postProcessgroupId,
3424 node: cacheEntry.rowNode
3425 });
3426 cacheEntry.rowNode.detach();
3427 }
3428
3429 function queuePostProcessedCellForCleanup(cellnode, columnIdx, rowIdx) {
3430 postProcessedCleanupQueue.push({
3431 actionType: 'C',
3432 groupId: postProcessgroupId,
3433 node: cellnode,
3434 columnIdx: columnIdx,
3435 rowIdx: rowIdx
3436 });
3437 $(cellnode).detach();
3438 }
3439
3440 function removeRowFromCache(row) {
3441 var cacheEntry = rowsCache[row];
3442 if (!cacheEntry) {
3443 return;
3444 }
3445
3446 if (options.enableAsyncPostRenderCleanup && postProcessedRows[row]) {
3447 queuePostProcessedRowForCleanup(cacheEntry, postProcessedRows[row], row);
3448 } else {
3449 cacheEntry.rowNode.each(function() {
3450 if (this.parentElement) {
3451 this.parentElement.removeChild(this);
3452 }
3453 });
3454 }
3455
3456 delete rowsCache[row];
3457 delete postProcessedRows[row];
3458 renderedRows--;
3459 counter_rows_removed++;
3460 }
3461
3462 function invalidateRows(rows) {
3463 var i, rl;
3464 if (!rows || !rows.length) {
3465 return;
3466 }
3467 vScrollDir = 0;
3468 rl = rows.length;
3469 for (i = 0; i < rl; i++) {
3470 if (currentEditor && activeRow === rows[i]) {
3471 makeActiveCellNormal();
3472 }
3473 if (rowsCache[rows[i]]) {
3474 removeRowFromCache(rows[i]);
3475 }
3476 }
3477 if (options.enableAsyncPostRenderCleanup) { startPostProcessingCleanup(); }
3478 }
3479
3480 function invalidateRow(row) {
3481 if (!row && row !== 0) { return; }
3482 invalidateRows([row]);
3483 }
3484
3485 function applyFormatResultToCellNode(formatterResult, cellNode, suppressRemove) {
3486 if (formatterResult === null || formatterResult === undefined) { formatterResult = ''; }
3487 if (Object.prototype.toString.call(formatterResult) !== '[object Object]') {
3488 cellNode.innerHTML = sanitizeHtmlString(formatterResult);
3489 return;
3490 }
3491 cellNode.innerHTML = sanitizeHtmlString(formatterResult.text);
3492 if (formatterResult.removeClasses && !suppressRemove) {
3493 $(cellNode).removeClass(formatterResult.removeClasses);
3494 }
3495 if (formatterResult.addClasses) {
3496 $(cellNode).addClass(formatterResult.addClasses);
3497 }
3498 if (formatterResult.toolTip) {
3499 $(cellNode).attr("title", formatterResult.toolTip);
3500 }
3501 }
3502
3503 function updateCell(row, cell) {
3504 var cellNode = getCellNode(row, cell);
3505 if (!cellNode) {
3506 return;
3507 }
3508
3509 var m = columns[cell], d = getDataItem(row);
3510 if (currentEditor && activeRow === row && activeCell === cell) {
3511 currentEditor.loadValue(d);
3512 } else {
3513 var formatterResult = d ? getFormatter(row, m)(row, cell, getDataItemValueForColumn(d, m), m, d, self) : "";
3514 applyFormatResultToCellNode(formatterResult, cellNode);
3515 invalidatePostProcessingResults(row);
3516 }
3517 }
3518
3519 function updateRow(row) {
3520 var cacheEntry = rowsCache[row];
3521 if (!cacheEntry) {
3522 return;
3523 }
3524
3525 ensureCellNodesInRowsCache(row);
3526
3527 var formatterResult, d = getDataItem(row);
3528
3529 for (var columnIdx in cacheEntry.cellNodesByColumnIdx) {
3530 if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) {
3531 continue;
3532 }
3533
3534 columnIdx = columnIdx | 0;
3535 var m = columns[columnIdx],
3536 node = cacheEntry.cellNodesByColumnIdx[columnIdx][0];
3537
3538 if (row === activeRow && columnIdx === activeCell && currentEditor) {
3539 currentEditor.loadValue(d);
3540 } else if (d) {
3541 formatterResult = getFormatter(row, m)(row, columnIdx, getDataItemValueForColumn(d, m), m, d, self);
3542 applyFormatResultToCellNode(formatterResult, node);
3543 } else {
3544 node.innerHTML = "";
3545 }
3546 }
3547
3548 invalidatePostProcessingResults(row);
3549 }
3550
3551 function getViewportHeight() {
3552 if (!options.autoHeight || options.frozenColumn != -1) {
3553 topPanelH = ( options.showTopPanel ) ? options.topPanelHeight + getVBoxDelta($topPanelScroller) : 0;
3554 headerRowH = ( options.showHeaderRow ) ? options.headerRowHeight + getVBoxDelta($headerRowScroller) : 0;
3555 footerRowH = ( options.showFooterRow ) ? options.footerRowHeight + getVBoxDelta($footerRowScroller) : 0;
3556 }
3557 if (options.autoHeight) {
3558 var fullHeight = $paneHeaderL.outerHeight();
3559 fullHeight += ( options.showHeaderRow ) ? options.headerRowHeight + getVBoxDelta($headerRowScroller) : 0;
3560 fullHeight += ( options.showFooterRow ) ? options.footerRowHeight + getVBoxDelta($footerRowScroller) : 0;
3561 fullHeight += (getCanvasWidth() > viewportW) ? scrollbarDimensions.height : 0;
3562
3563 viewportH = options.rowHeight
3564 * getDataLengthIncludingAddNew()
3565 + ( ( options.frozenColumn == -1 ) ? fullHeight : 0 );
3566 } else {
3567 var columnNamesH = ( options.showColumnHeader ) ? parseFloat($.css($headerScroller[0], "height"))
3568 + getVBoxDelta($headerScroller) : 0;
3569 var preHeaderH = (options.createPreHeaderPanel && options.showPreHeaderPanel) ? options.preHeaderPanelHeight + getVBoxDelta($preHeaderPanelScroller) : 0;
3570
3571 viewportH = parseFloat($.css($container[0], "height", true))
3572 - parseFloat($.css($container[0], "paddingTop", true))
3573 - parseFloat($.css($container[0], "paddingBottom", true))
3574 - columnNamesH
3575 - topPanelH
3576 - headerRowH
3577 - footerRowH
3578 - preHeaderH;
3579 }
3580
3581 numVisibleRows = Math.ceil(viewportH / options.rowHeight);
3582 return viewportH;
3583 }
3584
3585 function getViewportWidth() {
3586 viewportW = parseFloat($container.width());
3587 }
3588
3589 function resizeCanvas() {
3590 if (!initialized) { return; }
3591 paneTopH = 0;
3592 paneBottomH = 0;
3593 viewportTopH = 0;
3594 viewportBottomH = 0;
3595
3596 getViewportWidth();
3597 getViewportHeight();
3598
3599 // Account for Frozen Rows
3600 if (hasFrozenRows) {
3601 if (options.frozenBottom) {
3602 paneTopH = viewportH - frozenRowsHeight - scrollbarDimensions.height;
3603 paneBottomH = frozenRowsHeight + scrollbarDimensions.height;
3604 } else {
3605 paneTopH = frozenRowsHeight;
3606 paneBottomH = viewportH - frozenRowsHeight;
3607 }
3608 } else {
3609 paneTopH = viewportH;
3610 }
3611
3612 // The top pane includes the top panel and the header row
3613 paneTopH += topPanelH + headerRowH + footerRowH;
3614
3615 if (hasFrozenColumns() && options.autoHeight) {
3616 paneTopH += scrollbarDimensions.height;
3617 }
3618
3619 // The top viewport does not contain the top panel or header row
3620 viewportTopH = paneTopH - topPanelH - headerRowH - footerRowH;
3621
3622 if (options.autoHeight) {
3623 if (hasFrozenColumns()) {
3624 $container.height(
3625 paneTopH
3626 + parseFloat($.css($headerScrollerL[0], "height"))
3627 );
3628 }
3629
3630 $paneTopL.css('position', 'relative');
3631 }
3632
3633 $paneTopL.css({
3634 'top': $paneHeaderL.height() || (options.showHeaderRow ? options.headerRowHeight : 0) + (options.showPreHeaderPanel ? options.preHeaderPanelHeight : 0),
3635 'height': paneTopH
3636 });
3637
3638 var paneBottomTop = $paneTopL.position().top
3639 + paneTopH;
3640
3641 if (!options.autoHeight) {
3642 $viewportTopL.height(viewportTopH);
3643 }
3644
3645 if (hasFrozenColumns()) {
3646 $paneTopR.css({
3647 'top': $paneHeaderL.height(), 'height': paneTopH
3648 });
3649
3650 $viewportTopR.height(viewportTopH);
3651
3652 if (hasFrozenRows) {
3653 $paneBottomL.css({
3654 'top': paneBottomTop, 'height': paneBottomH
3655 });
3656
3657 $paneBottomR.css({
3658 'top': paneBottomTop, 'height': paneBottomH
3659 });
3660
3661 $viewportBottomR.height(paneBottomH);
3662 }
3663 } else {
3664 if (hasFrozenRows) {
3665 $paneBottomL.css({
3666 'width': '100%', 'height': paneBottomH
3667 });
3668
3669 $paneBottomL.css('top', paneBottomTop);
3670 }
3671 }
3672
3673 if (hasFrozenRows) {
3674 $viewportBottomL.height(paneBottomH);
3675
3676 if (options.frozenBottom) {
3677 $canvasBottomL.height(frozenRowsHeight);
3678
3679 if (hasFrozenColumns()) {
3680 $canvasBottomR.height(frozenRowsHeight);
3681 }
3682 } else {
3683 $canvasTopL.height(frozenRowsHeight);
3684
3685 if (hasFrozenColumns()) {
3686 $canvasTopR.height(frozenRowsHeight);
3687 }
3688 }
3689 } else {
3690 $viewportTopR.height(viewportTopH);
3691 }
3692
3693 if (!scrollbarDimensions || !scrollbarDimensions.width) {
3694 scrollbarDimensions = measureScrollbar();
3695 }
3696
3697 if (options.autosizeColsMode === Slick.GridAutosizeColsMode.LegacyForceFit) {
3698 autosizeColumns();
3699 }
3700
3701 updateRowCount();
3702 handleScroll();
3703 // Since the width has changed, force the render() to reevaluate virtually rendered cells.
3704 lastRenderedScrollLeft = -1;
3705 render();
3706 }
3707
3708 function updatePagingStatusFromView( pagingInfo ) {
3709 pagingActive = (pagingInfo.pageSize !== 0);
3710 pagingIsLastPage = (pagingInfo.pageNum == pagingInfo.totalPages - 1);
3711 }
3712
3713 function updateRowCount() {
3714 if (!initialized) { return; }
3715
3716 var dataLength = getDataLength();
3717 var dataLengthIncludingAddNew = getDataLengthIncludingAddNew();
3718 var numberOfRows = 0;
3719 var oldH = ( hasFrozenRows && !options.frozenBottom ) ? $canvasBottomL.height() : $canvasTopL.height();
3720
3721 if (hasFrozenRows ) {
3722 var numberOfRows = getDataLength() - options.frozenRow;
3723 } else {
3724 var numberOfRows = dataLengthIncludingAddNew + (options.leaveSpaceForNewRows ? numVisibleRows - 1 : 0);
3725 }
3726
3727 var tempViewportH = $viewportScrollContainerY.height();
3728 var oldViewportHasVScroll = viewportHasVScroll;
3729 // with autoHeight, we do not need to accommodate the vertical scroll bar
3730 viewportHasVScroll = options.alwaysShowVerticalScroll || !options.autoHeight && (numberOfRows * options.rowHeight > tempViewportH);
3731
3732 makeActiveCellNormal();
3733
3734 // remove the rows that are now outside of the data range
3735 // this helps avoid redundant calls to .removeRow() when the size of the data decreased by thousands of rows
3736 var r1 = dataLength - 1;
3737 for (var i in rowsCache) {
3738 if (i > r1) {
3739 removeRowFromCache(i);
3740 }
3741 }
3742 if (options.enableAsyncPostRenderCleanup) { startPostProcessingCleanup(); }
3743
3744 if (activeCellNode && activeRow > r1) {
3745 resetActiveCell();
3746 }
3747
3748 var oldH = h;
3749 if (options.autoHeight) {
3750 h = options.rowHeight * numberOfRows;
3751 } else {
3752 th = Math.max(options.rowHeight * numberOfRows, tempViewportH - scrollbarDimensions.height);
3753 if (th < maxSupportedCssHeight) {
3754 // just one page
3755 h = ph = th;
3756 n = 1;
3757 cj = 0;
3758 } else {
3759 // break into pages
3760 h = maxSupportedCssHeight;
3761 ph = h / 100;
3762 n = Math.floor(th / ph);
3763 cj = (th - h) / (n - 1);
3764 }
3765 }
3766
3767 if (h !== oldH) {
3768 if (hasFrozenRows && !options.frozenBottom) {
3769 $canvasBottomL.css("height", h);
3770
3771 if (hasFrozenColumns()) {
3772 $canvasBottomR.css("height", h);
3773 }
3774 } else {
3775 $canvasTopL.css("height", h);
3776 $canvasTopR.css("height", h);
3777 }
3778
3779 scrollTop = $viewportScrollContainerY[0].scrollTop;
3780 }
3781
3782 var oldScrollTopInRange = (scrollTop + offset <= th - tempViewportH);
3783
3784 if (th == 0 || scrollTop == 0) {
3785 page = offset = 0;
3786 } else if (oldScrollTopInRange) {
3787 // maintain virtual position
3788 scrollTo(scrollTop + offset);
3789 } else {
3790 // scroll to bottom
3791 scrollTo(th - tempViewportH);
3792 }
3793
3794 if (h != oldH && options.autoHeight) {
3795 resizeCanvas();
3796 }
3797
3798 if (options.autosizeColsMode === Slick.GridAutosizeColsMode.LegacyForceFit && oldViewportHasVScroll != viewportHasVScroll) {
3799 autosizeColumns();
3800 }
3801 updateCanvasWidth(false);
3802 }
3803
3804 function getVisibleRange(viewportTop, viewportLeft) {
3805 if (viewportTop == null) {
3806 viewportTop = scrollTop;
3807 }
3808 if (viewportLeft == null) {
3809 viewportLeft = scrollLeft;
3810 }
3811
3812 return {
3813 top: getRowFromPosition(viewportTop),
3814 bottom: getRowFromPosition(viewportTop + viewportH) + 1,
3815 leftPx: viewportLeft,
3816 rightPx: viewportLeft + viewportW
3817 };
3818 }
3819
3820 function getRenderedRange(viewportTop, viewportLeft) {
3821 var range = getVisibleRange(viewportTop, viewportLeft);
3822 var buffer = Math.round(viewportH / options.rowHeight);
3823 var minBuffer = options.minRowBuffer;
3824
3825 if (vScrollDir == -1) {
3826 range.top -= buffer;
3827 range.bottom += minBuffer;
3828 } else if (vScrollDir == 1) {
3829 range.top -= minBuffer;
3830 range.bottom += buffer;
3831 } else {
3832 range.top -= minBuffer;
3833 range.bottom += minBuffer;
3834 }
3835
3836 range.top = Math.max(0, range.top);
3837 range.bottom = Math.min(getDataLengthIncludingAddNew() - 1, range.bottom);
3838
3839 range.leftPx -= viewportW;
3840 range.rightPx += viewportW;
3841
3842 range.leftPx = Math.max(0, range.leftPx);
3843 range.rightPx = Math.min(canvasWidth, range.rightPx);
3844
3845 return range;
3846 }
3847
3848 function ensureCellNodesInRowsCache(row) {
3849 var cacheEntry = rowsCache[row];
3850 if (cacheEntry) {
3851 if (cacheEntry.cellRenderQueue.length) {
3852 var $lastNode = cacheEntry.rowNode.children().last();
3853 while (cacheEntry.cellRenderQueue.length) {
3854 var columnIdx = cacheEntry.cellRenderQueue.pop();
3855
3856 cacheEntry.cellNodesByColumnIdx[columnIdx] = $lastNode;
3857 $lastNode = $lastNode.prev();
3858
3859 // Hack to retrieve the frozen columns because
3860 if ($lastNode.length === 0) {
3861 $lastNode = $(cacheEntry.rowNode[0]).children().last();
3862 }
3863 }
3864 }
3865 }
3866 }
3867
3868 function cleanUpCells(range, row) {
3869 // Ignore frozen rows
3870 if (hasFrozenRows
3871 && ( ( options.frozenBottom && row > actualFrozenRow ) // Frozen bottom rows
3872 || ( row <= actualFrozenRow ) // Frozen top rows
3873 )
3874 ) {
3875 return;
3876 }
3877
3878 var totalCellsRemoved = 0;
3879 var cacheEntry = rowsCache[row];
3880
3881 // Remove cells outside the range.
3882 var cellsToRemove = [];
3883 for (var i in cacheEntry.cellNodesByColumnIdx) {
3884 // I really hate it when people mess with Array.prototype.
3885 if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(i)) {
3886 continue;
3887 }
3888
3889 // This is a string, so it needs to be cast back to a number.
3890 i = i | 0;
3891
3892 // Ignore frozen columns
3893 if (i <= options.frozenColumn) {
3894 continue;
3895 }
3896
3897 // Ignore alwaysRenderedColumns
3898 if (Array.isArray(columns) && columns[i] && columns[i].alwaysRenderColumn){
3899 continue;
3900 }
3901
3902 var colspan = cacheEntry.cellColSpans[i];
3903 if (columnPosLeft[i] > range.rightPx ||
3904 columnPosRight[Math.min(columns.length - 1, i + colspan - 1)] < range.leftPx) {
3905 if (!(row == activeRow && i == activeCell)) {
3906 cellsToRemove.push(i);
3907 }
3908 }
3909 }
3910
3911 var cellToRemove, cellNode;
3912 while ((cellToRemove = cellsToRemove.pop()) != null) {
3913 cellNode = cacheEntry.cellNodesByColumnIdx[cellToRemove][0];
3914
3915 if (options.enableAsyncPostRenderCleanup && postProcessedRows[row] && postProcessedRows[row][cellToRemove]) {
3916 queuePostProcessedCellForCleanup(cellNode, cellToRemove, row);
3917 } else {
3918 cellNode.parentElement.removeChild(cellNode);
3919 }
3920
3921 delete cacheEntry.cellColSpans[cellToRemove];
3922 delete cacheEntry.cellNodesByColumnIdx[cellToRemove];
3923 if (postProcessedRows[row]) {
3924 delete postProcessedRows[row][cellToRemove];
3925 }
3926 totalCellsRemoved++;
3927 }
3928 }
3929
3930 function cleanUpAndRenderCells(range) {
3931 var cacheEntry;
3932 var stringArray = [];
3933 var processedRows = [];
3934 var cellsAdded;
3935 var totalCellsAdded = 0;
3936 var colspan;
3937
3938 for (var row = range.top, btm = range.bottom; row <= btm; row++) {
3939 cacheEntry = rowsCache[row];
3940 if (!cacheEntry) {
3941 continue;
3942 }
3943
3944 // cellRenderQueue populated in renderRows() needs to be cleared first
3945 ensureCellNodesInRowsCache(row);
3946
3947 cleanUpCells(range, row);
3948
3949 // Render missing cells.
3950 cellsAdded = 0;
3951
3952 var metadata = data.getItemMetadata && data.getItemMetadata(row);
3953 metadata = metadata && metadata.columns;
3954
3955 var d = getDataItem(row);
3956
3957 // TODO: shorten this loop (index? heuristics? binary search?)
3958 for (var i = 0, ii = columns.length; i < ii; i++) {
3959 // Cells to the right are outside the range.
3960 if (columnPosLeft[i] > range.rightPx) {
3961 break;
3962 }
3963
3964 // Already rendered.
3965 if ((colspan = cacheEntry.cellColSpans[i]) != null) {
3966 i += (colspan > 1 ? colspan - 1 : 0);
3967 continue;
3968 }
3969
3970 colspan = 1;
3971 if (metadata) {
3972 var columnData = metadata[columns[i].id] || metadata[i];
3973 colspan = (columnData && columnData.colspan) || 1;
3974 if (colspan === "*") {
3975 colspan = ii - i;
3976 }
3977 }
3978
3979 if (columnPosRight[Math.min(ii - 1, i + colspan - 1)] > range.leftPx) {
3980 appendCellHtml(stringArray, row, i, colspan, d);
3981 cellsAdded++;
3982 }
3983
3984 i += (colspan > 1 ? colspan - 1 : 0);
3985 }
3986
3987 if (cellsAdded) {
3988 totalCellsAdded += cellsAdded;
3989 processedRows.push(row);
3990 }
3991 }
3992
3993 if (!stringArray.length) {
3994 return;
3995 }
3996
3997 var x = document.createElement("div");
3998 x.innerHTML = sanitizeHtmlString(stringArray.join(""));
3999
4000 var processedRow;
4001 var node;
4002 while ((processedRow = processedRows.pop()) != null) {
4003 cacheEntry = rowsCache[processedRow];
4004 var columnIdx;
4005 while ((columnIdx = cacheEntry.cellRenderQueue.pop()) != null) {
4006 node = x.lastChild;
4007
4008 if (hasFrozenColumns() && (columnIdx > options.frozenColumn)) {
4009 cacheEntry.rowNode[1].appendChild(node);
4010 } else {
4011 cacheEntry.rowNode[0].appendChild(node);
4012 }
4013 cacheEntry.cellNodesByColumnIdx[columnIdx] = $(node);
4014 }
4015 }
4016 }
4017
4018 function renderRows(range) {
4019 var stringArrayL = [],
4020 stringArrayR = [],
4021 rows = [],
4022 needToReselectCell = false,
4023 dataLength = getDataLength();
4024
4025 for (var i = range.top, ii = range.bottom; i <= ii; i++) {
4026 if (rowsCache[i] || ( hasFrozenRows && options.frozenBottom && i == getDataLength() )) {
4027 continue;
4028 }
4029 renderedRows++;
4030 rows.push(i);
4031
4032 // Create an entry right away so that appendRowHtml() can
4033 // start populatating it.
4034 rowsCache[i] = {
4035 "rowNode": null,
4036
4037 // ColSpans of rendered cells (by column idx).
4038 // Can also be used for checking whether a cell has been rendered.
4039 "cellColSpans": [],
4040
4041 // Cell nodes (by column idx). Lazy-populated by ensureCellNodesInRowsCache().
4042 "cellNodesByColumnIdx": [],
4043
4044 // Column indices of cell nodes that have been rendered, but not yet indexed in
4045 // cellNodesByColumnIdx. These are in the same order as cell nodes added at the
4046 // end of the row.
4047 "cellRenderQueue": []
4048 };
4049
4050 appendRowHtml(stringArrayL, stringArrayR, i, range, dataLength);
4051 if (activeCellNode && activeRow === i) {
4052 needToReselectCell = true;
4053 }
4054 counter_rows_rendered++;
4055 }
4056
4057 if (!rows.length) { return; }
4058
4059 var x = document.createElement("div"),
4060 xRight = document.createElement("div");
4061
4062 x.innerHTML = sanitizeHtmlString(stringArrayL.join(""));
4063 xRight.innerHTML = sanitizeHtmlString(stringArrayR.join(""));
4064
4065 for (var i = 0, ii = rows.length; i < ii; i++) {
4066 if (( hasFrozenRows ) && ( rows[i] >= actualFrozenRow )) {
4067 if (hasFrozenColumns()) {
4068 rowsCache[rows[i]].rowNode = $()
4069 .add($(x.firstChild))
4070 .add($(xRight.firstChild));
4071 $canvasBottomL[0].appendChild(x.firstChild);
4072 $canvasBottomR[0].appendChild(xRight.firstChild);
4073 } else {
4074 rowsCache[rows[i]].rowNode = $()
4075 .add($(x.firstChild));
4076 $canvasBottomL[0].appendChild(x.firstChild);
4077 }
4078 } else if (hasFrozenColumns()) {
4079 rowsCache[rows[i]].rowNode = $()
4080 .add($(x.firstChild))
4081 .add($(xRight.firstChild));
4082 $canvasTopL[0].appendChild(x.firstChild);
4083 $canvasTopR[0].appendChild(xRight.firstChild);
4084 } else {
4085 rowsCache[rows[i]].rowNode = $()
4086 .add($(x.firstChild));
4087 $canvasTopL[0].appendChild(x.firstChild);
4088 }
4089 }
4090
4091 if (needToReselectCell) {
4092 activeCellNode = getCellNode(activeRow, activeCell);
4093 }
4094 }
4095
4096 function startPostProcessing() {
4097 if (!options.enableAsyncPostRender) {
4098 return;
4099 }
4100 clearTimeout(h_postrender);
4101 h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay);
4102 }
4103
4104 function startPostProcessingCleanup() {
4105 if (!options.enableAsyncPostRenderCleanup) {
4106 return;
4107 }
4108 clearTimeout(h_postrenderCleanup);
4109 h_postrenderCleanup = setTimeout(asyncPostProcessCleanupRows, options.asyncPostRenderCleanupDelay);
4110 }
4111
4112 function invalidatePostProcessingResults(row) {
4113 // change status of columns to be re-rendered
4114 for (var columnIdx in postProcessedRows[row]) {
4115 if (postProcessedRows[row].hasOwnProperty(columnIdx)) {
4116 postProcessedRows[row][columnIdx] = 'C';
4117 }
4118 }
4119 postProcessFromRow = Math.min(postProcessFromRow, row);
4120 postProcessToRow = Math.max(postProcessToRow, row);
4121 startPostProcessing();
4122 }
4123
4124 function updateRowPositions() {
4125 for (var row in rowsCache) {
4126 var rowNumber = row ? parseInt(row) : 0;
4127 rowsCache[rowNumber].rowNode[0].style.top = getRowTop(rowNumber) + "px";
4128 }
4129 }
4130
4131 function render() {
4132 if (!initialized) { return; }
4133
4134 scrollThrottle.dequeue();
4135
4136 var visible = getVisibleRange();
4137 var rendered = getRenderedRange();
4138
4139 // remove rows no longer in the viewport
4140 cleanupRows(rendered);
4141
4142 // add new rows & missing cells in existing rows
4143 if (lastRenderedScrollLeft != scrollLeft) {
4144
4145 if ( hasFrozenRows ) {
4146
4147 var renderedFrozenRows = jQuery.extend(true, {}, rendered);
4148
4149 if (options.frozenBottom) {
4150
4151 renderedFrozenRows.top=actualFrozenRow;
4152 renderedFrozenRows.bottom=getDataLength();
4153 }
4154 else {
4155
4156 renderedFrozenRows.top=0;
4157 renderedFrozenRows.bottom=options.frozenRow;
4158 }
4159
4160 cleanUpAndRenderCells(renderedFrozenRows);
4161 }
4162
4163 cleanUpAndRenderCells(rendered);
4164 }
4165
4166 // render missing rows
4167 renderRows(rendered);
4168
4169 // Render frozen rows
4170 if (hasFrozenRows) {
4171 if (options.frozenBottom) {
4172 renderRows({
4173 top: actualFrozenRow, bottom: getDataLength() - 1, leftPx: rendered.leftPx, rightPx: rendered.rightPx
4174 });
4175 }
4176 else {
4177 renderRows({
4178 top: 0, bottom: options.frozenRow - 1, leftPx: rendered.leftPx, rightPx: rendered.rightPx
4179 });
4180 }
4181 }
4182
4183 postProcessFromRow = visible.top;
4184 postProcessToRow = Math.min(getDataLengthIncludingAddNew() - 1, visible.bottom);
4185 startPostProcessing();
4186
4187 lastRenderedScrollTop = scrollTop;
4188 lastRenderedScrollLeft = scrollLeft;
4189 h_render = null;
4190 trigger(self.onRendered, { startRow: visible.top, endRow: visible.bottom, grid: self });
4191 }
4192
4193 function handleHeaderScroll() {
4194 handleElementScroll($headerScrollContainer[0]);
4195 }
4196
4197 function handleHeaderRowScroll() {
4198 var scrollLeft = $headerRowScrollContainer[0].scrollLeft;
4199 if (scrollLeft != $viewportScrollContainerX[0].scrollLeft) {
4200 $viewportScrollContainerX[0].scrollLeft = scrollLeft;
4201 }
4202 }
4203
4204 function handleFooterRowScroll() {
4205 var scrollLeft = $footerRowScrollContainer[0].scrollLeft;
4206 if (scrollLeft != $viewportScrollContainerX[0].scrollLeft) {
4207 $viewportScrollContainerX[0].scrollLeft = scrollLeft;
4208 }
4209 }
4210
4211 function handlePreHeaderPanelScroll() {
4212 handleElementScroll($preHeaderPanelScroller[0]);
4213 }
4214
4215 function handleElementScroll(element) {
4216 var scrollLeft = element.scrollLeft;
4217 if (scrollLeft != $viewportScrollContainerX[0].scrollLeft) {
4218 $viewportScrollContainerX[0].scrollLeft = scrollLeft;
4219 }
4220 }
4221
4222 function handleScroll() {
4223 scrollTop = $viewportScrollContainerY[0].scrollTop;
4224 scrollLeft = $viewportScrollContainerX[0].scrollLeft;
4225 return _handleScroll(false);
4226 }
4227
4228 function _handleScroll(isMouseWheel) {
4229 var maxScrollDistanceY = $viewportScrollContainerY[0].scrollHeight - $viewportScrollContainerY[0].clientHeight;
4230 var maxScrollDistanceX = $viewportScrollContainerY[0].scrollWidth - $viewportScrollContainerY[0].clientWidth;
4231
4232 // Protect against erroneous clientHeight/Width greater than scrollHeight/Width.
4233 // Sometimes seen in Chrome.
4234 maxScrollDistanceY = Math.max(0, maxScrollDistanceY);
4235 maxScrollDistanceX = Math.max(0, maxScrollDistanceX);
4236
4237 // Ceiling the max scroll values
4238 if (scrollTop > maxScrollDistanceY) {
4239 scrollTop = maxScrollDistanceY;
4240 }
4241 if (scrollLeft > maxScrollDistanceX) {
4242 scrollLeft = maxScrollDistanceX;
4243 }
4244
4245 var vScrollDist = Math.abs(scrollTop - prevScrollTop);
4246 var hScrollDist = Math.abs(scrollLeft - prevScrollLeft);
4247
4248 if (hScrollDist) {
4249 prevScrollLeft = scrollLeft;
4250
4251 $viewportScrollContainerX[0].scrollLeft = scrollLeft;
4252 $headerScrollContainer[0].scrollLeft = scrollLeft;
4253 $topPanelScroller[0].scrollLeft = scrollLeft;
4254 $headerRowScrollContainer[0].scrollLeft = scrollLeft;
4255 if (options.createFooterRow) {
4256 $footerRowScrollContainer[0].scrollLeft = scrollLeft;
4257 }
4258 if (options.createPreHeaderPanel) {
4259 if (hasFrozenColumns()) {
4260 $preHeaderPanelScrollerR[0].scrollLeft = scrollLeft;
4261 } else {
4262 $preHeaderPanelScroller[0].scrollLeft = scrollLeft;
4263 }
4264 }
4265
4266 if (hasFrozenColumns()) {
4267 if (hasFrozenRows) {
4268 $viewportTopR[0].scrollLeft = scrollLeft;
4269 }
4270 } else {
4271 if (hasFrozenRows) {
4272 $viewportTopL[0].scrollLeft = scrollLeft;
4273 }
4274 }
4275 }
4276
4277 if (vScrollDist) {
4278 vScrollDir = prevScrollTop < scrollTop ? 1 : -1;
4279 prevScrollTop = scrollTop;
4280
4281 if (isMouseWheel) {
4282 $viewportScrollContainerY[0].scrollTop = scrollTop;
4283 }
4284
4285 if (hasFrozenColumns()) {
4286 if (hasFrozenRows && !options.frozenBottom) {
4287 $viewportBottomL[0].scrollTop = scrollTop;
4288 } else {
4289 $viewportTopL[0].scrollTop = scrollTop;
4290 }
4291 }
4292
4293 // switch virtual pages if needed
4294 if (vScrollDist < viewportH) {
4295 scrollTo(scrollTop + offset);
4296 } else {
4297 var oldOffset = offset;
4298 if (h == viewportH) {
4299 page = 0;
4300 } else {
4301 page = Math.min(n - 1, Math.floor(scrollTop * ((th - viewportH) / (h - viewportH)) * (1 / ph)));
4302 }
4303 offset = Math.round(page * cj);
4304 if (oldOffset != offset) {
4305 invalidateAllRows();
4306 }
4307 }
4308 }
4309
4310 if (hScrollDist || vScrollDist) {
4311 var dx = Math.abs(lastRenderedScrollLeft - scrollLeft);
4312 var dy = Math.abs(lastRenderedScrollTop - scrollTop);
4313 if (dx > 20 || dy > 20) {
4314 // if rendering is forced or scrolling is small enough to be "easy", just render
4315 if (options.forceSyncScrolling || (dy < viewportH && dx < viewportW)) {
4316 render();
4317 } else {
4318 // otherwise, perform "difficult" renders at a capped frequency
4319 scrollThrottle.enqueue();
4320 }
4321
4322 trigger(self.onViewportChanged, {});
4323 }
4324 }
4325
4326 trigger(self.onScroll, {scrollLeft: scrollLeft, scrollTop: scrollTop});
4327
4328 if (hScrollDist || vScrollDist) return true;
4329 return false;
4330 }
4331
4332 /*
4333 limits the frequency at which the provided action is executed.
4334 call enqueue to execute the action - it will execute either immediately or, if it was executed less than minPeriod_ms in the past, as soon as minPeriod_ms has expired.
4335 call dequeue to cancel any pending action.
4336 */
4337 function ActionThrottle(action, minPeriod_ms) {
4338
4339 var blocked = false;
4340 var queued = false;
4341
4342 function enqueue() {
4343 if (!blocked) {
4344 blockAndExecute();
4345 } else {
4346 queued = true;
4347 }
4348 }
4349
4350 function dequeue() {
4351 queued = false;
4352 }
4353
4354 function blockAndExecute() {
4355 blocked = true;
4356 setTimeout(unblock, minPeriod_ms);
4357 action();
4358 }
4359
4360 function unblock() {
4361 if (queued) {
4362 dequeue();
4363 blockAndExecute();
4364 } else {
4365 blocked = false;
4366 }
4367 }
4368
4369 return {
4370 enqueue: enqueue,
4371 dequeue: dequeue
4372 };
4373 }
4374
4375 function asyncPostProcessRows() {
4376 var dataLength = getDataLength();
4377 while (postProcessFromRow <= postProcessToRow) {
4378 var row = (vScrollDir >= 0) ? postProcessFromRow++ : postProcessToRow--;
4379 var cacheEntry = rowsCache[row];
4380 if (!cacheEntry || row >= dataLength) {
4381 continue;
4382 }
4383
4384 if (!postProcessedRows[row]) {
4385 postProcessedRows[row] = {};
4386 }
4387
4388 ensureCellNodesInRowsCache(row);
4389 for (var columnIdx in cacheEntry.cellNodesByColumnIdx) {
4390 if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) {
4391 continue;
4392 }
4393
4394 columnIdx = columnIdx | 0;
4395
4396 var m = columns[columnIdx];
4397 var processedStatus = postProcessedRows[row][columnIdx]; // C=cleanup and re-render, R=rendered
4398 if (m.asyncPostRender && processedStatus !== 'R') {
4399 var node = cacheEntry.cellNodesByColumnIdx[columnIdx];
4400 if (node) {
4401 m.asyncPostRender(node, row, getDataItem(row), m, (processedStatus === 'C'));
4402 }
4403 postProcessedRows[row][columnIdx] = 'R';
4404 }
4405 }
4406
4407 h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay);
4408 return;
4409 }
4410 }
4411
4412 function asyncPostProcessCleanupRows() {
4413 if (postProcessedCleanupQueue.length > 0) {
4414 var groupId = postProcessedCleanupQueue[0].groupId;
4415
4416 // loop through all queue members with this groupID
4417 while (postProcessedCleanupQueue.length > 0 && postProcessedCleanupQueue[0].groupId == groupId) {
4418 var entry = postProcessedCleanupQueue.shift();
4419 if (entry.actionType == 'R') {
4420 $(entry.node).remove();
4421 }
4422 if (entry.actionType == 'C') {
4423 var column = columns[entry.columnIdx];
4424 if (column.asyncPostRenderCleanup && entry.node) {
4425 // cleanup must also remove element
4426 column.asyncPostRenderCleanup(entry.node, entry.rowIdx, column);
4427 }
4428 }
4429 }
4430
4431 // call this function again after the specified delay
4432 h_postrenderCleanup = setTimeout(asyncPostProcessCleanupRows, options.asyncPostRenderCleanupDelay);
4433 }
4434 }
4435
4436 function updateCellCssStylesOnRenderedRows(addedHash, removedHash) {
4437 var node, columnId, addedRowHash, removedRowHash;
4438 for (var row in rowsCache) {
4439 removedRowHash = removedHash && removedHash[row];
4440 addedRowHash = addedHash && addedHash[row];
4441
4442 if (removedRowHash) {
4443 for (columnId in removedRowHash) {
4444 if (!addedRowHash || removedRowHash[columnId] != addedRowHash[columnId]) {
4445 node = getCellNode(row, getColumnIndex(columnId));
4446 if (node) {
4447 $(node).removeClass(removedRowHash[columnId]);
4448 }
4449 }
4450 }
4451 }
4452
4453 if (addedRowHash) {
4454 for (columnId in addedRowHash) {
4455 if (!removedRowHash || removedRowHash[columnId] != addedRowHash[columnId]) {
4456 node = getCellNode(row, getColumnIndex(columnId));
4457 if (node) {
4458 $(node).addClass(addedRowHash[columnId]);
4459 }
4460 }
4461 }
4462 }
4463 }
4464 }
4465
4466 function addCellCssStyles(key, hash) {
4467 if (cellCssClasses[key]) {
4468 throw new Error("SlickGrid addCellCssStyles: cell CSS hash with key '" + key + "' already exists.");
4469 }
4470
4471 cellCssClasses[key] = hash;
4472 updateCellCssStylesOnRenderedRows(hash, null);
4473
4474 trigger(self.onCellCssStylesChanged, { "key": key, "hash": hash, "grid": self });
4475 }
4476
4477 function removeCellCssStyles(key) {
4478 if (!cellCssClasses[key]) {
4479 return;
4480 }
4481
4482 updateCellCssStylesOnRenderedRows(null, cellCssClasses[key]);
4483 delete cellCssClasses[key];
4484
4485 trigger(self.onCellCssStylesChanged, { "key": key, "hash": null, "grid": self });
4486 }
4487
4488 function setCellCssStyles(key, hash) {
4489 var prevHash = cellCssClasses[key];
4490
4491 cellCssClasses[key] = hash;
4492 updateCellCssStylesOnRenderedRows(hash, prevHash);
4493
4494 trigger(self.onCellCssStylesChanged, { "key": key, "hash": hash, "grid": self });
4495 }
4496
4497 function getCellCssStyles(key) {
4498 return cellCssClasses[key];
4499 }
4500
4501 function flashCell(row, cell, speed) {
4502 speed = speed || 100;
4503
4504 function toggleCellClass($cell, times) {
4505 if (!times) {
4506 return;
4507 }
4508
4509 setTimeout(function () {
4510 $cell.queue(function () {
4511 $cell.toggleClass(options.cellFlashingCssClass).dequeue();
4512 toggleCellClass($cell, times - 1);
4513 });
4514 }, speed);
4515 }
4516
4517 if (rowsCache[row]) {
4518 var $cell = $(getCellNode(row, cell));
4519
4520 toggleCellClass($cell, 4);
4521 }
4522 }
4523
4524 //////////////////////////////////////////////////////////////////////////////////////////////
4525 // Interactivity
4526
4527 function handleMouseWheel(e, delta, deltaX, deltaY) {
4528 scrollTop = Math.max(0, $viewportScrollContainerY[0].scrollTop - (deltaY * options.rowHeight));
4529 scrollLeft = $viewportScrollContainerX[0].scrollLeft + (deltaX * 10);
4530 var handled = _handleScroll(true);
4531 if (handled) e.preventDefault();
4532 }
4533
4534 function handleDragInit(e, dd) {
4535 var cell = getCellFromEvent(e);
4536 if (!cell || !cellExists(cell.row, cell.cell)) {
4537 return false;
4538 }
4539
4540 var retval = trigger(self.onDragInit, dd, e);
4541 if (e.isImmediatePropagationStopped()) {
4542 return retval;
4543 }
4544
4545 // if nobody claims to be handling drag'n'drop by stopping immediate propagation,
4546 // cancel out of it
4547 return false;
4548 }
4549
4550 function handleDragStart(e, dd) {
4551 var cell = getCellFromEvent(e);
4552 if (!cell || !cellExists(cell.row, cell.cell)) {
4553 return false;
4554 }
4555
4556 var retval = trigger(self.onDragStart, dd, e);
4557 if (e.isImmediatePropagationStopped()) {
4558 return retval;
4559 }
4560
4561 return false;
4562 }
4563
4564 function handleDrag(e, dd) {
4565 return trigger(self.onDrag, dd, e);
4566 }
4567
4568 function handleDragEnd(e, dd) {
4569 trigger(self.onDragEnd, dd, e);
4570 }
4571
4572 function handleKeyDown(e) {
4573 trigger(self.onKeyDown, {row: activeRow, cell: activeCell}, e);
4574 var handled = e.isImmediatePropagationStopped();
4575 var keyCode = Slick.keyCode;
4576
4577 if (!handled) {
4578 if (!e.shiftKey && !e.altKey) {
4579 if (options.editable && currentEditor && currentEditor.keyCaptureList) {
4580 if (currentEditor.keyCaptureList.indexOf(e.which) > -1) {
4581 return;
4582 }
4583 }
4584 if (e.which == keyCode.HOME) {
4585 handled = (e.ctrlKey) ? navigateTop() : navigateRowStart();
4586 } else if (e.which == keyCode.END) {
4587 handled = (e.ctrlKey) ? navigateBottom() : navigateRowEnd();
4588 }
4589 }
4590 }
4591 if (!handled) {
4592 if (!e.shiftKey && !e.altKey && !e.ctrlKey) {
4593 // editor may specify an array of keys to bubble
4594 if (options.editable && currentEditor && currentEditor.keyCaptureList) {
4595 if (currentEditor.keyCaptureList.indexOf( e.which ) > -1) {
4596 return;
4597 }
4598 }
4599 if (e.which == keyCode.ESCAPE) {
4600 if (!getEditorLock().isActive()) {
4601 return; // no editing mode to cancel, allow bubbling and default processing (exit without cancelling the event)
4602 }
4603 cancelEditAndSetFocus();
4604 } else if (e.which == keyCode.PAGE_DOWN) {
4605 navigatePageDown();
4606 handled = true;
4607 } else if (e.which == keyCode.PAGE_UP) {
4608 navigatePageUp();
4609 handled = true;
4610 } else if (e.which == keyCode.LEFT) {
4611 handled = navigateLeft();
4612 } else if (e.which == keyCode.RIGHT) {
4613 handled = navigateRight();
4614 } else if (e.which == keyCode.UP) {
4615 handled = navigateUp();
4616 } else if (e.which == keyCode.DOWN) {
4617 handled = navigateDown();
4618 } else if (e.which == keyCode.TAB) {
4619 handled = navigateNext();
4620 } else if (e.which == keyCode.ENTER) {
4621 if (options.editable) {
4622 if (currentEditor) {
4623 // adding new row
4624 if (activeRow === getDataLength()) {
4625 navigateDown();
4626 } else {
4627 commitEditAndSetFocus();
4628 }
4629 } else {
4630 if (getEditorLock().commitCurrentEdit()) {
4631 makeActiveCellEditable(undefined, undefined, e);
4632 }
4633 }
4634 }
4635 handled = true;
4636 }
4637 } else if (e.which == keyCode.TAB && e.shiftKey && !e.ctrlKey && !e.altKey) {
4638 handled = navigatePrev();
4639 }
4640 }
4641
4642 if (handled) {
4643 // the event has been handled so don't let parent element (bubbling/propagation) or browser (default) handle it
4644 e.stopPropagation();
4645 e.preventDefault();
4646 try {
4647 e.originalEvent.keyCode = 0; // prevent default behaviour for special keys in IE browsers (F3, F5, etc.)
4648 }
4649 // ignore exceptions - setting the original event's keycode throws access denied exception for "Ctrl"
4650 // (hitting control key only, nothing else), "Shift" (maybe others)
4651 catch (error) {
4652 }
4653 }
4654 }
4655
4656 function handleClick(e) {
4657 if (!currentEditor) {
4658 // if this click resulted in some cell child node getting focus,
4659 // don't steal it back - keyboard events will still bubble up
4660 // IE9+ seems to default DIVs to tabIndex=0 instead of -1, so check for cell clicks directly.
4661 if (e.target != document.activeElement || $(e.target).hasClass("slick-cell")) {
4662 var selection = getTextSelection(); //store text-selection and restore it after
4663 setFocus();
4664 setTextSelection(selection);
4665 }
4666 }
4667
4668 var cell = getCellFromEvent(e);
4669 if (!cell || (currentEditor !== null && activeRow == cell.row && activeCell == cell.cell)) {
4670 return;
4671 }
4672
4673 trigger(self.onClick, {row: cell.row, cell: cell.cell}, e);
4674 if (e.isImmediatePropagationStopped()) {
4675 return;
4676 }
4677
4678 // this optimisation causes trouble - MLeibman #329
4679 //if ((activeCell != cell.cell || activeRow != cell.row) && canCellBeActive(cell.row, cell.cell)) {
4680 if (canCellBeActive(cell.row, cell.cell)) {
4681 if (!getEditorLock().isActive() || getEditorLock().commitCurrentEdit()) {
4682 scrollRowIntoView(cell.row, false);
4683
4684 var preClickModeOn = (e.target && e.target.className === Slick.preClickClassName);
4685 var column = columns[cell.cell];
4686 var suppressActiveCellChangedEvent = !!(options.editable && column && column.editor && options.suppressActiveCellChangeOnEdit);
4687 setActiveCellInternal(getCellNode(cell.row, cell.cell), null, preClickModeOn, suppressActiveCellChangedEvent, e);
4688 }
4689 }
4690 }
4691
4692 function handleContextMenu(e) {
4693 var $cell = $(e.target).closest(".slick-cell", $canvas);
4694 if ($cell.length === 0) {
4695 return;
4696 }
4697
4698 // are we editing this cell?
4699 if (activeCellNode === $cell[0] && currentEditor !== null) {
4700 return;
4701 }
4702
4703 trigger(self.onContextMenu, {}, e);
4704 }
4705
4706 function handleDblClick(e) {
4707 var cell = getCellFromEvent(e);
4708 if (!cell || (currentEditor !== null && activeRow == cell.row && activeCell == cell.cell)) {
4709 return;
4710 }
4711
4712 trigger(self.onDblClick, {row: cell.row, cell: cell.cell}, e);
4713 if (e.isImmediatePropagationStopped()) {
4714 return;
4715 }
4716
4717 if (options.editable) {
4718 gotoCell(cell.row, cell.cell, true, e);
4719 }
4720 }
4721
4722 function handleHeaderMouseEnter(e) {
4723 trigger(self.onHeaderMouseEnter, {
4724 "column": $(this).data("column"),
4725 "grid": self
4726 }, e);
4727 }
4728
4729 function handleHeaderMouseLeave(e) {
4730 trigger(self.onHeaderMouseLeave, {
4731 "column": $(this).data("column"),
4732 "grid": self
4733 }, e);
4734 }
4735
4736 function handleHeaderRowMouseEnter(e) {
4737 trigger(self.onHeaderRowMouseEnter, {
4738 "column": $(this).data("column"),
4739 "grid": self
4740 }, e);
4741 }
4742
4743 function handleHeaderRowMouseLeave(e) {
4744 trigger(self.onHeaderRowMouseLeave, {
4745 "column": $(this).data("column"),
4746 "grid": self
4747 }, e);
4748 }
4749
4750 function handleHeaderContextMenu(e) {
4751 var $header = $(e.target).closest(".slick-header-column", ".slick-header-columns");
4752 var column = $header && $header.data("column");
4753 trigger(self.onHeaderContextMenu, {column: column}, e);
4754 }
4755
4756 function handleHeaderClick(e) {
4757 if (columnResizeDragging) return;
4758 var $header = $(e.target).closest(".slick-header-column", ".slick-header-columns");
4759 var column = $header && $header.data("column");
4760 if (column) {
4761 trigger(self.onHeaderClick, {column: column}, e);
4762 }
4763 }
4764
4765 function handleFooterContextMenu(e) {
4766 var $footer = $(e.target).closest(".slick-footerrow-column", ".slick-footerrow-columns");
4767 var column = $footer && $footer.data("column");
4768 trigger(self.onFooterContextMenu, {column: column}, e);
4769 }
4770
4771 function handleFooterClick(e) {
4772 var $footer = $(e.target).closest(".slick-footerrow-column", ".slick-footerrow-columns");
4773 var column = $footer && $footer.data("column");
4774 trigger(self.onFooterClick, {column: column}, e);
4775 }
4776
4777 function handleMouseEnter(e) {
4778 trigger(self.onMouseEnter, {}, e);
4779 }
4780
4781 function handleMouseLeave(e) {
4782 trigger(self.onMouseLeave, {}, e);
4783 }
4784
4785 function cellExists(row, cell) {
4786 return !(row < 0 || row >= getDataLength() || cell < 0 || cell >= columns.length);
4787 }
4788
4789 function getCellFromPoint(x, y) {
4790 var row = getRowFromPosition(y);
4791 var cell = 0;
4792
4793 var w = 0;
4794 for (var i = 0; i < columns.length && w < x; i++) {
4795 w += columns[i].width;
4796 cell++;
4797 }
4798
4799 if (cell < 0) {
4800 cell = 0;
4801 }
4802
4803 return {row: row, cell: cell - 1};
4804 }
4805
4806 function getCellFromNode(cellNode) {
4807 // read column number from .l<columnNumber> CSS class
4808 var cls = /l\d+/.exec(cellNode.className);
4809 if (!cls) {
4810 throw new Error("SlickGrid getCellFromNode: cannot get cell - " + cellNode.className);
4811 }
4812 return parseInt(cls[0].substr(1, cls[0].length - 1), 10);
4813 }
4814
4815 function getRowFromNode(rowNode) {
4816 for (var row in rowsCache) {
4817 for (var i in rowsCache[row].rowNode) {
4818 if (rowsCache[row].rowNode[i] === rowNode)
4819 return (row ? parseInt(row) : 0);
4820 }
4821 }
4822 return null;
4823 }
4824
4825 function getFrozenRowOffset(row) {
4826 var offset =
4827 ( hasFrozenRows )
4828 ? ( options.frozenBottom )
4829 ? ( row >= actualFrozenRow )
4830 ? ( h < viewportTopH )
4831 ? ( actualFrozenRow * options.rowHeight )
4832 : h
4833 : 0
4834 : ( row >= actualFrozenRow )
4835 ? frozenRowsHeight
4836 : 0
4837 : 0;
4838
4839 return offset;
4840 }
4841
4842 function getCellFromEvent(e) {
4843 var row, cell;
4844 var $cell = $(e.target).closest(".slick-cell", $canvas);
4845 if (!$cell.length) {
4846 return null;
4847 }
4848
4849 row = getRowFromNode($cell[0].parentNode);
4850
4851 if (hasFrozenRows) {
4852
4853 var c = $cell.parents('.grid-canvas').offset();
4854
4855 var rowOffset = 0;
4856 var isBottom = $cell.parents('.grid-canvas-bottom').length;
4857
4858 if (isBottom) {
4859 rowOffset = ( options.frozenBottom ) ? $canvasTopL.height() : frozenRowsHeight;
4860 }
4861
4862 row = getCellFromPoint(e.clientX - c.left, e.clientY - c.top + rowOffset + $(document).scrollTop()).row;
4863 }
4864
4865 cell = getCellFromNode($cell[0]);
4866
4867 if (row == null || cell == null) {
4868 return null;
4869 } else {
4870 return {
4871 "row": row,
4872 "cell": cell
4873 };
4874 }
4875 }
4876
4877 function getCellNodeBox(row, cell) {
4878 if (!cellExists(row, cell)) {
4879 return null;
4880 }
4881
4882 var frozenRowOffset = getFrozenRowOffset(row);
4883
4884 var y1 = getRowTop(row) - frozenRowOffset;
4885 var y2 = y1 + options.rowHeight - 1;
4886 var x1 = 0;
4887 for (var i = 0; i < cell; i++) {
4888 x1 += columns[i].width;
4889
4890 if (options.frozenColumn == i) {
4891 x1 = 0;
4892 }
4893 }
4894 var x2 = x1 + columns[cell].width;
4895
4896 return {
4897 top: y1,
4898 left: x1,
4899 bottom: y2,
4900 right: x2
4901 };
4902 }
4903
4904 //////////////////////////////////////////////////////////////////////////////////////////////
4905 // Cell switching
4906
4907 function resetActiveCell() {
4908 setActiveCellInternal(null, false);
4909 }
4910
4911 function setFocus() {
4912 if (tabbingDirection == -1) {
4913 $focusSink[0].focus();
4914 } else {
4915 $focusSink2[0].focus();
4916 }
4917 }
4918
4919 function scrollCellIntoView(row, cell, doPaging) {
4920 scrollRowIntoView(row, doPaging);
4921
4922 if (cell <= options.frozenColumn) {
4923 return;
4924 }
4925
4926 var colspan = getColspan(row, cell);
4927 internalScrollColumnIntoView(columnPosLeft[cell], columnPosRight[cell + (colspan > 1 ? colspan - 1 : 0)]);
4928 }
4929
4930 function internalScrollColumnIntoView(left, right) {
4931 var scrollRight = scrollLeft + $viewportScrollContainerX.width();
4932
4933 if (left < scrollLeft) {
4934 $viewportScrollContainerX.scrollLeft(left);
4935 handleScroll();
4936 render();
4937 } else if (right > scrollRight) {
4938 $viewportScrollContainerX.scrollLeft(Math.min(left, right - $viewportScrollContainerX[0].clientWidth));
4939 handleScroll();
4940 render();
4941 }
4942 }
4943
4944 function scrollColumnIntoView(cell) {
4945 internalScrollColumnIntoView(columnPosLeft[cell], columnPosRight[cell]);
4946 }
4947
4948 function setActiveCellInternal(newCell, opt_editMode, preClickModeOn, suppressActiveCellChangedEvent, e) {
4949 if (activeCellNode !== null) {
4950 makeActiveCellNormal();
4951 $(activeCellNode).removeClass("active");
4952 if (rowsCache[activeRow]) {
4953 rowsCache[activeRow].rowNode.removeClass("active");
4954 }
4955 }
4956
4957 var activeCellChanged = (activeCellNode !== newCell);
4958 activeCellNode = newCell;
4959
4960 if (activeCellNode != null) {
4961 var $activeCellNode = $(activeCellNode);
4962 var $activeCellOffset = $activeCellNode.offset();
4963
4964 var rowOffset = Math.floor($activeCellNode.parents('.grid-canvas').offset().top);
4965 var isBottom = $activeCellNode.parents('.grid-canvas-bottom').length;
4966
4967 if (hasFrozenRows && isBottom) {
4968 rowOffset -= ( options.frozenBottom )
4969 ? $canvasTopL.height()
4970 : frozenRowsHeight;
4971 }
4972
4973 var cell = getCellFromPoint($activeCellOffset.left, Math.ceil($activeCellOffset.top) - rowOffset);
4974
4975 activeRow = cell.row;
4976 activeCell = activePosX = activeCell = activePosX = getCellFromNode(activeCellNode);
4977
4978 if (opt_editMode == null) {
4979 opt_editMode = (activeRow == getDataLength()) || options.autoEdit;
4980 }
4981
4982 if (options.showCellSelection) {
4983 $activeCellNode.addClass("active");
4984 if (rowsCache[activeRow]) {
4985 rowsCache[activeRow].rowNode.addClass("active");
4986 }
4987 }
4988
4989 if (options.editable && opt_editMode && isCellPotentiallyEditable(activeRow, activeCell)) {
4990 clearTimeout(h_editorLoader);
4991
4992 if (options.asyncEditorLoading) {
4993 h_editorLoader = setTimeout(function () {
4994 makeActiveCellEditable(undefined, preClickModeOn, e);
4995 }, options.asyncEditorLoadDelay);
4996 } else {
4997 makeActiveCellEditable(undefined, preClickModeOn, e);
4998 }
4999 }
5000 } else {
5001 activeRow = activeCell = null;
5002 }
5003
5004 // this optimisation causes trouble - MLeibman #329
5005 //if (activeCellChanged) {
5006 if (!suppressActiveCellChangedEvent) { trigger(self.onActiveCellChanged, getActiveCell()); }
5007 //}
5008 }
5009
5010 function clearTextSelection() {
5011 if (document.selection && document.selection.empty) {
5012 try {
5013 //IE fails here if selected element is not in dom
5014 document.selection.empty();
5015 } catch (e) { }
5016 } else if (window.getSelection) {
5017 var sel = window.getSelection();
5018 if (sel && sel.removeAllRanges) {
5019 sel.removeAllRanges();
5020 }
5021 }
5022 }
5023
5024 function isCellPotentiallyEditable(row, cell) {
5025 var dataLength = getDataLength();
5026 // is the data for this row loaded?
5027 if (row < dataLength && !getDataItem(row)) {
5028 return false;
5029 }
5030
5031 // are we in the Add New row? can we create new from this cell?
5032 if (columns[cell].cannotTriggerInsert && row >= dataLength) {
5033 return false;
5034 }
5035
5036 // does this cell have an editor?
5037 if (!getEditor(row, cell)) {
5038 return false;
5039 }
5040
5041 return true;
5042 }
5043
5044 function makeActiveCellNormal() {
5045 if (!currentEditor) {
5046 return;
5047 }
5048 trigger(self.onBeforeCellEditorDestroy, {editor: currentEditor});
5049 currentEditor.destroy();
5050 currentEditor = null;
5051
5052 if (activeCellNode) {
5053 var d = getDataItem(activeRow);
5054 $(activeCellNode).removeClass("editable invalid");
5055 if (d) {
5056 var column = columns[activeCell];
5057 var formatter = getFormatter(activeRow, column);
5058 var formatterResult = formatter(activeRow, activeCell, getDataItemValueForColumn(d, column), column, d, self);
5059 applyFormatResultToCellNode(formatterResult, activeCellNode);
5060 invalidatePostProcessingResults(activeRow);
5061 }
5062 }
5063
5064 // if there previously was text selected on a page (such as selected text in the edit cell just removed),
5065 // IE can't set focus to anything else correctly
5066 if (navigator.userAgent.toLowerCase().match(/msie/)) {
5067 clearTextSelection();
5068 }
5069
5070 getEditorLock().deactivate(editController);
5071 }
5072
5073 function makeActiveCellEditable(editor, preClickModeOn, e) {
5074 if (!activeCellNode) {
5075 return;
5076 }
5077 if (!options.editable) {
5078 throw new Error("SlickGrid makeActiveCellEditable : should never get called when options.editable is false");
5079 }
5080
5081 // cancel pending async call if there is one
5082 clearTimeout(h_editorLoader);
5083
5084 if (!isCellPotentiallyEditable(activeRow, activeCell)) {
5085 return;
5086 }
5087
5088 var columnDef = columns[activeCell];
5089 var item = getDataItem(activeRow);
5090
5091 if (trigger(self.onBeforeEditCell, {row: activeRow, cell: activeCell, item: item, column: columnDef, target: 'grid' }) === false) {
5092 setFocus();
5093 return;
5094 }
5095
5096 getEditorLock().activate(editController);
5097 $(activeCellNode).addClass("editable");
5098
5099 var useEditor = editor || getEditor(activeRow, activeCell);
5100
5101 // don't clear the cell if a custom editor is passed through
5102 if (!editor && !useEditor.suppressClearOnEdit) {
5103 activeCellNode.innerHTML = "";
5104 }
5105
5106 var metadata = data.getItemMetadata && data.getItemMetadata(activeRow);
5107 metadata = metadata && metadata.columns;
5108 var columnMetaData = metadata && ( metadata[columnDef.id] || metadata[activeCell] );
5109
5110 currentEditor = new useEditor({
5111 grid: self,
5112 gridPosition: absBox($container[0]),
5113 position: absBox(activeCellNode),
5114 container: activeCellNode,
5115 column: columnDef,
5116 columnMetaData: columnMetaData,
5117 item: item || {},
5118 event: e,
5119 commitChanges: commitEditAndSetFocus,
5120 cancelChanges: cancelEditAndSetFocus
5121 });
5122
5123 if (item) {
5124 currentEditor.loadValue(item);
5125 if (preClickModeOn && currentEditor.preClick) {
5126 currentEditor.preClick();
5127 }
5128 }
5129
5130 serializedEditorValue = currentEditor.serializeValue();
5131
5132 if (currentEditor.position) {
5133 handleActiveCellPositionChange();
5134 }
5135 }
5136
5137 function commitEditAndSetFocus() {
5138 // if the commit fails, it would do so due to a validation error
5139 // if so, do not steal the focus from the editor
5140 if (getEditorLock().commitCurrentEdit()) {
5141 setFocus();
5142 if (options.autoEdit) {
5143 navigateDown();
5144 }
5145 }
5146 }
5147
5148 function cancelEditAndSetFocus() {
5149 if (getEditorLock().cancelCurrentEdit()) {
5150 setFocus();
5151 }
5152 }
5153
5154 function absBox(elem) {
5155 var box = {
5156 top: elem.offsetTop,
5157 left: elem.offsetLeft,
5158 bottom: 0,
5159 right: 0,
5160 width: $(elem).outerWidth(),
5161 height: $(elem).outerHeight(),
5162 visible: true
5163 };
5164 box.bottom = box.top + box.height;
5165 box.right = box.left + box.width;
5166
5167 // walk up the tree
5168 var offsetParent = elem.offsetParent;
5169 while ((elem = elem.parentNode) != document.body) {
5170 if (elem == null) break;
5171
5172 if (box.visible && elem.scrollHeight != elem.offsetHeight && $(elem).css("overflowY") != "visible") {
5173 box.visible = box.bottom > elem.scrollTop && box.top < elem.scrollTop + elem.clientHeight;
5174 }
5175
5176 if (box.visible && elem.scrollWidth != elem.offsetWidth && $(elem).css("overflowX") != "visible") {
5177 box.visible = box.right > elem.scrollLeft && box.left < elem.scrollLeft + elem.clientWidth;
5178 }
5179
5180 box.left -= elem.scrollLeft;
5181 box.top -= elem.scrollTop;
5182
5183 if (elem === offsetParent) {
5184 box.left += elem.offsetLeft;
5185 box.top += elem.offsetTop;
5186 offsetParent = elem.offsetParent;
5187 }
5188
5189 box.bottom = box.top + box.height;
5190 box.right = box.left + box.width;
5191 }
5192
5193 return box;
5194 }
5195
5196 function getActiveCellPosition() {
5197 return absBox(activeCellNode);
5198 }
5199
5200 function getGridPosition() {
5201 return absBox($container[0]);
5202 }
5203
5204 function handleActiveCellPositionChange() {
5205 if (!activeCellNode) {
5206 return;
5207 }
5208
5209 trigger(self.onActiveCellPositionChanged, {});
5210
5211 if (currentEditor) {
5212 var cellBox = getActiveCellPosition();
5213 if (currentEditor.show && currentEditor.hide) {
5214 if (!cellBox.visible) {
5215 currentEditor.hide();
5216 } else {
5217 currentEditor.show();
5218 }
5219 }
5220
5221 if (currentEditor.position) {
5222 currentEditor.position(cellBox);
5223 }
5224 }
5225 }
5226
5227 function getCellEditor() {
5228 return currentEditor;
5229 }
5230
5231 function getActiveCell() {
5232 if (!activeCellNode) {
5233 return null;
5234 } else {
5235 return {row: activeRow, cell: activeCell};
5236 }
5237 }
5238
5239 function getActiveCellNode() {
5240 return activeCellNode;
5241 }
5242
5243 //This get/set methods are used for keeping text-selection. These don't consider IE because they don't loose text-selection.
5244 //Fix for firefox selection. See https://github.com/mleibman/SlickGrid/pull/746/files
5245 function getTextSelection(){
5246 var textSelection = null;
5247 if (window.getSelection) {
5248 var selection = window.getSelection();
5249 if (selection.rangeCount > 0) {
5250 textSelection = selection.getRangeAt(0);
5251 }
5252 }
5253 return textSelection;
5254 }
5255
5256 function setTextSelection(selection){
5257 if (window.getSelection && selection) {
5258 var target = window.getSelection();
5259 target.removeAllRanges();
5260 target.addRange(selection);
5261 }
5262 }
5263
5264 function scrollRowIntoView(row, doPaging) {
5265 if (!hasFrozenRows ||
5266 ( !options.frozenBottom && row > actualFrozenRow - 1 ) ||
5267 ( options.frozenBottom && row < actualFrozenRow - 1 ) ) {
5268
5269 var viewportScrollH = $viewportScrollContainerY.height();
5270
5271 // if frozen row on top
5272 // subtract number of frozen row
5273 var rowNumber = ( hasFrozenRows && !options.frozenBottom ? row - options.frozenRow : row );
5274
5275 var rowAtTop = rowNumber * options.rowHeight;
5276 var rowAtBottom = (rowNumber + 1) * options.rowHeight
5277 - viewportScrollH
5278 + (viewportHasHScroll ? scrollbarDimensions.height : 0);
5279
5280 // need to page down?
5281 if ((rowNumber + 1) * options.rowHeight > scrollTop + viewportScrollH + offset) {
5282 scrollTo(doPaging ? rowAtTop : rowAtBottom);
5283 render();
5284 }
5285 // or page up?
5286 else if (rowNumber * options.rowHeight < scrollTop + offset) {
5287 scrollTo(doPaging ? rowAtBottom : rowAtTop);
5288 render();
5289 }
5290 }
5291 }
5292
5293 function scrollRowToTop(row) {
5294 scrollTo(row * options.rowHeight);
5295 render();
5296 }
5297
5298 function scrollPage(dir) {
5299 var deltaRows = dir * numVisibleRows;
5300 /// First fully visible row crosses the line with
5301 /// y == bottomOfTopmostFullyVisibleRow
5302 var bottomOfTopmostFullyVisibleRow = scrollTop + options.rowHeight - 1;
5303 scrollTo((getRowFromPosition(bottomOfTopmostFullyVisibleRow) + deltaRows) * options.rowHeight);
5304 render();
5305
5306 if (options.enableCellNavigation && activeRow != null) {
5307 var row = activeRow + deltaRows;
5308 var dataLengthIncludingAddNew = getDataLengthIncludingAddNew();
5309 if (row >= dataLengthIncludingAddNew) {
5310 row = dataLengthIncludingAddNew - 1;
5311 }
5312 if (row < 0) {
5313 row = 0;
5314 }
5315
5316 var cell = 0, prevCell = null;
5317 var prevActivePosX = activePosX;
5318 while (cell <= activePosX) {
5319 if (canCellBeActive(row, cell)) {
5320 prevCell = cell;
5321 }
5322 cell += getColspan(row, cell);
5323 }
5324
5325 if (prevCell !== null) {
5326 setActiveCellInternal(getCellNode(row, prevCell));
5327 activePosX = prevActivePosX;
5328 } else {
5329 resetActiveCell();
5330 }
5331 }
5332 }
5333
5334 function navigatePageDown() {
5335 scrollPage(1);
5336 }
5337
5338 function navigatePageUp() {
5339 scrollPage(-1);
5340 }
5341
5342 function navigateTop() {
5343 navigateToRow(0);
5344 }
5345
5346 function navigateBottom() {
5347 navigateToRow(getDataLength()-1);
5348 }
5349
5350 function navigateToRow(row) {
5351 var num_rows = getDataLength();
5352 if (!num_rows) return true;
5353
5354 if (row < 0) row = 0;
5355 else if (row >= num_rows) row = num_rows - 1;
5356
5357 scrollCellIntoView(row, 0, true);
5358 if (options.enableCellNavigation && activeRow != null) {
5359 var cell = 0, prevCell = null;
5360 var prevActivePosX = activePosX;
5361 while (cell <= activePosX) {
5362 if (canCellBeActive(row, cell)) {
5363 prevCell = cell;
5364 }
5365 cell += getColspan(row, cell);
5366 }
5367
5368 if (prevCell !== null) {
5369 setActiveCellInternal(getCellNode(row, prevCell));
5370 activePosX = prevActivePosX;
5371 } else {
5372 resetActiveCell();
5373 }
5374 }
5375 return true;
5376 }
5377
5378 function getColspan(row, cell) {
5379 var metadata = data.getItemMetadata && data.getItemMetadata(row);
5380 if (!metadata || !metadata.columns) {
5381 return 1;
5382 }
5383
5384 var columnData = metadata.columns[columns[cell].id] || metadata.columns[cell];
5385 var colspan = (columnData && columnData.colspan);
5386 if (colspan === "*") {
5387 colspan = columns.length - cell;
5388 } else {
5389 colspan = colspan || 1;
5390 }
5391
5392 return colspan;
5393 }
5394
5395 function findFirstFocusableCell(row) {
5396 var cell = 0;
5397 while (cell < columns.length) {
5398 if (canCellBeActive(row, cell)) {
5399 return cell;
5400 }
5401 cell += getColspan(row, cell);
5402 }
5403 return null;
5404 }
5405
5406 function findLastFocusableCell(row) {
5407 var cell = 0;
5408 var lastFocusableCell = null;
5409 while (cell < columns.length) {
5410 if (canCellBeActive(row, cell)) {
5411 lastFocusableCell = cell;
5412 }
5413 cell += getColspan(row, cell);
5414 }
5415 return lastFocusableCell;
5416 }
5417
5418 function gotoRight(row, cell, posX) {
5419 if (cell >= columns.length) {
5420 return null;
5421 }
5422
5423 do {
5424 cell += getColspan(row, cell);
5425 }
5426 while (cell < columns.length && !canCellBeActive(row, cell));
5427
5428 if (cell < columns.length) {
5429 return {
5430 "row": row,
5431 "cell": cell,
5432 "posX": cell
5433 };
5434 }
5435 return null;
5436 }
5437
5438 function gotoLeft(row, cell, posX) {
5439 if (cell <= 0) {
5440 return null;
5441 }
5442
5443 var firstFocusableCell = findFirstFocusableCell(row);
5444 if (firstFocusableCell === null || firstFocusableCell >= cell) {
5445 return null;
5446 }
5447
5448 var prev = {
5449 "row": row,
5450 "cell": firstFocusableCell,
5451 "posX": firstFocusableCell
5452 };
5453 var pos;
5454 while (true) {
5455 pos = gotoRight(prev.row, prev.cell, prev.posX);
5456 if (!pos) {
5457 return null;
5458 }
5459 if (pos.cell >= cell) {
5460 return prev;
5461 }
5462 prev = pos;
5463 }
5464 }
5465
5466 function gotoDown(row, cell, posX) {
5467 var prevCell;
5468 var dataLengthIncludingAddNew = getDataLengthIncludingAddNew();
5469 while (true) {
5470 if (++row >= dataLengthIncludingAddNew) {
5471 return null;
5472 }
5473
5474 prevCell = cell = 0;
5475 while (cell <= posX) {
5476 prevCell = cell;
5477 cell += getColspan(row, cell);
5478 }
5479
5480 if (canCellBeActive(row, prevCell)) {
5481 return {
5482 "row": row,
5483 "cell": prevCell,
5484 "posX": posX
5485 };
5486 }
5487 }
5488 }
5489
5490 function gotoUp(row, cell, posX) {
5491 var prevCell;
5492 while (true) {
5493 if (--row < 0) {
5494 return null;
5495 }
5496
5497 prevCell = cell = 0;
5498 while (cell <= posX) {
5499 prevCell = cell;
5500 cell += getColspan(row, cell);
5501 }
5502
5503 if (canCellBeActive(row, prevCell)) {
5504 return {
5505 "row": row,
5506 "cell": prevCell,
5507 "posX": posX
5508 };
5509 }
5510 }
5511 }
5512
5513 function gotoNext(row, cell, posX) {
5514 if (row == null && cell == null) {
5515 row = cell = posX = 0;
5516 if (canCellBeActive(row, cell)) {
5517 return {
5518 "row": row,
5519 "cell": cell,
5520 "posX": cell
5521 };
5522 }
5523 }
5524
5525 var pos = gotoRight(row, cell, posX);
5526 if (pos) {
5527 return pos;
5528 }
5529
5530 var firstFocusableCell = null;
5531 var dataLengthIncludingAddNew = getDataLengthIncludingAddNew();
5532
5533 // if at last row, cycle through columns rather than get stuck in the last one
5534 if (row === dataLengthIncludingAddNew - 1) { row--; }
5535
5536 while (++row < dataLengthIncludingAddNew) {
5537 firstFocusableCell = findFirstFocusableCell(row);
5538 if (firstFocusableCell !== null) {
5539 return {
5540 "row": row,
5541 "cell": firstFocusableCell,
5542 "posX": firstFocusableCell
5543 };
5544 }
5545 }
5546 return null;
5547 }
5548
5549 function gotoPrev(row, cell, posX) {
5550 if (row == null && cell == null) {
5551 row = getDataLengthIncludingAddNew() - 1;
5552 cell = posX = columns.length - 1;
5553 if (canCellBeActive(row, cell)) {
5554 return {
5555 "row": row,
5556 "cell": cell,
5557 "posX": cell
5558 };
5559 }
5560 }
5561
5562 var pos;
5563 var lastSelectableCell;
5564 while (!pos) {
5565 pos = gotoLeft(row, cell, posX);
5566 if (pos) {
5567 break;
5568 }
5569 if (--row < 0) {
5570 return null;
5571 }
5572
5573 cell = 0;
5574 lastSelectableCell = findLastFocusableCell(row);
5575 if (lastSelectableCell !== null) {
5576 pos = {
5577 "row": row,
5578 "cell": lastSelectableCell,
5579 "posX": lastSelectableCell
5580 };
5581 }
5582 }
5583 return pos;
5584 }
5585
5586 function gotoRowStart(row, cell, posX) {
5587 var newCell = findFirstFocusableCell(row);
5588 if (newCell === null) return null;
5589
5590 return {
5591 "row": row,
5592 "cell": newCell,
5593 "posX": newCell
5594 };
5595 }
5596
5597 function gotoRowEnd(row, cell, posX) {
5598 var newCell = findLastFocusableCell(row);
5599 if (newCell === null) return null;
5600
5601 return {
5602 "row": row,
5603 "cell": newCell,
5604 "posX": newCell
5605 };
5606 }
5607
5608 function navigateRight() {
5609 return navigate("right");
5610 }
5611
5612 function navigateLeft() {
5613 return navigate("left");
5614 }
5615
5616 function navigateDown() {
5617 return navigate("down");
5618 }
5619
5620 function navigateUp() {
5621 return navigate("up");
5622 }
5623
5624 function navigateNext() {
5625 return navigate("next");
5626 }
5627
5628 function navigatePrev() {
5629 return navigate("prev");
5630 }
5631
5632 function navigateRowStart() {
5633 return navigate("home");
5634 }
5635
5636 function navigateRowEnd() {
5637 return navigate("end");
5638 }
5639
5640 /**
5641 * @param {string} dir Navigation direction.
5642 * @return {boolean} Whether navigation resulted in a change of active cell.
5643 */
5644 function navigate(dir) {
5645 if (!options.enableCellNavigation) {
5646 return false;
5647 }
5648
5649 if (!activeCellNode && dir != "prev" && dir != "next") {
5650 return false;
5651 }
5652
5653 if (!getEditorLock().commitCurrentEdit()) {
5654 return true;
5655 }
5656 setFocus();
5657
5658 var tabbingDirections = {
5659 "up": -1,
5660 "down": 1,
5661 "left": -1,
5662 "right": 1,
5663 "prev": -1,
5664 "next": 1,
5665 "home": -1,
5666 "end": 1
5667 };
5668 tabbingDirection = tabbingDirections[dir];
5669
5670 var stepFunctions = {
5671 "up": gotoUp,
5672 "down": gotoDown,
5673 "left": gotoLeft,
5674 "right": gotoRight,
5675 "prev": gotoPrev,
5676 "next": gotoNext,
5677 "home": gotoRowStart,
5678 "end": gotoRowEnd
5679 };
5680 var stepFn = stepFunctions[dir];
5681 var pos = stepFn(activeRow, activeCell, activePosX);
5682 if (pos) {
5683 if (hasFrozenRows && options.frozenBottom & pos.row == getDataLength()) {
5684 return;
5685 }
5686
5687 var isAddNewRow = (pos.row == getDataLength());
5688
5689 if (( !options.frozenBottom && pos.row >= actualFrozenRow )
5690 || ( options.frozenBottom && pos.row < actualFrozenRow )
5691 ) {
5692 scrollCellIntoView(pos.row, pos.cell, !isAddNewRow && options.emulatePagingWhenScrolling);
5693 }
5694 setActiveCellInternal(getCellNode(pos.row, pos.cell));
5695 activePosX = pos.posX;
5696 return true;
5697 } else {
5698 setActiveCellInternal(getCellNode(activeRow, activeCell));
5699 return false;
5700 }
5701 }
5702
5703 function getCellNode(row, cell) {
5704 if (rowsCache[row]) {
5705 ensureCellNodesInRowsCache(row);
5706 try {
5707 if (rowsCache[row].cellNodesByColumnIdx.length > cell) {
5708 return rowsCache[row].cellNodesByColumnIdx[cell][0];
5709 }
5710 else {
5711 return null;
5712 }
5713 } catch (e) {
5714 return rowsCache[row].cellNodesByColumnIdx[cell];
5715 }
5716 }
5717 return null;
5718 }
5719
5720 function setActiveCell(row, cell, opt_editMode, preClickModeOn, suppressActiveCellChangedEvent) {
5721 if (!initialized) { return; }
5722 if (row > getDataLength() || row < 0 || cell >= columns.length || cell < 0) {
5723 return;
5724 }
5725
5726 if (!options.enableCellNavigation) {
5727 return;
5728 }
5729
5730 scrollCellIntoView(row, cell, false);
5731 setActiveCellInternal(getCellNode(row, cell), opt_editMode, preClickModeOn, suppressActiveCellChangedEvent);
5732 }
5733
5734 function setActiveRow(row, cell, suppressScrollIntoView) {
5735 if (!initialized) { return; }
5736 if (row > getDataLength() || row < 0 || cell >= columns.length || cell < 0) {
5737 return;
5738 }
5739
5740 activeRow = row;
5741 if (!suppressScrollIntoView) {
5742 scrollCellIntoView(row, cell || 0, false);
5743 }
5744 }
5745
5746 function canCellBeActive(row, cell) {
5747 if (!options.enableCellNavigation || row >= getDataLengthIncludingAddNew() ||
5748 row < 0 || cell >= columns.length || cell < 0) {
5749 return false;
5750 }
5751
5752 var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
5753 if (rowMetadata && typeof rowMetadata.focusable !== "undefined") {
5754 return !!rowMetadata.focusable;
5755 }
5756
5757 var columnMetadata = rowMetadata && rowMetadata.columns;
5758 if (columnMetadata && columnMetadata[columns[cell].id] && typeof columnMetadata[columns[cell].id].focusable !== "undefined") {
5759 return !!columnMetadata[columns[cell].id].focusable;
5760 }
5761 if (columnMetadata && columnMetadata[cell] && typeof columnMetadata[cell].focusable !== "undefined") {
5762 return !!columnMetadata[cell].focusable;
5763 }
5764
5765 return !!columns[cell].focusable;
5766 }
5767
5768 function canCellBeSelected(row, cell) {
5769 if (row >= getDataLength() || row < 0 || cell >= columns.length || cell < 0) {
5770 return false;
5771 }
5772
5773 var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
5774 if (rowMetadata && typeof rowMetadata.selectable !== "undefined") {
5775 return !!rowMetadata.selectable;
5776 }
5777
5778 var columnMetadata = rowMetadata && rowMetadata.columns && (rowMetadata.columns[columns[cell].id] || rowMetadata.columns[cell]);
5779 if (columnMetadata && typeof columnMetadata.selectable !== "undefined") {
5780 return !!columnMetadata.selectable;
5781 }
5782
5783 return !!columns[cell].selectable;
5784 }
5785
5786 function gotoCell(row, cell, forceEdit, e) {
5787 if (!initialized) { return; }
5788 if (!canCellBeActive(row, cell)) {
5789 return;
5790 }
5791
5792 if (!getEditorLock().commitCurrentEdit()) {
5793 return;
5794 }
5795
5796 scrollCellIntoView(row, cell, false);
5797
5798 var newCell = getCellNode(row, cell);
5799
5800 // if selecting the 'add new' row, start editing right away
5801 var column = columns[cell];
5802 var suppressActiveCellChangedEvent = !!(options.editable && column && column.editor && options.suppressActiveCellChangeOnEdit);
5803 setActiveCellInternal(newCell, (forceEdit || (row === getDataLength()) || options.autoEdit), null, suppressActiveCellChangedEvent, e);
5804
5805 // if no editor was created, set the focus back on the grid
5806 if (!currentEditor) {
5807 setFocus();
5808 }
5809 }
5810
5811
5812 //////////////////////////////////////////////////////////////////////////////////////////////
5813 // IEditor implementation for the editor lock
5814
5815 function commitCurrentEdit() {
5816 var item = getDataItem(activeRow);
5817 var column = columns[activeCell];
5818
5819 if (currentEditor) {
5820 if (currentEditor.isValueChanged()) {
5821 var validationResults = currentEditor.validate();
5822
5823 if (validationResults.valid) {
5824 if (activeRow < getDataLength()) {
5825 var editCommand = {
5826 row: activeRow,
5827 cell: activeCell,
5828 editor: currentEditor,
5829 serializedValue: currentEditor.serializeValue(),
5830 prevSerializedValue: serializedEditorValue,
5831 execute: function () {
5832 this.editor.applyValue(item, this.serializedValue);
5833 updateRow(this.row);
5834 trigger(self.onCellChange, {
5835 command: 'execute',
5836 row: this.row,
5837 cell: this.cell,
5838 item: item,
5839 column: column
5840 });
5841 },
5842 undo: function () {
5843 this.editor.applyValue(item, this.prevSerializedValue);
5844 updateRow(this.row);
5845 trigger(self.onCellChange, {
5846 command: 'undo',
5847 row: this.row,
5848 cell: this.cell,
5849 item: item,
5850 column: column
5851 });
5852 }
5853 };
5854
5855 if (options.editCommandHandler) {
5856 makeActiveCellNormal();
5857 options.editCommandHandler(item, column, editCommand);
5858 } else {
5859 editCommand.execute();
5860 makeActiveCellNormal();
5861 }
5862
5863 } else {
5864 var newItem = {};
5865 currentEditor.applyValue(newItem, currentEditor.serializeValue());
5866 makeActiveCellNormal();
5867 trigger(self.onAddNewRow, {item: newItem, column: column});
5868 }
5869
5870 // check whether the lock has been re-acquired by event handlers
5871 return !getEditorLock().isActive();
5872 } else {
5873 // Re-add the CSS class to trigger transitions, if any.
5874 $(activeCellNode).removeClass("invalid");
5875 $(activeCellNode).width(); // force layout
5876 $(activeCellNode).addClass("invalid");
5877
5878 trigger(self.onValidationError, {
5879 editor: currentEditor,
5880 cellNode: activeCellNode,
5881 validationResults: validationResults,
5882 row: activeRow,
5883 cell: activeCell,
5884 column: column
5885 });
5886
5887 currentEditor.focus();
5888 return false;
5889 }
5890 }
5891
5892 makeActiveCellNormal();
5893 }
5894 return true;
5895 }
5896
5897 function cancelCurrentEdit() {
5898 makeActiveCellNormal();
5899 return true;
5900 }
5901
5902 function rowsToRanges(rows) {
5903 var ranges = [];
5904 var lastCell = columns.length - 1;
5905 for (var i = 0; i < rows.length; i++) {
5906 ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));
5907 }
5908 return ranges;
5909 }
5910
5911 function getSelectedRows() {
5912 if (!selectionModel) {
5913 throw new Error("SlickGrid Selection model is not set");
5914 }
5915 return selectedRows.slice(0);
5916 }
5917
5918 function setSelectedRows(rows) {
5919 if (!selectionModel) {
5920 throw new Error("SlickGrid Selection model is not set");
5921 }
5922 if (self && self.getEditorLock && !self.getEditorLock().isActive()) {
5923 selectionModel.setSelectedRanges(rowsToRanges(rows));
5924 }
5925 }
5926
5927 /** basic html sanitizer to avoid scripting attack */
5928 function sanitizeHtmlString(dirtyHtml) {
5929 return typeof dirtyHtml === 'string' ? dirtyHtml.replace(/(\b)(on\S+)(\s*)=|javascript:([^>]*)[^>]*|(<\s*)(\/*)script([<>]*).*(<\s*)(\/*)script(>*)|(&lt;)(\/*)(script|script defer)(.*)(&gt;|&gt;">)/gi, '') : dirtyHtml;
5930 }
5931
5932 //////////////////////////////////////////////////////////////////////////////////////////////
5933 // Debug
5934
5935 this.debug = function () {
5936 var s = "";
5937
5938 s += ("\n" + "counter_rows_rendered: " + counter_rows_rendered);
5939 s += ("\n" + "counter_rows_removed: " + counter_rows_removed);
5940 s += ("\n" + "renderedRows: " + renderedRows);
5941 s += ("\n" + "numVisibleRows: " + numVisibleRows);
5942 s += ("\n" + "maxSupportedCssHeight: " + maxSupportedCssHeight);
5943 s += ("\n" + "n(umber of pages): " + n);
5944 s += ("\n" + "(current) page: " + page);
5945 s += ("\n" + "page height (ph): " + ph);
5946 s += ("\n" + "vScrollDir: " + vScrollDir);
5947
5948 alert(s);
5949 };
5950
5951 // a debug helper to be able to access private members
5952 this.eval = function (expr) {
5953 return eval(expr);
5954 };
5955
5956 //////////////////////////////////////////////////////////////////////////////////////////////
5957 // Public API
5958
5959 $.extend(this, {
5960 "slickGridVersion": "2.4.43",
5961
5962 // Events
5963 "onScroll": new Slick.Event(),
5964 "onBeforeSort": new Slick.Event(),
5965 "onSort": new Slick.Event(),
5966 "onHeaderMouseEnter": new Slick.Event(),
5967 "onHeaderMouseLeave": new Slick.Event(),
5968 "onHeaderRowMouseEnter": new Slick.Event(),
5969 "onHeaderRowMouseLeave": new Slick.Event(),
5970 "onHeaderContextMenu": new Slick.Event(),
5971 "onHeaderClick": new Slick.Event(),
5972 "onHeaderCellRendered": new Slick.Event(),
5973 "onBeforeHeaderCellDestroy": new Slick.Event(),
5974 "onHeaderRowCellRendered": new Slick.Event(),
5975 "onFooterRowCellRendered": new Slick.Event(),
5976 "onFooterContextMenu": new Slick.Event(),
5977 "onFooterClick": new Slick.Event(),
5978 "onBeforeHeaderRowCellDestroy": new Slick.Event(),
5979 "onBeforeFooterRowCellDestroy": new Slick.Event(),
5980 "onMouseEnter": new Slick.Event(),
5981 "onMouseLeave": new Slick.Event(),
5982 "onClick": new Slick.Event(),
5983 "onDblClick": new Slick.Event(),
5984 "onContextMenu": new Slick.Event(),
5985 "onKeyDown": new Slick.Event(),
5986 "onAddNewRow": new Slick.Event(),
5987 "onBeforeAppendCell": new Slick.Event(),
5988 "onValidationError": new Slick.Event(),
5989 "onViewportChanged": new Slick.Event(),
5990 "onColumnsReordered": new Slick.Event(),
5991 "onColumnsDrag": new Slick.Event(),
5992 "onColumnsResized": new Slick.Event(),
5993 "onColumnsResizeDblClick": new Slick.Event(),
5994 "onBeforeColumnsResize": new Slick.Event(),
5995 "onCellChange": new Slick.Event(),
5996 "onCompositeEditorChange": new Slick.Event(),
5997 "onBeforeEditCell": new Slick.Event(),
5998 "onBeforeCellEditorDestroy": new Slick.Event(),
5999 "onBeforeDestroy": new Slick.Event(),
6000 "onActiveCellChanged": new Slick.Event(),
6001 "onActiveCellPositionChanged": new Slick.Event(),
6002 "onDragInit": new Slick.Event(),
6003 "onDragStart": new Slick.Event(),
6004 "onDrag": new Slick.Event(),
6005 "onDragEnd": new Slick.Event(),
6006 "onSelectedRowsChanged": new Slick.Event(),
6007 "onCellCssStylesChanged": new Slick.Event(),
6008 "onAutosizeColumns": new Slick.Event(),
6009 "onBeforeSetColumns": new Slick.Event(),
6010 "onRendered": new Slick.Event(),
6011 "onSetOptions": new Slick.Event(),
6012
6013 // Methods
6014 "registerPlugin": registerPlugin,
6015 "unregisterPlugin": unregisterPlugin,
6016 "getPluginByName": getPluginByName,
6017 "getColumns": getColumns,
6018 "setColumns": setColumns,
6019 "getColumnIndex": getColumnIndex,
6020 "updateColumnHeader": updateColumnHeader,
6021 "setSortColumn": setSortColumn,
6022 "setSortColumns": setSortColumns,
6023 "getSortColumns": getSortColumns,
6024 "autosizeColumns": autosizeColumns,
6025 "autosizeColumn": autosizeColumn,
6026 "getOptions": getOptions,
6027 "setOptions": setOptions,
6028 "getData": getData,
6029 "getDataLength": getDataLength,
6030 "getDataItem": getDataItem,
6031 "setData": setData,
6032 "getSelectionModel": getSelectionModel,
6033 "setSelectionModel": setSelectionModel,
6034 "getSelectedRows": getSelectedRows,
6035 "setSelectedRows": setSelectedRows,
6036 "getContainerNode": getContainerNode,
6037 "updatePagingStatusFromView": updatePagingStatusFromView,
6038 "applyFormatResultToCellNode": applyFormatResultToCellNode,
6039
6040 "render": render,
6041 "reRenderColumns": reRenderColumns,
6042 "invalidate": invalidate,
6043 "invalidateRow": invalidateRow,
6044 "invalidateRows": invalidateRows,
6045 "invalidateAllRows": invalidateAllRows,
6046 "updateCell": updateCell,
6047 "updateRow": updateRow,
6048 "getViewport": getVisibleRange,
6049 "getRenderedRange": getRenderedRange,
6050 "resizeCanvas": resizeCanvas,
6051 "updateRowCount": updateRowCount,
6052 "scrollRowIntoView": scrollRowIntoView,
6053 "scrollRowToTop": scrollRowToTop,
6054 "scrollCellIntoView": scrollCellIntoView,
6055 "scrollColumnIntoView": scrollColumnIntoView,
6056 "getCanvasNode": getCanvasNode,
6057 "getUID": getUID,
6058 "getHeaderColumnWidthDiff": getHeaderColumnWidthDiff,
6059 "getScrollbarDimensions": getScrollbarDimensions,
6060 "getHeadersWidth": getHeadersWidth,
6061 "getCanvasWidth": getCanvasWidth,
6062 "getCanvases": getCanvases,
6063 "getActiveCanvasNode": getActiveCanvasNode,
6064 "setActiveCanvasNode": setActiveCanvasNode,
6065 "getViewportNode": getViewportNode,
6066 "getViewports": getViewports,
6067 "getActiveViewportNode": getActiveViewportNode,
6068 "setActiveViewportNode": setActiveViewportNode,
6069 "focus": setFocus,
6070 "scrollTo": scrollTo,
6071 "cacheCssForHiddenInit": cacheCssForHiddenInit,
6072 "restoreCssFromHiddenInit": restoreCssFromHiddenInit,
6073
6074 "getCellFromPoint": getCellFromPoint,
6075 "getCellFromEvent": getCellFromEvent,
6076 "getActiveCell": getActiveCell,
6077 "setActiveCell": setActiveCell,
6078 "setActiveRow": setActiveRow,
6079 "getActiveCellNode": getActiveCellNode,
6080 "getActiveCellPosition": getActiveCellPosition,
6081 "resetActiveCell": resetActiveCell,
6082 "editActiveCell": makeActiveCellEditable,
6083 "getCellEditor": getCellEditor,
6084 "getCellNode": getCellNode,
6085 "getCellNodeBox": getCellNodeBox,
6086 "canCellBeSelected": canCellBeSelected,
6087 "canCellBeActive": canCellBeActive,
6088 "navigatePrev": navigatePrev,
6089 "navigateNext": navigateNext,
6090 "navigateUp": navigateUp,
6091 "navigateDown": navigateDown,
6092 "navigateLeft": navigateLeft,
6093 "navigateRight": navigateRight,
6094 "navigatePageUp": navigatePageUp,
6095 "navigatePageDown": navigatePageDown,
6096 "navigateTop": navigateTop,
6097 "navigateBottom": navigateBottom,
6098 "navigateRowStart": navigateRowStart,
6099 "navigateRowEnd": navigateRowEnd,
6100 "gotoCell": gotoCell,
6101 "getTopPanel": getTopPanel,
6102 "setTopPanelVisibility": setTopPanelVisibility,
6103 "getPreHeaderPanel": getPreHeaderPanel,
6104 "getPreHeaderPanelLeft": getPreHeaderPanel,
6105 "getPreHeaderPanelRight": getPreHeaderPanelRight,
6106 "setPreHeaderPanelVisibility": setPreHeaderPanelVisibility,
6107 "getHeader": getHeader,
6108 "getHeaderColumn": getHeaderColumn,
6109 "setHeaderRowVisibility": setHeaderRowVisibility,
6110 "getHeaderRow": getHeaderRow,
6111 "getHeaderRowColumn": getHeaderRowColumn,
6112 "setFooterRowVisibility": setFooterRowVisibility,
6113 "getFooterRow": getFooterRow,
6114 "getFooterRowColumn": getFooterRowColumn,
6115 "getGridPosition": getGridPosition,
6116 "flashCell": flashCell,
6117 "addCellCssStyles": addCellCssStyles,
6118 "setCellCssStyles": setCellCssStyles,
6119 "removeCellCssStyles": removeCellCssStyles,
6120 "getCellCssStyles": getCellCssStyles,
6121 "getFrozenRowOffset": getFrozenRowOffset,
6122 "setColumnHeaderVisibility": setColumnHeaderVisibility,
6123 "sanitizeHtmlString": sanitizeHtmlString,
6124
6125 "init": finishInitialization,
6126 "destroy": destroy,
6127
6128 // IEditor implementation
6129 "getEditorLock": getEditorLock,
6130 "getEditController": getEditController
6131 });
6132
6133 init();
6134 }
6135
6136 // exports
6137 $.extend(true, window, {
6138 Slick: {
6139 Grid: SlickGrid
6140 }
6141 });
6142}(jQuery));
\No newline at end of file