UNPKG

xrm

Version:

1,284 lines (1,090 loc) 97.7 kB
/** * @license * (c) 2009-2010 Michael Leibman * michael{dot}leibman{at}gmail{dot}com * http://github.com/mleibman/slickgrid * Distributed under MIT license. * All rights reserved. * * SlickGrid v2.0 alpha * * 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 "SlickGrid requires jquery module to be loaded"; } if (!jQuery.fn.drag) { throw "SlickGrid requires jquery.event.drag module to be loaded"; } if (typeof Slick === "undefined") { throw "slick.core.js not loaded"; } (function($) { // Slick.Grid $.extend(true, window, { Slick: { Grid: SlickGrid } }); var scrollbarDimensions; // shared across all grids on this page ////////////////////////////////////////////////////////////////////////////////////////////// // SlickGrid class implementation (available as Slick.Grid) /** * @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) { /// <summary> /// Create and manage virtual grid in the specified $container, /// connecting it to the specified data source. Data is presented /// as a grid with the specified columns and data.length rows. /// Options alter behaviour of the grid. /// </summary> // settings var defaults = { headerHeight: 25, rowHeight: 25, defaultColumnWidth: 80, enableAddRow: false, leaveSpaceForNewRows: false, editable: false, autoEdit: true, enableCellNavigation: true, enableCellRangeSelection: false, enableColumnReorder: true, asyncEditorLoading: false, asyncEditorLoadDelay: 100, forceFitColumns: false, enableAsyncPostRender: false, asyncPostRenderDelay: 60, autoHeight: false, editorLock: Slick.GlobalEditorLock, showHeaderRow: false, headerRowHeight: 25, showTopPanel: false, topPanelHeight: 25, formatterFactory: null, editorFactory: null, cellFlashingCssClass: "flashing", selectedCellCssClass: "selected", multiSelect: true, enableTextSelectionOnCells: false }; var columnDefaults = { name: "", resizable: true, sortable: false, minWidth: 30, rerenderOnResize: false, headerCssClass: null }; // scroller var maxSupportedCssHeight; // browser's breaking point 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 scrollDir = 1; // private var $container; var uid = "slickgrid_" + Math.round(1000000 * Math.random()); var self = this; var $headerScroller; var $headers; var $headerRow, $headerRowScroller; var $topPanelScroller; var $topPanel; var $viewport; var $canvas; var $style; var stylesheet; var viewportH, viewportW; var viewportHasHScroll; var headerColumnWidthDiff, headerColumnHeightDiff, cellWidthDiff, cellHeightDiff; // padding+border var absoluteColumnMinWidth; var activePosX; var activeRow, activeCell; var activeCellNode = null; var currentEditor = null; var serializedEditorValue; var editController; var rowsCache = {}; var renderedRows = 0; var numVisibleRows; var prevScrollTop = 0; var scrollTop = 0; var lastRenderedScrollTop = 0; var prevScrollLeft = 0; var avgRowRenderTime = 10; var selectionModel; var selectedRows = []; var plugins = []; var cellCssClasses = {}; var columnsById = {}; var sortColumnId; var sortAsc = true; // async call handles var h_editorLoader = null; var h_render = null; var h_postrender = null; var postProcessedRows = {}; var postProcessToRow = null; var postProcessFromRow = null; // perf counters var counter_rows_rendered = 0; var counter_rows_removed = 0; ////////////////////////////////////////////////////////////////////////////////////////////// // Initialization function init() { /// <summary> /// Initialize 'this' (self) instance of a SlickGrid. /// This function is called by the constructor. /// </summary> $container = $(container); if($container.length < 1) { throw new Error("SlickGrid requires a valid container, "+container+" does not exist in the DOM."); } maxSupportedCssHeight = getMaxSupportedCssHeight(); scrollbarDimensions = scrollbarDimensions || measureScrollbar(); // skip measurement if already have dimensions options = $.extend({},defaults,options); columnDefaults.width = options.defaultColumnWidth; // 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() .attr("tabIndex",0) .attr("hideFocus",true) .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"); $headerScroller = $("<div class='slick-header ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container); $headers = $("<div class='slick-header-columns' style='width:10000px; left:-1000px' />").appendTo($headerScroller); $headerRowScroller = $("<div class='slick-headerrow ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container); $headerRow = $("<div class='slick-headerrow-columns' style='width:10000px;' />").appendTo($headerRowScroller); $topPanelScroller = $("<div class='slick-top-panel-scroller ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container); $topPanel = $("<div class='slick-top-panel' style='width:10000px' />").appendTo($topPanelScroller); if (!options.showTopPanel) { $topPanelScroller.hide(); } if (!options.showHeaderRow) { $headerRowScroller.hide(); } $viewport = $("<div class='slick-viewport' tabIndex='0' hideFocus style='width:100%;overflow-x:auto;outline:0;position:relative;overflow-y:auto;'>").appendTo($container); $canvas = $("<div class='grid-canvas' tabIndex='0' hideFocus />").appendTo($viewport); // 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.bind("selectstart.ui", function (event) { return $(event.target).is("input,textarea"); }); } viewportW = parseFloat($.css($container[0], "width", true)); createColumnHeaders(); setupColumnSort(); createCssRules(); resizeAndRender(); bindAncestorScrollEvents(); $viewport.bind("scroll.slickgrid", handleScroll); $container.bind("resize.slickgrid", resizeAndRender); $headerScroller .bind("contextmenu.slickgrid", handleHeaderContextMenu) .bind("click.slickgrid", handleHeaderClick); $canvas .bind("keydown.slickgrid", handleKeyDown) .bind("click.slickgrid", handleClick) .bind("dblclick.slickgrid", handleDblClick) .bind("contextmenu.slickgrid", handleContextMenu) .bind("draginit", handleDragInit) .bind("dragstart", handleDragStart) .bind("drag", handleDrag) .bind("dragend", handleDragEnd); $canvas.delegate(".slick-cell", "mouseenter", handleMouseEnter); $canvas.delegate(".slick-cell", "mouseleave", handleMouseLeave); } 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 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() { return $canvas[0]; } function measureScrollbar() { /// <summary> /// Measure width of a vertical scrollbar /// and height of a horizontal scrollbar. /// </summary /// <returns> /// { width: pixelWidth, height: pixelHeight } /// </returns> var $c = $("<div style='position:absolute; top:-10000px; left:-10000px; width:100px; height:100px; overflow:scroll;'></div>").appendTo("body"); var dim = { width: $c.width() - $c[0].clientWidth, height: $c.height() - $c[0].clientHeight }; $c.remove(); return dim; } function getRowWidth() { var rowWidth = 0; var i = columns.length; while (i--) { rowWidth += (columns[i].width || columnDefaults.width); } return rowWidth; } function setCanvasWidth(width) { $canvas.width(width); viewportHasHScroll = (width > viewportW - scrollbarDimensions.width); } function disableSelection($target) { /// <summary> /// Disable text selection (using mouse) in /// the specified target. /// </summary if ($target && $target.jquery) { $target .attr('unselectable', 'on') .css('MozUserSelect', 'none') .bind('selectstart.ui', function() { return false; }); // from jquery:ui.core.js 1.7.2 } } function getMaxSupportedCssHeight() { var increment = 1000000; var supportedHeight = increment; // FF reports the height back but still renders blank after ~6M px var testUpTo = ($.browser.mozilla) ? 5000000 : 1000000000; var div = $("<div style='display:none' />").appendTo(document.body); while (supportedHeight <= testUpTo) { div.css("height", supportedHeight + increment); if (div.height() !== supportedHeight + increment) break; else supportedHeight += increment; } div.remove(); return supportedHeight; } // TODO: this is static. need to handle page mutation. function bindAncestorScrollEvents() { var elem = $canvas[0]; while ((elem = elem.parentNode) != document.body) { // bind to scroll containers only if (elem == $viewport[0] || elem.scrollWidth != elem.clientWidth || elem.scrollHeight != elem.clientHeight) $(elem).bind("scroll.slickgrid", handleActiveCellPositionChange); } } function unbindAncestorScrollEvents() { $canvas.parents().unbind("scroll.slickgrid"); } function updateColumnHeader(columnId, title, toolTip) { var idx = getColumnIndex(columnId); var $header = $headers.children().eq(idx); if ($header) { columns[idx].name = title; columns[idx].toolTip = toolTip; $header .attr("title", toolTip || title || "") .children().eq(0).html(title); } } function getHeaderRow() { return $headerRow[0]; } function getHeaderRowColumn(columnId) { var idx = getColumnIndex(columnId); var $header = $headerRow.children().eq(idx); return $header && $header[0]; } function createColumnHeaders() { var i; function hoverBegin() { $(this).addClass("ui-state-hover"); } function hoverEnd() { $(this).removeClass("ui-state-hover"); } $headers.empty(); $headerRow.empty(); columnsById = {}; for (i = 0; i < columns.length; i++) { var m = columns[i] = $.extend({},columnDefaults,columns[i]); columnsById[m.id] = i; var header = $("<div class='ui-state-default slick-header-column' id='" + uid + m.id + "' />") .html("<span class='slick-column-name'>" + m.name + "</span>") .width(m.width - headerColumnWidthDiff) .attr("title", m.toolTip || m.name || "") .data("fieldId", m.id) .addClass(m.headerCssClass || "") .appendTo($headers); if (options.enableColumnReorder || m.sortable) { header.hover(hoverBegin, hoverEnd); } if (m.sortable) { header.append("<span class='slick-sort-indicator' />"); } if (options.showHeaderRow) { $("<div class='ui-state-default slick-headerrow-column c" + i + "'></div>").appendTo($headerRow); } } setSortColumn(sortColumnId,sortAsc); setupColumnResize(); if (options.enableColumnReorder) { setupColumnReorder(); } } function setupColumnSort() { $headers.click(function(e) { if ($(e.target).hasClass("slick-resizable-handle")) { return; } var $col = $(e.target).closest(".slick-header-column"); if (!$col.length) return; var column = columns[getColumnIndex($col.data("fieldId"))]; if (column.sortable) { if (!getEditorLock().commitCurrentEdit()) return; if (column.id === sortColumnId) { sortAsc = !sortAsc; } else { sortColumnId = column.id; sortAsc = true; } setSortColumn(sortColumnId,sortAsc); trigger(self.onSort, {sortCol:column,sortAsc:sortAsc}); } }); } function setupColumnReorder() { $headers.sortable({ containment: "parent", axis: "x", cursor: "default", tolerance: "intersection", helper: "clone", placeholder: "slick-sortable-placeholder ui-state-default slick-header-column", forcePlaceholderSize: true, start: function(e, ui) { $(ui.helper).addClass("slick-header-column-active"); }, beforeStop: function(e, ui) { $(ui.helper).removeClass("slick-header-column-active"); }, stop: function(e) { if (!getEditorLock().commitCurrentEdit()) { $(this).sortable("cancel"); return; } var reorderedIds = $headers.sortable("toArray"); var reorderedColumns = []; for (var i=0; i<reorderedIds.length; i++) { reorderedColumns.push(columns[getColumnIndex(reorderedIds[i].replace(uid,""))]); } setColumns(reorderedColumns); trigger(self.onColumnsReordered, {}); e.stopPropagation(); setupColumnResize(); } }); } function setupColumnResize() { var $col, j, c, pageX, columnElements, minPageX, maxPageX, firstResizable, lastResizable, originalCanvasWidth; columnElements = $headers.children(); columnElements.find(".slick-resizable-handle").remove(); columnElements.each(function(i,e) { if (columns[i].resizable) { if (firstResizable === undefined) { firstResizable = i; } lastResizable = i; } }); if (firstResizable === undefined) { return; } columnElements.each(function(i,e) { if (i < firstResizable || (options.forceFitColumns && i >= lastResizable)) { return; } $col = $(e); $("<div class='slick-resizable-handle' />") .appendTo(e) .bind("dragstart", function(e,dd) { if (!getEditorLock().commitCurrentEdit()) { return false; } pageX = e.pageX; $(this).parent().addClass("slick-header-column-active"); var shrinkLeewayOnRight = null, stretchLeewayOnRight = null; // lock each column's width option to current width columnElements.each(function(i,e) { columns[i].previousWidth = $(e).outerWidth(); }); if (options.forceFitColumns) { shrinkLeewayOnRight = 0; stretchLeewayOnRight = 0; // colums on right affect maxPageX/minPageX for (j = i + 1; j < columnElements.length; j++) { c = columns[j]; if (c.resizable) { if (stretchLeewayOnRight !== null) { if (c.maxWidth) { stretchLeewayOnRight += c.maxWidth - c.previousWidth; } else { stretchLeewayOnRight = null; } } shrinkLeewayOnRight += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth); } } } var shrinkLeewayOnLeft = 0, stretchLeewayOnLeft = 0; for (j = 0; j <= i; j++) { // columns on left only affect minPageX c = columns[j]; if (c.resizable) { if (stretchLeewayOnLeft !== null) { if (c.maxWidth) { stretchLeewayOnLeft += c.maxWidth - c.previousWidth; } else { stretchLeewayOnLeft = null; } } shrinkLeewayOnLeft += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth); } } if (shrinkLeewayOnRight === null) { shrinkLeewayOnRight = 100000; } if (shrinkLeewayOnLeft === null) { shrinkLeewayOnLeft = 100000; } if (stretchLeewayOnRight === null) { stretchLeewayOnRight = 100000; } if (stretchLeewayOnLeft === null) { stretchLeewayOnLeft = 100000; } maxPageX = pageX + Math.min(shrinkLeewayOnRight, stretchLeewayOnLeft); minPageX = pageX - Math.min(shrinkLeewayOnLeft, stretchLeewayOnRight); originalCanvasWidth = $canvas.width(); }) .bind("drag", function(e,dd) { var actualMinWidth, d = Math.min(maxPageX, Math.max(minPageX, e.pageX)) - pageX, x, ci; if (d < 0) { // shrink column x = d; for (j = i; j >= 0; j--) { c = columns[j]; if (c.resizable) { actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth); if (x && c.previousWidth + x < actualMinWidth) { x += c.previousWidth - actualMinWidth; c.width = actualMinWidth; } else { c.width = c.previousWidth + x; x = 0; } } } if (options.forceFitColumns) { x = -d; for (j = i + 1; j < columnElements.length; j++) { c = columns[j]; if (c.resizable) { if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) { x -= c.maxWidth - c.previousWidth; c.width = c.maxWidth; } else { c.width = c.previousWidth + x; x = 0; } } } } else if (options.syncColumnCellResize) { setCanvasWidth(originalCanvasWidth + d); } } else { // stretch column x = d; for (j = i; j >= 0; j--) { c = columns[j]; if (c.resizable) { if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) { x -= c.maxWidth - c.previousWidth; c.width = c.maxWidth; } else { c.width = c.previousWidth + x; x = 0; } } } if (options.forceFitColumns) { x = -d; for (j = i + 1; j < columnElements.length; j++) { c = columns[j]; if (c.resizable) { actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth); if (x && c.previousWidth + x < actualMinWidth) { x += c.previousWidth - actualMinWidth; c.width = actualMinWidth; } else { c.width = c.previousWidth + x; x = 0; } } } } else if (options.syncColumnCellResize) { setCanvasWidth(originalCanvasWidth + d); } } applyColumnHeaderWidths(); if (options.syncColumnCellResize) { applyColumnWidths(); } }) .bind("dragend", function(e,dd) { var newWidth; $(this).parent().removeClass("slick-header-column-active"); for (j = 0; j < columnElements.length; j++) { c = columns[j]; newWidth = $(columnElements[j]).outerWidth(); if (c.previousWidth !== newWidth && c.rerenderOnResize) { invalidateAllRows(); } } applyColumnWidths(); resizeCanvas(); trigger(self.onColumnsResized, {}); }); }); } function getVBoxDelta($el) { var p = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"]; var delta = 0; $.each(p, function(n,val) { delta += parseFloat($el.css(val)) || 0; }); return delta; } function measureCellPaddingAndBorder() { var el; var h = ["borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight"]; var v = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"]; el = $("<div class='ui-state-default slick-header-column' style='visibility:hidden'>-</div>").appendTo($headers); headerColumnWidthDiff = headerColumnHeightDiff = 0; $.each(h, function(n,val) { headerColumnWidthDiff += parseFloat(el.css(val)) || 0; }); $.each(v, function(n,val) { headerColumnHeightDiff += parseFloat(el.css(val)) || 0; }); el.remove(); var r = $("<div class='slick-row' />").appendTo($canvas); el = $("<div class='slick-cell' id='' style='visibility:hidden'>-</div>").appendTo(r); cellWidthDiff = cellHeightDiff = 0; $.each(h, function(n,val) { cellWidthDiff += parseFloat(el.css(val)) || 0; }); $.each(v, function(n,val) { cellHeightDiff += parseFloat(el.css(val)) || 0; }); r.remove(); absoluteColumnMinWidth = Math.max(headerColumnWidthDiff,cellWidthDiff); } function createCssRules() { $style = $("<style type='text/css' rel='stylesheet' />").appendTo($("head")); var rowHeight = (options.rowHeight - cellHeightDiff); var rules = [ "." + uid + " .slick-header-column { left: 1000px; }", "." + uid + " .slick-top-panel { height:" + options.topPanelHeight + "px; }", "." + uid + " .slick-headerrow-columns { height:" + options.headerRowHeight + "px; }", "." + uid + " .slick-cell { height:" + rowHeight + "px; }", "." + uid + " .slick-row { width:" + getRowWidth() + "px; height:" + options.rowHeight + "px; }", "." + uid + " .lr { float:none; position:absolute; }" ]; var rowWidth = getRowWidth(); var x = 0, w; for (var i=0; i<columns.length; i++) { w = columns[i].width; rules.push("." + uid + " .l" + i + " { left: " + x + "px; }"); rules.push("." + uid + " .r" + i + " { right: " + (rowWidth - x - w) + "px; }"); x += columns[i].width; } if ($style[0].styleSheet) { // IE $style[0].styleSheet.cssText = rules.join(" "); } else { $style[0].appendChild(document.createTextNode(rules.join(" "))); } var sheets = document.styleSheets; for (var i=0; i<sheets.length; i++) { if ((sheets[i].ownerNode || sheets[i].owningElement) == $style[0]) { stylesheet = sheets[i]; break; } } } function findCssRule(selector) { var rules = (stylesheet.cssRules || stylesheet.rules); for (var i=0; i<rules.length; i++) { if (rules[i].selectorText == selector) return rules[i]; } return null; } function removeCssRules() { $style.remove(); } function destroy() { getEditorLock().cancelCurrentEdit(); trigger(self.onBeforeDestroy, {}); for (var i = 0; i < plugins.length; i++) { unregisterPlugin(plugins[i]); } if (options.enableColumnReorder && $headers.sortable) $headers.sortable("destroy"); unbindAncestorScrollEvents(); $container.unbind(".slickgrid"); removeCssRules(); $canvas.unbind("draginit dragstart dragend drag"); $container.empty().removeClass(uid); } ////////////////////////////////////////////////////////////////////////////////////////////// // General function trigger(evt, args, e) { e = e || new Slick.EventData(); args = args || {}; args.grid = self; return evt.notify(args, e, self); } function getEditorLock() { return options.editorLock; } function getEditController() { return editController; } function getColumnIndex(id) { return columnsById[id]; } function autosizeColumns() { var i, c, widths = [], shrinkLeeway = 0, availWidth = (options.autoHeight ? viewportW : viewportW - scrollbarDimensions.width), // with AutoHeight, we do not need to accomodate the vertical scroll bar total = 0, existingTotal = 0; for (i = 0; i < columns.length; i++) { c = columns[i]; widths.push(c.width); existingTotal += c.width; shrinkLeeway += c.width - Math.max(c.minWidth || 0, absoluteColumnMinWidth); } total = existingTotal; invalidateAllRows(); // shrink while (total > availWidth) { if (!shrinkLeeway) { return; } var shrinkProportion = (total - availWidth) / shrinkLeeway; for (i = 0; i < columns.length && total > availWidth; i++) { c = columns[i]; if (!c.resizable || c.minWidth === c.width || c.width === absoluteColumnMinWidth) { continue; } var shrinkSize = Math.floor(shrinkProportion * (c.width - Math.max(c.minWidth || 0, absoluteColumnMinWidth))) || 1; total -= shrinkSize; widths[i] -= shrinkSize; } } // grow var previousTotal = total; while (total < availWidth) { var growProportion = availWidth / total; for (i = 0; i < columns.length && total < availWidth; i++) { c = columns[i]; if (!c.resizable || c.maxWidth <= c.width) { continue; } var growSize = Math.min(Math.floor(growProportion * c.width) - c.width, (c.maxWidth - c.width) || 1000000) || 1; total += growSize; widths[i] += growSize; } if (previousTotal == total) break; // if total is not changing, will result in infinite loop previousTotal = total; } for (i=0; i<columns.length; i++) { columns[i].width = widths[i]; } applyColumnHeaderWidths(); applyColumnWidths(); resizeCanvas(); } function applyColumnHeaderWidths() { var h; for (var i = 0, headers = $headers.children(), ii = headers.length; i < ii; i++) { h = $(headers[i]); if (h.width() !== columns[i].width - headerColumnWidthDiff) { h.width(columns[i].width - headerColumnWidthDiff); } } } function applyColumnWidths() { var rowWidth = getRowWidth(); var x = 0, w, rule; for (var i = 0; i < columns.length; i++) { w = columns[i].width; rule = findCssRule("." + uid + " .l" + i); rule.style.left = x + "px"; rule = findCssRule("." + uid + " .r" + i); rule.style.right = (rowWidth - x - w) + "px"; x += columns[i].width; } rule = findCssRule("." + uid + " .slick-row"); rule.style.width = rowWidth + "px"; } function setSortColumn(columnId, ascending) { sortColumnId = columnId; sortAsc = ascending; var columnIndex = getColumnIndex(sortColumnId); $headers.children().removeClass("slick-header-column-sorted"); $headers.find(".slick-sort-indicator").removeClass("slick-sort-indicator-asc slick-sort-indicator-desc"); if (columnIndex != null) { $headers.children().eq(columnIndex) .addClass("slick-header-column-sorted") .find(".slick-sort-indicator") .addClass(sortAsc ? "slick-sort-indicator-asc" : "slick-sort-indicator-desc"); } } function handleSelectedRangesChanged(e, ranges) { selectedRows = []; var hash = {}; for (var i = 0; i < ranges.length; i++) { for (var j = ranges[i].fromRow; j <= ranges[i].toRow; j++) { if (!hash[j]) { // prevent duplicates selectedRows.push(j); } hash[j] = {}; for (var k = ranges[i].fromCell; k <= ranges[i].toCell; k++) { if (canCellBeSelected(j, k)) { hash[j][columns[k].id] = options.selectedCellCssClass; } } } } setCellCssStyles(options.selectedCellCssClass, hash); trigger(self.onSelectedRowsChanged, {rows:getSelectedRows()}, e); } function getColumns() { return columns; } function setColumns(columnDefinitions) { columns = columnDefinitions; invalidateAllRows(); createColumnHeaders(); removeCssRules(); createCssRules(); resizeAndRender(); handleScroll(); } function getOptions() { return options; } function setOptions(args) { if (!getEditorLock().commitCurrentEdit()) { return; } makeActiveCellNormal(); if (options.enableAddRow !== args.enableAddRow) { invalidateRow(getDataLength()); } options = $.extend(options,args); render(); } function setData(newData,scrollToTop) { invalidateAllRows(); data = newData; if (scrollToTop) scrollTo(0); } function getData() { return data; } function getDataLength() { if (data.getLength) { return data.getLength(); } else { return data.length; } } function getDataItem(i) { if (data.getItem) { return data.getItem(i); } else { return data[i]; } } function getTopPanel() { return $topPanel[0]; } function showTopPanel() { options.showTopPanel = true; $topPanelScroller.slideDown("fast", resizeCanvas); } function hideTopPanel() { options.showTopPanel = false; $topPanelScroller.slideUp("fast", resizeCanvas); } function showHeaderRowColumns() { options.showHeaderRow = true; $headerRowScroller.slideDown("fast", resizeCanvas); } function hideHeaderRowColumns() { options.showHeaderRow = false; $headerRowScroller.slideUp("fast", resizeCanvas); } ////////////////////////////////////////////////////////////////////////////////////////////// // Rendering / Scrolling function scrollTo(y) { var oldOffset = offset; page = Math.min(n-1, Math.floor(y / ph)); offset = Math.round(page * cj); var newScrollTop = y - offset; if (offset != oldOffset) { var range = getVisibleRange(newScrollTop); cleanupRows(range.top,range.bottom); updateRowPositions(); } if (prevScrollTop != newScrollTop) { scrollDir = (prevScrollTop + oldOffset < newScrollTop + offset) ? 1 : -1; $viewport[0].scrollTop = (lastRenderedScrollTop = scrollTop = prevScrollTop = newScrollTop); trigger(self.onViewportChanged, {}); } } function defaultFormatter(row, cell, value, columnDef, dataContext) { return (value === null || value === undefined) ? "" : value; } function getFormatter(row, column) { var rowMetadata = data.getItemMetadata && data.getItemMetadata(row); // look up by id, then index var columnOverrides = rowMetadata && rowMetadata.columns && (rowMetadata.columns[column.id] || rowMetadata.columns[getColumnIndex(column.id)]); return (columnOverrides && columnOverrides.formatter) || (rowMetadata && rowMetadata.formatter) || column.formatter || (options.formatterFactory && options.formatterFactory.getFormatter(column)) || defaultFormatter; } function getEditor(row, cell) { var column = columns[cell]; var rowMetadata = data.getItemMetadata && data.getItemMetadata(row); var columnMetadata = rowMetadata && rowMetadata.columns; if (columnMetadata && columnMetadata[column.id] && columnMetadata[column.id].editor !== undefined) { return columnMetadata[column.id].editor; } if (columnMetadata && columnMetadata[cell] && columnMetadata[cell].editor !== undefined) { return columnMetadata[cell].editor; } return column.editor || (options.editorFactory && options.editorFactory.getEditor(column)); } function appendRowHtml(stringArray, row) { var d = getDataItem(row); var dataLoading = row < getDataLength() && !d; var cellCss; var rowCss = "slick-row " + (dataLoading ? " loading" : "") + (row % 2 == 1 ? ' odd' : ' even'); var metadata = data.getItemMetadata && data.getItemMetadata(row); if (metadata && metadata.cssClasses) { rowCss += " " + metadata.cssClasses; } stringArray.push("<div class='ui-widget-content " + rowCss + "' row='" + row + "' style='top:" + (options.rowHeight*row-offset) + "px'>"); var colspan; var rowHasColumnData = metadata && metadata.columns; for (var i=0, cols=columns.length; i<cols; i++) { var m = columns[i]; colspan = getColspan(row, i); // TODO: don't calc unless we have to cellCss = "slick-cell lr l" + i + " r" + Math.min(columns.length -1, i + colspan - 1) + (m.cssClass ? " " + m.cssClass : ""); if (row === activeRow && i === activeCell) { cellCss += (" active"); } // TODO: merge them together in the setter for (var key in cellCssClasses) { if (cellCssClasses[key][row] && cellCssClasses[key][row][m.id]) { cellCss += (" " + cellCssClasses[key][row][m.id]); } } stringArray.push("<div class='" + cellCss + "'>"); // if there is a corresponding row (if not, this is the Add New row or this data hasn't been loaded yet) if (d) { stringArray.push(getFormatter(row, m)(row, i, d[m.field], m, d)); } stringArray.push("</div>"); if (colspan) i += (colspan - 1); } stringArray.push("</div>"); } function cleanupRows(rangeToKeep) { for (var i in rowsCache) { if (((i = parseInt(i, 10)) !== activeRow) && (i < rangeToKeep.top || i > rangeToKeep.bottom)) { removeRowFromCache(i); } } } function invalidate() { updateRowCount(); invalidateAllRows(); render(); } function invalidateAllRows() { if (currentEditor) { makeActiveCellNormal(); } for (var row in rowsCache) { removeRowFromCache(row); } } function removeRowFromCache(row) { var node = rowsCache[row]; if (!node) { return; } $canvas[0].removeChild(node); delete rowsCache[row]; delete postProcessedRows[row]; renderedRows--; counter_rows_removed++; } function invalidateRows(rows) { var i, rl; if (!rows || !rows.length) { return; } scrollDir = 0; for (i=0, rl=rows.length; i<rl; i++) { if (currentEditor && activeRow === i) { makeActiveCellNormal(); } if (rowsCache[rows[i]]) { removeRowFromCache(rows[i]); } } } function invalidateRow(row) { invalidateRows([row]); } function updateCell(row,cell) { var cellNode = getCellNode(row,cell); if (!cellNode) { return; } var m = columns[cell], d = getDataItem(row); if (currentEditor && activeRow === row && activeCell === cell) { currentEditor.loadValue(d); } else { cellNode.innerHTML = d ? getFormatter(row, m)(row, cell, d[m.field], m, d) : ""; invalidatePostProcessingResults(row); } } function updateRow(row) { if (!rowsCache[row]) { return; } $(rowsCache[row]).children().each(function(i) { var m = columns[i]; if (row === activeRow && i === activeCell && currentEditor) { currentEditor.loadValue(getDataItem(activeRow)); } else if (getDataItem(row)) { this.innerHTML = getFormatter(row, m)(row, i, getDataItem(row)[m.field], m, getDataItem(row)); } else { this.innerHTML = ""; } }); invalidatePostProcessingResults(row); } function getViewportHeight() { return parseFloat($.css($container[0], "height", true)) - options.headerHeight - getVBoxDelta($headers) - (options.showTopPanel ? options.topPanelHeight + getVBoxDelta($topPanelScroller) : 0) - (options.showHeaderRow ? options.headerRowHeight + getVBoxDelta($headerRowScroller) : 0); } function resizeCanvas() { if (options.autoHeight) { viewportH = options.rowHeight * (getDataLength() + (options.enableAddRow ? 1 : 0) + (options.leaveSpaceForNewRows? numVisibleRows - 1 : 0)); } else { viewportH = getViewportHeight(); } numVisibleRows = Math.ceil(viewportH / options.rowHeight); viewportW = parseFloat($.css($container[0], "width", true)); $viewport.height(viewportH); var w = 0, i = columns.length; while (i--) { w += columns[i].width; } setCanvasWidth(w); updateRowCount(); render(); } function resizeAndRender() { if (options.forceFitColumns) { autosizeColumns(); } else { resizeCanvas(); } } function updateRowCount() { var newRowCount = getDataLength() + (options.enableAddRow?1:0) + (options.leaveSpaceForNewRows?numVisibleRows-1:0); var oldH = h; // remove the rows that are now outside of the data range // this helps avoid redundant calls to .removeRow() when the size of the data decreased by thousands of rows var l = options.enableAddRow ? getDataLength() : getDataLength() - 1; for (var i in rowsCache) { if (i >= l) { removeRowFromCache(i); } } th = Math.max(options.rowHeight * newRowCount, viewportH - scrollbarDimensions.height); if (th < maxSupportedCssHeight) { // just one page h = ph = th; n = 1; cj = 0; } else { // break into pages h = maxSupportedCssHeight; ph = h / 100; n = Math.floor(th / ph); cj = (th - h) / (n - 1); } if (h !== oldH) { $canvas.css("height",h); scrollTop = $viewport[0].scrollTop; } var oldScrollTopInRange = (scrollTop + offset <= th - viewportH); if (th == 0 || scrollTop == 0) { page = offset = 0; } else if (oldScrollTopInRange) {