UNPKG

slickgrid

Version:

A lightning fast JavaScript grid/spreadsheet

1,410 lines (1,169 loc) 213 kB
/** * @license * (c) 2009-2016 Michael Leibman * michael{dot}leibman{at}gmail{dot}com * http://github.com/mleibman/slickgrid * * Distributed under MIT license. * All rights reserved. * * SlickGrid v2.4 * * NOTES: * Cell/row DOM manipulations are done directly bypassing jQuery's DOM manipulation methods. * This increases the speed dramatically, but can only be done safely because there are no event handlers * or data associated with any cell/row DOM nodes. Cell editors must make sure they implement .destroy() * and do proper cleanup. */ // make sure required JavaScript modules are loaded if (typeof jQuery === "undefined") { throw new Error("SlickGrid requires jquery module to be loaded"); } if (!jQuery.fn.drag) { throw new Error("SlickGrid requires jquery.event.drag module to be loaded"); } if (typeof Slick === "undefined") { throw new Error("slick.core.js not loaded"); } (function ($) { "use strict"; // shared across all grids on the page var scrollbarDimensions; var maxSupportedCssHeight; // browser's breaking point ////////////////////////////////////////////////////////////////////////////////////////////// // SlickGrid class implementation (available as Slick.Grid) /** * Creates a new instance of the grid. * @class SlickGrid * @constructor * @param {Node} container Container node to create the grid in. * @param {Array,Object} data An array of objects for databinding. * @param {Array} columns An array of column definitions. * @param {Object} options Grid options. **/ function SlickGrid(container, data, columns, options) { // settings var defaults = { alwaysShowVerticalScroll: false, alwaysAllowHorizontalScroll: false, explicitInitialization: false, rowHeight: 25, defaultColumnWidth: 80, enableAddRow: false, leaveSpaceForNewRows: false, editable: false, autoEdit: true, suppressActiveCellChangeOnEdit: false, enableCellNavigation: true, enableColumnReorder: true, asyncEditorLoading: false, asyncEditorLoadDelay: 100, forceFitColumns: false, enableAsyncPostRender: false, asyncPostRenderDelay: 50, enableAsyncPostRenderCleanup: false, asyncPostRenderCleanupDelay: 40, autoHeight: false, editorLock: Slick.GlobalEditorLock, showColumnHeader: true, showHeaderRow: false, headerRowHeight: 25, createFooterRow: false, showFooterRow: false, footerRowHeight: 25, createPreHeaderPanel: false, showPreHeaderPanel: false, preHeaderPanelHeight: 25, showTopPanel: false, topPanelHeight: 25, formatterFactory: null, editorFactory: null, cellFlashingCssClass: "flashing", selectedCellCssClass: "selected", multiSelect: true, enableTextSelectionOnCells: false, dataItemColumnValueExtractor: null, frozenBottom: false, frozenColumn: -1, frozenRow: -1, frozenRightViewportMinWidth: 100, fullWidthRows: false, multiColumnSort: false, numberedMultiColumnSort: false, tristateMultiColumnSort: false, sortColNumberInSeparateSpan: false, defaultFormatter: defaultFormatter, forceSyncScrolling: false, addNewRowCssClass: "new-row", preserveCopiedSelectionOnPaste: false, showCellSelection: true, viewportClass: null, minRowBuffer: 3, emulatePagingWhenScrolling: true, // when scrolling off bottom of viewport, place new row at top of viewport editorCellNavOnLRKeys: false, enableMouseWheelScrollHandler: true, doPaging: true, autosizeColsMode: Slick.GridAutosizeColsMode.LegacyOff, autosizeColPaddingPx: 4, autosizeTextAvgToMWidthRatio: 0.75, viewportSwitchToScrollModeWidthPercent: undefined, viewportMinWidthPx: undefined, viewportMaxWidthPx: undefined, suppressCssChangesOnHiddenInit: false, ffMaxSupportedCssHeight: 6000000, maxSupportedCssHeight: 1000000000, sanitizer: undefined, // sanitize function, built in basic sanitizer is: Slick.RegexSanitizer(dirtyHtml) logSanitizedHtml: false // log to console when sanitised - recommend true for testing of dev and production }; var columnDefaults = { name: "", resizable: true, sortable: false, minWidth: 30, maxWidth: undefined, rerenderOnResize: false, headerCssClass: null, defaultSortAsc: true, focusable: true, selectable: true, }; var columnAutosizeDefaults = { ignoreHeaderText: false, colValueArray: undefined, allowAddlPercent: undefined, formatterOverride: undefined, autosizeMode: Slick.ColAutosizeMode.ContentIntelligent, rowSelectionModeOnInit: undefined, rowSelectionMode: Slick.RowSelectionMode.FirstNRows, rowSelectionCount: 100, valueFilterMode: Slick.ValueFilterMode.None, widthEvalMode: Slick.WidthEvalMode.CanvasTextSize, sizeToRemaining: undefined, widthPx: undefined, colDataTypeOf: undefined }; // scroller var th; // virtual height var h; // real scrollable height var ph; // page height var n; // number of pages var cj; // "jumpiness" coefficient var page = 0; // current page var offset = 0; // current page offset var vScrollDir = 1; // private var initialized = false; var $container; var uid = "slickgrid_" + Math.round(1000000 * Math.random()); var self = this; var $focusSink, $focusSink2; var $groupHeaders = $(); var $headerScroller; var $headers; var $headerRow, $headerRowScroller, $headerRowSpacerL, $headerRowSpacerR; var $footerRow, $footerRowScroller, $footerRowSpacerL, $footerRowSpacerR; var $preHeaderPanel, $preHeaderPanelScroller, $preHeaderPanelSpacer; var $preHeaderPanelR, $preHeaderPanelScrollerR, $preHeaderPanelSpacerR; var $topPanelScroller; var $topPanel; var $viewport; var $canvas; var $style; var $boundAncestors; var treeColumns; var stylesheet, columnCssRulesL, columnCssRulesR; var viewportH, viewportW; var canvasWidth, canvasWidthL, canvasWidthR; var headersWidth, headersWidthL, headersWidthR; var viewportHasHScroll, viewportHasVScroll; var headerColumnWidthDiff = 0, headerColumnHeightDiff = 0, // border+padding cellWidthDiff = 0, cellHeightDiff = 0, jQueryNewWidthBehaviour = false; var absoluteColumnMinWidth; var hasFrozenRows = false; var frozenRowsHeight = 0; var actualFrozenRow = -1; var paneTopH = 0; var paneBottomH = 0; var viewportTopH = 0; var viewportBottomH = 0; var topPanelH = 0; var headerRowH = 0; var footerRowH = 0; var tabbingDirection = 1; var $activeCanvasNode; var $activeViewportNode; var activePosX; var activeRow, activeCell; var activeCellNode = null; var currentEditor = null; var serializedEditorValue; var editController; var rowsCache = {}; var renderedRows = 0; var numVisibleRows = 0; var prevScrollTop = 0; var scrollTop = 0; var lastRenderedScrollTop = 0; var lastRenderedScrollLeft = 0; var prevScrollLeft = 0; var scrollLeft = 0; var selectionModel; var selectedRows = []; var plugins = []; var cellCssClasses = {}; var columnsById = {}; var sortColumns = []; var columnPosLeft = []; var columnPosRight = []; var pagingActive = false; var pagingIsLastPage = false; var scrollThrottle = ActionThrottle(render, 50); // async call handles var h_editorLoader = null; var h_render = null; var h_postrender = null; var h_postrenderCleanup = null; var postProcessedRows = {}; var postProcessToRow = null; var postProcessFromRow = null; var postProcessedCleanupQueue = []; var postProcessgroupId = 0; // perf counters var counter_rows_rendered = 0; var counter_rows_removed = 0; var $paneHeaderL; var $paneHeaderR; var $paneTopL; var $paneTopR; var $paneBottomL; var $paneBottomR; var $headerScrollerL; var $headerScrollerR; var $headerL; var $headerR; var $groupHeadersL; var $groupHeadersR; var $headerRowScrollerL; var $headerRowScrollerR; var $footerRowScrollerL; var $footerRowScrollerR; var $headerRowL; var $headerRowR; var $footerRowL; var $footerRowR; var $topPanelScrollerL; var $topPanelScrollerR; var $topPanelL; var $topPanelR; var $viewportTopL; var $viewportTopR; var $viewportBottomL; var $viewportBottomR; var $canvasTopL; var $canvasTopR; var $canvasBottomL; var $canvasBottomR; var $viewportScrollContainerX; var $viewportScrollContainerY; var $headerScrollContainer; var $headerRowScrollContainer; var $footerRowScrollContainer; // store css attributes if display:none is active in container or parent var cssShow = { position: 'absolute', visibility: 'hidden', display: 'block' }; var $hiddenParents; var oldProps = []; var columnResizeDragging = false; ////////////////////////////////////////////////////////////////////////////////////////////// // Initialization function init() { if (container instanceof jQuery) { $container = container; } else { $container = $(container); } if ($container.length < 1) { throw new Error("SlickGrid requires a valid container, " + container + " does not exist in the DOM."); } // calculate these only once and share between grid instances maxSupportedCssHeight = maxSupportedCssHeight || getMaxSupportedCssHeight(); options = $.extend({}, defaults, options); validateAndEnforceOptions(); columnDefaults.width = options.defaultColumnWidth; if (!options.suppressCssChangesOnHiddenInit) { cacheCssForHiddenInit(); } treeColumns = new Slick.TreeColumns(columns); columns = treeColumns.extractColumns(); updateColumnProps(); // validate loaded JavaScript modules against requested options if (options.enableColumnReorder && !$.fn.sortable) { throw new Error("SlickGrid's 'enableColumnReorder = true' option requires jquery-ui.sortable module to be loaded"); } editController = { "commitCurrentEdit": commitCurrentEdit, "cancelCurrentEdit": cancelCurrentEdit }; $container .empty() .css("overflow", "hidden") .css("outline", 0) .addClass(uid) .addClass("ui-widget"); // set up a positioning container if needed if (!(/relative|absolute|fixed/).test($container.css("position"))) { $container.css("position", "relative"); } $focusSink = $("<div tabIndex='0' hideFocus style='position:fixed;width:0;height:0;top:0;left:0;outline:0;'></div>").appendTo($container); // Containers used for scrolling frozen columns and rows $paneHeaderL = $("<div class='slick-pane slick-pane-header slick-pane-left' tabIndex='0' />").appendTo($container); $paneHeaderR = $("<div class='slick-pane slick-pane-header slick-pane-right' tabIndex='0' />").appendTo($container); $paneTopL = $("<div class='slick-pane slick-pane-top slick-pane-left' tabIndex='0' />").appendTo($container); $paneTopR = $("<div class='slick-pane slick-pane-top slick-pane-right' tabIndex='0' />").appendTo($container); $paneBottomL = $("<div class='slick-pane slick-pane-bottom slick-pane-left' tabIndex='0' />").appendTo($container); $paneBottomR = $("<div class='slick-pane slick-pane-bottom slick-pane-right' tabIndex='0' />").appendTo($container); if (options.createPreHeaderPanel) { $preHeaderPanelScroller = $("<div class='slick-preheader-panel ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($paneHeaderL); $preHeaderPanel = $("<div />").appendTo($preHeaderPanelScroller); $preHeaderPanelSpacer = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>") .appendTo($preHeaderPanelScroller); $preHeaderPanelScrollerR = $("<div class='slick-preheader-panel ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($paneHeaderR); $preHeaderPanelR = $("<div />").appendTo($preHeaderPanelScrollerR); $preHeaderPanelSpacerR = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>") .appendTo($preHeaderPanelScrollerR); if (!options.showPreHeaderPanel) { $preHeaderPanelScroller.hide(); $preHeaderPanelScrollerR.hide(); } } // Append the header scroller containers $headerScrollerL = $("<div class='slick-header ui-state-default slick-header-left' />").appendTo($paneHeaderL); $headerScrollerR = $("<div class='slick-header ui-state-default slick-header-right' />").appendTo($paneHeaderR); // Cache the header scroller containers $headerScroller = $().add($headerScrollerL).add($headerScrollerR); if (treeColumns.hasDepth()) { $groupHeadersL = []; $groupHeadersR = []; for (var index = 0; index < treeColumns.getDepth() - 1; index++) { $groupHeadersL[index] = $("<div class='slick-group-header-columns slick-group-header-columns-left' style='left:-1000px' />").appendTo($headerScrollerL); $groupHeadersR[index] = $("<div class='slick-group-header-columns slick-group-header-columns-right' style='left:-1000px' />").appendTo($headerScrollerR); } $groupHeaders = $().add($groupHeadersL).add($groupHeadersR); } // Append the columnn containers to the headers $headerL = $("<div class='slick-header-columns slick-header-columns-left' style='left:-1000px' />").appendTo($headerScrollerL); $headerR = $("<div class='slick-header-columns slick-header-columns-right' style='left:-1000px' />").appendTo($headerScrollerR); // Cache the header columns $headers = $().add($headerL).add($headerR); $headerRowScrollerL = $("<div class='slick-headerrow ui-state-default' />").appendTo($paneTopL); $headerRowScrollerR = $("<div class='slick-headerrow ui-state-default' />").appendTo($paneTopR); $headerRowScroller = $().add($headerRowScrollerL).add($headerRowScrollerR); $headerRowSpacerL = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>") .appendTo($headerRowScrollerL); $headerRowSpacerR = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>") .appendTo($headerRowScrollerR); $headerRowL = $("<div class='slick-headerrow-columns slick-headerrow-columns-left' />").appendTo($headerRowScrollerL); $headerRowR = $("<div class='slick-headerrow-columns slick-headerrow-columns-right' />").appendTo($headerRowScrollerR); $headerRow = $().add($headerRowL).add($headerRowR); // Append the top panel scroller $topPanelScrollerL = $("<div class='slick-top-panel-scroller ui-state-default' />").appendTo($paneTopL); $topPanelScrollerR = $("<div class='slick-top-panel-scroller ui-state-default' />").appendTo($paneTopR); $topPanelScroller = $().add($topPanelScrollerL).add($topPanelScrollerR); // Append the top panel $topPanelL = $("<div class='slick-top-panel' style='width:10000px' />").appendTo($topPanelScrollerL); $topPanelR = $("<div class='slick-top-panel' style='width:10000px' />").appendTo($topPanelScrollerR); $topPanel = $().add($topPanelL).add($topPanelR); if (!options.showColumnHeader) { $headerScroller.hide(); } if (!options.showTopPanel) { $topPanelScroller.hide(); } if (!options.showHeaderRow) { $headerRowScroller.hide(); } // Append the viewport containers $viewportTopL = $("<div class='slick-viewport slick-viewport-top slick-viewport-left' tabIndex='0' hideFocus />").appendTo($paneTopL); $viewportTopR = $("<div class='slick-viewport slick-viewport-top slick-viewport-right' tabIndex='0' hideFocus />").appendTo($paneTopR); $viewportBottomL = $("<div class='slick-viewport slick-viewport-bottom slick-viewport-left' tabIndex='0' hideFocus />").appendTo($paneBottomL); $viewportBottomR = $("<div class='slick-viewport slick-viewport-bottom slick-viewport-right' tabIndex='0' hideFocus />").appendTo($paneBottomR); // Cache the viewports $viewport = $().add($viewportTopL).add($viewportTopR).add($viewportBottomL).add($viewportBottomR); // Default the active viewport to the top left $activeViewportNode = $viewportTopL; // Append the canvas containers $canvasTopL = $("<div class='grid-canvas grid-canvas-top grid-canvas-left' tabIndex='0' hideFocus />").appendTo($viewportTopL); $canvasTopR = $("<div class='grid-canvas grid-canvas-top grid-canvas-right' tabIndex='0' hideFocus />").appendTo($viewportTopR); $canvasBottomL = $("<div class='grid-canvas grid-canvas-bottom grid-canvas-left' tabIndex='0' hideFocus />").appendTo($viewportBottomL); $canvasBottomR = $("<div class='grid-canvas grid-canvas-bottom grid-canvas-right' tabIndex='0' hideFocus />").appendTo($viewportBottomR); if (options.viewportClass) $viewport.toggleClass(options.viewportClass, true); // Cache the canvases $canvas = $().add($canvasTopL).add($canvasTopR).add($canvasBottomL).add($canvasBottomR); scrollbarDimensions = scrollbarDimensions || measureScrollbar(); // Default the active canvas to the top left $activeCanvasNode = $canvasTopL; // pre-header if ($preHeaderPanelSpacer) $preHeaderPanelSpacer.css("width", getCanvasWidth() + scrollbarDimensions.width + "px"); $headers.width(getHeadersWidth()); $headerRowSpacerL.css("width", getCanvasWidth() + scrollbarDimensions.width + "px"); $headerRowSpacerR.css("width", getCanvasWidth() + scrollbarDimensions.width + "px"); // footer Row if (options.createFooterRow) { $footerRowScrollerR = $("<div class='slick-footerrow ui-state-default' />").appendTo($paneTopR); $footerRowScrollerL = $("<div class='slick-footerrow ui-state-default' />").appendTo($paneTopL); $footerRowScroller = $().add($footerRowScrollerL).add($footerRowScrollerR); $footerRowSpacerL = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>") .css("width", getCanvasWidth() + scrollbarDimensions.width + "px") .appendTo($footerRowScrollerL); $footerRowSpacerR = $("<div style='display:block;height:1px;position:absolute;top:0;left:0;'></div>") .css("width", getCanvasWidth() + scrollbarDimensions.width + "px") .appendTo($footerRowScrollerR); $footerRowL = $("<div class='slick-footerrow-columns slick-footerrow-columns-left' />").appendTo($footerRowScrollerL); $footerRowR = $("<div class='slick-footerrow-columns slick-footerrow-columns-right' />").appendTo($footerRowScrollerR); $footerRow = $().add($footerRowL).add($footerRowR); if (!options.showFooterRow) { $footerRowScroller.hide(); } } $focusSink2 = $focusSink.clone().appendTo($container); if (!options.explicitInitialization) { finishInitialization(); } } function finishInitialization() { if (!initialized) { initialized = true; getViewportWidth(); getViewportHeight(); // header columns and cells may have different padding/border skewing width calculations (box-sizing, hello?) // calculate the diff so we can set consistent sizes measureCellPaddingAndBorder(); // for usability reasons, all text selection in SlickGrid is disabled // with the exception of input and textarea elements (selection must // be enabled there so that editors work as expected); note that // selection in grid cells (grid body) is already unavailable in // all browsers except IE disableSelection($headers); // disable all text selection in header (including input and textarea) if (!options.enableTextSelectionOnCells) { // disable text selection in grid cells except in input and textarea elements // (this is IE-specific, because selectstart event will only fire in IE) $viewport.on("selectstart.ui", function (event) { return $(event.target).is("input,textarea"); }); } setFrozenOptions(); setPaneVisibility(); setScroller(); setOverflow(); updateColumnCaches(); createColumnHeaders(); createColumnGroupHeaders(); createColumnFooter(); setupColumnSort(); createCssRules(); resizeCanvas(); bindAncestorScrollEvents(); $container .on("resize.slickgrid", resizeCanvas); $viewport .on("scroll", handleScroll); if (jQuery.fn.mousewheel && options.enableMouseWheelScrollHandler) { $viewport.on("mousewheel", handleMouseWheel); } $headerScroller //.on("scroll", handleHeaderScroll) .on("contextmenu", handleHeaderContextMenu) .on("click", handleHeaderClick) .on("mouseenter", ".slick-header-column", handleHeaderMouseEnter) .on("mouseleave", ".slick-header-column", handleHeaderMouseLeave); $headerRowScroller .on("scroll", handleHeaderRowScroll); if (options.showHeaderRow) { $headerRow .on("mouseenter", ".slick-headerrow-column", handleHeaderRowMouseEnter) .on("mouseleave", ".slick-headerrow-column", handleHeaderRowMouseLeave); } if (options.createFooterRow) { $footerRow .on("contextmenu", handleFooterContextMenu) .on("click", handleFooterClick); $footerRowScroller .on("scroll", handleFooterRowScroll); } if (options.createPreHeaderPanel) { $preHeaderPanelScroller .on("scroll", handlePreHeaderPanelScroll); } $focusSink.add($focusSink2) .on("keydown", handleKeyDown); $canvas .on("keydown", handleKeyDown) .on("click", handleClick) .on("dblclick", handleDblClick) .on("contextmenu", handleContextMenu) .on("draginit", handleDragInit) .on("dragstart", {distance: 3}, handleDragStart) .on("drag", handleDrag) .on("dragend", handleDragEnd) .on("mouseenter", ".slick-cell", handleMouseEnter) .on("mouseleave", ".slick-cell", handleMouseLeave); if (!options.suppressCssChangesOnHiddenInit) { restoreCssFromHiddenInit(); } } } function cacheCssForHiddenInit() { // handle display:none on container or container parents $hiddenParents = $container.parents().addBack().not(':visible'); $hiddenParents.each(function() { var old = {}; for ( var name in cssShow ) { old[ name ] = this.style[ name ]; this.style[ name ] = cssShow[ name ]; } oldProps.push(old); }); } function restoreCssFromHiddenInit() { // finish handle display:none on container or container parents // - put values back the way they were $hiddenParents.each(function(i) { var old = oldProps[i]; for ( var name in cssShow ) { this.style[ name ] = old[ name ]; } }); } function hasFrozenColumns() { return options.frozenColumn > -1; } function registerPlugin(plugin) { plugins.unshift(plugin); plugin.init(self); } function unregisterPlugin(plugin) { for (var i = plugins.length; i >= 0; i--) { if (plugins[i] === plugin) { if (plugins[i].destroy) { plugins[i].destroy(); } plugins.splice(i, 1); break; } } } function getPluginByName(name) { for (var i = plugins.length-1; i >= 0; i--) { if (plugins[i].pluginName === name) { return plugins[i]; } } return undefined; } function setSelectionModel(model) { if (selectionModel) { selectionModel.onSelectedRangesChanged.unsubscribe(handleSelectedRangesChanged); if (selectionModel.destroy) { selectionModel.destroy(); } } selectionModel = model; if (selectionModel) { selectionModel.init(self); selectionModel.onSelectedRangesChanged.subscribe(handleSelectedRangesChanged); } } function getSelectionModel() { return selectionModel; } function getCanvasNode(columnIdOrIdx, rowIndex) { return _getContainerElement(getCanvases(), columnIdOrIdx, rowIndex); } function getActiveCanvasNode(element) { setActiveCanvasNode(element); return $activeCanvasNode[0]; } function getCanvases() { return $canvas; } function setActiveCanvasNode(element) { if (element) { $activeCanvasNode = $(element.target).closest('.grid-canvas'); } } function getViewportNode(columnIdOrIdx, rowIndex) { return _getContainerElement(getViewports(), columnIdOrIdx, rowIndex); } function getViewports() { return $viewport; } function getActiveViewportNode(element) { setActiveViewportNode(element); return $activeViewportNode[0]; } function setActiveViewportNode(element) { if (element) { $activeViewportNode = $(element.target).closest('.slick-viewport'); } } function _getContainerElement($targetContainers, columnIdOrIdx, rowIndex) { if (!$targetContainers) { return } if (!columnIdOrIdx) { columnIdOrIdx = 0; } if (!rowIndex) { rowIndex = 0; } var idx = (typeof columnIdOrIdx === "number" ? columnIdOrIdx : getColumnIndex(columnIdOrIdx)); var isBottomSide = hasFrozenRows && rowIndex >= actualFrozenRow + (options.frozenBottom ? 0 : 1); var isRightSide = hasFrozenColumns() && idx > options.frozenColumn; return $targetContainers[(isBottomSide ? 2 : 0) + (isRightSide ? 1 : 0)]; } function measureScrollbar() { var $outerdiv = $('<div class="' + $viewport.className + '" style="position:absolute; top:-10000px; left:-10000px; overflow:auto; width:100px; height:100px;"></div>').appendTo('body'); var $innerdiv = $('<div style="width:200px; height:200px; overflow:auto;"></div>').appendTo($outerdiv); var dim = { width: $outerdiv[0].offsetWidth - $outerdiv[0].clientWidth, height: $outerdiv[0].offsetHeight - $outerdiv[0].clientHeight }; $innerdiv.remove(); $outerdiv.remove(); return dim; } function getHeadersWidth() { headersWidth = headersWidthL = headersWidthR = 0; var includeScrollbar = !options.autoHeight; for (var i = 0, ii = columns.length; i < ii; i++) { var width = columns[ i ].width; if (( options.frozenColumn ) > -1 && ( i > options.frozenColumn )) { headersWidthR += width; } else { headersWidthL += width; } } if (includeScrollbar) { if (( options.frozenColumn ) > -1 && ( i > options.frozenColumn )) { headersWidthR += scrollbarDimensions.width; } else { headersWidthL += scrollbarDimensions.width; } } if (hasFrozenColumns()) { headersWidthL = headersWidthL + 1000; headersWidthR = Math.max(headersWidthR, viewportW) + headersWidthL; headersWidthR += scrollbarDimensions.width; } else { headersWidthL += scrollbarDimensions.width; headersWidthL = Math.max(headersWidthL, viewportW) + 1000; } headersWidth = headersWidthL + headersWidthR; return Math.max(headersWidth, viewportW) + 1000; } function getHeadersWidthL() { headersWidthL =0; columns.forEach(function(column, i) { if (!(( options.frozenColumn ) > -1 && ( i > options.frozenColumn ))) headersWidthL += column.width; }); if (hasFrozenColumns()) { headersWidthL += 1000; } else { headersWidthL += scrollbarDimensions.width; headersWidthL = Math.max(headersWidthL, viewportW) + 1000; } return headersWidthL; } function getHeadersWidthR() { headersWidthR =0; columns.forEach(function(column, i) { if (( options.frozenColumn ) > -1 && ( i > options.frozenColumn )) headersWidthR += column.width; }); if (hasFrozenColumns()) { headersWidthR = Math.max(headersWidthR, viewportW) + getHeadersWidthL(); headersWidthR += scrollbarDimensions.width; } return headersWidthR; } function getCanvasWidth() { var availableWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW; var i = columns.length; canvasWidthL = canvasWidthR = 0; while (i--) { if (hasFrozenColumns() && (i > options.frozenColumn)) { canvasWidthR += columns[i].width; } else { canvasWidthL += columns[i].width; } } var totalRowWidth = canvasWidthL + canvasWidthR; return options.fullWidthRows ? Math.max(totalRowWidth, availableWidth) : totalRowWidth; } function updateCanvasWidth(forceColumnWidthsUpdate) { var oldCanvasWidth = canvasWidth; var oldCanvasWidthL = canvasWidthL; var oldCanvasWidthR = canvasWidthR; var widthChanged; canvasWidth = getCanvasWidth(); widthChanged = canvasWidth !== oldCanvasWidth || canvasWidthL !== oldCanvasWidthL || canvasWidthR !== oldCanvasWidthR; if (widthChanged || hasFrozenColumns() || hasFrozenRows) { $canvasTopL.width(canvasWidthL); getHeadersWidth(); $headerL.width(headersWidthL); $headerR.width(headersWidthR); if (hasFrozenColumns()) { $canvasTopR.width(canvasWidthR); $paneHeaderL.width(canvasWidthL); $paneHeaderR.css('left', canvasWidthL); $paneHeaderR.css('width', viewportW - canvasWidthL); $paneTopL.width(canvasWidthL); $paneTopR.css('left', canvasWidthL); $paneTopR.css('width', viewportW - canvasWidthL); $headerRowScrollerL.width(canvasWidthL); $headerRowScrollerR.width(viewportW - canvasWidthL); $headerRowL.width(canvasWidthL); $headerRowR.width(canvasWidthR); if (options.createFooterRow) { $footerRowScrollerL.width(canvasWidthL); $footerRowScrollerR.width(viewportW - canvasWidthL); $footerRowL.width(canvasWidthL); $footerRowR.width(canvasWidthR); } if (options.createPreHeaderPanel) { $preHeaderPanel.width(canvasWidth); } $viewportTopL.width(canvasWidthL); $viewportTopR.width(viewportW - canvasWidthL); if (hasFrozenRows) { $paneBottomL.width(canvasWidthL); $paneBottomR.css('left', canvasWidthL); $viewportBottomL.width(canvasWidthL); $viewportBottomR.width(viewportW - canvasWidthL); $canvasBottomL.width(canvasWidthL); $canvasBottomR.width(canvasWidthR); } } else { $paneHeaderL.width('100%'); $paneTopL.width('100%'); $headerRowScrollerL.width('100%'); $headerRowL.width(canvasWidth); if (options.createFooterRow) { $footerRowScrollerL.width('100%'); $footerRowL.width(canvasWidth); } if (options.createPreHeaderPanel) { $preHeaderPanel.width('100%'); $preHeaderPanel.width(canvasWidth); } $viewportTopL.width('100%'); if (hasFrozenRows) { $viewportBottomL.width('100%'); $canvasBottomL.width(canvasWidthL); } } viewportHasHScroll = (canvasWidth >= viewportW - scrollbarDimensions.width); } $headerRowSpacerL.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0)); $headerRowSpacerR.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0)); if (options.createFooterRow) { $footerRowSpacerL.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0)); $footerRowSpacerR.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0)); } if (widthChanged || forceColumnWidthsUpdate) { applyColumnWidths(); } } function disableSelection($target) { if ($target && $target.jquery) { $target .attr("unselectable", "on") .css("MozUserSelect", "none") .on("selectstart.ui", function () { return false; }); // from jquery:ui.core.js 1.7.2 } } function getMaxSupportedCssHeight() { var supportedHeight = 1000000; // FF reports the height back but still renders blank after ~6M px //var testUpTo = navigator.userAgent.toLowerCase().match(/firefox/) ? 6000000 : 1000000000; var testUpTo = navigator.userAgent.toLowerCase().match(/firefox/) ? options.ffMaxSupportedCssHeight : options.maxSupportedCssHeight; var div = $("<div style='display:none' />").appendTo(document.body); while (true) { var test = supportedHeight * 2; div.css("height", test); if (test > testUpTo || div.height() !== test) { break; } else { supportedHeight = test; } } div.remove(); return supportedHeight; } function getUID() { return uid; } function getHeaderColumnWidthDiff() { return headerColumnWidthDiff; } function getScrollbarDimensions() { return scrollbarDimensions; } function getDisplayedScrollbarDimensions() { return { width: viewportHasVScroll ? scrollbarDimensions.width : 0, height: viewportHasHScroll ? scrollbarDimensions.height : 0 }; } function getAbsoluteColumnMinWidth() { return absoluteColumnMinWidth; } // TODO: this is static. need to handle page mutation. function bindAncestorScrollEvents() { var elem = (hasFrozenRows && !options.frozenBottom) ? $canvasBottomL[0] : $canvasTopL[0]; while ((elem = elem.parentNode) != document.body && elem != null) { // bind to scroll containers only if (elem == $viewportTopL[0] || elem.scrollWidth != elem.clientWidth || elem.scrollHeight != elem.clientHeight) { var $elem = $(elem); if (!$boundAncestors) { $boundAncestors = $elem; } else { $boundAncestors = $boundAncestors.add($elem); } $elem.on("scroll." + uid, handleActiveCellPositionChange); } } } function unbindAncestorScrollEvents() { if (!$boundAncestors) { return; } $boundAncestors.off("scroll." + uid); $boundAncestors = null; } function updateColumnHeader(columnId, title, toolTip) { if (!initialized) { return; } var idx = getColumnIndex(columnId); if (idx == null) { return; } var columnDef = columns[idx]; var $header = $headers.children().eq(idx); if ($header) { if (title !== undefined) { columns[idx].name = title; } if (toolTip !== undefined) { columns[idx].toolTip = toolTip; } trigger(self.onBeforeHeaderCellDestroy, { "node": $header[0], "column": columnDef, "grid": self }); $header .attr("title", toolTip || "") .children().eq(0).html(title); trigger(self.onHeaderCellRendered, { "node": $header[0], "column": columnDef, "grid": self }); } } function getHeader(columnDef) { if (!columnDef) { return hasFrozenColumns() ? $headers : $headerL; } var idx = getColumnIndex(columnDef.id); return hasFrozenColumns() ? ((idx <= options.frozenColumn) ? $headerL : $headerR) : $headerL; } function getHeaderColumn(columnIdOrIdx) { var idx = (typeof columnIdOrIdx === "number" ? columnIdOrIdx : getColumnIndex(columnIdOrIdx)); var targetHeader = hasFrozenColumns() ? ((idx <= options.frozenColumn) ? $headerL : $headerR) : $headerL; var targetIndex = hasFrozenColumns() ? ((idx <= options.frozenColumn) ? idx : idx - options.frozenColumn - 1) : idx; var $rtn = targetHeader.children().eq(targetIndex); return $rtn && $rtn[0]; } function getHeaderRow() { return hasFrozenColumns() ? $headerRow : $headerRow[0]; } function getFooterRow() { return hasFrozenColumns() ? $footerRow : $footerRow[0]; } function getPreHeaderPanel() { return $preHeaderPanel[0]; } function getPreHeaderPanelRight() { return $preHeaderPanelR[0]; } function getHeaderRowColumn(columnIdOrIdx) { var idx = (typeof columnIdOrIdx === "number" ? columnIdOrIdx : getColumnIndex(columnIdOrIdx)); var $headerRowTarget; if (hasFrozenColumns()) { if (idx <= options.frozenColumn) { $headerRowTarget = $headerRowL; } else { $headerRowTarget = $headerRowR; idx -= options.frozenColumn + 1; } } else { $headerRowTarget = $headerRowL; } var $header = $headerRowTarget.children().eq(idx); return $header && $header[0]; } function getFooterRowColumn(columnIdOrIdx) { var idx = (typeof columnIdOrIdx === "number" ? columnIdOrIdx : getColumnIndex(columnIdOrIdx)); var $footerRowTarget; if (hasFrozenColumns()) { if (idx <= options.frozenColumn) { $footerRowTarget = $footerRowL; } else { $footerRowTarget = $footerRowR; idx -= options.frozenColumn + 1; } } else { $footerRowTarget = $footerRowL; } var $footer = $footerRowTarget && $footerRowTarget.children().eq(idx); return $footer && $footer[0]; } function createColumnFooter() { if (options.createFooterRow) { $footerRow.find(".slick-footerrow-column") .each(function () { var columnDef = $(this).data("column"); if (columnDef) { trigger(self.onBeforeFooterRowCellDestroy, { "node": this, "column": columnDef, "grid": self }); } }); $footerRowL.empty(); $footerRowR.empty(); for (var i = 0; i < columns.length; i++) { var m = columns[i]; var footerRowCell = $("<div class='ui-state-default slick-footerrow-column l" + i + " r" + i + "'></div>") .data("column", m) .addClass(hasFrozenColumns() && i <= options.frozenColumn? 'frozen': '') .appendTo(hasFrozenColumns() && (i > options.frozenColumn)? $footerRowR: $footerRowL); trigger(self.onFooterRowCellRendered, { "node": footerRowCell[0], "column": m, "grid": self }); } } } function createColumnGroupHeaders() { var columnsLength = 0; var frozenColumnsValid = false; if (!treeColumns.hasDepth()) return; for (var index = 0; index < $groupHeadersL.length; index++) { $groupHeadersL[index].empty(); $groupHeadersR[index].empty(); var groupColumns = treeColumns.getColumnsInDepth(index); for (var indexGroup in groupColumns) { var m = groupColumns[indexGroup]; columnsLength += m.extractColumns().length; if (hasFrozenColumns() && index === 0 && (columnsLength-1) === options.frozenColumn) frozenColumnsValid = true; $("<div class='ui-state-default slick-group-header-column' />") .html("<span class='slick-column-name'>" + m.name + "</span>") .attr("id", "" + uid + m.id) .attr("title", m.toolTip || "") .data("column", m) .addClass(m.headerCssClass || "") .addClass(hasFrozenColumns() && (columnsLength - 1) > options.frozenColumn? 'frozen': '') .appendTo(hasFrozenColumns() && (columnsLength - 1) > options.frozenColumn? $groupHeadersR[index]: $groupHeadersL[index]); } if (hasFrozenColumns() && index === 0 && !frozenColumnsValid) { $groupHeadersL[index].empty(); $groupHeadersR[index].empty(); alert("All columns of group should to be grouped!"); break; } } applyColumnGroupHeaderWidths(); } function createColumnHeaders() { function onMouseEnter() { $(this).addClass("ui-state-hover"); } function onMouseLeave() { $(this).removeClass("ui-state-hover"); } $headers.find(".slick-header-column") .each(function() { var columnDef = $(this).data("column"); if (columnDef) { trigger(self.onBeforeHeaderCellDestroy, { "node": this, "column": columnDef, "grid": self }); } }); $headerL.empty(); $headerR.empty(); getHeadersWidth(); $headerL.width(headersWidthL); $headerR.width(headersWidthR); $headerRow.find(".slick-headerrow-column") .each(function() { var columnDef = $(this).data("column"); if (columnDef) { trigger(self.onBeforeHeaderRowCellDestroy, { "node": this, "column": columnDef, "grid": self }); } }); $headerRowL.empty(); $headerRowR.empty(); if (options.createFooterRow) { $footerRowL.find(".slick-footerrow-column") .each(function() { var columnDef = $(this).data("column"); if (columnDef) { trigger(self.onBeforeFooterRowCellDestroy, { "node": this, "column": columnDef, "grid": self }); } }); $footerRowL.empty(); if (hasFrozenColumns()) { $footerRowR.find(".slick-footerrow-column") .each(function() { var columnDef = $(this).data("column"); if (columnDef) { trigger(self.onBeforeFooterRowCellDestroy, { "node": this, "column": columnDef, "grid": self }); } }); $footerRowR.empty(); } } for (var i = 0; i < columns.length; i++) { var m = columns[i]; var $headerTarget = hasFrozenColumns() ? ((i <= options.frozenColumn) ? $headerL : $headerR) : $headerL; var $headerRowTarget = hasFrozenColumns() ? ((i <= options.frozenColumn) ? $headerRowL : $headerRowR) : $headerRowL; var header = $("<div class='ui-state-default slick-header-column' />") .html("<span class='slick-column-name'>" + m.name + "</span>") .width(m.width - headerColumnWidthDiff) .attr("id", "" + uid + m.id) .attr("title", m.toolTip || "") .data("column", m) .addClass(m.headerCssClass || "") .addClass(hasFrozenColumns() && i <= options.frozenColumn? 'frozen': '') .appendTo($headerTarget); if (options.enableColumnReorder || m.sortable) { header .on('mouseenter', onMouseEnter) .on('mouseleave', onMouseLeave); } if(m.hasOwnProperty('headerCellAttrs') && m.headerCellAttrs instanceof Object) { for (var key in m.headerCellAttrs) { if (m.headerCellAttrs.hasOwnProperty(key)) { header.attr(key, m.headerCellAttrs[key]); } } } if (m.sortable) { header.addClass("slick-header-sortable"); header.append("<span class='slick-sort-indicator" + (options.numberedMultiColumnSort && !options.sortColNumberInSeparateSpan ? " slick-sort-indicator-numbered" : "" ) + "' />"); if (options.numberedMultiColumnSort && options.sortColNumberInSeparateSpan) { header.append("<span class='slick-sort-indicator-numbered' />"); } } trigger(self.onHeaderCellRendered, { "node": header[0], "column": m, "grid": self }); if (options.showHeaderRow) { var headerRowCell = $("<div class='ui-state-default slick-headerrow-column l" + i + " r" + i + "'></div>") .data("column", m) .addClass(hasFrozenColumns() && i <= options.frozenColumn? 'frozen': '') .appendTo($headerRowTarget); trigger(self.onHeaderRowCellRendered, { "node": headerRowCell[0], "column": m, "grid": self }); } if (options.createFooterRow && options.showFooterRow) { var footerRowCell = $("<div class='ui-state-default slick-footerrow-column l" + i + " r" + i + "'></div>") .data("column", m) .appendTo($footerRow); trigger(self.onFooterRowCellRendered, { "node": footerRowCell[0], "column": m, "grid": self }); } } setSortColumns(sortColumns); setupColumnResize(); if (options.enableColumnReorder) { if (typeof options.enableColumnReorder == 'function') { options.enableColumnReorder(self, $headers, headerColumnWidthDiff, setColumns, setupColumnResize, columns, getColumnIndex, uid, trigger); } else { setupColumnReorder(); } } } function setupColumnSort() { $headers.click(function (e) { if (columnResizeDragging) return; // temporary workaround for a bug in jQuery 1.7.1 (http://bugs.jquery.com/ticket/11328) e.metaKey = e.metaKey || e.ctrlKey; if ($(e.target).hasClass("slick-resizable-handle")) { return; } var $col = $(e.target).closest(".slick-header-column"); if (!$col.length) { return; } var column = $col.data("column"); if (column.sortable) { if (!getEditorLock().commitCurrentEdit()) { return; } var previousSortColumns = $.extend(true, [], sortColumns); var sortColumn = null; var i = 0; for (; i < sortColumns.length; i++) { if (sortColumns[i].columnId == column.id) { sortColumn = sortColumns[i]; sortColumn.sortAsc = !sortColumn.sortAsc; break; } } var hadSortCol = !!sortColumn; if (options.tristateMultiColumnSort) { if (!sortColumn) { sortColumn = { columnId: column.id, sortAsc: column.defaultSortAsc }; } if (hadSortCol && sortColumn.sortAsc) { // three state: remove sort rather than go back to ASC sortColumns.splice(i, 1); sortColumn = null; } if (!options.multiColumnSort) { sortColumns = []; } if (sortColumn && (!hadSortCol || !options.multiColumnSort)) { sortColumns.push(sortColumn); } } else { // legacy behaviour if (e.metaKey && options.multiColumnSort) { if (sortColumn) { sortColumns.splice(i, 1); } } else { if ((!e.shiftKey && !e.metaKey) || !options.multiColumnSort) { sortColumns = []; } if (