3 * (c) 2009-2012 Michael Leibman
\r
4 * michael{dot}leibman{at}gmail{dot}com
\r
5 * http://github.com/mleibman/slickgrid
\r
7 * Distributed under MIT license.
\r
8 * All rights reserved.
\r
13 * Cell/row DOM manipulations are done directly bypassing jQuery's DOM manipulation methods.
\r
14 * This increases the speed dramatically, but can only be done safely because there are no event handlers
\r
15 * or data associated with any cell/row DOM nodes. Cell editors must make sure they implement .destroy()
\r
16 * and do proper cleanup.
\r
19 // make sure required JavaScript modules are loaded
\r
20 if (typeof jQuery === "undefined") {
\r
21 throw "SlickGrid requires jquery module to be loaded";
\r
23 if (!jQuery.fn.drag) {
\r
24 throw "SlickGrid requires jquery.event.drag module to be loaded";
\r
26 if (typeof Slick === "undefined") {
\r
27 throw "slick.core.js not loaded";
\r
33 $.extend(true, window, {
\r
39 // shared across all grids on the page
\r
40 var scrollbarDimensions;
\r
41 var maxSupportedCssHeight; // browser's breaking point
\r
43 //////////////////////////////////////////////////////////////////////////////////////////////
\r
44 // SlickGrid class implementation (available as Slick.Grid)
\r
47 * Creates a new instance of the grid.
\r
50 * @param {Node} container Container node to create the grid in.
\r
51 * @param {Array,Object} data An array of objects for databinding.
\r
52 * @param {Array} columns An array of column definitions.
\r
53 * @param {Object} options Grid options.
\r
55 function SlickGrid(container, data, columns, options) {
\r
58 explicitInitialization: false,
\r
60 defaultColumnWidth: 80,
\r
61 enableAddRow: false,
\r
62 leaveSpaceForNewRows: false,
\r
65 enableCellNavigation: true,
\r
66 enableColumnReorder: true,
\r
67 asyncEditorLoading: false,
\r
68 asyncEditorLoadDelay: 100,
\r
69 forceFitColumns: false,
\r
70 enableAsyncPostRender: false,
\r
71 asyncPostRenderDelay: 50,
\r
73 editorLock: Slick.GlobalEditorLock,
\r
74 showHeaderRow: false,
\r
75 headerRowHeight: 25,
\r
76 showTopPanel: false,
\r
78 formatterFactory: null,
\r
79 editorFactory: null,
\r
80 cellFlashingCssClass: "flashing",
\r
81 selectedCellCssClass: "selected",
\r
83 enableTextSelectionOnCells: false,
\r
84 dataItemColumnValueExtractor: null,
\r
85 fullWidthRows: false,
\r
86 multiColumnSort: false,
\r
87 defaultFormatter: defaultFormatter,
\r
88 forceSyncScrolling: false
\r
91 var columnDefaults = {
\r
96 rerenderOnResize: false,
\r
97 headerCssClass: null,
\r
98 defaultSortAsc: true,
\r
104 var th; // virtual height
\r
105 var h; // real scrollable height
\r
106 var ph; // page height
\r
107 var n; // number of pages
\r
108 var cj; // "jumpiness" coefficient
\r
110 var page = 0; // current page
\r
111 var offset = 0; // current page offset
\r
112 var vScrollDir = 1;
\r
115 var initialized = false;
\r
117 var uid = "slickgrid_" + Math.round(1000000 * Math.random());
\r
119 var $focusSink, $focusSink2;
\r
120 var $headerScroller;
\r
122 var $headerRow, $headerRowScroller, $headerRowSpacer;
\r
123 var $topPanelScroller;
\r
128 var $boundAncestors;
\r
129 var stylesheet, columnCssRulesL, columnCssRulesR;
\r
130 var viewportH, viewportW;
\r
132 var viewportHasHScroll, viewportHasVScroll;
\r
133 var headerColumnWidthDiff = 0, headerColumnHeightDiff = 0, // border+padding
\r
134 cellWidthDiff = 0, cellHeightDiff = 0;
\r
135 var absoluteColumnMinWidth;
\r
136 var numberOfRows = 0;
\r
138 var tabbingDirection = 1;
\r
140 var activeRow, activeCell;
\r
141 var activeCellNode = null;
\r
142 var currentEditor = null;
\r
143 var serializedEditorValue;
\r
144 var editController;
\r
146 var rowsCache = {};
\r
147 var renderedRows = 0;
\r
148 var numVisibleRows;
\r
149 var prevScrollTop = 0;
\r
151 var lastRenderedScrollTop = 0;
\r
152 var lastRenderedScrollLeft = 0;
\r
153 var prevScrollLeft = 0;
\r
154 var scrollLeft = 0;
\r
156 var selectionModel;
\r
157 var selectedRows = [];
\r
160 var cellCssClasses = {};
\r
162 var columnsById = {};
\r
163 var sortColumns = [];
\r
164 var columnPosLeft = [];
\r
165 var columnPosRight = [];
\r
168 // async call handles
\r
169 var h_editorLoader = null;
\r
170 var h_render = null;
\r
171 var h_postrender = null;
\r
172 var postProcessedRows = {};
\r
173 var postProcessToRow = null;
\r
174 var postProcessFromRow = null;
\r
177 var counter_rows_rendered = 0;
\r
178 var counter_rows_removed = 0;
\r
181 //////////////////////////////////////////////////////////////////////////////////////////////
\r
185 $container = $(container);
\r
186 if ($container.length < 1) {
\r
187 throw new Error("SlickGrid requires a valid container, " + container + " does not exist in the DOM.");
\r
190 // calculate these only once and share between grid instances
\r
191 maxSupportedCssHeight = maxSupportedCssHeight || getMaxSupportedCssHeight();
\r
192 scrollbarDimensions = scrollbarDimensions || measureScrollbar();
\r
194 options = $.extend({}, defaults, options);
\r
195 validateAndEnforceOptions();
\r
196 columnDefaults.width = options.defaultColumnWidth;
\r
199 for (var i = 0; i < columns.length; i++) {
\r
200 var m = columns[i] = $.extend({}, columnDefaults, columns[i]);
\r
201 columnsById[m.id] = i;
\r
202 if (m.minWidth && m.width < m.minWidth) {
\r
203 m.width = m.minWidth;
\r
205 if (m.maxWidth && m.width > m.maxWidth) {
\r
206 m.width = m.maxWidth;
\r
210 // validate loaded JavaScript modules against requested options
\r
211 if (options.enableColumnReorder && !$.fn.sortable) {
\r
212 throw new Error("SlickGrid's 'enableColumnReorder = true' option requires jquery-ui.sortable module to be loaded");
\r
216 "commitCurrentEdit": commitCurrentEdit,
\r
217 "cancelCurrentEdit": cancelCurrentEdit
\r
222 .css("overflow", "hidden")
\r
225 .addClass("ui-widget");
\r
227 // set up a positioning container if needed
\r
228 if (!/relative|absolute|fixed/.test($container.css("position"))) {
\r
229 $container.css("position", "relative");
\r
232 $focusSink = $("<div tabIndex='0' hideFocus style='position:fixed;width:0;height:0;top:0;left:0;outline:0;'></div>").appendTo($container);
\r
234 $headerScroller = $("<div class='slick-header ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
\r
235 $headers = $("<div class='slick-header-columns' style='left:-1000px' />").appendTo($headerScroller);
\r
236 $headers.width(getHeadersWidth());
\r
238 $headerRowScroller = $("<div class='slick-headerrow ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
\r
239 $headerRow = $("<div class='slick-headerrow-columns' />").appendTo($headerRowScroller);
\r
240 $headerRowSpacer = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>")
\r
241 .css("width", getCanvasWidth() + scrollbarDimensions.width + "px")
\r
242 .appendTo($headerRowScroller);
\r
244 $topPanelScroller = $("<div class='slick-top-panel-scroller ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
\r
245 $topPanel = $("<div class='slick-top-panel' style='width:10000px' />").appendTo($topPanelScroller);
\r
247 if (!options.showTopPanel) {
\r
248 $topPanelScroller.hide();
\r
251 if (!options.showHeaderRow) {
\r
252 $headerRowScroller.hide();
\r
255 $viewport = $("<div class='slick-viewport' style='width:100%;overflow:auto;outline:0;position:relative;;'>").appendTo($container);
\r
256 $viewport.css("overflow-y", options.autoHeight ? "hidden" : "auto");
\r
258 $canvas = $("<div class='grid-canvas' />").appendTo($viewport);
\r
260 $focusSink2 = $focusSink.clone().appendTo($container);
\r
262 if (!options.explicitInitialization) {
\r
263 finishInitialization();
\r
267 function finishInitialization() {
\r
268 if (!initialized) {
\r
269 initialized = true;
\r
271 viewportW = parseFloat($.css($container[0], "width", true));
\r
273 // header columns and cells may have different padding/border skewing width calculations (box-sizing, hello?)
\r
274 // calculate the diff so we can set consistent sizes
\r
275 measureCellPaddingAndBorder();
\r
277 // for usability reasons, all text selection in SlickGrid is disabled
\r
278 // with the exception of input and textarea elements (selection must
\r
279 // be enabled there so that editors work as expected); note that
\r
280 // selection in grid cells (grid body) is already unavailable in
\r
281 // all browsers except IE
\r
282 disableSelection($headers); // disable all text selection in header (including input and textarea)
\r
284 if (!options.enableTextSelectionOnCells) {
\r
285 // disable text selection in grid cells except in input and textarea elements
\r
286 // (this is IE-specific, because selectstart event will only fire in IE)
\r
287 $viewport.bind("selectstart.ui", function (event) {
\r
288 return $(event.target).is("input,textarea");
\r
292 updateColumnCaches();
\r
293 createColumnHeaders();
\r
297 bindAncestorScrollEvents();
\r
300 .bind("resize.slickgrid", resizeCanvas);
\r
302 .bind("scroll", handleScroll);
\r
304 .bind("contextmenu", handleHeaderContextMenu)
\r
305 .bind("click", handleHeaderClick)
\r
306 .delegate(".slick-header-column", "mouseenter", handleHeaderMouseEnter)
\r
307 .delegate(".slick-header-column", "mouseleave", handleHeaderMouseLeave);
\r
309 .bind("scroll", handleHeaderRowScroll);
\r
310 $focusSink.add($focusSink2)
\r
311 .bind("keydown", handleKeyDown);
\r
313 .bind("keydown", handleKeyDown)
\r
314 .bind("click", handleClick)
\r
315 .bind("dblclick", handleDblClick)
\r
316 .bind("contextmenu", handleContextMenu)
\r
317 .bind("draginit", handleDragInit)
\r
318 .bind("dragstart", {distance: 3}, handleDragStart)
\r
319 .bind("drag", handleDrag)
\r
320 .bind("dragend", handleDragEnd)
\r
321 .delegate(".slick-cell", "mouseenter", handleMouseEnter)
\r
322 .delegate(".slick-cell", "mouseleave", handleMouseLeave);
\r
326 function registerPlugin(plugin) {
\r
327 plugins.unshift(plugin);
\r
331 function unregisterPlugin(plugin) {
\r
332 for (var i = plugins.length; i >= 0; i--) {
\r
333 if (plugins[i] === plugin) {
\r
334 if (plugins[i].destroy) {
\r
335 plugins[i].destroy();
\r
337 plugins.splice(i, 1);
\r
343 function setSelectionModel(model) {
\r
344 if (selectionModel) {
\r
345 selectionModel.onSelectedRangesChanged.unsubscribe(handleSelectedRangesChanged);
\r
346 if (selectionModel.destroy) {
\r
347 selectionModel.destroy();
\r
351 selectionModel = model;
\r
352 if (selectionModel) {
\r
353 selectionModel.init(self);
\r
354 selectionModel.onSelectedRangesChanged.subscribe(handleSelectedRangesChanged);
\r
358 function getSelectionModel() {
\r
359 return selectionModel;
\r
362 function getCanvasNode() {
\r
366 function measureScrollbar() {
\r
367 var $c = $("<div style='position:absolute; top:-10000px; left:-10000px; width:100px; height:100px; overflow:scroll;'></div>").appendTo("body");
\r
369 width: $c.width() - $c[0].clientWidth,
\r
370 height: $c.height() - $c[0].clientHeight
\r
376 function getHeadersWidth() {
\r
377 var headersWidth = 0;
\r
378 for (var i = 0, ii = columns.length; i < ii; i++) {
\r
379 var width = columns[i].width;
\r
380 headersWidth += width;
\r
382 headersWidth += scrollbarDimensions.width;
\r
383 return Math.max(headersWidth, viewportW) + 1000;
\r
386 function getCanvasWidth() {
\r
387 var availableWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW;
\r
389 var i = columns.length;
\r
391 rowWidth += columns[i].width;
\r
393 return options.fullWidthRows ? Math.max(rowWidth, availableWidth) : rowWidth;
\r
396 function updateCanvasWidth(forceColumnWidthsUpdate) {
\r
397 var oldCanvasWidth = canvasWidth;
\r
398 canvasWidth = getCanvasWidth();
\r
400 if (canvasWidth != oldCanvasWidth) {
\r
401 $canvas.width(canvasWidth);
\r
402 $headerRow.width(canvasWidth);
\r
403 $headers.width(getHeadersWidth());
\r
404 viewportHasHScroll = (canvasWidth > viewportW - scrollbarDimensions.width);
\r
407 $headerRowSpacer.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0));
\r
409 if (canvasWidth != oldCanvasWidth || forceColumnWidthsUpdate) {
\r
410 applyColumnWidths();
\r
414 function disableSelection($target) {
\r
415 if ($target && $target.jquery) {
\r
417 .attr("unselectable", "on")
\r
418 .css("MozUserSelect", "none")
\r
419 .bind("selectstart.ui", function () {
\r
421 }); // from jquery:ui.core.js 1.7.2
\r
425 function getMaxSupportedCssHeight() {
\r
426 var supportedHeight = 1000000;
\r
427 // FF reports the height back but still renders blank after ~6M px
\r
428 var testUpTo = navigator.userAgent.toLowerCase().match(/firefox/) ? 6000000 : 1000000000;
\r
429 var div = $("<div style='display:none' />").appendTo(document.body);
\r
432 var test = supportedHeight * 2;
\r
433 div.css("height", test);
\r
434 if (test > testUpTo || div.height() !== test) {
\r
437 supportedHeight = test;
\r
442 return supportedHeight;
\r
445 // TODO: this is static. need to handle page mutation.
\r
446 function bindAncestorScrollEvents() {
\r
447 var elem = $canvas[0];
\r
448 while ((elem = elem.parentNode) != document.body && elem != null) {
\r
449 // bind to scroll containers only
\r
450 if (elem == $viewport[0] || elem.scrollWidth != elem.clientWidth || elem.scrollHeight != elem.clientHeight) {
\r
451 var $elem = $(elem);
\r
452 if (!$boundAncestors) {
\r
453 $boundAncestors = $elem;
\r
455 $boundAncestors = $boundAncestors.add($elem);
\r
457 $elem.bind("scroll." + uid, handleActiveCellPositionChange);
\r
462 function unbindAncestorScrollEvents() {
\r
463 if (!$boundAncestors) {
\r
466 $boundAncestors.unbind("scroll." + uid);
\r
467 $boundAncestors = null;
\r
470 function updateColumnHeader(columnId, title, toolTip) {
\r
471 if (!initialized) { return; }
\r
472 var idx = getColumnIndex(columnId);
\r
477 var columnDef = columns[idx];
\r
478 var $header = $headers.children().eq(idx);
\r
480 if (title !== undefined) {
\r
481 columns[idx].name = title;
\r
483 if (toolTip !== undefined) {
\r
484 columns[idx].toolTip = toolTip;
\r
487 trigger(self.onBeforeHeaderCellDestroy, {
\r
488 "node": $header[0],
\r
489 "column": columnDef
\r
493 .attr("title", toolTip || "")
\r
494 .children().eq(0).html(title);
\r
496 trigger(self.onHeaderCellRendered, {
\r
497 "node": $header[0],
\r
498 "column": columnDef
\r
503 function getHeaderRow() {
\r
504 return $headerRow[0];
\r
507 function getHeaderRowColumn(columnId) {
\r
508 var idx = getColumnIndex(columnId);
\r
509 var $header = $headerRow.children().eq(idx);
\r
510 return $header && $header[0];
\r
513 function createColumnHeaders() {
\r
514 function onMouseEnter() {
\r
515 $(this).addClass("ui-state-hover");
\r
518 function onMouseLeave() {
\r
519 $(this).removeClass("ui-state-hover");
\r
522 $headers.find(".slick-header-column")
\r
524 var columnDef = $(this).data("column");
\r
526 trigger(self.onBeforeHeaderCellDestroy, {
\r
528 "column": columnDef
\r
533 $headers.width(getHeadersWidth());
\r
535 $headerRow.find(".slick-headerrow-column")
\r
537 var columnDef = $(this).data("column");
\r
539 trigger(self.onBeforeHeaderRowCellDestroy, {
\r
541 "column": columnDef
\r
545 $headerRow.empty();
\r
547 for (var i = 0; i < columns.length; i++) {
\r
548 var m = columns[i];
\r
550 var header = $("<div class='ui-state-default slick-header-column' />")
\r
551 .html("<span class='slick-column-name'>" + m.name + "</span>")
\r
552 .width(m.width - headerColumnWidthDiff)
\r
553 .attr("id", "" + uid + m.id)
\r
554 .attr("title", m.toolTip || "")
\r
556 .addClass(m.headerCssClass || "")
\r
557 .appendTo($headers);
\r
559 if (options.enableColumnReorder || m.sortable) {
\r
561 .on('mouseenter', onMouseEnter)
\r
562 .on('mouseleave', onMouseLeave);
\r
566 header.addClass("slick-header-sortable");
\r
567 header.append("<span class='slick-sort-indicator' />");
\r
570 trigger(self.onHeaderCellRendered, {
\r
575 if (options.showHeaderRow) {
\r
576 var headerRowCell = $("<div class='ui-state-default slick-headerrow-column l" + i + " r" + i + "'></div>")
\r
578 .appendTo($headerRow);
\r
580 trigger(self.onHeaderRowCellRendered, {
\r
581 "node": headerRowCell[0],
\r
587 setSortColumns(sortColumns);
\r
588 setupColumnResize();
\r
589 if (options.enableColumnReorder) {
\r
590 setupColumnReorder();
\r
594 function setupColumnSort() {
\r
595 $headers.click(function (e) {
\r
596 // temporary workaround for a bug in jQuery 1.7.1 (http://bugs.jquery.com/ticket/11328)
\r
597 e.metaKey = e.metaKey || e.ctrlKey;
\r
599 if ($(e.target).hasClass("slick-resizable-handle")) {
\r
603 var $col = $(e.target).closest(".slick-header-column");
\r
604 if (!$col.length) {
\r
608 var column = $col.data("column");
\r
609 if (column.sortable) {
\r
610 if (!getEditorLock().commitCurrentEdit()) {
\r
614 var sortOpts = null;
\r
616 for (; i < sortColumns.length; i++) {
\r
617 if (sortColumns[i].columnId == column.id) {
\r
618 sortOpts = sortColumns[i];
\r
619 sortOpts.sortAsc = !sortOpts.sortAsc;
\r
624 if (e.metaKey && options.multiColumnSort) {
\r
626 sortColumns.splice(i, 1);
\r
630 if ((!e.shiftKey && !e.metaKey) || !options.multiColumnSort) {
\r
635 sortOpts = { columnId: column.id, sortAsc: column.defaultSortAsc };
\r
636 sortColumns.push(sortOpts);
\r
637 } else if (sortColumns.length == 0) {
\r
638 sortColumns.push(sortOpts);
\r
642 setSortColumns(sortColumns);
\r
644 if (!options.multiColumnSort) {
\r
645 trigger(self.onSort, {
\r
646 multiColumnSort: false,
\r
648 sortAsc: sortOpts.sortAsc}, e);
\r
650 trigger(self.onSort, {
\r
651 multiColumnSort: true,
\r
652 sortCols: $.map(sortColumns, function(col) {
\r
653 return {sortCol: columns[getColumnIndex(col.columnId)], sortAsc: col.sortAsc };
\r
660 function setupColumnReorder() {
\r
661 $headers.filter(":ui-sortable").sortable("destroy");
\r
662 $headers.sortable({
\r
663 containment: "parent",
\r
667 tolerance: "intersection",
\r
669 placeholder: "slick-sortable-placeholder ui-state-default slick-header-column",
\r
670 forcePlaceholderSize: true,
\r
671 start: function (e, ui) {
\r
672 $(ui.helper).addClass("slick-header-column-active");
\r
674 beforeStop: function (e, ui) {
\r
675 $(ui.helper).removeClass("slick-header-column-active");
\r
677 stop: function (e) {
\r
678 if (!getEditorLock().commitCurrentEdit()) {
\r
679 $(this).sortable("cancel");
\r
683 var reorderedIds = $headers.sortable("toArray");
\r
684 var reorderedColumns = [];
\r
685 for (var i = 0; i < reorderedIds.length; i++) {
\r
686 reorderedColumns.push(columns[getColumnIndex(reorderedIds[i].replace(uid, ""))]);
\r
688 setColumns(reorderedColumns);
\r
690 trigger(self.onColumnsReordered, {});
\r
691 e.stopPropagation();
\r
692 setupColumnResize();
\r
697 function setupColumnResize() {
\r
698 var $col, j, c, pageX, columnElements, minPageX, maxPageX, firstResizable, lastResizable;
\r
699 columnElements = $headers.children();
\r
700 columnElements.find(".slick-resizable-handle").remove();
\r
701 columnElements.each(function (i, e) {
\r
702 if (columns[i].resizable) {
\r
703 if (firstResizable === undefined) {
\r
704 firstResizable = i;
\r
709 if (firstResizable === undefined) {
\r
712 columnElements.each(function (i, e) {
\r
713 if (i < firstResizable || (options.forceFitColumns && i >= lastResizable)) {
\r
717 $("<div class='slick-resizable-handle' />")
\r
719 .bind("dragstart", function (e, dd) {
\r
720 if (!getEditorLock().commitCurrentEdit()) {
\r
724 $(this).parent().addClass("slick-header-column-active");
\r
725 var shrinkLeewayOnRight = null, stretchLeewayOnRight = null;
\r
726 // lock each column's width option to current width
\r
727 columnElements.each(function (i, e) {
\r
728 columns[i].previousWidth = $(e).outerWidth();
\r
730 if (options.forceFitColumns) {
\r
731 shrinkLeewayOnRight = 0;
\r
732 stretchLeewayOnRight = 0;
\r
733 // colums on right affect maxPageX/minPageX
\r
734 for (j = i + 1; j < columnElements.length; j++) {
\r
737 if (stretchLeewayOnRight !== null) {
\r
739 stretchLeewayOnRight += c.maxWidth - c.previousWidth;
\r
741 stretchLeewayOnRight = null;
\r
744 shrinkLeewayOnRight += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth);
\r
748 var shrinkLeewayOnLeft = 0, stretchLeewayOnLeft = 0;
\r
749 for (j = 0; j <= i; j++) {
\r
750 // columns on left only affect minPageX
\r
753 if (stretchLeewayOnLeft !== null) {
\r
755 stretchLeewayOnLeft += c.maxWidth - c.previousWidth;
\r
757 stretchLeewayOnLeft = null;
\r
760 shrinkLeewayOnLeft += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth);
\r
763 if (shrinkLeewayOnRight === null) {
\r
764 shrinkLeewayOnRight = 100000;
\r
766 if (shrinkLeewayOnLeft === null) {
\r
767 shrinkLeewayOnLeft = 100000;
\r
769 if (stretchLeewayOnRight === null) {
\r
770 stretchLeewayOnRight = 100000;
\r
772 if (stretchLeewayOnLeft === null) {
\r
773 stretchLeewayOnLeft = 100000;
\r
775 maxPageX = pageX + Math.min(shrinkLeewayOnRight, stretchLeewayOnLeft);
\r
776 minPageX = pageX - Math.min(shrinkLeewayOnLeft, stretchLeewayOnRight);
\r
778 .bind("drag", function (e, dd) {
\r
779 var actualMinWidth, d = Math.min(maxPageX, Math.max(minPageX, e.pageX)) - pageX, x;
\r
780 if (d < 0) { // shrink column
\r
782 for (j = i; j >= 0; j--) {
\r
785 actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth);
\r
786 if (x && c.previousWidth + x < actualMinWidth) {
\r
787 x += c.previousWidth - actualMinWidth;
\r
788 c.width = actualMinWidth;
\r
790 c.width = c.previousWidth + x;
\r
796 if (options.forceFitColumns) {
\r
798 for (j = i + 1; j < columnElements.length; j++) {
\r
801 if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {
\r
802 x -= c.maxWidth - c.previousWidth;
\r
803 c.width = c.maxWidth;
\r
805 c.width = c.previousWidth + x;
\r
811 } else { // stretch column
\r
813 for (j = i; j >= 0; j--) {
\r
816 if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {
\r
817 x -= c.maxWidth - c.previousWidth;
\r
818 c.width = c.maxWidth;
\r
820 c.width = c.previousWidth + x;
\r
826 if (options.forceFitColumns) {
\r
828 for (j = i + 1; j < columnElements.length; j++) {
\r
831 actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth);
\r
832 if (x && c.previousWidth + x < actualMinWidth) {
\r
833 x += c.previousWidth - actualMinWidth;
\r
834 c.width = actualMinWidth;
\r
836 c.width = c.previousWidth + x;
\r
843 applyColumnHeaderWidths();
\r
844 if (options.syncColumnCellResize) {
\r
845 applyColumnWidths();
\r
848 .bind("dragend", function (e, dd) {
\r
850 $(this).parent().removeClass("slick-header-column-active");
\r
851 for (j = 0; j < columnElements.length; j++) {
\r
853 newWidth = $(columnElements[j]).outerWidth();
\r
855 if (c.previousWidth !== newWidth && c.rerenderOnResize) {
\r
856 invalidateAllRows();
\r
859 updateCanvasWidth(true);
\r
861 trigger(self.onColumnsResized, {});
\r
866 function getVBoxDelta($el) {
\r
867 var p = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"];
\r
869 $.each(p, function (n, val) {
\r
870 delta += parseFloat($el.css(val)) || 0;
\r
875 function measureCellPaddingAndBorder() {
\r
877 var h = ["borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight"];
\r
878 var v = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"];
\r
880 el = $("<div class='ui-state-default slick-header-column' style='visibility:hidden'>-</div>").appendTo($headers);
\r
881 headerColumnWidthDiff = headerColumnHeightDiff = 0;
\r
882 $.each(h, function (n, val) {
\r
883 headerColumnWidthDiff += parseFloat(el.css(val)) || 0;
\r
885 $.each(v, function (n, val) {
\r
886 headerColumnHeightDiff += parseFloat(el.css(val)) || 0;
\r
890 var r = $("<div class='slick-row' />").appendTo($canvas);
\r
891 el = $("<div class='slick-cell' id='' style='visibility:hidden'>-</div>").appendTo(r);
\r
892 cellWidthDiff = cellHeightDiff = 0;
\r
893 $.each(h, function (n, val) {
\r
894 cellWidthDiff += parseFloat(el.css(val)) || 0;
\r
896 $.each(v, function (n, val) {
\r
897 cellHeightDiff += parseFloat(el.css(val)) || 0;
\r
901 absoluteColumnMinWidth = Math.max(headerColumnWidthDiff, cellWidthDiff);
\r
904 function createCssRules() {
\r
905 $style = $("<style type='text/css' rel='stylesheet' />").appendTo($("head"));
\r
906 var rowHeight = (options.rowHeight - cellHeightDiff);
\r
908 "." + uid + " .slick-header-column { left: 1000px; }",
\r
909 "." + uid + " .slick-top-panel { height:" + options.topPanelHeight + "px; }",
\r
910 "." + uid + " .slick-headerrow-columns { height:" + options.headerRowHeight + "px; }",
\r
911 "." + uid + " .slick-cell { height:" + rowHeight + "px; }",
\r
912 "." + uid + " .slick-row { height:" + options.rowHeight + "px; }"
\r
915 for (var i = 0; i < columns.length; i++) {
\r
916 rules.push("." + uid + " .l" + i + " { }");
\r
917 rules.push("." + uid + " .r" + i + " { }");
\r
920 if ($style[0].styleSheet) { // IE
\r
921 $style[0].styleSheet.cssText = rules.join(" ");
\r
923 $style[0].appendChild(document.createTextNode(rules.join(" ")));
\r
927 function getColumnCssRules(idx) {
\r
929 var sheets = document.styleSheets;
\r
930 for (var i = 0; i < sheets.length; i++) {
\r
931 if ((sheets[i].ownerNode || sheets[i].owningElement) == $style[0]) {
\r
932 stylesheet = sheets[i];
\r
938 throw new Error("Cannot find stylesheet.");
\r
941 // find and cache column CSS rules
\r
942 columnCssRulesL = [];
\r
943 columnCssRulesR = [];
\r
944 var cssRules = (stylesheet.cssRules || stylesheet.rules);
\r
945 var matches, columnIdx;
\r
946 for (var i = 0; i < cssRules.length; i++) {
\r
947 var selector = cssRules[i].selectorText;
\r
948 if (matches = /\.l\d+/.exec(selector)) {
\r
949 columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10);
\r
950 columnCssRulesL[columnIdx] = cssRules[i];
\r
951 } else if (matches = /\.r\d+/.exec(selector)) {
\r
952 columnIdx = parseInt(matches[0].substr(2, matches[0].length - 2), 10);
\r
953 columnCssRulesR[columnIdx] = cssRules[i];
\r
959 "left": columnCssRulesL[idx],
\r
960 "right": columnCssRulesR[idx]
\r
964 function removeCssRules() {
\r
969 function destroy() {
\r
970 getEditorLock().cancelCurrentEdit();
\r
972 trigger(self.onBeforeDestroy, {});
\r
974 var i = plugins.length;
\r
976 unregisterPlugin(plugins[i]);
\r
979 if (options.enableColumnReorder) {
\r
980 $headers.filter(":ui-sortable").sortable("destroy");
\r
983 unbindAncestorScrollEvents();
\r
984 $container.unbind(".slickgrid");
\r
987 $canvas.unbind("draginit dragstart dragend drag");
\r
988 $container.empty().removeClass(uid);
\r
992 //////////////////////////////////////////////////////////////////////////////////////////////
\r
995 function trigger(evt, args, e) {
\r
996 e = e || new Slick.EventData();
\r
999 return evt.notify(args, e, self);
\r
1002 function getEditorLock() {
\r
1003 return options.editorLock;
\r
1006 function getEditController() {
\r
1007 return editController;
\r
1010 function getColumnIndex(id) {
\r
1011 return columnsById[id];
\r
1014 function autosizeColumns() {
\r
1020 availWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW;
\r
1022 for (i = 0; i < columns.length; i++) {
\r
1024 widths.push(c.width);
\r
1026 if (c.resizable) {
\r
1027 shrinkLeeway += c.width - Math.max(c.minWidth, absoluteColumnMinWidth);
\r
1032 prevTotal = total;
\r
1033 while (total > availWidth && shrinkLeeway) {
\r
1034 var shrinkProportion = (total - availWidth) / shrinkLeeway;
\r
1035 for (i = 0; i < columns.length && total > availWidth; i++) {
\r
1037 var width = widths[i];
\r
1038 if (!c.resizable || width <= c.minWidth || width <= absoluteColumnMinWidth) {
\r
1041 var absMinWidth = Math.max(c.minWidth, absoluteColumnMinWidth);
\r
1042 var shrinkSize = Math.floor(shrinkProportion * (width - absMinWidth)) || 1;
\r
1043 shrinkSize = Math.min(shrinkSize, width - absMinWidth);
\r
1044 total -= shrinkSize;
\r
1045 shrinkLeeway -= shrinkSize;
\r
1046 widths[i] -= shrinkSize;
\r
1048 if (prevTotal == total) { // avoid infinite loop
\r
1051 prevTotal = total;
\r
1055 prevTotal = total;
\r
1056 while (total < availWidth) {
\r
1057 var growProportion = availWidth / total;
\r
1058 for (i = 0; i < columns.length && total < availWidth; i++) {
\r
1060 if (!c.resizable || c.maxWidth <= c.width) {
\r
1063 var growSize = Math.min(Math.floor(growProportion * c.width) - c.width, (c.maxWidth - c.width) || 1000000) || 1;
\r
1064 total += growSize;
\r
1065 widths[i] += growSize;
\r
1067 if (prevTotal == total) { // avoid infinite loop
\r
1070 prevTotal = total;
\r
1073 var reRender = false;
\r
1074 for (i = 0; i < columns.length; i++) {
\r
1075 if (columns[i].rerenderOnResize && columns[i].width != widths[i]) {
\r
1078 columns[i].width = widths[i];
\r
1081 applyColumnHeaderWidths();
\r
1082 updateCanvasWidth(true);
\r
1084 invalidateAllRows();
\r
1089 function applyColumnHeaderWidths() {
\r
1090 if (!initialized) { return; }
\r
1092 for (var i = 0, headers = $headers.children(), ii = headers.length; i < ii; i++) {
\r
1093 h = $(headers[i]);
\r
1094 if (h.width() !== columns[i].width - headerColumnWidthDiff) {
\r
1095 h.width(columns[i].width - headerColumnWidthDiff);
\r
1099 updateColumnCaches();
\r
1102 function applyColumnWidths() {
\r
1103 var x = 0, w, rule;
\r
1104 for (var i = 0; i < columns.length; i++) {
\r
1105 w = columns[i].width;
\r
1107 rule = getColumnCssRules(i);
\r
1108 rule.left.style.left = x + "px";
\r
1109 rule.right.style.right = (canvasWidth - x - w) + "px";
\r
1111 x += columns[i].width;
\r
1115 function setSortColumn(columnId, ascending) {
\r
1116 setSortColumns([{ columnId: columnId, sortAsc: ascending}]);
\r
1119 function setSortColumns(cols) {
\r
1120 sortColumns = cols;
\r
1122 var headerColumnEls = $headers.children();
\r
1124 .removeClass("slick-header-column-sorted")
\r
1125 .find(".slick-sort-indicator")
\r
1126 .removeClass("slick-sort-indicator-asc slick-sort-indicator-desc");
\r
1128 $.each(sortColumns, function(i, col) {
\r
1129 if (col.sortAsc == null) {
\r
1130 col.sortAsc = true;
\r
1132 var columnIndex = getColumnIndex(col.columnId);
\r
1133 if (columnIndex != null) {
\r
1134 headerColumnEls.eq(columnIndex)
\r
1135 .addClass("slick-header-column-sorted")
\r
1136 .find(".slick-sort-indicator")
\r
1137 .addClass(col.sortAsc ? "slick-sort-indicator-asc" : "slick-sort-indicator-desc");
\r
1142 function getSortColumns() {
\r
1143 return sortColumns;
\r
1146 function handleSelectedRangesChanged(e, ranges) {
\r
1147 selectedRows = [];
\r
1149 for (var i = 0; i < ranges.length; i++) {
\r
1150 for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
\r
1151 if (!hash[j]) { // prevent duplicates
\r
1152 selectedRows.push(j);
\r
1155 for (var k = ranges[i].fromCell; k <= ranges[i].toCell; k++) {
\r
1156 if (canCellBeSelected(j, k)) {
\r
1157 hash[j][columns[k].id] = options.selectedCellCssClass;
\r
1163 setCellCssStyles(options.selectedCellCssClass, hash);
\r
1165 trigger(self.onSelectedRowsChanged, {rows: getSelectedRows()}, e);
\r
1168 function getColumns() {
\r
1172 function updateColumnCaches() {
\r
1173 // Pre-calculate cell boundaries.
\r
1174 columnPosLeft = [];
\r
1175 columnPosRight = [];
\r
1177 for (var i = 0, ii = columns.length; i < ii; i++) {
\r
1178 columnPosLeft[i] = x;
\r
1179 columnPosRight[i] = x + columns[i].width;
\r
1180 x += columns[i].width;
\r
1184 function setColumns(columnDefinitions) {
\r
1185 columns = columnDefinitions;
\r
1188 for (var i = 0; i < columns.length; i++) {
\r
1189 var m = columns[i] = $.extend({}, columnDefaults, columns[i]);
\r
1190 columnsById[m.id] = i;
\r
1191 if (m.minWidth && m.width < m.minWidth) {
\r
1192 m.width = m.minWidth;
\r
1194 if (m.maxWidth && m.width > m.maxWidth) {
\r
1195 m.width = m.maxWidth;
\r
1199 updateColumnCaches();
\r
1201 if (initialized) {
\r
1202 invalidateAllRows();
\r
1203 createColumnHeaders();
\r
1207 applyColumnWidths();
\r
1212 function getOptions() {
\r
1216 function setOptions(args) {
\r
1217 if (!getEditorLock().commitCurrentEdit()) {
\r
1221 makeActiveCellNormal();
\r
1223 if (options.enableAddRow !== args.enableAddRow) {
\r
1224 invalidateRow(getDataLength());
\r
1227 options = $.extend(options, args);
\r
1228 validateAndEnforceOptions();
\r
1230 $viewport.css("overflow-y", options.autoHeight ? "hidden" : "auto");
\r
1234 function validateAndEnforceOptions() {
\r
1235 if (options.autoHeight) {
\r
1236 options.leaveSpaceForNewRows = false;
\r
1240 function setData(newData, scrollToTop) {
\r
1242 invalidateAllRows();
\r
1244 if (scrollToTop) {
\r
1249 function getData() {
\r
1253 function getDataLength() {
\r
1254 if (data.getLength) {
\r
1255 return data.getLength();
\r
1257 return data.length;
\r
1261 function getDataItem(i) {
\r
1262 if (data.getItem) {
\r
1263 return data.getItem(i);
\r
1269 function getTopPanel() {
\r
1270 return $topPanel[0];
\r
1273 function setTopPanelVisibility(visible) {
\r
1274 if (options.showTopPanel != visible) {
\r
1275 options.showTopPanel = visible;
\r
1277 $topPanelScroller.slideDown("fast", resizeCanvas);
\r
1279 $topPanelScroller.slideUp("fast", resizeCanvas);
\r
1284 function setHeaderRowVisibility(visible) {
\r
1285 if (options.showHeaderRow != visible) {
\r
1286 options.showHeaderRow = visible;
\r
1288 $headerRowScroller.slideDown("fast", resizeCanvas);
\r
1290 $headerRowScroller.slideUp("fast", resizeCanvas);
\r
1295 function getContainerNode() {
\r
1296 return $container.get(0);
\r
1299 //////////////////////////////////////////////////////////////////////////////////////////////
\r
1300 // Rendering / Scrolling
\r
1302 function getRowTop(row) {
\r
1303 return options.rowHeight * row - offset;
\r
1306 function getRowFromPosition(y) {
\r
1307 return Math.floor((y + offset) / options.rowHeight);
\r
1310 function scrollTo(y) {
\r
1311 y = Math.max(y, 0);
\r
1312 y = Math.min(y, th - viewportH + (viewportHasHScroll ? scrollbarDimensions.height : 0));
\r
1314 var oldOffset = offset;
\r
1316 page = Math.min(n - 1, Math.floor(y / ph));
\r
1317 offset = Math.round(page * cj);
\r
1318 var newScrollTop = y - offset;
\r
1320 if (offset != oldOffset) {
\r
1321 var range = getVisibleRange(newScrollTop);
\r
1322 cleanupRows(range);
\r
1323 updateRowPositions();
\r
1326 if (prevScrollTop != newScrollTop) {
\r
1327 vScrollDir = (prevScrollTop + oldOffset < newScrollTop + offset) ? 1 : -1;
\r
1328 $viewport[0].scrollTop = (lastRenderedScrollTop = scrollTop = prevScrollTop = newScrollTop);
\r
1330 trigger(self.onViewportChanged, {});
\r
1334 function defaultFormatter(row, cell, value, columnDef, dataContext) {
\r
1335 if (value == null) {
\r
1338 return (value + "").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
\r
1342 function getFormatter(row, column) {
\r
1343 var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
\r
1345 // look up by id, then index
\r
1346 var columnOverrides = rowMetadata &&
\r
1347 rowMetadata.columns &&
\r
1348 (rowMetadata.columns[column.id] || rowMetadata.columns[getColumnIndex(column.id)]);
\r
1350 return (columnOverrides && columnOverrides.formatter) ||
\r
1351 (rowMetadata && rowMetadata.formatter) ||
\r
1352 column.formatter ||
\r
1353 (options.formatterFactory && options.formatterFactory.getFormatter(column)) ||
\r
1354 options.defaultFormatter;
\r
1357 function getEditor(row, cell) {
\r
1358 var column = columns[cell];
\r
1359 var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
\r
1360 var columnMetadata = rowMetadata && rowMetadata.columns;
\r
1362 if (columnMetadata && columnMetadata[column.id] && columnMetadata[column.id].editor !== undefined) {
\r
1363 return columnMetadata[column.id].editor;
\r
1365 if (columnMetadata && columnMetadata[cell] && columnMetadata[cell].editor !== undefined) {
\r
1366 return columnMetadata[cell].editor;
\r
1369 return column.editor || (options.editorFactory && options.editorFactory.getEditor(column));
\r
1372 function getDataItemValueForColumn(item, columnDef) {
\r
1373 if (options.dataItemColumnValueExtractor) {
\r
1374 return options.dataItemColumnValueExtractor(item, columnDef);
\r
1376 return item[columnDef.field];
\r
1379 function appendRowHtml(stringArray, row, range, dataLength) {
\r
1380 var d = getDataItem(row);
\r
1381 var dataLoading = row < dataLength && !d;
\r
1382 var rowCss = "slick-row" +
\r
1383 (dataLoading ? " loading" : "") +
\r
1384 (row === activeRow ? " active" : "") +
\r
1385 (row % 2 == 1 ? " odd" : " even");
\r
1387 var metadata = data.getItemMetadata && data.getItemMetadata(row);
\r
1389 if (metadata && metadata.cssClasses) {
\r
1390 rowCss += " " + metadata.cssClasses;
\r
1393 stringArray.push("<div class='ui-widget-content " + rowCss + "' style='top:" + getRowTop(row) + "px'>");
\r
1396 for (var i = 0, ii = columns.length; i < ii; i++) {
\r
1399 if (metadata && metadata.columns) {
\r
1400 var columnData = metadata.columns[m.id] || metadata.columns[i];
\r
1401 colspan = (columnData && columnData.colspan) || 1;
\r
1402 if (colspan === "*") {
\r
1407 // Do not render cells outside of the viewport.
\r
1408 if (columnPosRight[Math.min(ii - 1, i + colspan - 1)] > range.leftPx) {
\r
1409 if (columnPosLeft[i] > range.rightPx) {
\r
1410 // All columns to the right are outside the range.
\r
1414 appendCellHtml(stringArray, row, i, colspan, d);
\r
1417 if (colspan > 1) {
\r
1418 i += (colspan - 1);
\r
1422 stringArray.push("</div>");
\r
1425 function appendCellHtml(stringArray, row, cell, colspan, item) {
\r
1426 var m = columns[cell];
\r
1427 var cellCss = "slick-cell l" + cell + " r" + Math.min(columns.length - 1, cell + colspan - 1) +
\r
1428 (m.cssClass ? " " + m.cssClass : "");
\r
1429 if (row === activeRow && cell === activeCell) {
\r
1430 cellCss += (" active");
\r
1433 // TODO: merge them together in the setter
\r
1434 for (var key in cellCssClasses) {
\r
1435 if (cellCssClasses[key][row] && cellCssClasses[key][row][m.id]) {
\r
1436 cellCss += (" " + cellCssClasses[key][row][m.id]);
\r
1440 stringArray.push("<div class='" + cellCss + "'>");
\r
1442 // if there is a corresponding row (if not, this is the Add New row or this data hasn't been loaded yet)
\r
1444 var value = getDataItemValueForColumn(item, m);
\r
1445 stringArray.push(getFormatter(row, m)(row, cell, value, m, item));
\r
1448 stringArray.push("</div>");
\r
1450 rowsCache[row].cellRenderQueue.push(cell);
\r
1451 rowsCache[row].cellColSpans[cell] = colspan;
\r
1455 function cleanupRows(rangeToKeep) {
\r
1456 for (var i in rowsCache) {
\r
1457 if (((i = parseInt(i, 10)) !== activeRow) && (i < rangeToKeep.top || i > rangeToKeep.bottom)) {
\r
1458 removeRowFromCache(i);
\r
1463 function invalidate() {
\r
1465 invalidateAllRows();
\r
1469 function invalidateAllRows() {
\r
1470 if (currentEditor) {
\r
1471 makeActiveCellNormal();
\r
1473 for (var row in rowsCache) {
\r
1474 removeRowFromCache(row);
\r
1478 function removeRowFromCache(row) {
\r
1479 var cacheEntry = rowsCache[row];
\r
1480 if (!cacheEntry) {
\r
1483 $canvas[0].removeChild(cacheEntry.rowNode);
\r
1484 delete rowsCache[row];
\r
1485 delete postProcessedRows[row];
\r
1487 counter_rows_removed++;
\r
1490 function invalidateRows(rows) {
\r
1492 if (!rows || !rows.length) {
\r
1496 for (i = 0, rl = rows.length; i < rl; i++) {
\r
1497 if (currentEditor && activeRow === rows[i]) {
\r
1498 makeActiveCellNormal();
\r
1500 if (rowsCache[rows[i]]) {
\r
1501 removeRowFromCache(rows[i]);
\r
1506 function invalidateRow(row) {
\r
1507 invalidateRows([row]);
\r
1510 function updateCell(row, cell) {
\r
1511 var cellNode = getCellNode(row, cell);
\r
1516 var m = columns[cell], d = getDataItem(row);
\r
1517 if (currentEditor && activeRow === row && activeCell === cell) {
\r
1518 currentEditor.loadValue(d);
\r
1520 cellNode.innerHTML = d ? getFormatter(row, m)(row, cell, getDataItemValueForColumn(d, m), m, d) : "";
\r
1521 invalidatePostProcessingResults(row);
\r
1525 function updateRow(row) {
\r
1526 var cacheEntry = rowsCache[row];
\r
1527 if (!cacheEntry) {
\r
1531 ensureCellNodesInRowsCache(row);
\r
1533 var d = getDataItem(row);
\r
1535 for (var columnIdx in cacheEntry.cellNodesByColumnIdx) {
\r
1536 if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) {
\r
1540 columnIdx = columnIdx | 0;
\r
1541 var m = columns[columnIdx],
\r
1542 node = cacheEntry.cellNodesByColumnIdx[columnIdx];
\r
1544 if (row === activeRow && columnIdx === activeCell && currentEditor) {
\r
1545 currentEditor.loadValue(d);
\r
1547 node.innerHTML = getFormatter(row, m)(row, columnIdx, getDataItemValueForColumn(d, m), m, d);
\r
1549 node.innerHTML = "";
\r
1553 invalidatePostProcessingResults(row);
\r
1556 function getViewportHeight() {
\r
1557 return parseFloat($.css($container[0], "height", true)) -
\r
1558 parseFloat($.css($container[0], "paddingTop", true)) -
\r
1559 parseFloat($.css($container[0], "paddingBottom", true)) -
\r
1560 parseFloat($.css($headerScroller[0], "height")) - getVBoxDelta($headerScroller) -
\r
1561 (options.showTopPanel ? options.topPanelHeight + getVBoxDelta($topPanelScroller) : 0) -
\r
1562 (options.showHeaderRow ? options.headerRowHeight + getVBoxDelta($headerRowScroller) : 0);
\r
1565 function resizeCanvas() {
\r
1566 if (!initialized) { return; }
\r
1567 if (options.autoHeight) {
\r
1568 viewportH = options.rowHeight * (getDataLength() + (options.enableAddRow ? 1 : 0));
\r
1570 viewportH = getViewportHeight();
\r
1573 numVisibleRows = Math.ceil(viewportH / options.rowHeight);
\r
1574 viewportW = parseFloat($.css($container[0], "width", true));
\r
1575 if (!options.autoHeight) {
\r
1576 $viewport.height(viewportH);
\r
1579 if (options.forceFitColumns) {
\r
1580 autosizeColumns();
\r
1588 function updateRowCount() {
\r
1589 var dataLength = getDataLength();
\r
1590 if (!initialized) { return; }
\r
1591 numberOfRows = dataLength +
\r
1592 (options.enableAddRow ? 1 : 0) +
\r
1593 (options.leaveSpaceForNewRows ? numVisibleRows - 1 : 0);
\r
1595 var oldViewportHasVScroll = viewportHasVScroll;
\r
1596 // with autoHeight, we do not need to accommodate the vertical scroll bar
\r
1597 viewportHasVScroll = !options.autoHeight && (numberOfRows * options.rowHeight > viewportH);
\r
1599 // remove the rows that are now outside of the data range
\r
1600 // this helps avoid redundant calls to .removeRow() when the size of the data decreased by thousands of rows
\r
1601 var l = options.enableAddRow ? dataLength : dataLength - 1;
\r
1602 for (var i in rowsCache) {
\r
1604 removeRowFromCache(i);
\r
1608 if (activeCellNode && activeRow > l) {
\r
1609 resetActiveCell();
\r
1613 th = Math.max(options.rowHeight * numberOfRows, viewportH - scrollbarDimensions.height);
\r
1614 if (th < maxSupportedCssHeight) {
\r
1620 // break into pages
\r
1621 h = maxSupportedCssHeight;
\r
1623 n = Math.floor(th / ph);
\r
1624 cj = (th - h) / (n - 1);
\r
1628 $canvas.css("height", h);
\r
1629 scrollTop = $viewport[0].scrollTop;
\r
1632 var oldScrollTopInRange = (scrollTop + offset <= th - viewportH);
\r
1634 if (th == 0 || scrollTop == 0) {
\r
1635 page = offset = 0;
\r
1636 } else if (oldScrollTopInRange) {
\r
1637 // maintain virtual position
\r
1638 scrollTo(scrollTop + offset);
\r
1640 // scroll to bottom
\r
1641 scrollTo(th - viewportH);
\r
1644 if (h != oldH && options.autoHeight) {
\r
1648 if (options.forceFitColumns && oldViewportHasVScroll != viewportHasVScroll) {
\r
1649 autosizeColumns();
\r
1651 updateCanvasWidth(false);
\r
1654 function getVisibleRange(viewportTop, viewportLeft) {
\r
1655 if (viewportTop == null) {
\r
1656 viewportTop = scrollTop;
\r
1658 if (viewportLeft == null) {
\r
1659 viewportLeft = scrollLeft;
\r
1663 top: getRowFromPosition(viewportTop),
\r
1664 bottom: getRowFromPosition(viewportTop + viewportH) + 1,
\r
1665 leftPx: viewportLeft,
\r
1666 rightPx: viewportLeft + viewportW
\r
1670 function getRenderedRange(viewportTop, viewportLeft) {
\r
1671 var range = getVisibleRange(viewportTop, viewportLeft);
\r
1672 var buffer = Math.round(viewportH / options.rowHeight);
\r
1673 var minBuffer = 3;
\r
1675 if (vScrollDir == -1) {
\r
1676 range.top -= buffer;
\r
1677 range.bottom += minBuffer;
\r
1678 } else if (vScrollDir == 1) {
\r
1679 range.top -= minBuffer;
\r
1680 range.bottom += buffer;
\r
1682 range.top -= minBuffer;
\r
1683 range.bottom += minBuffer;
\r
1686 range.top = Math.max(0, range.top);
\r
1687 range.bottom = Math.min(options.enableAddRow ? getDataLength() : getDataLength() - 1, range.bottom);
\r
1689 range.leftPx -= viewportW;
\r
1690 range.rightPx += viewportW;
\r
1692 range.leftPx = Math.max(0, range.leftPx);
\r
1693 range.rightPx = Math.min(canvasWidth, range.rightPx);
\r
1698 function ensureCellNodesInRowsCache(row) {
\r
1699 var cacheEntry = rowsCache[row];
\r
1701 if (cacheEntry.cellRenderQueue.length) {
\r
1702 var lastChild = cacheEntry.rowNode.lastChild;
\r
1703 while (cacheEntry.cellRenderQueue.length) {
\r
1704 var columnIdx = cacheEntry.cellRenderQueue.pop();
\r
1705 cacheEntry.cellNodesByColumnIdx[columnIdx] = lastChild;
\r
1706 lastChild = lastChild.previousSibling;
\r
1712 function cleanUpCells(range, row) {
\r
1713 var totalCellsRemoved = 0;
\r
1714 var cacheEntry = rowsCache[row];
\r
1716 // Remove cells outside the range.
\r
1717 var cellsToRemove = [];
\r
1718 for (var i in cacheEntry.cellNodesByColumnIdx) {
\r
1719 // I really hate it when people mess with Array.prototype.
\r
1720 if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(i)) {
\r
1724 // This is a string, so it needs to be cast back to a number.
\r
1727 var colspan = cacheEntry.cellColSpans[i];
\r
1728 if (columnPosLeft[i] > range.rightPx ||
\r
1729 columnPosRight[Math.min(columns.length - 1, i + colspan - 1)] < range.leftPx) {
\r
1730 if (!(row == activeRow && i == activeCell)) {
\r
1731 cellsToRemove.push(i);
\r
1737 while ((cellToRemove = cellsToRemove.pop()) != null) {
\r
1738 cacheEntry.rowNode.removeChild(cacheEntry.cellNodesByColumnIdx[cellToRemove]);
\r
1739 delete cacheEntry.cellColSpans[cellToRemove];
\r
1740 delete cacheEntry.cellNodesByColumnIdx[cellToRemove];
\r
1741 if (postProcessedRows[row]) {
\r
1742 delete postProcessedRows[row][cellToRemove];
\r
1744 totalCellsRemoved++;
\r
1748 function cleanUpAndRenderCells(range) {
\r
1750 var stringArray = [];
\r
1751 var processedRows = [];
\r
1753 var totalCellsAdded = 0;
\r
1756 for (var row = range.top, btm = range.bottom; row <= btm; row++) {
\r
1757 cacheEntry = rowsCache[row];
\r
1758 if (!cacheEntry) {
\r
1762 // cellRenderQueue populated in renderRows() needs to be cleared first
\r
1763 ensureCellNodesInRowsCache(row);
\r
1765 cleanUpCells(range, row);
\r
1767 // Render missing cells.
\r
1770 var metadata = data.getItemMetadata && data.getItemMetadata(row);
\r
1771 metadata = metadata && metadata.columns;
\r
1773 var d = getDataItem(row);
\r
1775 // TODO: shorten this loop (index? heuristics? binary search?)
\r
1776 for (var i = 0, ii = columns.length; i < ii; i++) {
\r
1777 // Cells to the right are outside the range.
\r
1778 if (columnPosLeft[i] > range.rightPx) {
\r
1782 // Already rendered.
\r
1783 if ((colspan = cacheEntry.cellColSpans[i]) != null) {
\r
1784 i += (colspan > 1 ? colspan - 1 : 0);
\r
1790 var columnData = metadata[columns[i].id] || metadata[i];
\r
1791 colspan = (columnData && columnData.colspan) || 1;
\r
1792 if (colspan === "*") {
\r
1797 if (columnPosRight[Math.min(ii - 1, i + colspan - 1)] > range.leftPx) {
\r
1798 appendCellHtml(stringArray, row, i, colspan, d);
\r
1802 i += (colspan > 1 ? colspan - 1 : 0);
\r
1806 totalCellsAdded += cellsAdded;
\r
1807 processedRows.push(row);
\r
1811 if (!stringArray.length) {
\r
1815 var x = document.createElement("div");
\r
1816 x.innerHTML = stringArray.join("");
\r
1820 while ((processedRow = processedRows.pop()) != null) {
\r
1821 cacheEntry = rowsCache[processedRow];
\r
1823 while ((columnIdx = cacheEntry.cellRenderQueue.pop()) != null) {
\r
1824 node = x.lastChild;
\r
1825 cacheEntry.rowNode.appendChild(node);
\r
1826 cacheEntry.cellNodesByColumnIdx[columnIdx] = node;
\r
1831 function renderRows(range) {
\r
1832 var parentNode = $canvas[0],
\r
1835 needToReselectCell = false,
\r
1836 dataLength = getDataLength();
\r
1838 for (var i = range.top, ii = range.bottom; i <= ii; i++) {
\r
1839 if (rowsCache[i]) {
\r
1845 // Create an entry right away so that appendRowHtml() can
\r
1846 // start populatating it.
\r
1850 // ColSpans of rendered cells (by column idx).
\r
1851 // Can also be used for checking whether a cell has been rendered.
\r
1852 "cellColSpans": [],
\r
1854 // Cell nodes (by column idx). Lazy-populated by ensureCellNodesInRowsCache().
\r
1855 "cellNodesByColumnIdx": [],
\r
1857 // Column indices of cell nodes that have been rendered, but not yet indexed in
\r
1858 // cellNodesByColumnIdx. These are in the same order as cell nodes added at the
\r
1859 // end of the row.
\r
1860 "cellRenderQueue": []
\r
1863 appendRowHtml(stringArray, i, range, dataLength);
\r
1864 if (activeCellNode && activeRow === i) {
\r
1865 needToReselectCell = true;
\r
1867 counter_rows_rendered++;
\r
1870 if (!rows.length) { return; }
\r
1872 var x = document.createElement("div");
\r
1873 x.innerHTML = stringArray.join("");
\r
1875 for (var i = 0, ii = rows.length; i < ii; i++) {
\r
1876 rowsCache[rows[i]].rowNode = parentNode.appendChild(x.firstChild);
\r
1879 if (needToReselectCell) {
\r
1880 activeCellNode = getCellNode(activeRow, activeCell);
\r
1884 function startPostProcessing() {
\r
1885 if (!options.enableAsyncPostRender) {
\r
1888 clearTimeout(h_postrender);
\r
1889 h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay);
\r
1892 function invalidatePostProcessingResults(row) {
\r
1893 delete postProcessedRows[row];
\r
1894 postProcessFromRow = Math.min(postProcessFromRow, row);
\r
1895 postProcessToRow = Math.max(postProcessToRow, row);
\r
1896 startPostProcessing();
\r
1899 function updateRowPositions() {
\r
1900 for (var row in rowsCache) {
\r
1901 rowsCache[row].rowNode.style.top = getRowTop(row) + "px";
\r
1905 function render() {
\r
1906 if (!initialized) { return; }
\r
1907 var visible = getVisibleRange();
\r
1908 var rendered = getRenderedRange();
\r
1910 // remove rows no longer in the viewport
\r
1911 cleanupRows(rendered);
\r
1913 // add new rows & missing cells in existing rows
\r
1914 if (lastRenderedScrollLeft != scrollLeft) {
\r
1915 cleanUpAndRenderCells(rendered);
\r
1918 // render missing rows
\r
1919 renderRows(rendered);
\r
1921 postProcessFromRow = visible.top;
\r
1922 postProcessToRow = Math.min(options.enableAddRow ? getDataLength() : getDataLength() - 1, visible.bottom);
\r
1923 startPostProcessing();
\r
1925 lastRenderedScrollTop = scrollTop;
\r
1926 lastRenderedScrollLeft = scrollLeft;
\r
1930 function handleHeaderRowScroll() {
\r
1931 var scrollLeft = $headerRowScroller[0].scrollLeft;
\r
1932 if (scrollLeft != $viewport[0].scrollLeft) {
\r
1933 $viewport[0].scrollLeft = scrollLeft;
\r
1937 function handleScroll() {
\r
1938 scrollTop = $viewport[0].scrollTop;
\r
1939 scrollLeft = $viewport[0].scrollLeft;
\r
1940 var vScrollDist = Math.abs(scrollTop - prevScrollTop);
\r
1941 var hScrollDist = Math.abs(scrollLeft - prevScrollLeft);
\r
1943 if (hScrollDist) {
\r
1944 prevScrollLeft = scrollLeft;
\r
1945 $headerScroller[0].scrollLeft = scrollLeft;
\r
1946 $topPanelScroller[0].scrollLeft = scrollLeft;
\r
1947 $headerRowScroller[0].scrollLeft = scrollLeft;
\r
1950 if (vScrollDist) {
\r
1951 vScrollDir = prevScrollTop < scrollTop ? 1 : -1;
\r
1952 prevScrollTop = scrollTop;
\r
1954 // switch virtual pages if needed
\r
1955 if (vScrollDist < viewportH) {
\r
1956 scrollTo(scrollTop + offset);
\r
1958 var oldOffset = offset;
\r
1959 if (h == viewportH) {
\r
1962 page = Math.min(n - 1, Math.floor(scrollTop * ((th - viewportH) / (h - viewportH)) * (1 / ph)));
\r
1964 offset = Math.round(page * cj);
\r
1965 if (oldOffset != offset) {
\r
1966 invalidateAllRows();
\r
1971 if (hScrollDist || vScrollDist) {
\r
1973 clearTimeout(h_render);
\r
1976 if (Math.abs(lastRenderedScrollTop - scrollTop) > 20 ||
\r
1977 Math.abs(lastRenderedScrollLeft - scrollLeft) > 20) {
\r
1978 if (options.forceSyncScrolling || (
\r
1979 Math.abs(lastRenderedScrollTop - scrollTop) < viewportH &&
\r
1980 Math.abs(lastRenderedScrollLeft - scrollLeft) < viewportW)) {
\r
1983 h_render = setTimeout(render, 50);
\r
1986 trigger(self.onViewportChanged, {});
\r
1990 trigger(self.onScroll, {scrollLeft: scrollLeft, scrollTop: scrollTop});
\r
1993 function asyncPostProcessRows() {
\r
1994 while (postProcessFromRow <= postProcessToRow) {
\r
1995 var row = (vScrollDir >= 0) ? postProcessFromRow++ : postProcessToRow--;
\r
1996 var cacheEntry = rowsCache[row];
\r
1997 if (!cacheEntry || row >= getDataLength()) {
\r
2001 if (!postProcessedRows[row]) {
\r
2002 postProcessedRows[row] = {};
\r
2005 ensureCellNodesInRowsCache(row);
\r
2006 for (var columnIdx in cacheEntry.cellNodesByColumnIdx) {
\r
2007 if (!cacheEntry.cellNodesByColumnIdx.hasOwnProperty(columnIdx)) {
\r
2011 columnIdx = columnIdx | 0;
\r
2013 var m = columns[columnIdx];
\r
2014 if (m.asyncPostRender && !postProcessedRows[row][columnIdx]) {
\r
2015 var node = cacheEntry.cellNodesByColumnIdx[columnIdx];
\r
2017 m.asyncPostRender(node, row, getDataItem(row), m);
\r
2019 postProcessedRows[row][columnIdx] = true;
\r
2023 h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay);
\r
2028 function updateCellCssStylesOnRenderedRows(addedHash, removedHash) {
\r
2029 var node, columnId, addedRowHash, removedRowHash;
\r
2030 for (var row in rowsCache) {
\r
2031 removedRowHash = removedHash && removedHash[row];
\r
2032 addedRowHash = addedHash && addedHash[row];
\r
2034 if (removedRowHash) {
\r
2035 for (columnId in removedRowHash) {
\r
2036 if (!addedRowHash || removedRowHash[columnId] != addedRowHash[columnId]) {
\r
2037 node = getCellNode(row, getColumnIndex(columnId));
\r
2039 $(node).removeClass(removedRowHash[columnId]);
\r
2045 if (addedRowHash) {
\r
2046 for (columnId in addedRowHash) {
\r
2047 if (!removedRowHash || removedRowHash[columnId] != addedRowHash[columnId]) {
\r
2048 node = getCellNode(row, getColumnIndex(columnId));
\r
2050 $(node).addClass(addedRowHash[columnId]);
\r
2058 function addCellCssStyles(key, hash) {
\r
2059 if (cellCssClasses[key]) {
\r
2060 throw "addCellCssStyles: cell CSS hash with key '" + key + "' already exists.";
\r
2063 cellCssClasses[key] = hash;
\r
2064 updateCellCssStylesOnRenderedRows(hash, null);
\r
2066 trigger(self.onCellCssStylesChanged, { "key": key, "hash": hash });
\r
2069 function removeCellCssStyles(key) {
\r
2070 if (!cellCssClasses[key]) {
\r
2074 updateCellCssStylesOnRenderedRows(null, cellCssClasses[key]);
\r
2075 delete cellCssClasses[key];
\r
2077 trigger(self.onCellCssStylesChanged, { "key": key, "hash": null });
\r
2080 function setCellCssStyles(key, hash) {
\r
2081 var prevHash = cellCssClasses[key];
\r
2083 cellCssClasses[key] = hash;
\r
2084 updateCellCssStylesOnRenderedRows(hash, prevHash);
\r
2086 trigger(self.onCellCssStylesChanged, { "key": key, "hash": hash });
\r
2089 function getCellCssStyles(key) {
\r
2090 return cellCssClasses[key];
\r
2093 function flashCell(row, cell, speed) {
\r
2094 speed = speed || 100;
\r
2095 if (rowsCache[row]) {
\r
2096 var $cell = $(getCellNode(row, cell));
\r
2098 function toggleCellClass(times) {
\r
2102 setTimeout(function () {
\r
2103 $cell.queue(function () {
\r
2104 $cell.toggleClass(options.cellFlashingCssClass).dequeue();
\r
2105 toggleCellClass(times - 1);
\r
2111 toggleCellClass(4);
\r
2115 //////////////////////////////////////////////////////////////////////////////////////////////
\r
2118 function handleDragInit(e, dd) {
\r
2119 var cell = getCellFromEvent(e);
\r
2120 if (!cell || !cellExists(cell.row, cell.cell)) {
\r
2124 var retval = trigger(self.onDragInit, dd, e);
\r
2125 if (e.isImmediatePropagationStopped()) {
\r
2129 // if nobody claims to be handling drag'n'drop by stopping immediate propagation,
\r
2130 // cancel out of it
\r
2134 function handleDragStart(e, dd) {
\r
2135 var cell = getCellFromEvent(e);
\r
2136 if (!cell || !cellExists(cell.row, cell.cell)) {
\r
2140 var retval = trigger(self.onDragStart, dd, e);
\r
2141 if (e.isImmediatePropagationStopped()) {
\r
2148 function handleDrag(e, dd) {
\r
2149 return trigger(self.onDrag, dd, e);
\r
2152 function handleDragEnd(e, dd) {
\r
2153 trigger(self.onDragEnd, dd, e);
\r
2156 function handleKeyDown(e) {
\r
2157 trigger(self.onKeyDown, {row: activeRow, cell: activeCell}, e);
\r
2158 var handled = e.isImmediatePropagationStopped();
\r
2161 if (!e.shiftKey && !e.altKey && !e.ctrlKey) {
\r
2162 if (e.which == 27) {
\r
2163 if (!getEditorLock().isActive()) {
\r
2164 return; // no editing mode to cancel, allow bubbling and default processing (exit without cancelling the event)
\r
2166 cancelEditAndSetFocus();
\r
2167 } else if (e.which == 37) {
\r
2168 handled = navigateLeft();
\r
2169 } else if (e.which == 39) {
\r
2170 handled = navigateRight();
\r
2171 } else if (e.which == 38) {
\r
2172 handled = navigateUp();
\r
2173 } else if (e.which == 40) {
\r
2174 handled = navigateDown();
\r
2175 } else if (e.which == 9) {
\r
2176 handled = navigateNext();
\r
2177 } else if (e.which == 13) {
\r
2178 if (options.editable) {
\r
2179 if (currentEditor) {
\r
2181 if (activeRow === getDataLength()) {
\r
2184 commitEditAndSetFocus();
\r
2187 if (getEditorLock().commitCurrentEdit()) {
\r
2188 makeActiveCellEditable();
\r
2194 } else if (e.which == 9 && e.shiftKey && !e.ctrlKey && !e.altKey) {
\r
2195 handled = navigatePrev();
\r
2200 // the event has been handled so don't let parent element (bubbling/propagation) or browser (default) handle it
\r
2201 e.stopPropagation();
\r
2202 e.preventDefault();
\r
2204 e.originalEvent.keyCode = 0; // prevent default behaviour for special keys in IE browsers (F3, F5, etc.)
\r
2206 // ignore exceptions - setting the original event's keycode throws access denied exception for "Ctrl"
\r
2207 // (hitting control key only, nothing else), "Shift" (maybe others)
\r
2213 function handleClick(e) {
\r
2214 if (!currentEditor) {
\r
2215 // if this click resulted in some cell child node getting focus,
\r
2216 // don't steal it back - keyboard events will still bubble up
\r
2217 if (e.target != document.activeElement) {
\r
2222 var cell = getCellFromEvent(e);
\r
2223 if (!cell || (currentEditor !== null && activeRow == cell.row && activeCell == cell.cell)) {
\r
2227 trigger(self.onClick, {row: cell.row, cell: cell.cell}, e);
\r
2228 if (e.isImmediatePropagationStopped()) {
\r
2232 if ((activeCell != cell.cell || activeRow != cell.row) && canCellBeActive(cell.row, cell.cell)) {
\r
2233 if (!getEditorLock().isActive() || getEditorLock().commitCurrentEdit()) {
\r
2234 scrollRowIntoView(cell.row, false);
\r
2235 setActiveCellInternal(getCellNode(cell.row, cell.cell), (cell.row === getDataLength()) || options.autoEdit);
\r
2240 function handleContextMenu(e) {
\r
2241 var $cell = $(e.target).closest(".slick-cell", $canvas);
\r
2242 if ($cell.length === 0) {
\r
2246 // are we editing this cell?
\r
2247 if (activeCellNode === $cell[0] && currentEditor !== null) {
\r
2251 trigger(self.onContextMenu, {}, e);
\r
2254 function handleDblClick(e) {
\r
2255 var cell = getCellFromEvent(e);
\r
2256 if (!cell || (currentEditor !== null && activeRow == cell.row && activeCell == cell.cell)) {
\r
2260 trigger(self.onDblClick, {row: cell.row, cell: cell.cell}, e);
\r
2261 if (e.isImmediatePropagationStopped()) {
\r
2265 if (options.editable) {
\r
2266 gotoCell(cell.row, cell.cell, true);
\r
2270 function handleHeaderMouseEnter(e) {
\r
2271 trigger(self.onHeaderMouseEnter, {
\r
2272 "column": $(this).data("column")
\r
2276 function handleHeaderMouseLeave(e) {
\r
2277 trigger(self.onHeaderMouseLeave, {
\r
2278 "column": $(this).data("column")
\r
2282 function handleHeaderContextMenu(e) {
\r
2283 var $header = $(e.target).closest(".slick-header-column", ".slick-header-columns");
\r
2284 var column = $header && $header.data("column");
\r
2285 trigger(self.onHeaderContextMenu, {column: column}, e);
\r
2288 function handleHeaderClick(e) {
\r
2289 var $header = $(e.target).closest(".slick-header-column", ".slick-header-columns");
\r
2290 var column = $header && $header.data("column");
\r
2292 trigger(self.onHeaderClick, {column: column}, e);
\r
2296 function handleMouseEnter(e) {
\r
2297 trigger(self.onMouseEnter, {}, e);
\r
2300 function handleMouseLeave(e) {
\r
2301 trigger(self.onMouseLeave, {}, e);
\r
2304 function cellExists(row, cell) {
\r
2305 return !(row < 0 || row >= getDataLength() || cell < 0 || cell >= columns.length);
\r
2308 function getCellFromPoint(x, y) {
\r
2309 var row = getRowFromPosition(y);
\r
2313 for (var i = 0; i < columns.length && w < x; i++) {
\r
2314 w += columns[i].width;
\r
2322 return {row: row, cell: cell - 1};
\r
2325 function getCellFromNode(cellNode) {
\r
2326 // read column number from .l<columnNumber> CSS class
\r
2327 var cls = /l\d+/.exec(cellNode.className);
\r
2329 throw "getCellFromNode: cannot get cell - " + cellNode.className;
\r
2331 return parseInt(cls[0].substr(1, cls[0].length - 1), 10);
\r
2334 function getRowFromNode(rowNode) {
\r
2335 for (var row in rowsCache) {
\r
2336 if (rowsCache[row].rowNode === rowNode) {
\r
2344 function getCellFromEvent(e) {
\r
2345 var $cell = $(e.target).closest(".slick-cell", $canvas);
\r
2346 if (!$cell.length) {
\r
2350 var row = getRowFromNode($cell[0].parentNode);
\r
2351 var cell = getCellFromNode($cell[0]);
\r
2353 if (row == null || cell == null) {
\r
2363 function getCellNodeBox(row, cell) {
\r
2364 if (!cellExists(row, cell)) {
\r
2368 var y1 = getRowTop(row);
\r
2369 var y2 = y1 + options.rowHeight - 1;
\r
2371 for (var i = 0; i < cell; i++) {
\r
2372 x1 += columns[i].width;
\r
2374 var x2 = x1 + columns[cell].width;
\r
2384 //////////////////////////////////////////////////////////////////////////////////////////////
\r
2387 function resetActiveCell() {
\r
2388 setActiveCellInternal(null, false);
\r
2391 function setFocus() {
\r
2392 if (tabbingDirection == -1) {
\r
2393 $focusSink[0].focus();
\r
2395 $focusSink2[0].focus();
\r
2399 function scrollCellIntoView(row, cell, doPaging) {
\r
2400 scrollRowIntoView(row, doPaging);
\r
2402 var colspan = getColspan(row, cell);
\r
2403 var left = columnPosLeft[cell],
\r
2404 right = columnPosRight[cell + (colspan > 1 ? colspan - 1 : 0)],
\r
2405 scrollRight = scrollLeft + viewportW;
\r
2407 if (left < scrollLeft) {
\r
2408 $viewport.scrollLeft(left);
\r
2411 } else if (right > scrollRight) {
\r
2412 $viewport.scrollLeft(Math.min(left, right - $viewport[0].clientWidth));
\r
2418 function setActiveCellInternal(newCell, editMode) {
\r
2419 if (activeCellNode !== null) {
\r
2420 makeActiveCellNormal();
\r
2421 $(activeCellNode).removeClass("active");
\r
2422 if (rowsCache[activeRow]) {
\r
2423 $(rowsCache[activeRow].rowNode).removeClass("active");
\r
2427 var activeCellChanged = (activeCellNode !== newCell);
\r
2428 activeCellNode = newCell;
\r
2430 if (activeCellNode != null) {
\r
2431 activeRow = getRowFromNode(activeCellNode.parentNode);
\r
2432 activeCell = activePosX = getCellFromNode(activeCellNode);
\r
2434 $(activeCellNode).addClass("active");
\r
2435 $(rowsCache[activeRow].rowNode).addClass("active");
\r
2437 if (options.editable && editMode && isCellPotentiallyEditable(activeRow, activeCell)) {
\r
2438 clearTimeout(h_editorLoader);
\r
2440 if (options.asyncEditorLoading) {
\r
2441 h_editorLoader = setTimeout(function () {
\r
2442 makeActiveCellEditable();
\r
2443 }, options.asyncEditorLoadDelay);
\r
2445 makeActiveCellEditable();
\r
2449 activeRow = activeCell = null;
\r
2452 if (activeCellChanged) {
\r
2453 trigger(self.onActiveCellChanged, getActiveCell());
\r
2457 function clearTextSelection() {
\r
2458 if (document.selection && document.selection.empty) {
\r
2460 //IE fails here if selected element is not in dom
\r
2461 document.selection.empty();
\r
2463 } else if (window.getSelection) {
\r
2464 var sel = window.getSelection();
\r
2465 if (sel && sel.removeAllRanges) {
\r
2466 sel.removeAllRanges();
\r
2471 function isCellPotentiallyEditable(row, cell) {
\r
2472 // is the data for this row loaded?
\r
2473 if (row < getDataLength() && !getDataItem(row)) {
\r
2477 // are we in the Add New row? can we create new from this cell?
\r
2478 if (columns[cell].cannotTriggerInsert && row >= getDataLength()) {
\r
2482 // does this cell have an editor?
\r
2483 if (!getEditor(row, cell)) {
\r
2490 function makeActiveCellNormal() {
\r
2491 if (!currentEditor) {
\r
2494 trigger(self.onBeforeCellEditorDestroy, {editor: currentEditor});
\r
2495 currentEditor.destroy();
\r
2496 currentEditor = null;
\r
2498 if (activeCellNode) {
\r
2499 var d = getDataItem(activeRow);
\r
2500 $(activeCellNode).removeClass("editable invalid");
\r
2502 var column = columns[activeCell];
\r
2503 var formatter = getFormatter(activeRow, column);
\r
2504 activeCellNode.innerHTML = formatter(activeRow, activeCell, getDataItemValueForColumn(d, column), column, d);
\r
2505 invalidatePostProcessingResults(activeRow);
\r
2509 // if there previously was text selected on a page (such as selected text in the edit cell just removed),
\r
2510 // IE can't set focus to anything else correctly
\r
2511 if (navigator.userAgent.toLowerCase().match(/msie/)) {
\r
2512 clearTextSelection();
\r
2515 getEditorLock().deactivate(editController);
\r
2518 function makeActiveCellEditable(editor) {
\r
2519 if (!activeCellNode) {
\r
2522 if (!options.editable) {
\r
2523 throw "Grid : makeActiveCellEditable : should never get called when options.editable is false";
\r
2526 // cancel pending async call if there is one
\r
2527 clearTimeout(h_editorLoader);
\r
2529 if (!isCellPotentiallyEditable(activeRow, activeCell)) {
\r
2533 var columnDef = columns[activeCell];
\r
2534 var item = getDataItem(activeRow);
\r
2536 if (trigger(self.onBeforeEditCell, {row: activeRow, cell: activeCell, item: item, column: columnDef}) === false) {
\r
2541 getEditorLock().activate(editController);
\r
2542 $(activeCellNode).addClass("editable");
\r
2544 // don't clear the cell if a custom editor is passed through
\r
2546 activeCellNode.innerHTML = "";
\r
2549 currentEditor = new (editor || getEditor(activeRow, activeCell))({
\r
2551 gridPosition: absBox($container[0]),
\r
2552 position: absBox(activeCellNode),
\r
2553 container: activeCellNode,
\r
2554 column: columnDef,
\r
2556 commitChanges: commitEditAndSetFocus,
\r
2557 cancelChanges: cancelEditAndSetFocus
\r
2561 currentEditor.loadValue(item);
\r
2564 serializedEditorValue = currentEditor.serializeValue();
\r
2566 if (currentEditor.position) {
\r
2567 handleActiveCellPositionChange();
\r
2571 function commitEditAndSetFocus() {
\r
2572 // if the commit fails, it would do so due to a validation error
\r
2573 // if so, do not steal the focus from the editor
\r
2574 if (getEditorLock().commitCurrentEdit()) {
\r
2576 if (options.autoEdit) {
\r
2582 function cancelEditAndSetFocus() {
\r
2583 if (getEditorLock().cancelCurrentEdit()) {
\r
2588 function absBox(elem) {
\r
2590 top: elem.offsetTop,
\r
2591 left: elem.offsetLeft,
\r
2594 width: $(elem).outerWidth(),
\r
2595 height: $(elem).outerHeight(),
\r
2597 box.bottom = box.top + box.height;
\r
2598 box.right = box.left + box.width;
\r
2600 // walk up the tree
\r
2601 var offsetParent = elem.offsetParent;
\r
2602 while ((elem = elem.parentNode) != document.body) {
\r
2603 if (box.visible && elem.scrollHeight != elem.offsetHeight && $(elem).css("overflowY") != "visible") {
\r
2604 box.visible = box.bottom > elem.scrollTop && box.top < elem.scrollTop + elem.clientHeight;
\r
2607 if (box.visible && elem.scrollWidth != elem.offsetWidth && $(elem).css("overflowX") != "visible") {
\r
2608 box.visible = box.right > elem.scrollLeft && box.left < elem.scrollLeft + elem.clientWidth;
\r
2611 box.left -= elem.scrollLeft;
\r
2612 box.top -= elem.scrollTop;
\r
2614 if (elem === offsetParent) {
\r
2615 box.left += elem.offsetLeft;
\r
2616 box.top += elem.offsetTop;
\r
2617 offsetParent = elem.offsetParent;
\r
2620 box.bottom = box.top + box.height;
\r
2621 box.right = box.left + box.width;
\r
2627 function getActiveCellPosition() {
\r
2628 return absBox(activeCellNode);
\r
2631 function getGridPosition() {
\r
2632 return absBox($container[0])
\r
2635 function handleActiveCellPositionChange() {
\r
2636 if (!activeCellNode) {
\r
2640 trigger(self.onActiveCellPositionChanged, {});
\r
2642 if (currentEditor) {
\r
2643 var cellBox = getActiveCellPosition();
\r
2644 if (currentEditor.show && currentEditor.hide) {
\r
2645 if (!cellBox.visible) {
\r
2646 currentEditor.hide();
\r
2648 currentEditor.show();
\r
2652 if (currentEditor.position) {
\r
2653 currentEditor.position(cellBox);
\r
2658 function getCellEditor() {
\r
2659 return currentEditor;
\r
2662 function getActiveCell() {
\r
2663 if (!activeCellNode) {
\r
2666 return {row: activeRow, cell: activeCell};
\r
2670 function getActiveCellNode() {
\r
2671 return activeCellNode;
\r
2674 function scrollRowIntoView(row, doPaging) {
\r
2675 var rowAtTop = row * options.rowHeight;
\r
2676 var rowAtBottom = (row + 1) * options.rowHeight - viewportH + (viewportHasHScroll ? scrollbarDimensions.height : 0);
\r
2678 // need to page down?
\r
2679 if ((row + 1) * options.rowHeight > scrollTop + viewportH + offset) {
\r
2680 scrollTo(doPaging ? rowAtTop : rowAtBottom);
\r
2684 else if (row * options.rowHeight < scrollTop + offset) {
\r
2685 scrollTo(doPaging ? rowAtBottom : rowAtTop);
\r
2690 function scrollRowToTop(row) {
\r
2691 scrollTo(row * options.rowHeight);
\r
2695 function getColspan(row, cell) {
\r
2696 var metadata = data.getItemMetadata && data.getItemMetadata(row);
\r
2697 if (!metadata || !metadata.columns) {
\r
2701 var columnData = metadata.columns[columns[cell].id] || metadata.columns[cell];
\r
2702 var colspan = (columnData && columnData.colspan);
\r
2703 if (colspan === "*") {
\r
2704 colspan = columns.length - cell;
\r
2706 colspan = colspan || 1;
\r
2712 function findFirstFocusableCell(row) {
\r
2714 while (cell < columns.length) {
\r
2715 if (canCellBeActive(row, cell)) {
\r
2718 cell += getColspan(row, cell);
\r
2723 function findLastFocusableCell(row) {
\r
2725 var lastFocusableCell = null;
\r
2726 while (cell < columns.length) {
\r
2727 if (canCellBeActive(row, cell)) {
\r
2728 lastFocusableCell = cell;
\r
2730 cell += getColspan(row, cell);
\r
2732 return lastFocusableCell;
\r
2735 function gotoRight(row, cell, posX) {
\r
2736 if (cell >= columns.length) {
\r
2741 cell += getColspan(row, cell);
\r
2743 while (cell < columns.length && !canCellBeActive(row, cell));
\r
2745 if (cell < columns.length) {
\r
2755 function gotoLeft(row, cell, posX) {
\r
2760 var firstFocusableCell = findFirstFocusableCell(row);
\r
2761 if (firstFocusableCell === null || firstFocusableCell >= cell) {
\r
2767 "cell": firstFocusableCell,
\r
2768 "posX": firstFocusableCell
\r
2772 pos = gotoRight(prev.row, prev.cell, prev.posX);
\r
2776 if (pos.cell >= cell) {
\r
2783 function gotoDown(row, cell, posX) {
\r
2786 if (++row >= getDataLength() + (options.enableAddRow ? 1 : 0)) {
\r
2790 prevCell = cell = 0;
\r
2791 while (cell <= posX) {
\r
2793 cell += getColspan(row, cell);
\r
2796 if (canCellBeActive(row, prevCell)) {
\r
2806 function gotoUp(row, cell, posX) {
\r
2813 prevCell = cell = 0;
\r
2814 while (cell <= posX) {
\r
2816 cell += getColspan(row, cell);
\r
2819 if (canCellBeActive(row, prevCell)) {
\r
2829 function gotoNext(row, cell, posX) {
\r
2830 if (row == null && cell == null) {
\r
2831 row = cell = posX = 0;
\r
2832 if (canCellBeActive(row, cell)) {
\r
2841 var pos = gotoRight(row, cell, posX);
\r
2846 var firstFocusableCell = null;
\r
2847 while (++row < getDataLength() + (options.enableAddRow ? 1 : 0)) {
\r
2848 firstFocusableCell = findFirstFocusableCell(row);
\r
2849 if (firstFocusableCell !== null) {
\r
2852 "cell": firstFocusableCell,
\r
2853 "posX": firstFocusableCell
\r
2860 function gotoPrev(row, cell, posX) {
\r
2861 if (row == null && cell == null) {
\r
2862 row = getDataLength() + (options.enableAddRow ? 1 : 0) - 1;
\r
2863 cell = posX = columns.length - 1;
\r
2864 if (canCellBeActive(row, cell)) {
\r
2874 var lastSelectableCell;
\r
2876 pos = gotoLeft(row, cell, posX);
\r
2885 lastSelectableCell = findLastFocusableCell(row);
\r
2886 if (lastSelectableCell !== null) {
\r
2889 "cell": lastSelectableCell,
\r
2890 "posX": lastSelectableCell
\r
2897 function navigateRight() {
\r
2898 return navigate("right");
\r
2901 function navigateLeft() {
\r
2902 return navigate("left");
\r
2905 function navigateDown() {
\r
2906 return navigate("down");
\r
2909 function navigateUp() {
\r
2910 return navigate("up");
\r
2913 function navigateNext() {
\r
2914 return navigate("next");
\r
2917 function navigatePrev() {
\r
2918 return navigate("prev");
\r
2922 * @param {string} dir Navigation direction.
\r
2923 * @return {boolean} Whether navigation resulted in a change of active cell.
\r
2925 function navigate(dir) {
\r
2926 if (!options.enableCellNavigation) {
\r
2930 if (!activeCellNode && dir != "prev" && dir != "next") {
\r
2934 if (!getEditorLock().commitCurrentEdit()) {
\r
2939 var tabbingDirections = {
\r
2947 tabbingDirection = tabbingDirections[dir];
\r
2949 var stepFunctions = {
\r
2953 "right": gotoRight,
\r
2957 var stepFn = stepFunctions[dir];
\r
2958 var pos = stepFn(activeRow, activeCell, activePosX);
\r
2960 var isAddNewRow = (pos.row == getDataLength());
\r
2961 scrollCellIntoView(pos.row, pos.cell, !isAddNewRow);
\r
2962 setActiveCellInternal(getCellNode(pos.row, pos.cell), isAddNewRow || options.autoEdit);
\r
2963 activePosX = pos.posX;
\r
2966 setActiveCellInternal(getCellNode(activeRow, activeCell), (activeRow == getDataLength()) || options.autoEdit);
\r
2971 function getCellNode(row, cell) {
\r
2972 if (rowsCache[row]) {
\r
2973 ensureCellNodesInRowsCache(row);
\r
2974 return rowsCache[row].cellNodesByColumnIdx[cell];
\r
2979 function setActiveCell(row, cell) {
\r
2980 if (!initialized) { return; }
\r
2981 if (row > getDataLength() || row < 0 || cell >= columns.length || cell < 0) {
\r
2985 if (!options.enableCellNavigation) {
\r
2989 scrollCellIntoView(row, cell, false);
\r
2990 setActiveCellInternal(getCellNode(row, cell), false);
\r
2993 function canCellBeActive(row, cell) {
\r
2994 if (!options.enableCellNavigation || row >= getDataLength() + (options.enableAddRow ? 1 : 0) ||
\r
2995 row < 0 || cell >= columns.length || cell < 0) {
\r
2999 var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
\r
3000 if (rowMetadata && typeof rowMetadata.focusable === "boolean") {
\r
3001 return rowMetadata.focusable;
\r
3004 var columnMetadata = rowMetadata && rowMetadata.columns;
\r
3005 if (columnMetadata && columnMetadata[columns[cell].id] && typeof columnMetadata[columns[cell].id].focusable === "boolean") {
\r
3006 return columnMetadata[columns[cell].id].focusable;
\r
3008 if (columnMetadata && columnMetadata[cell] && typeof columnMetadata[cell].focusable === "boolean") {
\r
3009 return columnMetadata[cell].focusable;
\r
3012 return columns[cell].focusable;
\r
3015 function canCellBeSelected(row, cell) {
\r
3016 if (row >= getDataLength() || row < 0 || cell >= columns.length || cell < 0) {
\r
3020 var rowMetadata = data.getItemMetadata && data.getItemMetadata(row);
\r
3021 if (rowMetadata && typeof rowMetadata.selectable === "boolean") {
\r
3022 return rowMetadata.selectable;
\r
3025 var columnMetadata = rowMetadata && rowMetadata.columns && (rowMetadata.columns[columns[cell].id] || rowMetadata.columns[cell]);
\r
3026 if (columnMetadata && typeof columnMetadata.selectable === "boolean") {
\r
3027 return columnMetadata.selectable;
\r
3030 return columns[cell].selectable;
\r
3033 function gotoCell(row, cell, forceEdit) {
\r
3034 if (!initialized) { return; }
\r
3035 if (!canCellBeActive(row, cell)) {
\r
3039 if (!getEditorLock().commitCurrentEdit()) {
\r
3043 scrollCellIntoView(row, cell, false);
\r
3045 var newCell = getCellNode(row, cell);
\r
3047 // if selecting the 'add new' row, start editing right away
\r
3048 setActiveCellInternal(newCell, forceEdit || (row === getDataLength()) || options.autoEdit);
\r
3050 // if no editor was created, set the focus back on the grid
\r
3051 if (!currentEditor) {
\r
3057 //////////////////////////////////////////////////////////////////////////////////////////////
\r
3058 // IEditor implementation for the editor lock
\r
3060 function commitCurrentEdit() {
\r
3061 var item = getDataItem(activeRow);
\r
3062 var column = columns[activeCell];
\r
3064 if (currentEditor) {
\r
3065 if (currentEditor.isValueChanged()) {
\r
3066 var validationResults = currentEditor.validate();
\r
3068 if (validationResults.valid) {
\r
3069 if (activeRow < getDataLength()) {
\r
3070 var editCommand = {
\r
3073 editor: currentEditor,
\r
3074 serializedValue: currentEditor.serializeValue(),
\r
3075 prevSerializedValue: serializedEditorValue,
\r
3076 execute: function () {
\r
3077 this.editor.applyValue(item, this.serializedValue);
\r
3078 updateRow(this.row);
\r
3080 undo: function () {
\r
3081 this.editor.applyValue(item, this.prevSerializedValue);
\r
3082 updateRow(this.row);
\r
3086 if (options.editCommandHandler) {
\r
3087 makeActiveCellNormal();
\r
3088 options.editCommandHandler(item, column, editCommand);
\r
3090 editCommand.execute();
\r
3091 makeActiveCellNormal();
\r
3094 trigger(self.onCellChange, {
\r
3101 currentEditor.applyValue(newItem, currentEditor.serializeValue());
\r
3102 makeActiveCellNormal();
\r
3103 trigger(self.onAddNewRow, {item: newItem, column: column});
\r
3106 // check whether the lock has been re-acquired by event handlers
\r
3107 return !getEditorLock().isActive();
\r
3109 // Re-add the CSS class to trigger transitions, if any.
\r
3110 $(activeCellNode).removeClass("invalid");
\r
3111 $(activeCellNode).width(); // force layout
\r
3112 $(activeCellNode).addClass("invalid");
\r
3114 trigger(self.onValidationError, {
\r
3115 editor: currentEditor,
\r
3116 cellNode: activeCellNode,
\r
3117 validationResults: validationResults,
\r
3123 currentEditor.focus();
\r
3128 makeActiveCellNormal();
\r
3133 function cancelCurrentEdit() {
\r
3134 makeActiveCellNormal();
\r
3138 function rowsToRanges(rows) {
\r
3140 var lastCell = columns.length - 1;
\r
3141 for (var i = 0; i < rows.length; i++) {
\r
3142 ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));
\r
3147 function getSelectedRows() {
\r
3148 if (!selectionModel) {
\r
3149 throw "Selection model is not set";
\r
3151 return selectedRows;
\r
3154 function setSelectedRows(rows) {
\r
3155 if (!selectionModel) {
\r
3156 throw "Selection model is not set";
\r
3158 selectionModel.setSelectedRanges(rowsToRanges(rows));
\r
3162 //////////////////////////////////////////////////////////////////////////////////////////////
\r
3165 this.debug = function () {
\r
3168 s += ("\n" + "counter_rows_rendered: " + counter_rows_rendered);
\r
3169 s += ("\n" + "counter_rows_removed: " + counter_rows_removed);
\r
3170 s += ("\n" + "renderedRows: " + renderedRows);
\r
3171 s += ("\n" + "numVisibleRows: " + numVisibleRows);
\r
3172 s += ("\n" + "maxSupportedCssHeight: " + maxSupportedCssHeight);
\r
3173 s += ("\n" + "n(umber of pages): " + n);
\r
3174 s += ("\n" + "(current) page: " + page);
\r
3175 s += ("\n" + "page height (ph): " + ph);
\r
3176 s += ("\n" + "vScrollDir: " + vScrollDir);
\r
3181 // a debug helper to be able to access private members
\r
3182 this.eval = function (expr) {
\r
3183 return eval(expr);
\r
3186 //////////////////////////////////////////////////////////////////////////////////////////////
\r
3190 "slickGridVersion": "2.1",
\r
3193 "onScroll": new Slick.Event(),
\r
3194 "onSort": new Slick.Event(),
\r
3195 "onHeaderMouseEnter": new Slick.Event(),
\r
3196 "onHeaderMouseLeave": new Slick.Event(),
\r
3197 "onHeaderContextMenu": new Slick.Event(),
\r
3198 "onHeaderClick": new Slick.Event(),
\r
3199 "onHeaderCellRendered": new Slick.Event(),
\r
3200 "onBeforeHeaderCellDestroy": new Slick.Event(),
\r
3201 "onHeaderRowCellRendered": new Slick.Event(),
\r
3202 "onBeforeHeaderRowCellDestroy": new Slick.Event(),
\r
3203 "onMouseEnter": new Slick.Event(),
\r
3204 "onMouseLeave": new Slick.Event(),
\r
3205 "onClick": new Slick.Event(),
\r
3206 "onDblClick": new Slick.Event(),
\r
3207 "onContextMenu": new Slick.Event(),
\r
3208 "onKeyDown": new Slick.Event(),
\r
3209 "onAddNewRow": new Slick.Event(),
\r
3210 "onValidationError": new Slick.Event(),
\r
3211 "onViewportChanged": new Slick.Event(),
\r
3212 "onColumnsReordered": new Slick.Event(),
\r
3213 "onColumnsResized": new Slick.Event(),
\r
3214 "onCellChange": new Slick.Event(),
\r
3215 "onBeforeEditCell": new Slick.Event(),
\r
3216 "onBeforeCellEditorDestroy": new Slick.Event(),
\r
3217 "onBeforeDestroy": new Slick.Event(),
\r
3218 "onActiveCellChanged": new Slick.Event(),
\r
3219 "onActiveCellPositionChanged": new Slick.Event(),
\r
3220 "onDragInit": new Slick.Event(),
\r
3221 "onDragStart": new Slick.Event(),
\r
3222 "onDrag": new Slick.Event(),
\r
3223 "onDragEnd": new Slick.Event(),
\r
3224 "onSelectedRowsChanged": new Slick.Event(),
\r
3225 "onCellCssStylesChanged": new Slick.Event(),
\r
3228 "registerPlugin": registerPlugin,
\r
3229 "unregisterPlugin": unregisterPlugin,
\r
3230 "getColumns": getColumns,
\r
3231 "setColumns": setColumns,
\r
3232 "getColumnIndex": getColumnIndex,
\r
3233 "updateColumnHeader": updateColumnHeader,
\r
3234 "setSortColumn": setSortColumn,
\r
3235 "setSortColumns": setSortColumns,
\r
3236 "getSortColumns": getSortColumns,
\r
3237 "autosizeColumns": autosizeColumns,
\r
3238 "getOptions": getOptions,
\r
3239 "setOptions": setOptions,
\r
3240 "getData": getData,
\r
3241 "getDataLength": getDataLength,
\r
3242 "getDataItem": getDataItem,
\r
3243 "setData": setData,
\r
3244 "getSelectionModel": getSelectionModel,
\r
3245 "setSelectionModel": setSelectionModel,
\r
3246 "getSelectedRows": getSelectedRows,
\r
3247 "setSelectedRows": setSelectedRows,
\r
3248 "getContainerNode": getContainerNode,
\r
3251 "invalidate": invalidate,
\r
3252 "invalidateRow": invalidateRow,
\r
3253 "invalidateRows": invalidateRows,
\r
3254 "invalidateAllRows": invalidateAllRows,
\r
3255 "updateCell": updateCell,
\r
3256 "updateRow": updateRow,
\r
3257 "getViewport": getVisibleRange,
\r
3258 "getRenderedRange": getRenderedRange,
\r
3259 "resizeCanvas": resizeCanvas,
\r
3260 "updateRowCount": updateRowCount,
\r
3261 "scrollRowIntoView": scrollRowIntoView,
\r
3262 "scrollRowToTop": scrollRowToTop,
\r
3263 "scrollCellIntoView": scrollCellIntoView,
\r
3264 "getCanvasNode": getCanvasNode,
\r
3265 "focus": setFocus,
\r
3267 "getCellFromPoint": getCellFromPoint,
\r
3268 "getCellFromEvent": getCellFromEvent,
\r
3269 "getActiveCell": getActiveCell,
\r
3270 "setActiveCell": setActiveCell,
\r
3271 "getActiveCellNode": getActiveCellNode,
\r
3272 "getActiveCellPosition": getActiveCellPosition,
\r
3273 "resetActiveCell": resetActiveCell,
\r
3274 "editActiveCell": makeActiveCellEditable,
\r
3275 "getCellEditor": getCellEditor,
\r
3276 "getCellNode": getCellNode,
\r
3277 "getCellNodeBox": getCellNodeBox,
\r
3278 "canCellBeSelected": canCellBeSelected,
\r
3279 "canCellBeActive": canCellBeActive,
\r
3280 "navigatePrev": navigatePrev,
\r
3281 "navigateNext": navigateNext,
\r
3282 "navigateUp": navigateUp,
\r
3283 "navigateDown": navigateDown,
\r
3284 "navigateLeft": navigateLeft,
\r
3285 "navigateRight": navigateRight,
\r
3286 "gotoCell": gotoCell,
\r
3287 "getTopPanel": getTopPanel,
\r
3288 "setTopPanelVisibility": setTopPanelVisibility,
\r
3289 "setHeaderRowVisibility": setHeaderRowVisibility,
\r
3290 "getHeaderRow": getHeaderRow,
\r
3291 "getHeaderRowColumn": getHeaderRowColumn,
\r
3292 "getGridPosition": getGridPosition,
\r
3293 "flashCell": flashCell,
\r
3294 "addCellCssStyles": addCellCssStyles,
\r
3295 "setCellCssStyles": setCellCssStyles,
\r
3296 "removeCellCssStyles": removeCellCssStyles,
\r
3297 "getCellCssStyles": getCellCssStyles,
\r
3299 "init": finishInitialization,
\r
3300 "destroy": destroy,
\r
3302 // IEditor implementation
\r
3303 "getEditorLock": getEditorLock,
\r
3304 "getEditController": getEditController
\r