diff --git a/.gitignore b/.gitignore index d2f89b666..14bc275d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ .gitk* .idea/* .DS_Store + +# node stuff +node_modules/ +npm-debug.log + +# sass stuff +.sass-cache/ \ No newline at end of file diff --git a/README.md b/README.md index 46db4d227..36ebe6167 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,65 @@ -# Welcome to SlickGrid +# About the column pinning effort -Find documentation and examples in [the wiki](https://github.com/mleibman/SlickGrid/wiki). +This fork adds pinned columns to slickgrid. There was a fair amount of code rewrite involved. Work in progress. + +## [Pinned Column Examples](http://git.simple.gy/SlickGrid/) + +## Renaming + +To support pinned columns, we slice up the grid regions, and try to be very clear and consistent about the naming. +This is because having a left and right region for every content area makes a flat list of naming conventions multiply quickly. +I also was uncomfortable with the proliferation of names like `header`, `headerScroller`, and `headerRow`. + +Two big changes: +1. UI components are labeled top to bottom by what control they are. +2. `headerRow` renamed to `subHeader`. The old name was confusing. TODO: This means the name of related options has changed, too. + +Canvases always represent content size, viewports always represent scrollable regions. + +Every element has side `[0]` and side `[1]`, for left and right. + +``` +Visual Grid Components + [0] [1] + .................... +topViewport . . . // The scrolling region + topCanvas . . . // The full size of content (both off and on screen) + header . . . // The column headers + subHeader . . . // Optional row of cells below the column headers + .................... +contentViewportWrap . . . +contentViewport . . . // The scrolling region for the grid rows + contentCanvas . . . // Full size of row content, both width and height + . . . + . . . + . . . + . . . + . . . + .................... +``` +## Other Changes: -**UPDATE: March 5th, 2014 - I have too many things going on in my life right now to really give SlickGrid support and development the time and attention it deserves. I am not stopping it, but I will most likely be unresponsive for some time. Sorry.** +**Adds some methods** that make it more performant to do auto column resizing and exposes some methods that make it easier to work with multiple grid instances and pinned columns. + +* `grid.updateColumnWidths(columnDefinitions)` + * Using this method improves the performance of changing the width of one or more grid columns by a lot. The existing API only allows for a whole grid redraw, which can be very slow. Pull request with notes [here](https://github.com/mleibman/SlickGrid/pull/897). Use cases for fast column size adjustment may be: auto-sizing columns to fit content, responsive sizing cells to fill the screen, and similar. +* `grid.getId()` lets you get the uid of the grid instance +* Triggers existing event `onColumnsResized` when you change the column widths +* Triggers a new event `onColumnsChanged` when you set the columns +* Exposes the existing method `grid.setupColumnResize`, which allows you to re-enable column resizing if you're manually screwing around with the headers. +* Some new options on `setColumns` and `resizeCanvas` let you prevent some of the expensive calculations, useful if you're doing them yourself externally. + +**Adds [antiscroll](https://github.com/learnboost/antiscroll) compatability** to enable a uniform, OSX-style scrolling experience across browsers. Enable antiscroll by including the antiscroll library on your page, and passing the `useAntiscroll: true` option to your SlickGrid instance. By default we don't show scrollbars until the user begins scrolling (to mimic the way OSX does it); to change that behavior, you can set the `showScrollbarsOnHover` option. + + +## (Original Documentation) Welcome to SlickGrid + +**UPDATE From Mr. Leibman: March 5th, 2014 - I have too many things going on in my life right now to really give SlickGrid support and development the time and attention it deserves. I am not stopping it, but I will most likely be unresponsive for some time. Sorry.** + +Find documentation and examples in [the wiki](https://github.com/mleibman/SlickGrid/wiki). -## SlickGrid is an advanced JavaScript grid/spreadsheet component +### SlickGrid is an advanced JavaScript grid/spreadsheet component Some highlights: diff --git a/bower.json b/bower.json new file mode 100644 index 000000000..7f8f1e59a --- /dev/null +++ b/bower.json @@ -0,0 +1,25 @@ +{ + "name": "slickgrid", + "version": "2.3.4", + "homepage": "https://github.com/SimpleAsCouldBe/SlickGrid", + "authors": [ + "Michael Leibman", + "Eric Miller" + ], + "description": "Slickgrid with pinning, more customizability, and faster column sizing.", + "moduleType": [ + "globals" + ], + "keywords": [ + "slickgrid", + "pinned", + "performance", + "autosize", + "column", + "size" + ], + "license": "MIT", + "dependencies": { + "antiscroll": "bcherny/antiscroll" + } +} diff --git a/examples/example-header-row.html b/examples/example-header-row.html index 1d0110c98..39ed10d2a 100644 --- a/examples/example-header-row.html +++ b/examples/example-header-row.html @@ -60,12 +60,14 @@

View Source:

enableCellNavigation: true, showHeaderRow: true, headerRowHeight: 30, - explicitInitialization: true + explicitInitialization: true, + showSubHeader: true, +// subHeaderRenderer: function(col){ return $('
') } }; var columns = []; var columnFilters = {}; - for (var i = 0; i < 10; i++) { + for (var i = 0; i < 20; i++) { columns.push({ id: i, name: String.fromCharCode("A".charCodeAt(0) + i), @@ -111,7 +113,7 @@

View Source:

}); - $(grid.getHeaderRow()).delegate(":input", "change keyup", function (e) { + $(grid.getSubHeader()).delegate(":input", "change keyup", function (e) { var columnId = $(this).data("columnId"); if (columnId != null) { columnFilters[columnId] = $.trim($(this).val()); @@ -119,7 +121,7 @@

View Source:

} }); - grid.onHeaderRowCellRendered.subscribe(function(e, args) { + grid.onSubHeaderCellRendered.subscribe(function(e, args) { $(args.node).empty(); $("") .data("columnId", args.column.id) diff --git a/examples/example-spreadsheet.html b/examples/example-spreadsheet.html index 0eadeaa3d..96a86b397 100644 --- a/examples/example-spreadsheet.html +++ b/examples/example-spreadsheet.html @@ -7,7 +7,7 @@ - - -

Examples

-
- -
- - diff --git a/examples/pincushion1-simple.html b/examples/pincushion1-simple.html new file mode 100644 index 000000000..21fa7c17a --- /dev/null +++ b/examples/pincushion1-simple.html @@ -0,0 +1,97 @@ + + + + + Pinned Columns - Pincushion Example 1 + + + + + + + + + +
+
+
+

Demonstrates:

+
    +
  • Simplest case of pinned columns
  • +
+

+
+
+ + + + + + + + + + diff --git a/examples/pincushion2-subHeader.html b/examples/pincushion2-subHeader.html new file mode 100644 index 000000000..336828027 --- /dev/null +++ b/examples/pincushion2-subHeader.html @@ -0,0 +1,137 @@ + + + + + Header Row / SubHeader with Pinned Columns + + + + + + + + + +
+
+
+

Demonstrates:

+
    +
  • Pinned columns.
  • +
  • Sub Headers under the main headers. They are locked to the top of the grid. Renamed from `headerRow` to `subHeader`.
  • +
  • Use of `subHeaderRenderer` to draw subHeader cell contents.
  • +
+ + + +
+ + + + + + + + + + + diff --git a/examples/pincushion3-asyncCols.html b/examples/pincushion3-asyncCols.html new file mode 100644 index 000000000..765aed640 --- /dev/null +++ b/examples/pincushion3-asyncCols.html @@ -0,0 +1,85 @@ + + + + + Fill in Columns Asynchronously + + + + + + + + + +
+
+
+

Demonstrates:

+
    +
  • Pinned columns.
  • +
  • Starting a grid with no columns, then using setColumns after the grid has already been built.
  • +
+
+ + + + + + + + + + + diff --git a/examples/pincushion4-clickHandling.html b/examples/pincushion4-clickHandling.html new file mode 100644 index 000000000..e65f04d25 --- /dev/null +++ b/examples/pincushion4-clickHandling.html @@ -0,0 +1,126 @@ + + + + + Context Menu and Header Click Handling + + + + + + + + + +
+
+
+

Demonstrates:

+ **open the browser console** +
    +
  • Handling and logging to console the names and arguments of grid events as they happen.
  • +
  • Click around in the grid to see what events fire.
  • +
  • onScroll and some other common events were muted so they don't clog the display, but you can easily turn logging for those back on.
  • +
+
+ + + + + + + + + + + diff --git a/examples/pincushion5-manualColSizing.html b/examples/pincushion5-manualColSizing.html new file mode 100644 index 000000000..065386a17 --- /dev/null +++ b/examples/pincushion5-manualColSizing.html @@ -0,0 +1,75 @@ + + + + + Context Menu and Header Click Handling + + + + + + + + + +
+
+
+

Demonstrates:

+
    +
  • Resizing columns with cell size synced as you drag.
  • +
  • Resizing only impacts the column you're dragging, not nearby ones.
  • +
  • Works with or without `maxWidth` set on columns.
  • +
  • Resizes the canvas and viewport of the pinned area as you drag it.
  • +
+
+ + + + + + + + + + + diff --git a/examples/pincushion6-grouping.html b/examples/pincushion6-grouping.html new file mode 100644 index 000000000..2ed331c8d --- /dev/null +++ b/examples/pincushion6-grouping.html @@ -0,0 +1,113 @@ + + + + + Grouping + + + + + + + + + +
+
+
+

Demonstrates:

+
    +
  • Rows grouped by the value of a column.
  • +
+
+
+
+
+ + + + + + + + + + + + diff --git a/examples/pincushion7-selectableHeaders.html b/examples/pincushion7-selectableHeaders.html new file mode 100644 index 000000000..d650db1c8 --- /dev/null +++ b/examples/pincushion7-selectableHeaders.html @@ -0,0 +1,106 @@ + + + + + Selectable Headers + + + + + + + + + +
+
+
+

Demonstrates:

+
    +
  • Clicking a header to select all the cells in the column
  • +
+
+ + + + + + + + + + + + diff --git a/examples/pincushion8-fullWidthRows.html b/examples/pincushion8-fullWidthRows.html new file mode 100644 index 000000000..91201d682 --- /dev/null +++ b/examples/pincushion8-fullWidthRows.html @@ -0,0 +1,82 @@ + + + + + Slickgrid - Pinned Columns with Full Width Rows + + + + + + + + + +
+
+
+

Demonstrates:

+
    +
  • Full width rows with and without pinned columns
  • +
  • test by resizing columns
  • +
+

+
+
+ + + + + + + + + + diff --git a/gulpfile.coffee b/gulpfile.coffee new file mode 100644 index 000000000..49ebf976b --- /dev/null +++ b/gulpfile.coffee @@ -0,0 +1,21 @@ + + +gulp = require 'gulp' +browserSync = require 'browser-sync' + + +# --------------------------------------- Task Flows +gulp.task 'liveServer', -> + browserSync server: baseDir: './' + gulp.watch [ + "index.html" + "slick.*.js" + "slick.*.css" + "examples/**" + ], + cwd: '', + browserSync.reload + + +# --------------------------------------- Task Bundles +gulp.task 'default', [ 'liveServer' ] diff --git a/index.html b/index.html new file mode 100644 index 000000000..6f68d43ce --- /dev/null +++ b/index.html @@ -0,0 +1,102 @@ + + + + SlickGrid Examples + + + +

Pinned Column Examples

+
+
    +
  1. Basic pinned column
  2. +
  3. Pinned columns with subHeader (fka: headerRow)
  4. +
  5. Fill in Columns Asynchronously
  6. +
  7. Event and Click Handling
  8. +
  9. Manual Column Sizing
  10. +
  11. Grouping
  12. +
  13. Selectable Headers
  14. +
  15. Full Width Rows
  16. +
+
+ +

Classic Examples

+
+ +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 000000000..9c7f7c86b --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "name": "SlickGrid-Pincushion", + "version": "0.0.1", + "devDependencies": { + "browser-sync": "^1.6.0", + "coffee-script": "^1.8.0", + "gulp": "^3.8.9" + } +} diff --git a/slick.dataview.js b/slick.dataview.js index f1c1b5e34..d4c2d9153 100644 --- a/slick.dataview.js +++ b/slick.dataview.js @@ -103,7 +103,7 @@ for (var i = startingIndex, l = items.length; i < l; i++) { id = items[i][idProperty]; if (id === undefined) { - throw "Each data element must implement a unique 'id' property"; + throw "Each data element must implement a unique 'id' property, it can't be undefined." ; } idxById[id] = i; } @@ -114,7 +114,7 @@ for (var i = 0, l = items.length; i < l; i++) { id = items[i][idProperty]; if (id === undefined || idxById[id] !== i) { - throw "Each data element must implement a unique 'id' property"; + throw "Each data element must implement a unique 'id' property. `"+ id +"` is not unique." ; } } } diff --git a/slick.grid.css b/slick.grid.css index 6a416db1e..55a0c5127 100644 --- a/slick.grid.css +++ b/slick.grid.css @@ -1,47 +1,133 @@ -/* -IMPORTANT: -In order to preserve the uniform grid appearance, all cell styles need to have padding, margin and border sizes. -No built-in (selected, editable, highlight, flashing, invalid, loading, :focus) or user-specified CSS -classes should alter those! -*/ - -.slick-header.ui-state-default, .slick-headerrow.ui-state-default { - width: 100%; +.slickGrid .header, +.slickGrid .subHeader, +.slickGrid .row, +.slickGrid .cell { + box-sizing: border-box; } + +.slickGrid [hideFocus] { + outline: none !important; } + +.slickGrid .header { + height: 40px; } + .slickGrid .header .cell { + padding: 4px; + line-height: 32px; } + +.slickGrid .subHeader { + height: 21px; } + .slickGrid .subHeader .cell { + padding: 2px; + line-height: 17px; } + +.slickGrid .row { + height: 30px; + line-height: 30px; } + .slickGrid .row .cell { + padding: 0 2px; } + +.slickGrid { overflow: hidden; - border-left: 0px; -} - -.slick-header-columns, .slick-headerrow-columns { + outline: 0; } + .slickGrid .row.even { + background: #f9f9f9; } + .slickGrid .focus-sink { + position: fixed; + width: 0; + height: 0; + top: 0; + left: 0; + outline: 0; } + .slickGrid .viewport-wrap, + .slickGrid .viewport { + position: absolute; + width: 100%; } + .slickGrid .viewport-wrap { + bottom: 0; + overflow: hidden; } + .slickGrid .viewport-wrap .antiscroll-scrollbar { + z-index: 1; } + .slickGrid .viewport { + overflow: auto; + outline: 0; } + .slickGrid .viewport.T.L, .slickGrid .viewport.T.R, .slickGrid .viewport.pinned { + overflow: hidden; } + .slickGrid .viewport.antiscroll-inner { + height: 100%; + left: 0; + top: 0; } + .slickGrid .canvas { + position: relative; } + .slickGrid .row { + position: absolute; + border: 0; + width: 100%; } + .slickGrid .cell { + position: absolute; + height: 100%; + border: 1px solid transparent; + border-right: 1px dotted silver; + border-bottom-color: silver; + overflow: hidden; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + vertical-align: middle; + z-index: 1; + margin: 0; + white-space: nowrap; + cursor: default; } + .slickGrid .cell.highlighted { + background: lightskyblue; + background: rgba(0, 0, 255, 0.2); + -webkit-transition: all 0.5s; + -moz-transition: all 0.5s; + -o-transition: all 0.5s; + transition: all 0.5s; } + .slickGrid .cell.flashing { + border: 1px solid red !important; } + .slickGrid .cell.editable { + z-index: 11; + overflow: visible; + background: white; + border-color: black; + border-style: solid; } + .slickGrid .cell:focus { + outline: none; } + .slickGrid .cell.selected { + background: beige; } + .slickGrid .cell.active { + background: lightskyblue; } + +.slickGrid .header, +.slickGrid .subHeader { position: relative; white-space: nowrap; cursor: default; overflow: hidden; -} + border-bottom: 1px solid silver; + background: #f7f7f7; } -.slick-header-column.ui-state-default { - position: relative; - display: inline-block; - overflow: hidden; - -o-text-overflow: ellipsis; - text-overflow: ellipsis; - height: 16px; - line-height: 16px; - margin: 0; - padding: 4px; +.slickGrid .header .cell, +.slickGrid .subHeader .cell { border-right: 1px solid silver; - border-left: 0px; - border-top: 0px; - border-bottom: 0px; - float: left; -} - -.slick-headerrow-column.ui-state-default { - padding: 4px; -} - -.slick-header-column-sorted { - font-style: italic; -} + border-left: 0; + border-top: 0; + border-bottom: 0; } + .slickGrid .header .cell.active, + .slickGrid .subHeader .cell.active { + background: transparent; } + +.slickGrid .subHeader { + background: #ddd; } + .slickGrid .subHeader .cell input { + box-sizing: border-box; + width: 100%; + height: 100%; } + +.slickGrid .row.slick-group { + background-color: #eef7ff; } + +.slickGrid .row.slick-group-totals { + background-color: #f2fff9; } .slick-sort-indicator { display: inline-block; @@ -49,18 +135,15 @@ classes should alter those! height: 5px; margin-left: 4px; margin-top: 6px; - float: left; -} + float: left; } .slick-sort-indicator-desc { - background: url(images/sort-desc.gif); -} + background: url(images/sort-desc.gif); } .slick-sort-indicator-asc { - background: url(images/sort-asc.gif); -} + background: url(images/sort-asc.gif); } -.slick-resizable-handle { +.slickGrid .resizer { position: absolute; font-size: 0.1px; display: block; @@ -68,90 +151,31 @@ classes should alter those! width: 4px; right: 0px; top: 0; - height: 100%; -} + height: 100%; } .slick-sortable-placeholder { - background: silver; -} - -.grid-canvas { - position: relative; - outline: 0; -} - -.slick-row.ui-widget-content, .slick-row.ui-state-active { - position: absolute; - border: 0px; - width: 100%; -} - -.slick-cell, .slick-headerrow-column { - position: absolute; - border: 1px solid transparent; - border-right: 1px dotted silver; - border-bottom-color: silver; - overflow: hidden; - -o-text-overflow: ellipsis; - text-overflow: ellipsis; - vertical-align: middle; - z-index: 1; - padding: 1px 2px 2px 1px; - margin: 0; - white-space: nowrap; - cursor: default; -} - -.slick-group { -} + background: silver; } .slick-group-toggle { - display: inline-block; -} - -.slick-cell.highlighted { - background: lightskyblue; - background: rgba(0, 0, 255, 0.2); - -webkit-transition: all 0.5s; - -moz-transition: all 0.5s; - -o-transition: all 0.5s; - transition: all 0.5s; -} - -.slick-cell.flashing { - border: 1px solid red !important; -} - -.slick-cell.editable { - z-index: 11; - overflow: visible; - background: white; - border-color: black; - border-style: solid; -} - -.slick-cell:focus { - outline: none; -} + display: inline-block; } .slick-reorder-proxy { display: inline-block; background: blue; opacity: 0.15; - filter: alpha(opacity = 15); - cursor: move; -} + filter: alpha(opacity=15); + cursor: move; } .slick-reorder-guide { display: inline-block; height: 2px; background: blue; opacity: 0.7; - filter: alpha(opacity = 70); -} + filter: alpha(opacity=70); } .slick-selection { z-index: 10; position: absolute; - border: 2px dashed black; -} + border: 2px dashed black; } + +/*# sourceMappingURL=slick.grid.css.map */ diff --git a/slick.grid.css.map b/slick.grid.css.map new file mode 100644 index 000000000..4245d45b8 --- /dev/null +++ b/slick.grid.css.map @@ -0,0 +1,7 @@ +{ +"version": 3, +"mappings": "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", +"sources": ["slick.grid.sass"], +"names": [], +"file": "slick.grid.css" +} diff --git a/slick.grid.js b/slick.grid.js index c12bae9bb..f4b7a665a 100644 --- a/slick.grid.js +++ b/slick.grid.js @@ -12,8 +12,9 @@ * 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() + * 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 @@ -27,7 +28,6 @@ if (typeof Slick === "undefined") { throw "slick.core.js not loaded"; } - (function ($) { // Slick.Grid $.extend(true, window, { @@ -38,9 +38,9 @@ if (typeof Slick === "undefined") { // shared across all grids on the page var scrollbarDimensions; - var maxSupportedCssHeight; // browser's breaking point + var maxSupportedCssHeight; // browser's breaking point - ////////////////////////////////////////////////////////////////////////////////////////////// + // //////////////////////////////////////////////////////////////////////////////////////////// // SlickGrid class implementation (available as Slick.Grid) /** @@ -55,15 +55,17 @@ if (typeof Slick === "undefined") { function SlickGrid(container, data, columns, options) { // settings var defaults = { + debug: false, // bool for debug mode. turns on some css styling and console logging. explicitInitialization: false, - rowHeight: 25, + //rowHeight: 25, defaultColumnWidth: 80, + absoluteColumnMinWidth: 20, // Don't let folks resize smaller than this, Should be the width of ellipsis. May need to take box-sizing into account enableAddRow: false, leaveSpaceForNewRows: false, editable: false, autoEdit: true, enableCellNavigation: true, - enableColumnReorder: true, + enableColumnReorder: false, // Breaking change to default. Don't want to depend on jQuery UI by default asyncEditorLoading: false, asyncEditorLoadDelay: 100, forceFitColumns: false, @@ -71,10 +73,10 @@ if (typeof Slick === "undefined") { asyncPostRenderDelay: 50, autoHeight: false, editorLock: Slick.GlobalEditorLock, - showHeaderRow: false, - headerRowHeight: 25, - showTopPanel: false, - topPanelHeight: 25, + showSubHeader: false, + addRowIndexToClassName: true, +// showTopPanel: false, +// topPanelHeight: 25, formatterFactory: null, editorFactory: null, cellFlashingCssClass: "flashing", @@ -85,15 +87,19 @@ if (typeof Slick === "undefined") { fullWidthRows: false, multiColumnSort: false, defaultFormatter: defaultFormatter, + columnHeaderRenderer: columnHeaderRenderer, + subHeaderRenderer: subHeaderRenderer, forceSyncScrolling: false, - addNewRowCssClass: "new-row" + addNewRowCssClass: "new-row", + useAntiscroll: false, + showScrollbarsOnHover: false }; var columnDefaults = { name: "", resizable: true, sortable: false, - minWidth: 30, + minWidth: defaults.absoluteColumnMinWidth, rerenderOnResize: false, headerCssClass: null, defaultSortAsc: true, @@ -115,25 +121,19 @@ if (typeof Slick === "undefined") { // private var initialized = false; var $container; - var uid = "slickgrid_" + Math.round(1000000 * Math.random()); + var objectName = 'slickGrid'; + var uid = objectName + '_' + Math.round(1000000 * Math.random()); + var isPinned; var self = this; var $focusSink, $focusSink2; - var $headerScroller; - var $headers; - var $headerRow, $headerRowScroller, $headerRowSpacer; - var $topPanelScroller; - var $topPanel; - var $viewport; - var $canvas; + +// var $topPanelScroller, $topPanel; + 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; - var absoluteColumnMinWidth; var tabbingDirection = 1; var activePosX; @@ -177,10 +177,68 @@ if (typeof Slick === "undefined") { 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. + var $activeCanvasNode; + + // This variable works 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 + // The index of the row that started the latest bout of scrolling is temporarily protected from removal. + var protectedRowIdx; + + + + /* + ## Visual Grid Components + + To support pinned columns, we slice up the grid regions, and try to be very clear and consistent about the naming. + All UI region info objects start as an array with a left [0] and right [1] side + Dom elements are stored at the top level together (still in a left/right pair) because jquery deals with multiple elements nicely. (eg: el.empty(), el.children()) + topViewport.width // combined width + topViewport[0].width // left width + topViewport.el // both els + topViewport.el[0] // left el + + + [0] [1] + ..................... + . . . + . TL . TR . + . . . + ..................... + . . . + . . . + . . . + . CL . CR . + . . . + . . . + . . . + ..................... + + */ + + var topViewport = [{},{}], // The scrolling region + topCanvas = [{},{}], // The full size of content (both off and on screen) + header = [{},{}], // The column headers + subHeader = [{},{}], // Optional row of cells below the column headers + contentViewportWrap = {}, // Content viewports are wrapped with elements that have + // the same dimensions as the viewports themselves. + // This is in service of the antiscroll plugin. + contentViewport = [{},{}], // The scrolling region for the grid rows + contentCanvas = [{},{}], // Full size of row content, both width and height + rows = [{},{}]; // Container for information about rows + + // Renaming Objects / Variables + // yep, an array objectk instance with properties. yay @js! + // $viewport > contentViewport.el + // $canvas > contentCanvas.el + // canvasWidth > contentCanvas.width + // canvasWidthL > contentCanvas[0].width + // canvasWidthR > contentCanvas[1].width + // headersWidth > header.width + // headersWidthL > header[0].width + // headersWidthR > header[1].width + // all.viewportWidth > contentViewport.width + // c.viewportHeight > contentViewport.height + // c.paneHeight > DEPRECIATED. difference from contentViewport.height? ////////////////////////////////////////////////////////////////////////////////////////////// @@ -192,25 +250,19 @@ if (typeof Slick === "undefined") { throw new Error("SlickGrid requires a valid container, " + container + " does not exist in the DOM."); } + if (options.useAntiscroll && !$.isFunction($.fn.antiscroll)) { + throw new ReferenceError('The { useAntiscroll: true } option was passed to SlickGrid, but the antiscroll library is not loaded. You can download the library here: https://github.com/bcherny/antiscroll.'); + } + // calculate these only once and share between grid instances maxSupportedCssHeight = maxSupportedCssHeight || getMaxSupportedCssHeight(); - scrollbarDimensions = scrollbarDimensions || measureScrollbar(); + scrollbarDimensions = scrollbarDimensions || measureScrollbar(); 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; - } - } + enforceWidthLimits(columns); // validate loaded JavaScript modules against requested options if (options.enableColumnReorder && !$.fn.sortable) { @@ -222,47 +274,103 @@ if (typeof Slick === "undefined") { "cancelCurrentEdit": cancelCurrentEdit }; - $container - .empty() - .css("overflow", "hidden") - .css("outline", 0) - .addClass(uid) - .addClass("ui-widget"); + $container.empty().addClass(objectName +' '+ uid +' ui-widget'); + if (options.debug) { $container.addClass('debug') } // set up a positioning container if needed if (!/relative|absolute|fixed/.test($container.css("position"))) { $container.css("position", "relative"); } - $focusSink = $("
").appendTo($container); - - $headerScroller = $("
").appendTo($container); - $headers = $("
").appendTo($headerScroller); - $headers.width(getHeadersWidth()); - - $headerRowScroller = $("
").appendTo($container); - $headerRow = $("
").appendTo($headerRowScroller); - $headerRowSpacer = $("
") - .css("width", getCanvasWidth() + scrollbarDimensions.width + "px") - .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.autoHeight ? "hidden" : "auto"); - - $canvas = $("
").appendTo($viewport); - - $focusSink2 = $focusSink.clone().appendTo($container); + $focusSink = $("
").appendTo($container); + + /* SlickGrid Dom structure: + .slickGrid + .viewport.T.L > .canvas.T.L + .header + .subHeader + .viewport.T.R > .canvas.T.R + .header + .subHeader + .viewport.C.L > .canvas.C.L + .row * N + .viewport.C.R > .canvas.C.R + .row * N + */ + + + // ----------------------- Create the elements + topViewport.el = $( + "
" + + "
" + ); + topCanvas.el = $( + "
" + + "
" + ); + header.el = $( +// "
" + + "
" + + "
" + ); + + // TODO: what are these spacers for? +// cl.subHeaderSpacer = $("
") +// .css("width", canvasWidth + scrollbarDimensions.width + "px") +// .appendTo(cl.subHeaderViewport); +// cr.subHeaderSpacer = $("
") +// .css("width", canvasWidth + scrollbarDimensions.width + "px") +// .appendTo(cr.subHeaderViewport); +// subHeaderSpacer = $().add(cl.subHeaderSpacer).add(cr.subHeaderSpacer); + subHeader.el = $( + "
" + + "
" + ); + + if (!options.showSubHeader) { subHeader.el.hide(); } + + // Top Panel +// $topPanelScroller = $("
").appendTo($container); +// $topPanel = $("
").appendTo($topPanelScroller); +// if (!options.showTopPanel) { +// $topPanelScroller.hide(); +// } + + contentViewportWrap.el = $( + "
" + + "
" + ); + + contentViewport.el = $( + "
" + + "
" + ); + contentCanvas.el = $( + "
" + + "
" + ); + + + // ----------------------- Matryoshka the elements together + topCanvas.el[0].appendChild(header.el[0]); + topCanvas.el[1].appendChild(header.el[1]); + topCanvas.el[0].appendChild(subHeader.el[0]); + topCanvas.el[1].appendChild(subHeader.el[1]); + topViewport.el[0].appendChild(topCanvas.el[0]); + topViewport.el[1].appendChild(topCanvas.el[1]); + contentViewport.el[0].appendChild(contentCanvas.el[0]); + contentViewport.el[1].appendChild(contentCanvas.el[1]); + contentViewportWrap.el[0].appendChild(contentViewport.el[0]); + contentViewportWrap.el[1].appendChild(contentViewport.el[1]); + $container.append( topViewport.el, contentViewportWrap.el ); + + measureCssSizes(); // Wins award for most 's'es in a row. + + + // Default the active canvas to the top left + $activeCanvasNode = contentCanvas.el.eq(0); + + $focusSink2 = $focusSink.clone().appendTo($container); // after the grid, in tab index order. if (!options.explicitInitialization) { finishInitialization(); @@ -273,64 +381,75 @@ if (typeof Slick === "undefined") { if (!initialized) { initialized = true; - viewportW = parseFloat($.css($container[0], "width", true)); + calculateViewportWidth(); // 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(); +// 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) + disableSelection(header.el); // disable all text selection in header (including input and textarea) if (!options.enableTextSelectionOnCells) { // disable text selection in grid cells except in input and textarea elements // (this is IE-specific, because selectstart event will only fire in IE) - $viewport.bind("selectstart.ui", function (event) { + contentViewport.el.bind("selectstart.ui", function (event) { return $(event.target).is("input,textarea"); }); } updateColumnCaches(); - createColumnHeaders(); - setupColumnSort(); createCssRules(); + updatePinnedState(); + setupColumnSort(); resizeCanvas(); + updateAntiscroll(); bindAncestorScrollEvents(); $container - .bind("resize.slickgrid", resizeCanvas); - $viewport - //.bind("click", handleClick) - .bind("scroll", handleScroll); - $headerScroller - .bind("contextmenu", handleHeaderContextMenu) - .bind("click", handleHeaderClick) - .delegate(".slick-header-column", "mouseenter", handleHeaderMouseEnter) - .delegate(".slick-header-column", "mouseleave", handleHeaderMouseLeave); - $headerRowScroller - .bind("scroll", handleHeaderRowScroll); + .bind("resize.slickgrid", resizeCanvas); + contentViewport.el + .bind("scroll", onScroll); + topViewport.el + .bind("mousewheel", onHeaderMouseWheel); // modern browsers only, not in gecko + header.el + .bind("contextmenu", handleHeaderContextMenu) + .bind("click", handleHeaderClick) + .delegate(".slick-header-column", "mouseenter", handleHeaderMouseEnter) + .delegate(".slick-header-column", "mouseleave", handleHeaderMouseLeave); + //$subHeaderScroller + // .bind("scroll", handleSubHeaderScroll); + subHeader.el + .bind('contextmenu', handleSubHeaderContextMenu); $focusSink.add($focusSink2) - .bind("keydown", handleKeyDown); - $canvas - .bind("keydown", handleKeyDown) - .bind("click", handleClick) - .bind("dblclick", handleDblClick) - .bind("contextmenu", handleContextMenu) - .bind("draginit", handleDragInit) - .bind("dragstart", {distance: 3}, handleDragStart) - .bind("drag", handleDrag) - .bind("dragend", handleDragEnd) - .delegate(".slick-cell", "mouseenter", handleMouseEnter) - .delegate(".slick-cell", "mouseleave", handleMouseLeave); + .bind("keydown", handleKeyDown); + contentCanvas.el + .bind("keydown", handleKeyDown) + .bind("click", handleClick) + .bind("dblclick", handleDblClick) + .bind("contextmenu", handleContextMenu) + .bind("draginit", handleDragInit) + .bind("dragstart", {distance: 3}, handleDragStart) + .bind("drag", handleDrag) + .bind("dragend", handleDragEnd) + .delegate(".cell", "mouseenter", handleMouseEnter) + .delegate(".cell", "mouseleave", handleMouseLeave); // Work around http://crbug.com/312427. if (navigator.userAgent.toLowerCase().match(/webkit/) && - navigator.userAgent.toLowerCase().match(/macintosh/)) { - $canvas.bind("mousewheel", handleMouseWheel); + navigator.userAgent.toLowerCase().match(/macintosh/)) { + contentCanvas.el.bind("mousewheel", function(evt){ + var scrolledRow = $(evt.target).closest(".row")[0]; + protectedRowIdx = getRowFromNode(scrolledRow); + // console.log('handleOsxMousewheel', { + // rowIdx: getRowFromPosition(scrolledRow.offsetTop + e.originalEvent.offsetY), // the row's offset plus the cursor's offset in the cell + // protectedRowIdx: protectedRowIdx + // }); + }); } } } @@ -371,8 +490,11 @@ if (typeof Slick === "undefined") { return selectionModel; } - function getCanvasNode() { - return $canvas[0]; + function getContentCanvasNode() { + return contentCanvas.el; // could be one or two elements, depending on whether columns are pinned. Always a jquery element. + } + function getTopCanvasNode() { + return topCanvas.el; } function measureScrollbar() { @@ -385,40 +507,96 @@ if (typeof Slick === "undefined") { return dim; } - function getHeadersWidth() { - var headersWidth = 0; - for (var i = 0, ii = columns.length; i < ii; i++) { - var width = columns[i].width; - headersWidth += width; - } - headersWidth += scrollbarDimensions.width; - return Math.max(headersWidth, viewportW) + 1000; - } - - function getCanvasWidth() { - var availableWidth = viewportHasVScroll ? viewportW - scrollbarDimensions.width : viewportW; - var rowWidth = 0; + function calculateCanvasWidth() { + var availableWidth = viewportHasVScroll ? contentViewport.width - scrollbarDimensions.width : contentViewport.width; var i = columns.length; + contentCanvas.width = contentCanvas[0].width = contentCanvas[1].width = 0; + while (i--) { - rowWidth += columns[i].width; + if (columns[i].width == null) { + console.warn('width shouldn\'t be null/undefined', columns[i]); + continue; + } + if (i > options.pinnedColumn) { + contentCanvas[1].width += columns[i].width; + } else { + contentCanvas[0].width += columns[i].width; + } } - return options.fullWidthRows ? Math.max(rowWidth, availableWidth) : rowWidth; + + contentCanvas.width = contentCanvas[0].width + contentCanvas[1].width; + if (options.fullWidthRows) { + var extraRoom = Math.max(0, availableWidth - contentCanvas.width); + contentCanvas.width += extraRoom; + if (options.pinnedColumn != null) { + contentCanvas[1].width += extraRoom + } else { + contentCanvas[0].width += extraRoom + } + } + + //console.log('calculateCanvasWidth', { + // available: availableWidth, + // left: contentCanvas[0].width, + // right: contentCanvas[1].width, + // both: contentCanvas.width, + // extraRoom: extraRoom + // allCols: columns.reduce(function(sum, col){ return sum += col.width }, 0) + //}); } function updateCanvasWidth(forceColumnWidthsUpdate) { - var oldCanvasWidth = canvasWidth; - canvasWidth = getCanvasWidth(); - - if (canvasWidth != oldCanvasWidth) { - $canvas.width(canvasWidth); - $headerRow.width(canvasWidth); - $headers.width(getHeadersWidth()); - viewportHasHScroll = (canvasWidth > viewportW - scrollbarDimensions.width); + var oldCanvasWidth = contentCanvas.width, + oldCanvasWidthL = contentCanvas[0].width, + oldCanvasWidthR = contentCanvas[1].width, + widthChanged; + + calculateCanvasWidth(); + + var canvasWidth = contentCanvas.width, + canvasWidthL = contentCanvas[0].width, + canvasWidthR = contentCanvas[1].width; + + widthChanged = canvasWidth !== oldCanvasWidth || + canvasWidthL !== oldCanvasWidthL || + canvasWidthR !== oldCanvasWidthR; + + if (widthChanged || isPinned) { // TODO: why would it always do this work if there is a pinned column? +// setHeadersWidth(); + topCanvas.el[0].style.width = + contentCanvas.el[0].style.width = + canvasWidthL + 'px'; + + if (isPinned) { + topCanvas.el[1].style.width = + contentCanvas.el[1].style.width = + canvasWidthR + 'px'; + + // Set widths on the left side, and width+left offset on the right side + topViewport.el[0].style.width = + topViewport.el[1].style.left = + contentViewportWrap.el[0].style.width = + contentViewportWrap.el[1].style.left = + canvasWidthL + 'px'; + topViewport.el[1].style.width = + contentViewportWrap.el[1].style.width = + (contentViewport.width - canvasWidthL) + 'px'; + + // Viewport + //cl.viewport.width(canvasWidthL); + //cr.viewport.width(contentViewport.width - canvasWidthL); + } else { + topViewport.el[0].style.width = + contentViewportWrap.el[0].style.width = + null; + } + viewportHasHScroll = (canvasWidth > contentViewport.width - scrollbarDimensions.width); } - $headerRowSpacer.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0)); +// cl.subHeaderSpacer.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0)); +// cr.subHeaderSpacer.width(canvasWidth + (viewportHasVScroll ? scrollbarDimensions.width : 0)); - if (canvasWidth != oldCanvasWidth || forceColumnWidthsUpdate) { + if (true || widthChanged || forceColumnWidthsUpdate) { applyColumnWidths(); } } @@ -426,11 +604,11 @@ if (typeof Slick === "undefined") { function disableSelection($target) { if ($target && $target.jquery) { $target - .attr("unselectable", "on") - .css("MozUserSelect", "none") - .bind("selectstart.ui", function () { - return false; - }); // from jquery:ui.core.js 1.7.2 + .attr("unselectable", "on") + .css("MozUserSelect", "none") + .bind("selectstart.ui", function () { + return false; + }); // from jquery:ui.core.js 1.7.2 } } @@ -456,10 +634,10 @@ if (typeof Slick === "undefined") { // TODO: this is static. need to handle page mutation. function bindAncestorScrollEvents() { - var elem = $canvas[0]; + var elem = contentCanvas.el[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) { + if (elem == contentViewport.el[0] || elem.scrollWidth != elem.clientWidth || elem.scrollHeight != elem.clientHeight) { var $elem = $(elem); if (!$boundAncestors) { $boundAncestors = $elem; @@ -482,12 +660,10 @@ if (typeof Slick === "undefined") { function updateColumnHeader(columnId, title, toolTip) { if (!initialized) { return; } var idx = getColumnIndex(columnId); - if (idx == null) { - return; - } + if (idx == null) { return; } var columnDef = columns[idx]; - var $header = $headers.children().eq(idx); + var $header = header.el.children().eq(idx); //var $header = topCanvas.el.children().eq(idx); if ($header) { if (title !== undefined) { columns[idx].name = title; @@ -502,8 +678,8 @@ if (typeof Slick === "undefined") { }); $header - .attr("title", toolTip || "") - .children().eq(0).html(title); + .attr("title", toolTip || "") + .children().eq(0).html(title); trigger(self.onHeaderCellRendered, { "node": $header[0], @@ -512,107 +688,156 @@ if (typeof Slick === "undefined") { } } - function getHeaderRow() { - return $headerRow[0]; + // Updates the contents of a single subHeader cell + // Does not destroy, remove event listeners, update any attached .data(), etc. + function updateSubHeader(columnId){ + if (!initialized) { return; } + var idx = getColumnIndex(columnId); + if (idx == null) { return; } + // Get needed data for this column + var columnDef = columns[idx]; + var $subHeader = subHeader.el.children().eq(idx); + // Replace only the contents, but copy over any className that the subHeaderRenderer might have added + newEl = options.subHeaderRenderer(columnDef); + $subHeader + .html(newEl.html()) + .addClass(newEl[0].className); } - function getHeaderRowColumn(columnId) { + function getSubHeader() { return subHeader.el; } + + // Use a columnId to return the related header dom element + function getSubHeaderColumn(columnId) { var idx = getColumnIndex(columnId); - var $header = $headerRow.children().eq(idx); - return $header && $header[0]; + return subHeader.el.children().eq(idx); + //var $target; + //if (isPinned) { + // if (idx <= options.pinnedColumn) { + // $target = cl.subHeaderCanvas; + // } else { + // $target = cr.subHeaderCanvas; + // idx -= options.pinnedColumn + 1; + // } + //} else { + // $target = cl.subHeaderCanvas; + //} + //var $header = $target.children().eq(idx); + //return $header && $header[0]; + + //var idx = getColumnIndex(columnId); + //var $header = subHeader.el.children().eq(idx); + //return $header && $header[0]; } function createColumnHeaders() { - function onMouseEnter() { - $(this).addClass("ui-state-hover"); - } + function onMouseEnter() { $(this).addClass("ui-state-hover"); } + function onMouseLeave() { $(this).removeClass("ui-state-hover"); } - function onMouseLeave() { - $(this).removeClass("ui-state-hover"); - } - - $headers.find(".slick-header-column") - .each(function() { + // Broadcast destroy events and empty out any current headers + header.el.children() + .each(function () { var columnDef = $(this).data("column"); if (columnDef) { - trigger(self.onBeforeHeaderCellDestroy, { - "node": this, - "column": columnDef - }); + trigger(self.onBeforeHeaderCellDestroy, { "node": this, "column": columnDef }); } }); - $headers.empty(); - $headers.width(getHeadersWidth()); - $headerRow.find(".slick-headerrow-column") - .each(function() { + // Broadcast destroy events and empty out any current subHeaders + subHeader.el.children() + .each(function () { var columnDef = $(this).data("column"); if (columnDef) { - trigger(self.onBeforeHeaderRowCellDestroy, { - "node": this, - "column": columnDef - }); + trigger(self.onBeforeSubHeaderCellDestroy, { "node": this, "column": columnDef }); } }); - $headerRow.empty(); + header.el.empty(); + subHeader.el.empty(); + + // Build new headers based on column data. + var $headerHolder, $subHeaderHolder, m, oneHeader, oneSubHeader; for (var i = 0; i < columns.length; i++) { - var m = columns[i]; + // Select the correct region to draw into based on the column index. + $headerHolder = i > options.pinnedColumn ? header.el.eq(1) : header.el.eq(0); + $subHeaderHolder = i > options.pinnedColumn ? subHeader.el.eq(1) : subHeader.el.eq(0); - 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); + m = columns[i]; + oneHeader = options.columnHeaderRenderer(m); + oneHeader +// .width(m.width - headerColumnWidthDiff) + .addClass("cell l" + i + " r" + i) + .attr("id", "" + uid +'_'+ m.id) + .attr("title", m.toolTip || "") + .data("column", m) + .addClass(m.headerCssClass || "") + .bind("dragstart", { distance: 3 }, function(e, dd) { + trigger(self.onHeaderColumnDragStart, { "origEvent": e, "dragData": dd, "node": this, "columnIndex": getColumnIndexFromEvent(e) }) + }) + .bind("drag", function(e, dd) { + trigger(self.onHeaderColumnDrag, { "origEvent": e, "dragData": dd, "node": this, "columnIndex": getColumnIndexFromEvent(e) }) + }) + .bind("dragend", function(e, dd) { + trigger(self.onHeaderColumnDragEnd, { "origEvent": e, "dragData": dd, "node": this, "columnIndex": getColumnIndexFromEvent(e) }) + }) + .appendTo($headerHolder); if (options.enableColumnReorder || m.sortable) { - header + oneHeader .on('mouseenter', onMouseEnter) .on('mouseleave', onMouseLeave); } if (m.sortable) { - header.addClass("slick-header-sortable"); - header.append(""); + oneHeader.addClass("slick-header-sortable"); + oneHeader.append(""); } - trigger(self.onHeaderCellRendered, { - "node": header[0], - "column": m - }); - - if (options.showHeaderRow) { - var headerRowCell = $("
") - .data("column", m) - .appendTo($headerRow); - - trigger(self.onHeaderRowCellRendered, { - "node": headerRowCell[0], + trigger(self.onHeaderCellRendered, { "node": oneHeader[0], "column": m }); + oneSubHeader = options.subHeaderRenderer(m); + if(oneSubHeader) { + oneSubHeader + .data("column", m) + .addClass("cell l" + i + " r" + i) + .appendTo($subHeaderHolder); + trigger(self.onSubHeaderCellRendered, { + "node": oneSubHeader[0], "column": m }); } } - setSortColumns(sortColumns); setupColumnResize(); if (options.enableColumnReorder) { setupColumnReorder(); } + trigger(self.onHeadersCreated); + } + + // Given a column object, return a jquery element with HTML for the column + // Can be overridden by providing a function to options.columnHeaderRenderer + function columnHeaderRenderer(column) { + var $el = $("
") + .html("" + column.name + "") + .attr("title", column.toolTip || ""); + return $el; + } + + // Given a column object, return a jquery element with HTML for a single subHeader column cell + // If you're using subHeaders, you should override this function + function subHeaderRenderer (col) { + return undefined; //$("
"); } function setupColumnSort() { - $headers.click(function (e) { + topCanvas.el.click(function (e) { // 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")) { + if ($(e.target).hasClass("resizer")) { return; } - var $col = $(e.target).closest(".slick-header-column"); + var $col = $(e.target).closest(".cell"); if (!$col.length) { return; } @@ -670,8 +895,8 @@ if (typeof Slick === "undefined") { } function setupColumnReorder() { - $headers.filter(":ui-sortable").sortable("destroy"); - $headers.sortable({ + topCanvas.el.filter(":ui-sortable").sortable("destroy"); + topCanvas.el.sortable({ containment: "parent", distance: 3, axis: "x", @@ -680,7 +905,7 @@ if (typeof Slick === "undefined") { helper: "clone", placeholder: "slick-sortable-placeholder ui-state-default slick-header-column", start: function (e, ui) { - ui.placeholder.width(ui.helper.outerWidth() - headerColumnWidthDiff); + ui.placeholder.width(ui.helper.outerWidth()); // - headerColumnWidthDiff); $(ui.helper).addClass("slick-header-column-active"); }, beforeStop: function (e, ui) { @@ -692,7 +917,7 @@ if (typeof Slick === "undefined") { return; } - var reorderedIds = $headers.sortable("toArray"); + var reorderedIds = topCanvas.el.sortable("toArray"); var reorderedColumns = []; for (var i = 0; i < reorderedIds.length; i++) { reorderedColumns.push(columns[getColumnIndex(reorderedIds[i].replace(uid, ""))]); @@ -707,9 +932,11 @@ if (typeof Slick === "undefined") { } function setupColumnResize() { - var $col, j, c, pageX, columnElements, minPageX, maxPageX, firstResizable, lastResizable; - columnElements = $headers.children(); - columnElements.find(".slick-resizable-handle").remove(); + var j, c, pageX, columnElements, minPageX, maxPageX, firstResizable, lastResizable; + if(!columns.length){ return; } + columnElements = getHeaderEls(); + columnElements.find(".resizer").remove(); + // Get the first and last resizable column columnElements.each(function (i, e) { if (columns[i].resizable) { if (firstResizable === undefined) { @@ -718,83 +945,89 @@ if (typeof Slick === "undefined") { lastResizable = i; } }); - if (firstResizable === undefined) { - return; - } + if (firstResizable === undefined) { return; } + // Configure resizing on each column columnElements.each(function (i, e) { if (i < firstResizable || (options.forceFitColumns && i >= lastResizable)) { return; } - $col = $(e); - $("
") - .appendTo(e) - .bind("dragstart", function (e, dd) { - if (!getEditorLock().commitCurrentEdit()) { - return false; - } - pageX = e.pageX; - $(this).parent().addClass("slick-header-column-active"); - var shrinkLeewayOnRight = null, stretchLeewayOnRight = null; - // lock each column's width option to current width - columnElements.each(function (i, e) { - columns[i].previousWidth = $(e).outerWidth(); - }); - if (options.forceFitColumns) { - shrinkLeewayOnRight = 0; - stretchLeewayOnRight = 0; - // colums on right affect maxPageX/minPageX - for (j = i + 1; j < columnElements.length; j++) { - c = columns[j]; - if (c.resizable) { - if (stretchLeewayOnRight !== null) { - if (c.maxWidth) { - stretchLeewayOnRight += c.maxWidth - c.previousWidth; - } else { - stretchLeewayOnRight = null; - } - } - shrinkLeewayOnRight += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth); - } - } - } - var shrinkLeewayOnLeft = 0, stretchLeewayOnLeft = 0; - for (j = 0; j <= i; j++) { - // columns on left only affect minPageX + $("
") + .appendTo(e) + .bind("dragstart", function (e, dd) { + if (!getEditorLock().commitCurrentEdit()) { + return false; + } + pageX = e.pageX; + $(this).parent().addClass("active"); + + // Get the dragged column object and set a flag on it + var idx = getCellFromNode($(this).parent()); + if (idx > -1) { columns[idx].manuallySized = true; } + + var shrinkLeewayOnRight = null, stretchLeewayOnRight = null; + // lock each column's width option to current width + columnElements.each(function (i, e) { + columns[i].previousWidth = $(e).outerWidth(); + }); + if (options.forceFitColumns) { + shrinkLeewayOnRight = 0; + stretchLeewayOnRight = 0; + // colums on right affect maxPageX/minPageX + for (j = i + 1; j < columnElements.length; j++) { c = columns[j]; if (c.resizable) { - if (stretchLeewayOnLeft !== null) { + if (stretchLeewayOnRight !== null) { if (c.maxWidth) { - stretchLeewayOnLeft += c.maxWidth - c.previousWidth; + stretchLeewayOnRight += c.maxWidth - c.previousWidth; } else { - stretchLeewayOnLeft = null; + stretchLeewayOnRight = null; } } - shrinkLeewayOnLeft += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth); + shrinkLeewayOnRight += c.previousWidth - Math.max(c.minWidth || 0, options.absoluteColumnMinWidth); } } - if (shrinkLeewayOnRight === null) { - shrinkLeewayOnRight = 100000; - } - if (shrinkLeewayOnLeft === null) { - shrinkLeewayOnLeft = 100000; - } - if (stretchLeewayOnRight === null) { - stretchLeewayOnRight = 100000; - } - if (stretchLeewayOnLeft === null) { - stretchLeewayOnLeft = 100000; + } + 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, options.absoluteColumnMinWidth); } - maxPageX = pageX + Math.min(shrinkLeewayOnRight, stretchLeewayOnLeft); - minPageX = pageX - Math.min(shrinkLeewayOnLeft, stretchLeewayOnRight); - }) - .bind("drag", function (e, dd) { - var actualMinWidth, d = Math.min(maxPageX, Math.max(minPageX, e.pageX)) - pageX, x; - if (d < 0) { // shrink column - x = d; + } + 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); + }) + .bind("drag", function (e, dd) { + var actualMinWidth, d = Math.min(maxPageX, Math.max(minPageX, e.pageX)) - pageX, x; + if (d < 0) { // shrink column + x = d; + if (options.resizeOnlyDraggedColumn) { + columns[i].width = Math.max(columns[i].previousWidth + x, (columns[i].minWidth || 0)); // apply shrinkage to this column only. + } else { for (j = i; j >= 0; j--) { c = columns[j]; if (c.resizable) { - actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth); + actualMinWidth = Math.max(c.minWidth || 0, options.absoluteColumnMinWidth); if (x && c.previousWidth + x < actualMinWidth) { x += c.previousWidth - actualMinWidth; c.width = actualMinWidth; @@ -804,24 +1037,28 @@ if (typeof Slick === "undefined") { } } } + } - if (options.forceFitColumns) { - x = -d; - for (j = i + 1; j < columnElements.length; j++) { - c = columns[j]; - if (c.resizable) { - if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) { - x -= c.maxWidth - c.previousWidth; - c.width = c.maxWidth; - } else { - c.width = c.previousWidth + x; - x = 0; - } + if (options.forceFitColumns) { + x = -d; + for (j = i + 1; j < columnElements.length; j++) { + c = columns[j]; + if (c.resizable) { + if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) { + x -= c.maxWidth - c.previousWidth; + c.width = c.maxWidth; + } else { + c.width = c.previousWidth + x; + x = 0; } } } - } else { // stretch column - x = d; + } + } else { // stretch column + x = d; + if (options.resizeOnlyDraggedColumn) { + columns[i].width = Math.min(columns[i].previousWidth + x, columns[i].maxWidth || maxPageX); + } else { for (j = i; j >= 0; j--) { c = columns[j]; if (c.resizable) { @@ -834,47 +1071,50 @@ if (typeof Slick === "undefined") { } } } + } - if (options.forceFitColumns) { - x = -d; - for (j = i + 1; j < columnElements.length; j++) { - c = columns[j]; - if (c.resizable) { - actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth); - if (x && c.previousWidth + x < actualMinWidth) { - x += c.previousWidth - actualMinWidth; - c.width = actualMinWidth; - } else { - c.width = c.previousWidth + x; - x = 0; - } + if (options.forceFitColumns) { + x = -d; + for (j = i + 1; j < columnElements.length; j++) { + c = columns[j]; + if (c.resizable) { + actualMinWidth = Math.max(c.minWidth || 0, options.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(); - } - }) - .bind("dragend", function (e, dd) { - var newWidth; - $(this).parent().removeClass("slick-header-column-active"); - for (j = 0; j < columnElements.length; j++) { - c = columns[j]; - newWidth = $(columnElements[j]).outerWidth(); - - if (c.previousWidth !== newWidth && c.rerenderOnResize) { - invalidateAllRows(); - } + } + applyColumnHeaderWidths(); + if (options.syncColumnCellResize) { + updateCanvasWidth(true); // If you're resizing one of the columns in the pinned section, we should update the size of that area as you drag + applyColumnWidths(); + } + }) + .bind("dragend", function (e, dd) { + var newWidth; + $(this).parent().removeClass("active"); + for (j = 0; j < columnElements.length; j++) { + c = columns[j]; + newWidth = $(columnElements[j]).outerWidth(); + + if (c.previousWidth !== newWidth && c.rerenderOnResize) { + invalidateAllRows(); } - updateCanvasWidth(true); - render(); - trigger(self.onColumnsResized, {}); - }); + } + updateCanvasWidth(true); + render(); + trigger(self.onColumnsResized, {}); + }); }); } + // Given an element, return the sum of vertical paddings and borders on that element. function getVBoxDelta($el) { var p = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"]; var delta = 0; @@ -884,48 +1124,153 @@ if (typeof Slick === "undefined") { return delta; } - function measureCellPaddingAndBorder() { - var el; - var h = ["borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight"]; - var v = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"]; + // Hide extra panes if they're not needed (eg: the grid is not using pinned columns) + function updatePinnedState() { + if (!isPinned) { + topViewport.el.eq(1).hide(); + contentViewportWrap.el.eq(1).hide(); + } else { + topViewport.el.eq(1).show(); + contentViewportWrap.el.eq(1).show(); + } + setScroller(); + setOverflow(); + createColumnHeaders(); + updateCanvasWidth(); + invalidateAllRows(); + } - 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; - }); + // enable antiscroll for an element + function disableAntiscroll ($element) { + + $element.removeClass('antiscroll-wrap'); + + if ($element.data('antiscroll')) { + $element.data('antiscroll').destroy(); } - 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; + } + + function enableAntiscroll ($element) { + + $element + .addClass('antiscroll-wrap') + .antiscroll({ + autoShow: options.showScrollbarsOnHover }); + + } + + function updateAntiscroll () { + + if (!options.useAntiscroll) { + return; } - r.remove(); - absoluteColumnMinWidth = Math.max(headerColumnWidthDiff, cellWidthDiff); + var cl = contentViewportWrap.el.filter('.C.L'), + cr = contentViewportWrap.el.filter('.C.R'); + + if (isPinned) { + enableAntiscroll(cr); + disableAntiscroll(cl); + } else { + enableAntiscroll(cl); + disableAntiscroll(cr); + } + + } + + // If columns are pinned, scrollers are in the right-side panes, otherwise they're in the left ones + function setScroller() { + if (options.pinnedColumn == undefined) { + //$headerScrollContainer = topViewport.el[0]; + topViewport.scroller = topViewport.el[0]; + //$vpScrollContainerX = $vpScrollContainerY = contentViewport.el[0]; + contentViewport.scroller = contentViewport.el[0]; + } else { + //$headerScrollContainer = topViewport.el[1]; + topViewport.scroller = topViewport.el[1]; + //$vpScrollContainerX = $vpScrollContainerY = contentViewport.el[1]; + contentViewport.scroller = contentViewport.el[1]; + } } + function setOverflow() { + if (isPinned) { + contentViewport.el.eq(0).addClass('pinned'); + } else { + contentViewport.el.eq(0).removeClass('pinned'); + } + } + + // Measures the computed sizes of important elements + // With this method, folks can set whatever CSS size they'd like, and the grid's js can figure it out from there + function measureCssSizes() { + if (!options.rowHeight) { + var el, + markup = ""; + el = $('
'+ markup +'
').appendTo(contentCanvas.el[0]); + options.rowHeight = el.outerHeight(); + el.remove(); + } + //console.log('measureCssSizes', { + // rowHeight: options.rowHeight + //}); + } + + // For every type of cell we're interested in measuring, record the amount of border and paddings each has, in both vertical and horizontal directions. + // Applies to header cells, subHeader cells, and row cells. +// function measureCellPaddingAndBorder() { +// var el; +// var h = ["borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight"]; +// var v = ["borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom"]; +// var cellMarkup = ""; +// +// el = $(cellMarkup).appendTo(header.el[0]); +// header.cellDiffW = header.cellDiffH = 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) { header.cellDiffW += parseFloat(el.css(val)) || 0; }); +// $.each(v, function (n, val) { header.cellDiffH += parseFloat(el.css(val)) || 0; }); +// } +// el.remove(); +// +// el = $(cellMarkup).appendTo(subHeader.el[0]); +// subHeader.cellDiffW = subHeader.cellDiffH = 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) { subHeader.cellDiffW += parseFloat(el.css(val)) || 0; }); +// $.each(v, function (n, val) { subHeader.cellDiffH += parseFloat(el.css(val)) || 0; }); +// } +// el.remove(); +// +// var r = $("
").appendTo(contentCanvas.el[0]); +// el = $(cellMarkup).appendTo(r); +// rows.cellDiffW = rows.cellDiffH = 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) { rows.cellDiffW += parseFloat(el.css(val)) || 0; }); +// $.each(v, function (n, val) { rows.cellDiffH += parseFloat(el.css(val)) || 0; }); +// } +// r.remove(); +// +// options.absoluteColumnMinWidth = Math.max(header.cellDiffW, subHeader.cellDiffW, rows.cellDiffW); +// +// console.log('measureCellPaddingAndBorder',{ +// headerWH: header.cellDiffW +','+ header.cellDiffH, +// subHeaderWH: subHeader.cellDiffW +','+ subHeader.cellDiffH, +// cellWH: rows.cellDiffW +','+ rows.cellDiffH, +// absoluteColumnMinWidth: options.absoluteColumnMinWidth +// }); +// } + function createCssRules() { $style = $("