From 9d0ad63a037ced68002167741ff78bd55552e3db Mon Sep 17 00:00:00 2001 From: Ghislain B Date: Mon, 14 May 2018 22:09:58 -0400 Subject: [PATCH 01/55] feature(grid): first working draft of X-Slickgrid merge to SlickGrid --- ...e-frozen-columns-and-rows-spreadsheet.html | 173 + examples/example-frozen-columns-and-rows.html | 564 + .../example-frozen-columns-autoheight.html | 84 + examples/example-frozen-columns-tabs.html | 435 + examples/example-frozen-columns.html | 424 + examples/example-frozen-row-reordering.html | 353 + examples/example-frozen-rows.html | 419 + lib/jquery.mousewheel.js | 92 + slick.core.js | 173 + slick.grid.css | 60 +- slick.grid.js | 9223 ++++++++++------- 11 files changed, 7993 insertions(+), 4007 deletions(-) create mode 100644 examples/example-frozen-columns-and-rows-spreadsheet.html create mode 100644 examples/example-frozen-columns-and-rows.html create mode 100644 examples/example-frozen-columns-autoheight.html create mode 100644 examples/example-frozen-columns-tabs.html create mode 100644 examples/example-frozen-columns.html create mode 100644 examples/example-frozen-row-reordering.html create mode 100644 examples/example-frozen-rows.html create mode 100644 lib/jquery.mousewheel.js diff --git a/examples/example-frozen-columns-and-rows-spreadsheet.html b/examples/example-frozen-columns-and-rows-spreadsheet.html new file mode 100644 index 00000000..fa267790 --- /dev/null +++ b/examples/example-frozen-columns-and-rows-spreadsheet.html @@ -0,0 +1,173 @@ + + + + + SlickGrid Example: Spreadsheet with Frozen Rows and Columns + + + + + + +
+
+
+
+ +
+

Demonstrates:

+
    +
  • Virtual scrolling on both rows and columns.
  • +
  • Select a range of cells with a mouse
  • +
  • Use Ctrl-C and Ctrl-V keyboard shortcuts to cut and paste cells
  • +
  • Use Esc to cancel a copy and paste operation
  • +
  • Edit the cell and select a cell range to paste the range
  • +
+
+
+ + + + + + + + + + + + + + + + + + + + diff --git a/examples/example-frozen-columns-and-rows.html b/examples/example-frozen-columns-and-rows.html new file mode 100644 index 00000000..064a6035 --- /dev/null +++ b/examples/example-frozen-columns-and-rows.html @@ -0,0 +1,564 @@ + + + + + SlickGrid example: Frozen Columns and Rows + + + + + + + + +
+
+
+ + +
+
+
+
+ +
+ Search: +
+
+ + + +

+ + + +

+ + +
+ +

Demonstrates:

+
    +
  • a filtered Model (DataView) as a data source instead of a simple array
  • +
  • grid reacting to model events (onRowCountChanged, onRowsChanged)
  • +
  • + FAST DataView recalculation and real-time grid updating in response to data changes.
    + The grid holds 50'000 rows, yet you are able to sort, filter, scroll, navigate and edit as if it had 50 + rows. +
  • +
  • adding new rows, bidirectional sorting
  • +
  • column options: cannotTriggerInsert
  • +
  • events: onCellChange, onAddNewRow, onKeyDown, onSelectedRowsChanged, onSort
  • +
  • NOTE: all filters are immediately applied to new/edited rows
  • +
  • Handling row selection against model changes.
  • +
  • Paging.
  • +
  • inline filter panel
  • +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/example-frozen-columns-autoheight.html b/examples/example-frozen-columns-autoheight.html new file mode 100644 index 00000000..23cdadbc --- /dev/null +++ b/examples/example-frozen-columns-autoheight.html @@ -0,0 +1,84 @@ + + + + + + SlickGrid example 11: No vertical scrolling (autoHeight) + + + + + +
+
+

Demonstrates:

+ +
+ + + + + + + + + + + + diff --git a/examples/example-frozen-columns-tabs.html b/examples/example-frozen-columns-tabs.html new file mode 100644 index 00000000..7ee0bb22 --- /dev/null +++ b/examples/example-frozen-columns-tabs.html @@ -0,0 +1,435 @@ + + + + + SlickGrid example: Frozen Columns + + + + + + + + +
+ +
+
+
+ + +
+
+
+
+
+ Search: +
+
+ + +
+
+
+
+ + +

+ + + +

+ + +
+ +

Demonstrates:

+
    +
  • a filtered Model (DataView) as a data source instead of a simple array
  • +
  • grid reacting to model events (onRowCountChanged, onRowsChanged)
  • +
  • + FAST DataView recalculation and real-time grid updating in response to data changes.
    + The grid holds 50'000 rows, yet you are able to sort, filter, scroll, navigate and edit as if it had + 50 rows. +
  • +
  • adding new rows, bidirectional sorting
  • +
  • column options: cannotTriggerInsert
  • +
  • events: onCellChange, onAddNewRow, onKeyDown, onSelectedRowsChanged, onSort
  • +
  • NOTE: all filters are immediately applied to new/edited rows
  • +
  • Handling row selection against model changes.
  • +
  • Paging.
  • +
  • inline filter panel
  • +
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/example-frozen-columns.html b/examples/example-frozen-columns.html new file mode 100644 index 00000000..66c88ba3 --- /dev/null +++ b/examples/example-frozen-columns.html @@ -0,0 +1,424 @@ + + + + + SlickGrid example: Frozen Columns + + + + + + + + +
+
+
+ + +
+
+
+
+ +
+ Search: +
+
+ + +
+
+
+
+ + +

+ + + +

+ + +
+ +

Demonstrates:

+
    +
  • a filtered Model (DataView) as a data source instead of a simple array
  • +
  • grid reacting to model events (onRowCountChanged, onRowsChanged)
  • +
  • + FAST DataView recalculation and real-time grid updating in response to data changes.
    + The grid holds 50'000 rows, yet you are able to sort, filter, scroll, navigate and edit as if it had 50 + rows. +
  • +
  • adding new rows, bidirectional sorting
  • +
  • column options: cannotTriggerInsert
  • +
  • events: onCellChange, onAddNewRow, onKeyDown, onSelectedRowsChanged, onSort
  • +
  • NOTE: all filters are immediately applied to new/edited rows
  • +
  • Handling row selection against model changes.
  • +
  • Paging.
  • +
  • inline filter panel
  • +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/example-frozen-row-reordering.html b/examples/example-frozen-row-reordering.html new file mode 100644 index 00000000..58b43d6c --- /dev/null +++ b/examples/example-frozen-row-reordering.html @@ -0,0 +1,353 @@ + + + + + SlickGrid example 9: Row reordering + + + + + + +
+
+
+ +
+
+
+
+ Tips: +
+
+ Click to select, Ctrl-click to toggle selection, Shift-click to select a range.
+ Drag one or more rows by the handle to reorder.
+ Drag one or more rows to the recycle bin to delete. + +
+
+ +
+ Recycle Bin +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + diff --git a/examples/example-frozen-rows.html b/examples/example-frozen-rows.html new file mode 100644 index 00000000..d9274813 --- /dev/null +++ b/examples/example-frozen-rows.html @@ -0,0 +1,419 @@ + + + + + SlickGrid example: Frozen Rows + + + + + + + + +
+
+
+ + +
+
+
+
+ +
+ Search: +
+
+ + +
+
+
+
+ + +

+ + + +
+ + + +

+ + +
+ +

Demonstrates:

+
    +
  • a filtered Model (DataView) as a data source instead of a simple array
  • +
  • grid reacting to model events (onRowCountChanged, onRowsChanged)
  • +
  • + FAST DataView recalculation and real-time grid updating in response to data changes.
    + The grid holds 50'000 rows, yet you are able to sort, filter, scroll, navigate and edit as if it had 50 + rows. +
  • +
  • adding new rows, bidirectional sorting
  • +
  • column options: cannotTriggerInsert
  • +
  • events: onCellChange, onAddNewRow, onKeyDown, onSelectedRowsChanged, onSort
  • +
  • NOTE: all filters are immediately applied to new/edited rows
  • +
  • Handling row selection against model changes.
  • +
  • Paging.
  • +
  • inline filter panel
  • +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/jquery.mousewheel.js b/lib/jquery.mousewheel.js new file mode 100644 index 00000000..1f5581f3 --- /dev/null +++ b/lib/jquery.mousewheel.js @@ -0,0 +1,92 @@ +/*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net) + * Licensed under the MIT License (LICENSE.txt). + * + * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. + * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. + * Thanks to: Seamus Leahy for adding deltaX and deltaY + * + * Version: 3.0.6 + * + * Requires: 1.2.2+ + */ + +(function ($) { + + var types = ['DOMMouseScroll', 'mousewheel']; + + if ($.event.fixHooks) { + for (var i = types.length; i;) { + $.event.fixHooks[ types[--i] ] = $.event.mouseHooks; + } + } + + $.event.special.mousewheel = { + setup: function () { + if (this.addEventListener) { + for (var i = types.length; i;) { + this.addEventListener(types[--i], handler, false); + } + } else { + this.onmousewheel = handler; + } + }, + + teardown: function () { + if (this.removeEventListener) { + for (var i = types.length; i;) { + this.removeEventListener(types[--i], handler, false); + } + } else { + this.onmousewheel = null; + } + } + }; + + $.fn.extend({ + mousewheel: function (fn) { + return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel"); + }, + + unmousewheel: function (fn) { + return this.unbind("mousewheel", fn); + } + }); + + + function handler(event) { + var orgEvent = event || window.event, args = [].slice.call(arguments, 1), delta = 0, returnValue = true, deltaX = 0, deltaY = 0; + event = $.event.fix(orgEvent); + event.type = "mousewheel"; + + // Old school scrollwheel delta + if (orgEvent.wheelDelta) { + delta = orgEvent.wheelDelta / 120; + } + if (orgEvent.detail) { + delta = -orgEvent.detail / 3; + } + + // New school multidimensional scroll (touchpads) deltas + deltaY = delta; + + // Gecko + if (orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS) { + deltaY = 0; + deltaX = -1 * delta; + } + + // Webkit + if (orgEvent.wheelDeltaY !== undefined) { + deltaY = orgEvent.wheelDeltaY / 120; + } + if (orgEvent.wheelDeltaX !== undefined) { + deltaX = -1 * orgEvent.wheelDeltaX / 120; + } + + // Add event and delta to the front of the arguments + args.unshift(event, delta, deltaX, deltaY); + + return ($.event.dispatch || $.event.handle).apply(this, args); + } + +})(jQuery); diff --git a/slick.core.js b/slick.core.js index eb68c8d2..9ab50647 100644 --- a/slick.core.js +++ b/slick.core.js @@ -24,6 +24,7 @@ * @constructor */ "GlobalEditorLock": new EditorLock(), + "TreeColumns": TreeColumns, "keyCode": { BACKSPACE: 8, @@ -488,6 +489,178 @@ return (activeEditController ? activeEditController.cancelCurrentEdit() : true); }; } + + /** + * + * @param {Array} treeColumns Array com levels of columns + * @returns {{hasDepth: 'hasDepth', getTreeColumns: 'getTreeColumns', extractColumns: 'extractColumns', getDepth: 'getDepth', getColumnsInDepth: 'getColumnsInDepth', getColumnsInGroup: 'getColumnsInGroup', visibleColumns: 'visibleColumns', filter: 'filter', reOrder: reOrder}} + * @constructor + */ + function TreeColumns(treeColumns) { + + var columnsById = {}; + + function init() { + mapToId(treeColumns); + } + + function mapToId(columns) { + columns + .forEach(function (column) { + columnsById[column.id] = column; + + if (column.columns) + mapToId(column.columns); + }); + } + + function filter(node, condition) { + + return node.filter(function (column) { + + var valid = condition.call(column); + + if (valid && column.columns) + column.columns = filter(column.columns, condition); + + return valid && (!column.columns || column.columns.length); + }); + + } + + function sort(columns, grid) { + columns + .sort(function (a, b) { + var indexA = getOrDefault(grid.getColumnIndex(a.id)), + indexB = getOrDefault(grid.getColumnIndex(b.id)); + + return indexA - indexB; + }) + .forEach(function (column) { + if (column.columns) + sort(column.columns, grid); + }); + } + + function getOrDefault(value) { + return typeof value === 'undefined' ? -1 : value; + } + + function getDepth(node) { + if (node.length) + for (var i in node) + return getDepth(node[i]); + else if (node.columns) + return 1 + getDepth(node.columns); + else + return 1; + } + + function getColumnsInDepth(node, depth, current) { + var columns = []; + current = current || 0; + + if (depth == current) { + + if (node.length) + node.forEach(function(n) { + if (n.columns) + n.extractColumns = function() { + return extractColumns(n); + }; + }); + + return node; + } else + for (var i in node) + if (node[i].columns) { + columns = columns.concat(getColumnsInDepth(node[i].columns, depth, current + 1)); + } + + return columns; + } + + function extractColumns(node) { + var result = []; + + if (node.hasOwnProperty('length')) { + + for (var i = 0; i < node.length; i++) + result = result.concat(extractColumns(node[i])); + + } else { + + if (node.hasOwnProperty('columns')) + + result = result.concat(extractColumns(node.columns)); + + else + return node; + + } + + return result; + } + + function cloneTreeColumns() { + return $.extend(true, [], treeColumns); + } + + init(); + + this.hasDepth = function () { + + for (var i in treeColumns) + if (treeColumns[i].hasOwnProperty('columns')) + return true; + + return false; + }; + + this.getTreeColumns = function () { + return treeColumns; + }; + + this.extractColumns = function () { + return this.hasDepth()? extractColumns(treeColumns): treeColumns; + }; + + this.getDepth = function () { + return getDepth(treeColumns); + }; + + this.getColumnsInDepth = function (depth) { + return getColumnsInDepth(treeColumns, depth); + }; + + this.getColumnsInGroup = function (groups) { + return extractColumns(groups); + }; + + this.visibleColumns = function () { + return filter(cloneTreeColumns(), function () { + return this.visible; + }); + }; + + this.filter = function (condition) { + return filter(cloneTreeColumns(), condition); + }; + + this.reOrder = function (grid) { + return sort(treeColumns, grid); + }; + + this.getById = function (id) { + return columnsById[id]; + }; + + this.getInIds = function (ids) { + return ids.map(function (id) { + return columnsById[id]; + }); + } + } })(jQuery); diff --git a/slick.grid.css b/slick.grid.css index 248a9812..d96f2fda 100644 --- a/slick.grid.css +++ b/slick.grid.css @@ -5,7 +5,7 @@ No built-in (selected, editable, highlight, flashing, invalid, loading, :focus) classes should alter those! */ -.slick-header.ui-state-default, .slick-headerrow.ui-state-default, .slick-footerrow.ui-state-default, .slick-top-panel-scroller.ui-state-default { +.slick-header.ui-state-default, .slick-headerrow.ui-state-default, .slick-footerrow.ui-state-default, .slick-top-panel-scroller.ui-state-default, .slick-group-header.ui-state-default { width: 100%; overflow: auto; position: relative; @@ -20,17 +20,17 @@ classes should alter those! display: none } -.slick-header-columns, .slick-headerrow-columns, .slick-footerrow-columns { +.slick-header-columns, .slick-headerrow-columns, .slick-footerrow-columns, .slick-group-header-columns { position: relative; white-space: nowrap; cursor: default; overflow: hidden; } -.slick-header-column.ui-state-default { +.slick-header-column.ui-state-default, .slick-group-header-column.ui-state-default { position: relative; display: inline-block; - /*box-sizing: content-box !important; use this for Firefox! */ + /*box-sizing: content-box !important; use this for Firefox! */ overflow: hidden; -o-text-overflow: ellipsis; text-overflow: ellipsis; @@ -45,6 +45,20 @@ classes should alter those! float: left; } +.slick-footerrow-column.ui-state-default { + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + margin: 0; + padding: 4px; + border-right: 1px solid silver; + border-left: 0px; + border-top: 0px; + border-bottom: 0px; + float: left; + line-height: 20px; + vertical-align: middle; +} + .slick-headerrow-column.ui-state-default, .slick-footerrow-column.ui-state-default { padding: 4px; } @@ -132,7 +146,7 @@ classes should alter those! .slick-footerrow-column { border-top-color: silver; } - + .slick-group { } @@ -186,3 +200,39 @@ classes should alter those! position: absolute; border: 2px dashed black; } + +.slick-pane { + position: absolute; + outline: 0; + overflow: hidden; + width: 100%; +} + +.slick-pane-header { + display: block; +} + +.slick-header { + overflow: hidden; + position: relative; +} + +.slick-headerrow { + overflow: hidden; + position: relative; +} + +.slick-top-panel-scroller { + overflow: hidden; + position: relative; +} + +.slick-top-panel { + width: 10000px +} + +.slick-viewport { + position: relative; + outline: 0; + width: 100%; +} diff --git a/slick.grid.js b/slick.grid.js index 7b8c6c2a..384aac25 100644 --- a/slick.grid.js +++ b/slick.grid.js @@ -1,4002 +1,5221 @@ -/** - * @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.3 - * - * 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 ($) { - // Slick.Grid - $.extend(true, window, { - Slick: { - Grid: SlickGrid - } - }); - - // 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, - explicitInitialization: false, - rowHeight: 25, - defaultColumnWidth: 80, - enableAddRow: false, - leaveSpaceForNewRows: false, - editable: false, - autoEdit: true, - enableCellNavigation: true, - enableColumnReorder: true, - asyncEditorLoading: false, - asyncEditorLoadDelay: 100, - forceFitColumns: false, - enableAsyncPostRender: false, - asyncPostRenderDelay: 50, - enableAsyncPostRenderCleanup: false, - asyncPostRenderCleanupDelay: 40, - autoHeight: false, - editorLock: Slick.GlobalEditorLock, - 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, - 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 - }; - - var columnDefaults = { - name: "", - resizable: true, - sortable: false, - minWidth: 30, - rerenderOnResize: false, - headerCssClass: null, - defaultSortAsc: true, - focusable: true, - selectable: true - }; - - // 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 $headerScroller; - var $headers; - var $headerRow, $headerRowScroller, $headerRowSpacer; - var $footerRow, $footerRowScroller, $footerRowSpacer; - var $preHeaderPanel, $preHeaderPanelScroller, $preHeaderPanelSpacer; - var $topPanelScroller; - var $topPanel; - var $viewport; - var $canvas; - var $style; - var $boundAncestors; - var stylesheet, columnCssRulesL, columnCssRulesR; - var viewportH, viewportW; - var canvasWidth; - var viewportHasHScroll, viewportHasVScroll; - var headerColumnWidthDiff = 0, headerColumnHeightDiff = 0, // border+padding - cellWidthDiff = 0, cellHeightDiff = 0, jQueryNewWidthBehaviour = false; - var absoluteColumnMinWidth; - - var tabbingDirection = 1; - 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 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; - - // 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; - - // These two variables work around a bug with inertial scrolling in Webkit/Blink on Mac. - // See http://crbug.com/312427. - var rowNodeFromLastMouseWheelEvent; // this node must not be deleted while inertial scrolling - var zombieRowNodeFromLastMouseWheelEvent; // node that was hidden instead of getting deleted - var zombieRowCacheFromLastMouseWheelEvent; // row cache for above node - var zombieRowPostProcessedFromLastMouseWheelEvent; // post processing references for above node - - // 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."); - } - - cacheCssForHiddenInit(); - - // calculate these only once and share between grid instances - maxSupportedCssHeight = maxSupportedCssHeight || getMaxSupportedCssHeight(); - - options = $.extend({}, defaults, options); - validateAndEnforceOptions(); - columnDefaults.width = options.defaultColumnWidth; - - columnsById = {}; - for (var i = 0; i < columns.length; i++) { - var m = columns[i] = $.extend({}, columnDefaults, columns[i]); - columnsById[m.id] = i; - if (m.minWidth && m.width < m.minWidth) { - m.width = m.minWidth; - } - if (m.maxWidth && m.width > m.maxWidth) { - m.width = m.maxWidth; - } - } - - // 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 = $("
").appendTo($container); - - if (options.createPreHeaderPanel) { - $preHeaderPanelScroller = $("
").appendTo($container); - $preHeaderPanel = $("
").appendTo($preHeaderPanelScroller); - $preHeaderPanelSpacer = $("
") - .appendTo($preHeaderPanelScroller); - - if (!options.showPreHeaderPanel) { - $preHeaderPanelScroller.hide(); - } - } - - $headerScroller = $("
").appendTo($container); - $headers = $("
").appendTo($headerScroller); - - $headerRowScroller = $("
").appendTo($container); - $headerRow = $("
").appendTo($headerRowScroller); - $headerRowSpacer = $("
") - .appendTo($headerRowScroller); - - $topPanelScroller = $("
").appendTo($container); - $topPanel = $("
").appendTo($topPanelScroller); - - if (!options.showTopPanel) { - $topPanelScroller.hide(); - } - - if (!options.showHeaderRow) { - $headerRowScroller.hide(); - } - - $viewport = $("
").appendTo($container); - $viewport.css("overflow-y", options.alwaysShowVerticalScroll ? "scroll" : (options.autoHeight ? "hidden" : "auto")); - $viewport.css("overflow-x", options.forceFitColumns ? "hidden" : "auto"); - if (options.viewportClass) $viewport.toggleClass(options.viewportClass, true); - - $canvas = $("
").appendTo($viewport); - - scrollbarDimensions = scrollbarDimensions || measureScrollbar(); - - if ($preHeaderPanelSpacer) $preHeaderPanelSpacer.css("width", getCanvasWidth() + scrollbarDimensions.width + "px"); - $headers.width(getHeadersWidth()); - $headerRowSpacer.css("width", getCanvasWidth() + scrollbarDimensions.width + "px"); - - - - if (options.createFooterRow) { - $footerRowScroller = $("
").appendTo($container); - $footerRow = $("
").appendTo($footerRowScroller); - $footerRowSpacer = $("
") - .css("width", getCanvasWidth() + scrollbarDimensions.width + "px") - .appendTo($footerRowScroller); - - if (!options.showFooterRow) { - $footerRowScroller.hide(); - } - } - - $focusSink2 = $focusSink.clone().appendTo($container); - - if (!options.explicitInitialization) { - finishInitialization(); - } - } - - function finishInitialization() { - if (!initialized) { - initialized = true; - - viewportW = parseFloat($.css($container[0], "width", true)); - - // 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"); - }); - } - - updateColumnCaches(); - createColumnHeaders(); - setupColumnSort(); - createCssRules(); - resizeCanvas(); - bindAncestorScrollEvents(); - - $container - .on("resize.slickgrid", resizeCanvas); - $viewport - //.on("click", handleClick) - .on("scroll", handleScroll); - $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.createFooterRow) { - $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); - - // Work around http://crbug.com/312427. - if (navigator.userAgent.toLowerCase().match(/webkit/) && - navigator.userAgent.toLowerCase().match(/macintosh/)) { - $canvas.on("mousewheel", handleMouseWheel); - } - 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 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() { - var $outerdiv = $('
').appendTo($viewport); - var $innerdiv = $('
').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 getColumnTotalWidth(includeScrollbar) { - var totalWidth = 0; - for (var i = 0, ii = columns.length; i < ii; i++) { - var width = columns[i].width; - totalWidth += width; - } - if (includeScrollbar) { - totalWidth += scrollbarDimensions.width; - } - return totalWidth; - } - - function getHeadersWidth() { - var headersWidth = getColumnTotalWidth(!options.autoHeight); - return Math.max(headersWidth, viewportW) + 1000; - } - - function getCanvasWidth() { - var availableWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW; - var rowWidth = 0; - var i = columns.length; - while (i--) { - rowWidth += columns[i].width; - } - return options.fullWidthRows ? Math.max(rowWidth, availableWidth) : rowWidth; - } - - function updateCanvasWidth(forceColumnWidthsUpdate) { - var oldCanvasWidth = canvasWidth; - canvasWidth = getCanvasWidth(); - - if (canvasWidth != oldCanvasWidth) { - $canvas.width(canvasWidth); - $headerRow.width(canvasWidth); - if (options.createFooterRow) { $footerRow.width(canvasWidth); } - if (options.createPreHeaderPanel) { $preHeaderPanel.width(canvasWidth); } - $headers.width(getHeadersWidth()); - viewportHasHScroll = (canvasWidth > viewportW - scrollbarDimensions.width); - } - - var w=canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0); - $headerRowSpacer.width(w); - if (options.createFooterRow) { $footerRowSpacer.width(w); } - if (options.createPreHeaderPanel) { $preHeaderPanelSpacer.width(w); } - - if (canvasWidth != oldCanvasWidth || 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 div = $("
").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; - } - - // TODO: this is static. need to handle page mutation. - function bindAncestorScrollEvents() { - var elem = $canvas[0]; - while ((elem = elem.parentNode) != document.body && elem != null) { - // bind to scroll containers only - if (elem == $viewport[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() { - return $headers[0]; - } - - function getHeaderColumn(columnIdOrIdx) { - var idx = (typeof columnIdOrIdx === "number" ? columnIdOrIdx : getColumnIndex(columnIdOrIdx)); - var $rtn = $headers.children().eq(idx); - return $rtn && $rtn[0]; - } - - function getHeaderRow() { - return $headerRow[0]; - } - - function getFooterRow() { - return $footerRow[0]; - } - - function getPreHeaderPanel() { - return $preHeaderPanel[0]; - } - - function getHeaderRowColumn(columnIdOrIdx) { - var idx = (typeof columnIdOrIdx === "number" ? columnIdOrIdx : getColumnIndex(columnIdOrIdx)); - var $rtn = $headerRow.children().eq(idx); - return $rtn && $rtn[0]; - } - - function getFooterRowColumn(columnIdOrIdx) { - var idx = (typeof columnIdOrIdx === "number" ? columnIdOrIdx : getColumnIndex(columnIdOrIdx)); - var $rtn = $footerRow.children().eq(idx); - return $rtn && $rtn[0]; - } - - 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 - }); - } - }); - $headers.empty(); - $headers.width(getHeadersWidth()); - - $headerRow.find(".slick-headerrow-column") - .each(function() { - var columnDef = $(this).data("column"); - if (columnDef) { - trigger(self.onBeforeHeaderRowCellDestroy, { - "node": this, - "column": columnDef, - "grid": self - }); - } - }); - $headerRow.empty(); - - if (options.createFooterRow) { - $footerRow.find(".slick-footerrow-column") - .each(function() { - var columnDef = $(this).data("column"); - if (columnDef) { - trigger(self.onBeforeFooterRowCellDestroy, { - "node": this, - "column": columnDef - }); - } - }); - $footerRow.empty(); - } - - for (var i = 0; i < columns.length; i++) { - var m = columns[i]; - - var header = $("
") - .html("" + m.name + "") - .width(m.width - headerColumnWidthDiff) - .attr("id", "" + uid + m.id) - .attr("title", m.toolTip || "") - .data("column", m) - .addClass(m.headerCssClass || "") - .appendTo($headers); - - if (options.enableColumnReorder || m.sortable) { - header - .on('mouseenter', onMouseEnter) - .on('mouseleave', onMouseLeave); - } - - if (m.sortable) { - header.addClass("slick-header-sortable"); - header.append(""); - if (options.numberedMultiColumnSort && options.sortColNumberInSeparateSpan) { header.append(""); } - } - - trigger(self.onHeaderCellRendered, { - "node": header[0], - "column": m, - "grid": self - }); - - if (options.showHeaderRow) { - var headerRowCell = $("
") - .data("column", m) - .appendTo($headerRow); - - trigger(self.onHeaderRowCellRendered, { - "node": headerRowCell[0], - "column": m, - "grid": self - }); - } - if (options.createFooterRow && options.showFooterRow) { - var footerRowCell = $("
") - .data("column", m) - .appendTo($footerRow); - - trigger(self.onFooterRowCellRendered, { - "node": footerRowCell[0], - "column": m - }); - } - } - - 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 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 (!sortColumn) { - sortColumn = { columnId: column.id, sortAsc: column.defaultSortAsc }; - sortColumns.push(sortColumn); - } else if (sortColumns.length == 0) { - sortColumns.push(sortColumn); - } - } - } - - setSortColumns(sortColumns); - - if (!options.multiColumnSort) { - trigger(self.onSort, { - multiColumnSort: false, - sortCol: (sortColumns.length > 0 ? column : null), - sortAsc: (sortColumns.length > 0 ? sortColumns[0].sortAsc : true), - grid: self}, e); - } else { - trigger(self.onSort, { - multiColumnSort: true, - sortCols: $.map(sortColumns, function(col) { - return {sortCol: columns[getColumnIndex(col.columnId)], sortAsc: col.sortAsc }; - }), - grid: self}, e); - } - } - }); - } - - function setupColumnReorder() { - $headers.filter(":ui-sortable").sortable("destroy"); - $headers.sortable({ - containment: "parent", - distance: 3, - axis: "x", - cursor: "default", - tolerance: "intersection", - helper: "clone", - placeholder: "slick-sortable-placeholder ui-state-default slick-header-column", - start: function (e, ui) { - ui.placeholder.width(ui.helper.outerWidth() - headerColumnWidthDiff); - $(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, {grid: self}); - e.stopPropagation(); - setupColumnResize(); - } - }); - } - - function setupColumnResize() { - var $col, j, c, pageX, columnElements, minPageX, maxPageX, firstResizable, lastResizable; - columnElements = $headers.children(); - columnElements.find(".slick-resizable-handle").remove(); - columnElements.each(function (i, e) { - if (i >= columns.length) { return; } - if (columns[i].resizable) { - if (firstResizable === undefined) { - firstResizable = i; - } - lastResizable = i; - } - }); - if (firstResizable === undefined) { - return; - } - columnElements.each(function (i, e) { - if (i >= columns.length) { return; } - if (i < firstResizable || (options.forceFitColumns && i >= lastResizable)) { - return; - } - $col = $(e); - $("
") - .appendTo(e) - .on("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) { - if (i >= columns.length) { return; } - columns[i].previousWidth = $(e).outerWidth(); - }); - if (options.forceFitColumns) { - shrinkLeewayOnRight = 0; - stretchLeewayOnRight = 0; - // colums on right affect maxPageX/minPageX - for (j = i + 1; j < columns.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); - }) - .on("drag", function (e, dd) { - columnResizeDragging = true; - var actualMinWidth, d = Math.min(maxPageX, Math.max(minPageX, e.pageX)) - pageX, x; - 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 < columns.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 { // 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 < columns.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; - } - } - } - } - } - applyColumnHeaderWidths(); - if (options.syncColumnCellResize) { - applyColumnWidths(); - } - }) - .on("dragend", function (e, dd) { - var newWidth; - $(this).parent().removeClass("slick-header-column-active"); - for (j = 0; j < columns.length; j++) { - c = columns[j]; - newWidth = $(columnElements[j]).outerWidth(); - - if (c.previousWidth !== newWidth && c.rerenderOnResize) { - invalidateAllRows(); - } - } - updateCanvasWidth(true); - render(); - trigger(self.onColumnsResized, {grid: self}); - setTimeout(function () { columnResizeDragging = false; }, 300); - }); - }); - } - - 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"]; - - // jquery prior to version 1.8 handles .width setter/getter as a direct css write/read - // jquery 1.8 changed .width to read the true inner element width if box-sizing is set to border-box, and introduced a setter for .outerWidth - // so for equivalent functionality, prior to 1.8 use .width, and after use .outerWidth - var verArray = $.fn.jquery.split('.'); - jQueryNewWidthBehaviour = (verArray[0]==1 && verArray[1]>=8) || verArray[0] >=2; - - el = $("").appendTo($headers); - headerColumnWidthDiff = headerColumnHeightDiff = 0; - if (el.css("box-sizing") != "border-box" && el.css("-moz-box-sizing") != "border-box" && el.css("-webkit-box-sizing") != "border-box") { - $.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 = $("
").appendTo($canvas); - el = $("").appendTo(r); - cellWidthDiff = cellHeightDiff = 0; - if (el.css("box-sizing") != "border-box" && el.css("-moz-box-sizing") != "border-box" && el.css("-webkit-box-sizing") != "border-box") { - $.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 = $(" + + +
+
+
+
+ +
+

Demonstrates:

+
    +
  • Using a fixed header row to implement column-level filters with Checkbox Selector
  • +
  • Type numbers in textboxes to filter grid data
  • +
  • Checkbox row select column
  • +

    + +

    +
+

View Source:

+ +
+
+ + + + + + + + + + + + + + + + + + diff --git a/examples/example-checkbox-row-select.html b/examples/example-checkbox-row-select.html index eb5d66a3..9af6ae70 100644 --- a/examples/example-checkbox-row-select.html +++ b/examples/example-checkbox-row-select.html @@ -7,7 +7,7 @@ - + diff --git a/plugins/slick.checkboxselectcolumn.js b/plugins/slick.checkboxselectcolumn.js index 87b6bf58..cfb70ca9 100644 --- a/plugins/slick.checkboxselectcolumn.js +++ b/plugins/slick.checkboxselectcolumn.js @@ -220,7 +220,7 @@ grid.onHeaderRowCellRendered.subscribe(function(e, args) { if (args.column.field === "sel") { $(args.node).empty(); - $("") + $("") .appendTo(args.node) .on('click', function(evnt) { handleHeaderClick(evnt, args) From 116d6d844ecbc597e276f18e6933f922e051cbee Mon Sep 17 00:00:00 2001 From: Ghislain B Date: Tue, 11 Sep 2018 12:32:17 -0400 Subject: [PATCH 21/55] refactor(selector): fix toggle show/hide select all with Font-Awesom --- examples/example-checkbox-header-row.html | 20 +++++++++----------- plugins/slick.checkboxselectcolumn.js | 7 ++++--- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/examples/example-checkbox-header-row.html b/examples/example-checkbox-header-row.html index 0d1e054e..1a9914c2 100644 --- a/examples/example-checkbox-header-row.html +++ b/examples/example-checkbox-header-row.html @@ -9,7 +9,7 @@ @@ -92,16 +92,16 @@

View Source:

}; var columns = []; var columnFilters = {}; - var checkboxSelector3; - var isSelectAllCheckbox3Hidden = false; + var checkboxSelector; + var isSelectAllCheckboxHidden = false; - checkboxSelector3 = new Slick.CheckboxSelectColumn({ + checkboxSelector = new Slick.CheckboxSelectColumn({ cssClass: "slick-cell-checkboxsel", showInColumnTitleRow: false, showInFilterHeaderRow: true }); - columns.push(checkboxSelector3.getColumnDefinition()); + columns.push(checkboxSelector.getColumnDefinition()); for (var i = 0; i < 10; i++) { columns.push({ @@ -125,8 +125,8 @@

View Source:

} function toggleHideSelectAllCheckbox() { - isSelectAllCheckbox3Hidden = !isSelectAllCheckbox3Hidden; - checkboxSelector3.setOptions({ hideSelectAllCheckbox: isSelectAllCheckbox3Hidden }); + isSelectAllCheckboxHidden = !isSelectAllCheckboxHidden; + checkboxSelector.setOptions({ hideSelectAllCheckbox: isSelectAllCheckboxHidden }); } $(function () { @@ -138,12 +138,10 @@

View Source:

} } - - dataView = new Slick.Data.DataView(); grid = new Slick.Grid("#myGrid", dataView, columns, options); grid.setSelectionModel(new Slick.RowSelectionModel({selectActiveRow: false})); - grid.registerPlugin(checkboxSelector3); + grid.registerPlugin(checkboxSelector); var columnpicker = new Slick.Controls.ColumnPicker(columns, grid, options); diff --git a/plugins/slick.checkboxselectcolumn.js b/plugins/slick.checkboxselectcolumn.js index cfb70ca9..7e3efb8e 100644 --- a/plugins/slick.checkboxselectcolumn.js +++ b/plugins/slick.checkboxselectcolumn.js @@ -53,7 +53,7 @@ if (_options.hideSelectAllCheckbox) { _grid.updateColumnHeader(_options.columnId, "", ""); - $("#header-filter-selector" + _selectAll_UID).hide(); + $("#filter-checkbox-selectall-container").hide(); } else { if (_options.showInColumnTitleRow) { if (_isSelectAllChecked) { @@ -63,7 +63,8 @@ } } if (_options.showInFilterHeaderRow) { - var selectAllElm = $("#header-filter-selector" + _selectAll_UID).show(); + $("#filter-checkbox-selectall-container").show(); + var selectAllElm = $("#header-filter-selector"); selectAllElm.prop("checked", _isSelectAllChecked); } } @@ -220,7 +221,7 @@ grid.onHeaderRowCellRendered.subscribe(function(e, args) { if (args.column.field === "sel") { $(args.node).empty(); - $("") + $("") .appendTo(args.node) .on('click', function(evnt) { handleHeaderClick(evnt, args) From 34243577e82c20141412f8bf5cc5b9d5d98efd7a Mon Sep 17 00:00:00 2001 From: Ghislain B Date: Tue, 11 Sep 2018 12:49:34 -0400 Subject: [PATCH 22/55] refactor(selector): add toggle to change row to display Select All btn --- examples/example-checkbox-header-row.html | 16 ++++++++++++++-- plugins/slick.checkboxselectcolumn.js | 17 +++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/examples/example-checkbox-header-row.html b/examples/example-checkbox-header-row.html index 1a9914c2..2ef9e3a4 100644 --- a/examples/example-checkbox-header-row.html +++ b/examples/example-checkbox-header-row.html @@ -58,6 +58,9 @@

Demonstrates:

+

+ +

View Source:

    @@ -94,11 +97,12 @@

    View Source:

    var columnFilters = {}; var checkboxSelector; var isSelectAllCheckboxHidden = false; + var isSelectAllShownAsColumnTitle = false; checkboxSelector = new Slick.CheckboxSelectColumn({ cssClass: "slick-cell-checkboxsel", - showInColumnTitleRow: false, - showInFilterHeaderRow: true + showInColumnTitleRow: isSelectAllShownAsColumnTitle, + showInFilterHeaderRow: !isSelectAllShownAsColumnTitle }); columns.push(checkboxSelector.getColumnDefinition()); @@ -129,6 +133,14 @@

    View Source:

    checkboxSelector.setOptions({ hideSelectAllCheckbox: isSelectAllCheckboxHidden }); } + function toggleWhichRowToShowSelectAll() { + isSelectAllShownAsColumnTitle = !isSelectAllShownAsColumnTitle; + checkboxSelector.setOptions({ + showInColumnTitleRow: isSelectAllShownAsColumnTitle, + showInFilterHeaderRow: !isSelectAllShownAsColumnTitle, + }); + } + $(function () { for (var i = 0; i < 100; i++) { var d = (data[i] = {}); diff --git a/plugins/slick.checkboxselectcolumn.js b/plugins/slick.checkboxselectcolumn.js index 7e3efb8e..83e1a89b 100644 --- a/plugins/slick.checkboxselectcolumn.js +++ b/plugins/slick.checkboxselectcolumn.js @@ -52,8 +52,8 @@ _options = $.extend(true, {}, _options, options); if (_options.hideSelectAllCheckbox) { - _grid.updateColumnHeader(_options.columnId, "", ""); - $("#filter-checkbox-selectall-container").hide(); + hideSelectAllFromColumnHeaderTitleRow(); + hideSelectAllFromColumnHeaderFilterRow(); } else { if (_options.showInColumnTitleRow) { if (_isSelectAllChecked) { @@ -61,15 +61,28 @@ } else { _grid.updateColumnHeader(_options.columnId, "", _options.toolTip); } + } else { + hideSelectAllFromColumnHeaderTitleRow(); } + if (_options.showInFilterHeaderRow) { $("#filter-checkbox-selectall-container").show(); var selectAllElm = $("#header-filter-selector"); selectAllElm.prop("checked", _isSelectAllChecked); + } else { + hideSelectAllFromColumnHeaderFilterRow(); } } } + function hideSelectAllFromColumnHeaderTitleRow() { + _grid.updateColumnHeader(_options.columnId, "", ""); + } + + function hideSelectAllFromColumnHeaderFilterRow() { + $("#filter-checkbox-selectall-container").hide(); + } + function handleSelectedRowsChanged(e, args) { var selectedRows = _grid.getSelectedRows(); var lookup = {}, row, i; From 2e65cedd2ea0598e2ecdf9ed489d40ab57ddc3c7 Mon Sep 17 00:00:00 2001 From: Ghislain B Date: Tue, 11 Sep 2018 12:58:45 -0400 Subject: [PATCH 23/55] fix Select All checked flag when using setOptions on filter row --- plugins/slick.checkboxselectcolumn.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/slick.checkboxselectcolumn.js b/plugins/slick.checkboxselectcolumn.js index 83e1a89b..21485cc4 100644 --- a/plugins/slick.checkboxselectcolumn.js +++ b/plugins/slick.checkboxselectcolumn.js @@ -66,9 +66,9 @@ } if (_options.showInFilterHeaderRow) { - $("#filter-checkbox-selectall-container").show(); - var selectAllElm = $("#header-filter-selector"); - selectAllElm.prop("checked", _isSelectAllChecked); + var selectAllContainer = $("#filter-checkbox-selectall-container"); + selectAllContainer.show(); + selectAllContainer.find('input[type="checkbox"]').prop("checked", _isSelectAllChecked); } else { hideSelectAllFromColumnHeaderFilterRow(); } From b4f99437e52a009efb1282710f42f1747a849fae Mon Sep 17 00:00:00 2001 From: Ghislain B Date: Tue, 11 Sep 2018 13:01:24 -0400 Subject: [PATCH 24/55] fix handle click from title not always working when using setOptions --- plugins/slick.checkboxselectcolumn.js | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/slick.checkboxselectcolumn.js b/plugins/slick.checkboxselectcolumn.js index 21485cc4..a08ca6fe 100644 --- a/plugins/slick.checkboxselectcolumn.js +++ b/plugins/slick.checkboxselectcolumn.js @@ -61,6 +61,7 @@ } else { _grid.updateColumnHeader(_options.columnId, "", _options.toolTip); } + _handler.subscribe(_grid.onHeaderClick, handleHeaderClick); } else { hideSelectAllFromColumnHeaderTitleRow(); } From 364b9e257befd78a611b30cb93c52459bd0f922b Mon Sep 17 00:00:00 2001 From: Ghislain B Date: Tue, 11 Sep 2018 18:31:03 -0400 Subject: [PATCH 25/55] refactor(selector): inversed flag from showX to hideX - to be consistent with all other flags, the 2 new flags are - hideInColumnTitleRow: false by default - hideInFilterHeaderRow: true by default --- examples/example-checkbox-header-row.html | 9 +++++---- examples/example-checkbox-row-select.html | 1 + plugins/slick.checkboxselectcolumn.js | 18 +++++++++--------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/examples/example-checkbox-header-row.html b/examples/example-checkbox-header-row.html index 2ef9e3a4..83607139 100644 --- a/examples/example-checkbox-header-row.html +++ b/examples/example-checkbox-header-row.html @@ -32,6 +32,7 @@ #myGrid input[type=checkbox] { display:none; } /* to hide the checkbox itself */ #myGrid input[type=checkbox] + label:before { + cursor: pointer; font-family: FontAwesome; color: rgb(143, 14, 106); font-weight: bold; @@ -101,8 +102,8 @@

    View Source:

    checkboxSelector = new Slick.CheckboxSelectColumn({ cssClass: "slick-cell-checkboxsel", - showInColumnTitleRow: isSelectAllShownAsColumnTitle, - showInFilterHeaderRow: !isSelectAllShownAsColumnTitle + hideInColumnTitleRow: !isSelectAllShownAsColumnTitle, + hideInFilterHeaderRow: isSelectAllShownAsColumnTitle }); columns.push(checkboxSelector.getColumnDefinition()); @@ -136,8 +137,8 @@

    View Source:

    function toggleWhichRowToShowSelectAll() { isSelectAllShownAsColumnTitle = !isSelectAllShownAsColumnTitle; checkboxSelector.setOptions({ - showInColumnTitleRow: isSelectAllShownAsColumnTitle, - showInFilterHeaderRow: !isSelectAllShownAsColumnTitle, + hideInColumnTitleRow: !isSelectAllShownAsColumnTitle, + hideInFilterHeaderRow: isSelectAllShownAsColumnTitle, }); } diff --git a/examples/example-checkbox-row-select.html b/examples/example-checkbox-row-select.html index 9af6ae70..8601af61 100644 --- a/examples/example-checkbox-row-select.html +++ b/examples/example-checkbox-row-select.html @@ -29,6 +29,7 @@ #myGrid2 input[type=checkbox] { display:none; } /* to hide the checkbox itself */ #myGrid2 input[type=checkbox] + label:before { + cursor: pointer; font-family: FontAwesome; color: rgb(143, 14, 106); font-weight: bold; diff --git a/plugins/slick.checkboxselectcolumn.js b/plugins/slick.checkboxselectcolumn.js index a08ca6fe..6ac9de19 100644 --- a/plugins/slick.checkboxselectcolumn.js +++ b/plugins/slick.checkboxselectcolumn.js @@ -18,8 +18,8 @@ hideSelectAllCheckbox: false, toolTip: "Select/Deselect All", width: 30, - showInColumnTitleRow: true, - showInFilterHeaderRow: false + hideInColumnTitleRow: false, + hideInFilterHeaderRow: true }; var _isSelectAllChecked = false; @@ -32,10 +32,10 @@ .subscribe(_grid.onClick, handleClick) .subscribe(_grid.onKeyDown, handleKeyDown); - if (_options.showInFilterHeaderRow) { + if (!_options.hideInFilterHeaderRow) { addCheckboxToFilterHeaderRow(grid); } - if (_options.showInColumnTitleRow) { + if (!_options.hideInColumnTitleRow) { _handler.subscribe(_grid.onHeaderClick, handleHeaderClick) } } @@ -55,7 +55,7 @@ hideSelectAllFromColumnHeaderTitleRow(); hideSelectAllFromColumnHeaderFilterRow(); } else { - if (_options.showInColumnTitleRow) { + if (!_options.hideInColumnTitleRow) { if (_isSelectAllChecked) { _grid.updateColumnHeader(_options.columnId, "", _options.toolTip); } else { @@ -66,7 +66,7 @@ hideSelectAllFromColumnHeaderTitleRow(); } - if (_options.showInFilterHeaderRow) { + if (!_options.hideInFilterHeaderRow) { var selectAllContainer = $("#filter-checkbox-selectall-container"); selectAllContainer.show(); selectAllContainer.find('input[type="checkbox"]').prop("checked", _isSelectAllChecked); @@ -102,14 +102,14 @@ _grid.render(); _isSelectAllChecked = selectedRows.length && selectedRows.length == _grid.getDataLength(); - if (_options.showInColumnTitleRow && !_options.hideSelectAllCheckbox) { + if (!_options.hideInColumnTitleRow && !_options.hideSelectAllCheckbox) { if (_isSelectAllChecked) { _grid.updateColumnHeader(_options.columnId, "", _options.toolTip); } else { _grid.updateColumnHeader(_options.columnId, "", _options.toolTip); } } - if (_options.showInFilterHeaderRow) { + if (!_options.hideInFilterHeaderRow) { var selectAllElm = $("#header-filter-selector" + _selectAll_UID); selectAllElm.prop("checked", _isSelectAllChecked); } @@ -219,7 +219,7 @@ function getColumnDefinition() { return { id: _options.columnId, - name: (_options.hideSelectAllCheckbox || !_options.showInColumnTitleRow) ? "" : "", + name: (_options.hideSelectAllCheckbox || _options.hideInColumnTitleRow) ? "" : "", toolTip: _options.toolTip, field: "sel", width: _options.width, From 449077d4ae14c26eb1dfdd9cb8b70f8dcf58d495 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Thu, 20 Dec 2018 15:40:19 -0500 Subject: [PATCH 26/55] remove jqueryui dependency in groupmetadataprovider as per PR #277 --- slick.core.js | 1 + 1 file changed, 1 insertion(+) diff --git a/slick.core.js b/slick.core.js index 9ab50647..2d3dc247 100644 --- a/slick.core.js +++ b/slick.core.js @@ -27,6 +27,7 @@ "TreeColumns": TreeColumns, "keyCode": { + SPACE: 8, BACKSPACE: 8, DELETE: 46, DOWN: 40, From 8aa0d2c9ef551e03573d9da41ca93dab60205b9d Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Thu, 20 Dec 2018 15:44:03 -0500 Subject: [PATCH 27/55] fix setSelectedRows should bypass when editor is active, closes #278 --- slick.grid.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/slick.grid.js b/slick.grid.js index f04c58c0..551dc0de 100644 --- a/slick.grid.js +++ b/slick.grid.js @@ -5140,7 +5140,9 @@ if (typeof Slick === "undefined") { if (!selectionModel) { throw new Error("Selection model is not set"); } - selectionModel.setSelectedRanges(rowsToRanges(rows)); + if (!grid.getEditorLock().isActive()) { + selectionModel.setSelectedRanges(rowsToRanges(rows)); + } } From c5dddb53f6a1b8ac1baf08ff4d9513db21e9dd36 Mon Sep 17 00:00:00 2001 From: Ghislain B Date: Mon, 19 Nov 2018 23:23:15 -0500 Subject: [PATCH 28/55] Feature/row detail view (#287) * feat(RowDetail): add 2x new subscribe methods & fixed multiple issues --- examples/example16-row-detail.html | 301 +++++++++++-- plugins/slick.rowdetailview.css | 19 +- plugins/slick.rowdetailview.js | 652 +++++++++++++++++++---------- 3 files changed, 701 insertions(+), 271 deletions(-) diff --git a/examples/example16-row-detail.html b/examples/example16-row-detail.html index b2baf163..24ec757b 100644 --- a/examples/example16-row-detail.html +++ b/examples/example16-row-detail.html @@ -14,13 +14,64 @@ .preload { font-size: 18px; } - - .dynamic-cell-detail > :first-child - { - vertical-align: middle; - line-height: 13px; - padding: 10px; - margin-left: 20px; + + .dynamic-cell-detail > :first-child { + vertical-align: middle; + line-height: 13px; + padding: 10px; + margin-left: 20px; + } + .slick-headerrow-column { + background: #87ceeb; + text-overflow: clip; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + + .slick-headerrow-column input { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + + /* Style the tab */ + .tab { + overflow: hidden; + border: 1px solid #ccc; + background-color: #f1f1f1; + } + + /* Style the buttons inside the tab */ + .tab button { + background-color: inherit; + float: left; + border: none; + outline: none; + cursor: pointer; + padding: 14px 16px; + transition: 0.3s; + font-size: 17px; + } + + /* Change background color of buttons on hover */ + .tab button:hover { + background-color: #ddd; + } + + /* Create an active/current tablink class */ + .tab button.active { + background-color: #ccc; + } + + /* Style the tab content */ + .tabcontent { + display: none; + padding: 6px 12px; + border: 1px solid #ccc; + border-top: none; } @@ -49,10 +100,17 @@

    Options:

      +

    + +

    + +

    +

      +

    View Source:

    • View the source for this example on Github
    • @@ -68,22 +126,24 @@

      View Source:

      - + diff --git a/plugins/slick.rowdetailview.css b/plugins/slick.rowdetailview.css index 2e33574d..b6fdc573 100644 --- a/plugins/slick.rowdetailview.css +++ b/plugins/slick.rowdetailview.css @@ -1,23 +1,21 @@ -.detailView-toggle -{ +.detailView-toggle { display: inline-block; cursor: pointer; } -.detailView-toggle.expand -{ + +.detailView-toggle.expand { height: 20px; width: 20px; background: url(../images/arrow-right.gif) no-repeat center center; } -.detailView-toggle.collapse -{ +.detailView-toggle.collapse { height: 20px; width: 20px; background: url(../images/sort-desc.gif) no-repeat center center; } -.dynamic-cell-detail -{ + +.dynamic-cell-detail { z-index: 10000; position: absolute; background-color: #dae5e8; @@ -26,9 +24,8 @@ width: 100%; overflow: auto; } - -.dynamic-cell-detail > :first-child -{ + +.dynamic-cell-detail > :first-child { vertical-align: middle; line-height: 13px; } diff --git a/plugins/slick.rowdetailview.js b/plugins/slick.rowdetailview.js index bec10c7f..d12139ed 100644 --- a/plugins/slick.rowdetailview.js +++ b/plugins/slick.rowdetailview.js @@ -3,50 +3,75 @@ * Original StackOverflow question & article making this possible (thanks to violet313) * https://stackoverflow.com/questions/10535164/can-slickgrids-row-height-be-dynamically-altered#29399927 * http://violet313.org/slickgrids/#intro - * * * USAGE: - * * Add the slick.rowDetailView.(js|css) files and register the plugin with the grid. * * AVAILABLE ROW DETAIL OPTIONS: - * cssClass: A CSS class to be added to the row detail - * loadOnce: Booleang flag, when True will load the data once and then reuse it. - * preTemplate: Template that will be used before the async process (typically used to show a spinner/loading) - * postTemplate: Template that will be loaded once the async function finishes - * process: Async server function call - * panelRows: Row count to use for the template panel - * useRowClick: Boolean flag, when True will open the row detail on a row click (from any column), default to False - * + * cssClass: A CSS class to be added to the row detail + * expandedClass: Extra classes to be added to the expanded Toggle + * collapsedClass: Extra classes to be added to the collapse Toggle + * loadOnce: Defaults to false, when set to True it will load the data once and then reuse it. + * preTemplate: Template that will be used before the async process (typically used to show a spinner/loading) + * postTemplate: Template that will be loaded once the async function finishes + * process: Async server function call + * panelRows: Row count to use for the template panel + * useRowClick: Boolean flag, when True will open the row detail on a row click (from any column), default to False + * keyPrefix: Defaults to '_', prefix used for all the plugin metadata added to the item object (meta e.g.: padding, collapsed, parent) + * collapseAllOnSort: Defaults to true, which will collapse all row detail views when user calls a sort. Unless user implements a sort to deal with padding + * saveDetailViewOnScroll: Defaults to true, which will save the row detail view in a cache when it detects that it will become out of the viewport buffer + * * AVAILABLE PUBLIC OPTIONS: * init: initiliaze the plugin * destroy: destroy the plugin and it's events * collapseAll: collapse all opened row detail panel - * getColumnDefinition: get the column definitions + * collapseDetailView: collapse a row by passing the item object (row detail) + * expandDetailView: expand a row by passing the item object (row detail) + * getColumnDefinition: get the column definitions + * getExpandedRows: get all the expanded rows + * getFilterItem: takes in the item we are filtering and if it is an expanded row returns it's parents row to filter on * getOptions: get current plugin options + * resizeDetailView: resize a row detail view, it will auto-calculate the number of rows it needs + * saveDetailView: save a row detail view content by passing the row object * setOptions: set or change some of the plugin options - * + * * THE PLUGIN EXPOSES THE FOLLOWING SLICK EVENTS: - * onAsyncResponse: This event must be used with the "notify" by the end user once the Asynchronous Server call returns the item detail - * Event args: - * itemDetail: Item detail returned from the async server call - * detailView: An explicit view to use instead of template (Optional) + * onAsyncResponse: This event must be used with the "notify" by the end user once the Asynchronous Server call returns the item detail + * Event args: + * item: Item detail returned from the async server call + * detailView: An explicit view to use instead of template (Optional) * * onAsyncEndUpdate: Fired when the async response finished - * Event args: - * grid: Reference to the grid. - * itemDetail: Column definition. - * + * Event args: + * grid: Reference to the grid. + * item: Item data context + * * onBeforeRowDetailToggle: Fired before the row detail gets toggled - * Event args: - * grid: Reference to the grid. - * item: Column definition. - * + * Event args: + * grid: Reference to the grid. + * item: Item data context + * * onAfterRowDetailToggle: Fired after the row detail gets toggled - * Event args: - * grid: Reference to the grid. - * item: Column definition. + * Event args: + * grid: Reference to the grid. + * item: Item data context + * expandedRows: Array of the Expanded Rows + * + * onRowOutOfViewportRange: Fired after a row becomes out of viewport range (user can't see the row anymore) + * Event args: + * grid: Reference to the grid. + * item: Item data context + * rowIndex: Index of the Row in the Grid + * expandedRows: Array of the Expanded Rows + * rowsOutOfViewport: Array of the Out of viewport Range Rows * + * onRowBackToViewportRange: Fired after the row detail gets toggled + * Event args: + * grid: Reference to the grid. + * item: Item data context + * rowIndex: Index of the Row in the Grid + * expandedRows: Array of the Expanded Rows + * rowsOutOfViewport: Array of the Out of viewport Range Rows */ (function ($) { // register namespace @@ -58,59 +83,115 @@ } }); - + /** Constructor of the Row Detail View Plugin */ function RowDetailView(options) { var _grid; + var _gridOptions; + var _gridUid; var _self = this; + var _lastRange = null; var _expandedRows = []; var _handler = new Slick.EventHandler(); + var _outsideRange = 5; var _defaults = { - columnId: "_detail_selector", - cssClass: null, - toolTip: "", - width: 30 + columnId: '_detail_selector', + cssClass: 'detailView-toggle', + expandedClass: null, + collapsedClass: null, + keyPrefix: '_', + loadOnce: false, + collapseAllOnSort: true, + saveDetailViewOnScroll: true, + toolTip: '', + width: 30, + maxRows: null }; - + var _keyPrefix = _defaults.keyPrefix; + var _gridRowBuffer = 0; + var _rowsOutOfViewport = []; var _options = $.extend(true, {}, _defaults, options); + /** + * Initialize the plugin, which requires user to pass the SlickGrid Grid object + * @param grid: SlickGrid Grid object + */ function init(grid) { + if (!grid) { + throw new Error('RowDetailView Plugin requires the Grid instance to be passed as argument to the "init()" method'); + } _grid = grid; + _gridUid = grid.getUID(); + _gridOptions = grid.getOptions() || {}; _dataView = _grid.getData(); + _keyPrefix = _options && _options.keyPrefix || '_'; // Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3 + _gridRowBuffer = _grid.getOptions().minRowBuffer; _grid.getOptions().minRowBuffer = _options.panelRows + 3; _handler .subscribe(_grid.onClick, handleClick) - .subscribe(_grid.onSort, handleSort) - .subscribe(_grid.onScroll, handleScroll); + .subscribe(_grid.onScroll, handleScroll); + + // Sort will, by default, Collapse all of the open items (unless user implements his own onSort which deals with open row and padding) + if (_options.collapseAllOnSort) { + _handler.subscribe(_grid.onSort, collapseAll); + _expandedRows = []; + _rowsOutOfViewport = []; + } - _grid.getData().onRowCountChanged.subscribe(function () { _grid.updateRowCount(); _grid.render(); }); - _grid.getData().onRowsChanged.subscribe(function (e, a) { _grid.invalidateRows(a.rows); _grid.render(); }); + _grid.getData().onRowCountChanged.subscribe(function () { + _grid.updateRowCount(); + _grid.render(); + }); + + _grid.getData().onRowsChanged.subscribe(function (e, a) { + _grid.invalidateRows(a.rows); + _grid.render(); + }); // subscribe to the onAsyncResponse so that the plugin knows when the user server side calls finished subscribeToOnAsyncResponse(); } + /** destroy the plugin and it's events */ function destroy() { _handler.unsubscribeAll(); _self.onAsyncResponse.unsubscribe(); _self.onAsyncEndUpdate.unsubscribe(); _self.onAfterRowDetailToggle.unsubscribe(); _self.onBeforeRowDetailToggle.unsubscribe(); + _self.onRowOutOfViewportRange.unsubscribe(); + _self.onRowBackToViewportRange.unsubscribe(); + $(window).off('resize'); } - function getOptions(options) { + /** Get current plugin options */ + function getOptions() { return _options; } + /** set or change some of the plugin options */ function setOptions(options) { _options = $.extend(true, {}, _options, options); } - + + /** Find a value in an array and return the index when (or -1 when not found) */ + function arrayFindIndex(sourceArray, value) { + if (sourceArray) { + for (var i = 0; i < sourceArray.length; i++) { + if (sourceArray[i] === value) { + return i; + } + } + } + return -1; + } + + /** Handle mouse click event */ function handleClick(e, args) { // clicking on a row select checkbox - if (_options.useRowClick || _grid.getColumns()[args.cell].id === _options.columnId && $(e.target).hasClass("detailView-toggle")) { + if (_options.useRowClick || _grid.getColumns()[args.cell].id === _options.columnId && $(e.target).hasClass(_options.cssClass)) { // if editing, try to commit if (_grid.getEditorLock().isActive() && !_grid.getEditorLock().commitCurrentEdit()) { e.preventDefault(); @@ -122,16 +203,17 @@ // trigger an event before toggling _self.onBeforeRowDetailToggle.notify({ - "grid": _grid, - "item": item + 'grid': _grid, + 'item': item }, e, _self); toggleRowSelection(item); // trigger an event after toggling _self.onAfterRowDetailToggle.notify({ - "grid": _grid, - "item": item + 'grid': _grid, + 'item': item, + 'expandedRows': _expandedRows, }, e, _self); e.stopPropagation(); @@ -139,113 +221,187 @@ } } - // Sort will just collapse all of the open items - function handleSort(e, args) { - collapseAll(); + /** If we scroll save detail views that go out of cache range */ + function handleScroll(e, args) { + calculateOutOfRangeViews(); } - // If we scroll save detail views that go out of cache range - function handleScroll(e, args) { - - var range = _grid.getRenderedRange(); - - var start = (range.top > 0 ? range.top : 0); - var end = (range.bottom > _dataView.getLength() ? range.bottom : _dataView.getLength()); - - // Get the item at the top of the view - var topMostItem = _dataView.getItemByIdx(start); - - // Check it is a parent item - if (topMostItem && topMostItem._parent == undefined) - { - // This is a standard row as we have no parent. - var nextItem = _dataView.getItemByIdx(start + 1); - if(nextItem !== undefined && nextItem._parent !== undefined) - { - // This is likely the expanded Detail Row View - // Check for safety - if(nextItem._parent == topMostItem) - { - saveDetailView(topMostItem); - } + /** Calculate when expanded rows become out of view range */ + function calculateOutOfRangeViews() { + if (_grid) { + var renderedRange = _grid.getRenderedRange(); + // Only check if we have expanded rows + if (_expandedRows.length > 0) { + // Assume scroll direction is down by default. + var scrollDir = 'DOWN'; + if (_lastRange) { + // Some scrolling isn't anything as the range is the same + if (_lastRange.top === renderedRange.top && _lastRange.bottom === renderedRange.bottom) { + return; } + + // If our new top is smaller we are scrolling up + if (_lastRange.top > renderedRange.top || + // Or we are at very top but our bottom is increasing + (_lastRange.top === 0 && renderedRange.top === 0) && _lastRange.bottom > renderedRange.bottom) { + scrollDir = 'UP'; + } + } } - // Find the bottom most item that is likely to go off screen - var bottomMostItem = _dataView.getItemByIdx(end - 1); + _expandedRows.forEach(function (row) { + var rowIndex = _dataView.getRowById(row.id); + + var rowPadding = row[_keyPrefix + 'sizePadding']; + var rowOutOfRange = arrayFindIndex(_rowsOutOfViewport, rowIndex) >= 0; - // If we are a detailView and we are about to go out of cache view - if (bottomMostItem && bottomMostItem._parent !== undefined) - { - saveDetailView(bottomMostItem._parent); - + if (scrollDir === 'UP') { + // save the view when asked + if (_options.saveDetailViewOnScroll) { + // If the bottom item within buffer range is an expanded row save it. + if (rowIndex >= renderedRange.bottom - _gridRowBuffer) { + saveDetailView(row); + } + } + + // If the row expanded area is within the buffer notify that it is back in range + if (rowOutOfRange && rowIndex - _outsideRange < renderedRange.top && rowIndex >= renderedRange.top) { + notifyBackToViewportWhenDomExist(row, rowIndex); + } + + // if our first expanded row is about to go off the bottom + else if (!rowOutOfRange && (rowIndex + rowPadding) > renderedRange.bottom) { + notifyOutOfViewport(row, rowIndex); + } + } + else if (scrollDir === 'DOWN') { + // save the view when asked + if (_options.saveDetailViewOnScroll) { + // If the top item within buffer range is an expanded row save it. + if (rowIndex <= renderedRange.top + _gridRowBuffer) { + saveDetailView(row); + } + } + + // If row index is i higher than bottom with some added value (To ignore top rows off view) and is with view and was our of range + if (rowOutOfRange && (rowIndex + rowPadding + _outsideRange) > renderedRange.bottom && rowIndex < rowIndex + rowPadding) { + notifyBackToViewportWhenDomExist(row, rowIndex); + } + + // if our row is outside top of and the buffering zone but not in the array of outOfVisable range notify it + else if (!rowOutOfRange && rowIndex < renderedRange.top) { + notifyOutOfViewport(row, rowIndex); + } + } + }); + _lastRange = renderedRange; + } + } + + /** Send a notification, through "onRowOutOfViewportRange", that is out of the viewport range */ + function notifyOutOfViewport(item, rowIndex) { + _self.onRowOutOfViewportRange.notify({ + 'grid': _grid, + 'item': item, + 'rowIndex': rowIndex, + 'expandedRows': _expandedRows, + 'rowsOutOfViewport': syncOutOfViewportArray(rowIndex, true) + }, null, _self); + } + + /** Send a notification, through "onRowBackToViewportRange", that a row came back to the viewport */ + function notifyBackToViewportWhenDomExist(item, rowIndex) { + var rowIndex = item.rowIndex || _dataView.getRowById(item.id); + setTimeout(function() { + // make sure View Row DOM Element really exist before notifying that it's a row that is visible again + if ($('.cellDetailView_' + item.id).length) { + _self.onRowBackToViewportRange.notify({ + 'grid': _grid, + 'item': item, + 'rowIndex': rowIndex, + 'expandedRows': _expandedRows, + 'rowsOutOfViewport': syncOutOfViewportArray(rowIndex, false) + }, null, _self); } + }, 100); + } + + /** + * This function will sync the out of viewport array whenever necessary. + * The sync can add a row (when necessary, no need to add again if it already exist) or delete a row from the array. + * @param rowIndex: number + * @param isAdding: are we adding or removing a row? + */ + function syncOutOfViewportArray(rowIndex, isAdding) { + var arrayRowIndex = arrayFindIndex(_rowsOutOfViewport, rowIndex); + + if (isAdding && arrayRowIndex < 0) { + _rowsOutOfViewport.push(rowIndex); + } else if (!isAdding && arrayRowIndex >= 0) { + _rowsOutOfViewport.splice(arrayRowIndex, 1); + } + return _rowsOutOfViewport; } - + // Toggle between showing and hiding a row function toggleRowSelection(row) { - _grid.getData().beginUpdate(); - HandleAccordionShowHide(row); - _grid.getData().endUpdate(); + _dataView.beginUpdate(); + handleAccordionShowHide(row); + _dataView.endUpdate(); } - // Collapse all of the open items + /** Collapse all of the open items */ function collapseAll() { + _dataView.beginUpdate(); for (var i = _expandedRows.length - 1; i >= 0; i--) { - collapseItem(_expandedRows[i]); + collapseDetailView(_expandedRows[i], true); } + _dataView.endUpdate(); } - - // Saves the current state of the detail view - function saveDetailView(item) - { - var view = $("#innerDetailView_" + item.id); - if (view) - { - var html = $("#innerDetailView_" + item.id).html(); - if(html !== undefined) - { - item._detailContent = html; - } - } - } - - // Colapse an Item so it is notlonger seen - function collapseItem(item) { - + + /** Colapse an Item so it is not longer seen */ + function collapseDetailView(item, isMultipleCollapsing) { + if (!isMultipleCollapsing) { + _dataView.beginUpdate(); + } // Save the details on the collapse assuming onetime loading if (_options.loadOnce) { - saveDetailView(item); + saveDetailView(item); } - - item._collapsed = true; - for (var idx = 1; idx <= item._sizePadding; idx++) { - _dataView.deleteItem(item.id + "." + idx); + + item[_keyPrefix + 'collapsed'] = true; + for (var idx = 1; idx <= item[_keyPrefix + 'sizePadding']; idx++) { + _dataView.deleteItem(item.id + '.' + idx); } - item._sizePadding = 0; + item[_keyPrefix + 'sizePadding'] = 0; _dataView.updateItem(item.id, item); // Remove the item from the expandedRows _expandedRows = _expandedRows.filter(function (r) { return r.id !== item.id; }); + + if (!isMultipleCollapsing) { + _dataView.endUpdate(); + } } - // Expand a row given the dataview item that is to be expanded - function expandItem(item) { - item._collapsed = false; + /** Expand a row given the dataview item that is to be expanded */ + function expandDetailView(item) { + item[_keyPrefix + 'collapsed'] = false; _expandedRows.push(item); - + // In the case something went wrong loading it the first time such a scroll of screen before loaded - if (!item._detailContent) item._detailViewLoaded = false; - + if (!item[_keyPrefix + 'detailContent']) item[_keyPrefix + 'detailViewLoaded'] = false; + // display pre-loading template - if (!item._detailViewLoaded || _options.loadOnce !== true) { - item._detailContent = _options.preTemplate(item); + if (!item[_keyPrefix + 'detailViewLoaded'] || _options.loadOnce !== true) { + item[_keyPrefix + 'detailContent'] = _options.preTemplate(item); } else { _self.onAsyncResponse.notify({ - "itemDetail": item, - "detailView": item._detailContent + 'item': item, + 'itemDetail': item, + 'detailView': item[_keyPrefix + 'detailContent'] }, undefined, this); applyTemplateNewLineHeight(item); _dataView.updateItem(item.id, item); @@ -260,91 +416,106 @@ _options.process(item); } + /** Saves the current state of the detail view */ + function saveDetailView(item) { + var view = $('.' + _gridUid + ' .innerDetailView_' + item.id); + if (view) { + var html = $('.' + _gridUid + ' .innerDetailView_' + item.id).html(); + if (html !== undefined) { + item[_keyPrefix + 'detailContent'] = html; + } + } + } + /** * subscribe to the onAsyncResponse so that the plugin knows when the user server side calls finished - * the response has to be as "args.itemDetail" with it's data back + * the response has to be as "args.item" (or "args.itemDetail") with it's data back */ - function subscribeToOnAsyncResponse() { - _self.onAsyncResponse.subscribe(function (e, args) { - if (!args || !args.itemDetail) { - throw 'Slick.RowDetailView plugin requires the onAsyncResponse() to supply "args.itemDetail" property.' + function subscribeToOnAsyncResponse() { + _self.onAsyncResponse.subscribe(function (e, args) { + if (!args || (!args.item && !args.itemDetail)) { + throw 'Slick.RowDetailView plugin requires the onAsyncResponse() to supply "args.item" property.' } + // we accept item/itemDetail, just get the one which has data + var itemDetail = args.item || args.itemDetail; + // If we just want to load in a view directly we can use detailView property to do so if (args.detailView) { - args.itemDetail._detailContent = args.detailView; + itemDetail[_keyPrefix + 'detailContent'] = args.detailView; } else { - args.itemDetail._detailContent = _options.postTemplate(args.itemDetail); + itemDetail[_keyPrefix + 'detailContent'] = _options.postTemplate(itemDetail); } - args.itemDetail._detailViewLoaded = true; - - var idxParent = _dataView.getIdxById(args.itemDetail.id); - _dataView.updateItem(args.itemDetail.id, args.itemDetail); + itemDetail[_keyPrefix + 'detailViewLoaded'] = true; + _dataView.updateItem(itemDetail.id, itemDetail); // trigger an event once the post template is finished loading _self.onAsyncEndUpdate.notify({ - "grid": _grid, - "itemDetail": args.itemDetail + 'grid': _grid, + 'item': itemDetail, + 'itemDetail': itemDetail }, e, _self); }); } - function HandleAccordionShowHide(item) { + /** When row is getting toggled, we will handle the action of collapsing/expanding */ + function handleAccordionShowHide(item) { if (item) { - if (!item._collapsed) { - collapseItem(item); + if (!item[_keyPrefix + 'collapsed']) { + collapseDetailView(item); } else { - expandItem(item); + expandDetailView(item); } } } ////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////// + + /** Get the Row Detail padding (which are the rows dedicated to the detail panel) */ var getPaddingItem = function (parent, offset) { var item = {}; for (var prop in _grid.getData()) { item[prop] = null; } - item.id = parent.id + "." + offset; + item.id = parent.id + '.' + offset; - //additional hidden padding metadata fields - item._collapsed = true; - item._isPadding = true; - item._parent = parent; - item._offset = offset; + // additional hidden padding metadata fields + item[_keyPrefix + 'collapsed'] = true; + item[_keyPrefix + 'isPadding'] = true; + item[_keyPrefix + 'parent'] = parent; + item[_keyPrefix + 'offset'] = offset; return item; } ////////////////////////////////////////////////////////////// - //create the detail ctr node. this belongs to the dev & can be custom-styled as per + // create the detail ctr node. this belongs to the dev & can be custom-styled as per ////////////////////////////////////////////////////////////// function applyTemplateNewLineHeight(item) { - // the height seems to be calculated by the template row count (how many line of items does the template have) + // the height is calculated by the template row count (how many line of items does the template view have) var rowCount = _options.panelRows; - //calculate padding requirements based on detail-content.. - //ie. worst-case: create an invisible dom node now &find it's height. - var lineHeight = 13; //we know cuz we wrote the custom css innit ;) - item._sizePadding = Math.ceil(((rowCount * 2) * lineHeight) / _grid.getOptions().rowHeight); - item._height = (item._sizePadding * _grid.getOptions().rowHeight); - + // calculate padding requirements based on detail-content.. + // ie. worst-case: create an invisible dom node now & find it's height. + var lineHeight = 13; // we know cuz we wrote the custom css init ;) + item[_keyPrefix + 'sizePadding'] = Math.ceil(((rowCount * 2) * lineHeight) / _gridOptions.rowHeight); + item[_keyPrefix + 'height'] = (item[_keyPrefix + 'sizePadding'] * _gridOptions.rowHeight); var idxParent = _dataView.getIdxById(item.id); - for (var idx = 1; idx <= item._sizePadding; idx++) { + for (var idx = 1; idx <= item[_keyPrefix + 'sizePadding']; idx++) { _dataView.insertItem(idxParent + idx, getPaddingItem(item, idx)); } } - + /** Get the Column Definition of the first column dedicated to toggling the Row Detail View */ function getColumnDefinition() { return { id: _options.columnId, - name: "", + name: '', toolTip: _options.toolTip, - field: "sel", + field: 'sel', width: _options.width, resizable: false, sortable: false, @@ -353,25 +524,41 @@ }; } - function detailSelectionFormatter(row, cell, value, columnDef, dataContext) { + /** return the currently expanded rows */ + function getExpandedRows() { + return _expandedRows; + } - if (dataContext._collapsed == undefined) { - dataContext._collapsed = true, - dataContext._sizePadding = 0, //the required number of pading rows - dataContext._height = 0, //the actual height in pixels of the detail field - dataContext._isPadding = false, - dataContext._parent = undefined, - dataContext._offset = 0 + /** The Formatter of the toggling icon of the Row Detail */ + function detailSelectionFormatter(row, cell, value, columnDef, dataContext) { + if (dataContext[_keyPrefix + 'collapsed'] == undefined) { + dataContext[_keyPrefix + 'collapsed'] = true, + dataContext[_keyPrefix + 'sizePadding'] = 0, //the required number of pading rows + dataContext[_keyPrefix + 'height'] = 0, //the actual height in pixels of the detail field + dataContext[_keyPrefix + 'isPadding'] = false, + dataContext[_keyPrefix + 'parent'] = undefined, + dataContext[_keyPrefix + 'offset'] = 0 } - if (dataContext._isPadding == true) { - //render nothing - } else if (dataContext._collapsed) { - return "
      "; - } else { + if (dataContext[_keyPrefix + 'isPadding'] == true) { + // render nothing + } + else if (dataContext[_keyPrefix + 'collapsed']) { + var collapsedClasses = _options.cssClass + ' expand '; + if (_options.collapsedClass) { + collapsedClasses += _options.collapsedClass; + } + return '
      '; + } + else { var html = []; - var rowHeight = _grid.getOptions().rowHeight; - var bottomMargin = 5; + var rowHeight = _gridOptions.rowHeight; + + var outterHeight = dataContext[_keyPrefix + 'sizePadding'] * _gridOptions.rowHeight; + if (_options.maxRows !== null && dataContext[_keyPrefix + 'sizePadding'] > _options.maxRows) { + outterHeight = _options.maxRows * rowHeight; + dataContext[_keyPrefix + 'sizePadding'] = _options.maxRows; + } //V313HAX: //putting in an extra closing div after the closing toggle div and ommiting a @@ -382,74 +569,109 @@ //slick-cell to escape the cell overflow clipping. //sneaky extra
inserted here-----------------v - html.push("
"); - - html.push("
"); //shift detail below 1st row - html.push("
"); //sub ctr for custom styling - html.push("
" , dataContext._detailContent, "
"); - //&omit a final closing detail container
that would come next - - return html.join(""); + var expandedClasses = _options.cssClass + ' collapse '; + if (_options.expandedClass) expandedClasses += _options.expandedClass; + html.push('
'); + + html.push('
'); //shift detail below 1st row + html.push('
'); //sub ctr for custom styling + html.push('
', dataContext[_keyPrefix + 'detailContent'], '
'); + // &omit a final closing detail container
that would come next + + return html.join(''); } return null; } + /** Resize the Row Detail View */ function resizeDetailView(item) { - if (!item) return; - - // Grad each of the dom items - var mainContainer = document.getElementById('detailViewContainer_' + item.id); - var cellItem = document.getElementById('cellDetailView_' + item.id); - var inner = document.getElementById('innerDetailView_' + item.id); - - if (!mainContainer || !cellItem || !inner) return; - - for (var idx = 1; idx <= item._sizePadding; idx++) { - _dataView.deleteItem(item.id + "." + idx); + if (!item) { + return; + } + + // Grad each of the DOM elements + var mainContainer = document.querySelector('.' + _gridUid + ' .detailViewContainer_' + item.id); + var cellItem = document.querySelector('.' + _gridUid + ' .cellDetailView_' + item.id); + var inner = document.querySelector('.' + _gridUid + ' .innerDetailView_' + item.id); + + if (!mainContainer || !cellItem || !inner) { + return; + } + + for (var idx = 1; idx <= item[_keyPrefix + 'sizePadding']; idx++) { + _dataView.deleteItem(item.id + '.' + idx); } - - var rowHeight = _grid.getOptions().rowHeight; // height of a row - var lineHeight = 13; //we know cuz we wrote the custom css innit ;) - - // Get the inner Item height as this will be the actual size - var itemHeight = inner.clientHeight; - - // Now work out how many rows - var rowCount = Math.ceil(itemHeight / rowHeight) + 1; - - item._sizePadding = Math.ceil(((rowCount * 2) * lineHeight) / rowHeight); - item._height = (item._sizePadding * rowHeight); - - // If the padding is now more than the original minRowBuff we need to increase it - if (_grid.getOptions().minRowBuffer < item._sizePadding) - { - // Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3 - _grid.getOptions().minRowBuffer =item._sizePadding + 3; + + var rowHeight = _gridOptions.rowHeight; // height of a row + var lineHeight = 13; // we know cuz we wrote the custom css innit ;) + + // remove the height so we can calculate the height + mainContainer.style.minHeight = null; + + // Get the scroll height for the main container so we know the actual size of the view + var itemHeight = mainContainer.scrollHeight; + + // Now work out how many rows + var rowCount = Math.ceil(itemHeight / rowHeight); + + item[_keyPrefix + 'sizePadding'] = Math.ceil(((rowCount * 2) * lineHeight) / rowHeight); + item[_keyPrefix + 'height'] = itemHeight; + + var outterHeight = (item[_keyPrefix + 'sizePadding'] * rowHeight); + if (_options.maxRows !== null && item[_keyPrefix + 'sizePadding'] > _options.maxRows) { + outterHeight = _options.maxRows * rowHeight; + item[_keyPrefix + 'sizePadding'] = _options.maxRows; } - - mainContainer.setAttribute("style", "max-height: " + item._height + "px"); - if (cellItem) cellItem.setAttribute("style", "height: " + item._height + "px;top:" + rowHeight + "px"); - + + // If the padding is now more than the original minRowBuff we need to increase it + if (_grid.getOptions().minRowBuffer < item[_keyPrefix + 'sizePadding']) { + // Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3 + _grid.getOptions().minRowBuffer = item[_keyPrefix + 'sizePadding'] + 3; + } + + mainContainer.setAttribute('style', 'min-height: ' + item[_keyPrefix + 'height'] + 'px'); + if (cellItem) cellItem.setAttribute('style', 'height: ' + outterHeight + 'px; top:' + rowHeight + 'px'); + var idxParent = _dataView.getIdxById(item.id); - for (var idx = 1; idx <= item._sizePadding; idx++) { - _dataView.insertItem(idxParent + idx, getPaddingItem(item, idx)); + for (var idx = 1; idx <= item[_keyPrefix + 'sizePadding']; idx++) { + _dataView.insertItem(idxParent + idx, getPaddingItem(item, idx)); + } + + // Lastly save the updated state + saveDetailView(item); + } + + /** Takes in the item we are filtering and if it is an expanded row returns it's parents row to filter on */ + function getFilterItem(item) { + if (item[_keyPrefix + 'isPadding'] && item[_keyPrefix + 'parent']) { + item = item[_keyPrefix + 'parent']; } + return item; } - + $.extend(this, { "init": init, "destroy": destroy, "collapseAll": collapseAll, + "collapseDetailView": collapseDetailView, + "expandDetailView": expandDetailView, "getColumnDefinition": getColumnDefinition, + "getExpandedRows": getExpandedRows, + "getFilterItem": getFilterItem, "getOptions": getOptions, + "resizeDetailView": resizeDetailView, + "saveDetailView": saveDetailView, "setOptions": setOptions, + + // events "onAsyncResponse": new Slick.Event(), "onAsyncEndUpdate": new Slick.Event(), "onAfterRowDetailToggle": new Slick.Event(), "onBeforeRowDetailToggle": new Slick.Event(), - "resizeDetailView": resizeDetailView + "onRowOutOfViewportRange": new Slick.Event(), + "onRowBackToViewportRange": new Slick.Event() }); } })(jQuery); From c5b2bb07b12852ab85e97d107e87d010cb8260ad Mon Sep 17 00:00:00 2001 From: Sehrope Sarkuni Date: Mon, 19 Nov 2018 23:29:22 -0500 Subject: [PATCH 29/55] Clean up .npmignore and exclude test/examples from final npm bundle (#256) * refactor: Sort and add trailing newline to .npmignore - misc: Exclude tests and examples from npm package --- .npmignore | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.npmignore b/.npmignore index f01fc0ed..da76d0e8 100644 --- a/.npmignore +++ b/.npmignore @@ -1,5 +1,7 @@ +.DS_Store +examples .gitk* .idea/* -.DS_Store +nuget* SlickgridRelease* -nuget* \ No newline at end of file +tests From 5a60f948168d92de965751c4ec69c0198d9481b8 Mon Sep 17 00:00:00 2001 From: Lev Kitsis Date: Tue, 27 Nov 2018 11:22:58 -0500 Subject: [PATCH 30/55] Fix hidden sort column when multiColumnSort = true When grid option "multiColumnSort " set to true. If you hide sorted column, you need to remove it from the sort columns array too. grid.getSortColumns => filter hidden column => grid. setSortColumns --- examples/example-plugin-headermenu.html | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/examples/example-plugin-headermenu.html b/examples/example-plugin-headermenu.html index 32eb8222..c37255c5 100644 --- a/examples/example-plugin-headermenu.html +++ b/examples/example-plugin-headermenu.html @@ -190,6 +190,11 @@

View Source:

var columnIndex = grid.getColumnIndex(args.column.id); visibleColumns = removeColumnByIndex(visibleColumns, columnIndex); grid.setColumns(visibleColumns); + // Fix hidden sort column when multiColumnSort = true + grid.setSortColumns(grid.getSortColumns().filter(function (el, i) { + return el.columnId !== args.column.id; + })); + }else if(args.command === "sort-asc" || args.command === "sort-desc") { // get previously sorted columns // getSortColumns() only returns sortAsc & columnId, we want the entire column definition @@ -218,6 +223,12 @@

View Source:

// subscribe to event when column visibility is changed via the menu columnpicker.onColumnsChanged.subscribe(function(e, args) { + // Fix hidden sort column when multiColumnSort = true + grid.setSortColumns(grid.getSortColumns().filter(function (el, i) { + return args.columns.reduce(function(inc, c) { + return inc + (el.columnId === c.id ? 1 : 0); + }, 0) !== 0; + })); console.log('Columns changed via the menu', args.columns); }); From d9ce5d843fd674fa9ebf7d28cafaf0366db5aac9 Mon Sep 17 00:00:00 2001 From: Ben McIntyre Date: Tue, 4 Dec 2018 21:02:30 +0930 Subject: [PATCH 31/55] update keycode constant to remove jQuery dependency --- examples/example-grid-menu.html | 3 +++ slick.groupitemmetadataprovider.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/example-grid-menu.html b/examples/example-grid-menu.html index ae31d22c..f948620f 100644 --- a/examples/example-grid-menu.html +++ b/examples/example-grid-menu.html @@ -244,6 +244,9 @@

View Source:

grid.autosizeColumns(); }); + grid.onAutosizeColumns.subscribe(function(e, args) { + console.log('onAutosize called') + }); }) diff --git a/slick.groupitemmetadataprovider.js b/slick.groupitemmetadataprovider.js index abdbe8a1..d20ca6ec 100644 --- a/slick.groupitemmetadataprovider.js +++ b/slick.groupitemmetadataprovider.js @@ -112,7 +112,7 @@ // TODO: add -/+ handling function handleGridKeyDown(e, args) { - if (options.enableExpandCollapse && (e.which == $.ui.keyCode.SPACE)) { + if (options.enableExpandCollapse && (e.which == Slick.keyCode.SPACE)) { var activeCell = this.getActiveCell(); if (activeCell) { var item = this.getDataItem(activeCell.row); From 0e300b6b94c16f1fe40b866f561b25978ee9780a Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Thu, 20 Dec 2018 15:53:38 -0500 Subject: [PATCH 32/55] add css patch line for firefox (this should have no impact for modern browsers) as per commit a8d4452b625270b7671eb0e2a0d84aa6cc556656 --- slick.grid.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slick.grid.css b/slick.grid.css index d96f2fda..896aa9f1 100644 --- a/slick.grid.css +++ b/slick.grid.css @@ -30,7 +30,7 @@ classes should alter those! .slick-header-column.ui-state-default, .slick-group-header-column.ui-state-default { position: relative; display: inline-block; - /*box-sizing: content-box !important; use this for Firefox! */ + box-sizing: content-box !important; /* this here only for Firefox! */ overflow: hidden; -o-text-overflow: ellipsis; text-overflow: ellipsis; From 2639fdd9e9d73db73c4ea444ae92175476af05b6 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Thu, 20 Dec 2018 15:56:19 -0500 Subject: [PATCH 33/55] fix gotoRowStart and gotoRowEnd bug as per issue #279 as per commit cb6efd57d9ef8a15c48db31bdcaaf2aab93d69ea --- slick.grid.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slick.grid.js b/slick.grid.js index 551dc0de..210adacc 100644 --- a/slick.grid.js +++ b/slick.grid.js @@ -4834,7 +4834,7 @@ if (typeof Slick === "undefined") { return { "row": row, "cell": newCell, - "posX": posX + "posX": newCell }; } @@ -4845,7 +4845,7 @@ if (typeof Slick === "undefined") { return { "row": row, "cell": newCell, - "posX": posX + "posX": newCell }; } From 2f0ae1a7de340dc03c59dfd16c6d835f9043bcd2 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Thu, 20 Dec 2018 16:07:25 -0500 Subject: [PATCH 34/55] Add event to editor initialisation code as per #276. Add column name injector to columnpicker and cellexternalcopymanager. Add ability to export header row to cellexternalcopymanager. as per commit 0abf4326793bb46c0bb949046bf30e9280fdddf1 --- controls/slick.columnpicker.js | 370 ++++++++++++----------- plugins/slick.cellexternalcopymanager.js | 34 ++- slick.grid.js | 17 +- 3 files changed, 225 insertions(+), 196 deletions(-) diff --git a/controls/slick.columnpicker.js b/controls/slick.columnpicker.js index 63bf3319..6c47bc74 100644 --- a/controls/slick.columnpicker.js +++ b/controls/slick.columnpicker.js @@ -16,6 +16,7 @@ * hideForceFitButton: false, // show/hide checkbox near the end "Force Fit Columns" (default:false) * hideSyncResizeButton: false, // show/hide checkbox near the end "Synchronous Resize" (default:false) * forceFitTitle: "Force fit columns", // default to "Force fit columns" + * headerColumnValueExtractor: "Extract the column label" // default to column.name * syncResizeTitle: "Synchronous resize", // default to "Synchronous resize" * } * }; @@ -24,200 +25,211 @@ * @constructor */ -'use strict'; - -(function ($) { - function SlickColumnPicker(columns, grid, options) { - var _grid = grid; - var $list; - var $menu; - var columnCheckboxes; - var onColumnsChanged = new Slick.Event(); - - var defaults = { - fadeSpeed: 250, - - // the last 2 checkboxes titles - hideForceFitButton: false, - hideSyncResizeButton: false, - forceFitTitle: "Force fit columns", - syncResizeTitle: "Synchronous resize" - }; - - function init(grid) { - grid.onHeaderContextMenu.subscribe(handleHeaderContextMenu); - grid.onColumnsReordered.subscribe(updateColumnOrder); - options = $.extend({}, defaults, options); - - $menu = $("