UNPKG

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