diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d0b874c198..7eb9031a632 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## HEAD +## [0.8.18](https://github.com/warpech/jquery-handsontable/tree/v0.8.18) (Apr 12, 2013) Features: - added "Maximize HOT table" button in first example on [Scroll demo](handsontable.com/demo/scroll.html) page ((#495)[https://github.com/warpech/jquery-handsontable/issues/495]) diff --git a/dist/jquery.handsontable.full.css b/dist/jquery.handsontable.full.css index 9cb7f6b9bf2..27484c595ae 100644 --- a/dist/jquery.handsontable.full.css +++ b/dist/jquery.handsontable.full.css @@ -1,12 +1,12 @@ /** - * Handsontable 0.8.17 + * Handsontable 0.8.18 * Handsontable is a simple jQuery plugin for editable tables with basic copy-paste compatibility with Excel and Google Docs * * Copyright 2012, Marcin Warpechowski * Licensed under the MIT license. * http://handsontable.com/ * - * Date: Sun Mar 31 2013 19:46:52 GMT+0200 (Central European Daylight Time) + * Date: Fri Apr 12 2013 11:44:21 GMT+0200 (Central European Daylight Time) */ .handsontable { diff --git a/dist/jquery.handsontable.full.js b/dist/jquery.handsontable.full.js index b0ad0b55905..909af93ec9d 100644 --- a/dist/jquery.handsontable.full.js +++ b/dist/jquery.handsontable.full.js @@ -1,12 +1,12 @@ /** - * Handsontable 0.8.17 + * Handsontable 0.8.18 * Handsontable is a simple jQuery plugin for editable tables with basic copy-paste compatibility with Excel and Google Docs * * Copyright 2012, Marcin Warpechowski * Licensed under the MIT license. * http://handsontable.com/ * - * Date: Sun Mar 31 2013 19:46:52 GMT+0200 (Central European Daylight Time) + * Date: Fri Apr 12 2013 11:44:21 GMT+0200 (Central European Daylight Time) */ /*jslint white: true, browser: true, plusplus: true, indent: 4, maxerr: 50 */ @@ -35,6 +35,7 @@ Handsontable.Core = function (rootElement, settings) { priv = { settings: {}, + settingsFromDOM: {}, selStart: (new Handsontable.SelectionPoint()), selEnd: (new Handsontable.SelectionPoint()), editProxy: false, @@ -1197,6 +1198,7 @@ Handsontable.Core = function (rootElement, settings) { else { rangeModifier({row: priv.selStart.row(), col: 0}); } + event.preventDefault(); //don't scroll the window event.stopPropagation(); //required by HandsontableEditor break; @@ -1207,6 +1209,7 @@ Handsontable.Core = function (rootElement, settings) { else { rangeModifier({row: priv.selStart.row(), col: self.countCols() - 1}); } + event.preventDefault(); //don't scroll the window event.stopPropagation(); //required by HandsontableEditor break; @@ -1303,6 +1306,7 @@ Handsontable.Core = function (rootElement, settings) { bindEvents(); this.updateSettings(settings); + this.parseSettingsFromDOM(); this.view = new Handsontable.TableView(this); this.forceFullRender = true; //used when data was changed @@ -1580,6 +1584,38 @@ Handsontable.Core = function (rootElement, settings) { } }; + /** + * Parse settings from DOM and CSS + * @public + */ + this.parseSettingsFromDOM = function () { + var overflow = this.rootElement.css('overflow'); + if (overflow === 'scroll' || overflow === 'auto') { + this.rootElement[0].style.overflow = 'visible'; + priv.settingsFromDOM.overflow = overflow; + } + else if (priv.settings.width === void 0 || priv.settings.height === void 0) { + priv.settingsFromDOM.overflow = 'auto'; + } + + if (priv.settings.width === void 0) { + priv.settingsFromDOM.width = this.rootElement.width(); + } + else { + priv.settingsFromDOM.width = void 0; + } + + priv.settingsFromDOM.height = void 0; + if (priv.settings.height === void 0) { + if (priv.settingsFromDOM.overflow === 'scroll' || priv.settingsFromDOM.overflow === 'auto') { + var computedHeight = this.rootElement.height(); + if (computedHeight > 0) { + priv.settingsFromDOM.height = computedHeight; + } + } + } + }; + /** * Render visible data * @public @@ -1732,6 +1768,14 @@ Handsontable.Core = function (rootElement, settings) { return priv.settings; }; + /** + * Returns current settingsFromDOM object + * @return {Object} + */ + this.getSettingsFromDOM = function () { + return priv.settingsFromDOM; + }; + /** * Clears grid * @public @@ -1913,9 +1957,9 @@ Handsontable.Core = function (rootElement, settings) { */ this.getColHeader = function (col, TH) { col = Handsontable.PluginModifiers.run(self, 'col', col); - var DIV = document.createElement('DIV'), - SPAN = document.createElement('SPAN'), - avoidInnerHTML = self.view.wt.wtDom.avoidInnerHTML; + var DIV = document.createElement('DIV'), + SPAN = document.createElement('SPAN'), + avoidInnerHTML = self.view.wt.wtDom.avoidInnerHTML; DIV.className = 'relative'; SPAN.className = 'colHeader'; @@ -1930,15 +1974,7 @@ Handsontable.Core = function (rootElement, settings) { avoidInnerHTML(SPAN, priv.settings.colHeaders(col)); } else if (priv.settings.colHeaders && typeof priv.settings.colHeaders !== 'string' && typeof priv.settings.colHeaders !== 'number') { - var dividend = col + 1; - var columnLabel = ''; - var modulo; - while (dividend > 0) { - modulo = (dividend - 1) % 26; - columnLabel = String.fromCharCode(65 + modulo) + columnLabel; - dividend = parseInt((dividend - modulo) / 26, 10); - } - SPAN.appendChild(document.createTextNode(columnLabel)); + SPAN.appendChild(document.createTextNode(Handsontable.helper.spreadsheetColumnLabel(col))); } else { avoidInnerHTML(SPAN, priv.settings.colHeaders); @@ -2219,7 +2255,7 @@ Handsontable.Core = function (rootElement, settings) { /** * Handsontable version */ - this.version = '0.8.17'; //inserted by grunt from package.json + this.version = '0.8.18'; //inserted by grunt from package.json }; var settings = { @@ -2297,39 +2333,26 @@ $.fn.handsontable = function (action) { * @param {Object} instance */ Handsontable.TableView = function (instance) { - var that = this; - var $window = $(window); + var that = this + , $window = $(window) + , $documentElement = $(document.documentElement); this.instance = instance; - var settings = this.instance.getSettings(); + this.settings = instance.getSettings(); + this.settingsFromDOM = instance.getSettingsFromDOM(); instance.rootElement.data('originalStyle', instance.rootElement.attr('style')); //needed to retrieve original style in jsFiddle link generator in HT examples. may be removed in future versions instance.rootElement.addClass('handsontable'); var table = document.createElement('TABLE'); - table.className = 'htCore'; + table.className = 'htCore'; + table.appendChild(document.createElement('THEAD')); + table.appendChild(document.createElement('TBODY')); - table.appendChild(document.createElement('THEAD')); - table.appendChild(document.createElement('TBODY')); + instance.$table = $(table); + instance.rootElement.prepend(instance.$table); - var $table = $(table); - - instance.$table = $table; - instance.rootElement.prepend($table); - - this.overflow = instance.rootElement.css('overflow'); - if ((settings.width || settings.height) && !(this.overflow === 'scroll' || this.overflow === 'auto')) { - this.overflow = 'auto'; - } - if (this.overflow === 'scroll' || this.overflow === 'auto') { - instance.rootElement[0].style.overflow = 'visible'; - //instance.rootElement[0].style.overflow = 'hidden'; - } - this.determineContainerSize(); - //instance.rootElement[0].style.height = ''; - //instance.rootElement[0].style.width = ''; - - $(document.documentElement).on('keyup.' + instance.guid, function (event) { + $documentElement.on('keyup.' + instance.guid, function (event) { if (instance.selection.isInProgress() && !event.shiftKey) { instance.selection.finish(); } @@ -2338,7 +2361,7 @@ Handsontable.TableView = function (instance) { var isMouseDown , dragInterval; - $(document.documentElement).on('mouseup.' + instance.guid, function (event) { + $documentElement.on('mouseup.' + instance.guid, function (event) { if (instance.selection.isInProgress() && event.which === 1) { //is left mouse button instance.selection.finish(); } @@ -2355,7 +2378,7 @@ Handsontable.TableView = function (instance) { } }); - $(document.documentElement).on('mousedown.' + instance.guid, function (event) { + $documentElement.on('mousedown.' + instance.guid, function (event) { var next = event.target; if (next !== that.wt.wtTable.spreader) { //immediate click on "spreader" means click on the right side of vertical scrollbar @@ -2367,34 +2390,34 @@ Handsontable.TableView = function (instance) { } } - if (that.instance.getSettings().outsideClickDeselects) { - that.instance.deselectCell(); + if (that.settings.outsideClickDeselects) { + instance.deselectCell(); } else { - that.instance.destroyEditor(); + instance.destroyEditor(); } }); - $table.on('selectstart', function (event) { + instance.$table.on('selectstart', function (event) { //https://github.com/warpech/jquery-handsontable/issues/160 //selectstart is IE only event. Prevent text from being selected when performing drag down in IE8 event.preventDefault(); }); - $table.on('mouseenter', function () { + instance.$table.on('mouseenter', function () { if (dragInterval) { //if dragInterval was set (that means mouse was really outside of table, not over an element that is outside of in DOM clearInterval(dragInterval); dragInterval = null; } }); - $table.on('mouseleave', function (event) { + instance.$table.on('mouseleave', function (event) { if (!(isMouseDown || (instance.autofill.handle && instance.autofill.handle.isDragged))) { return; } var tolerance = 1 //this is needed because width() and height() contains stuff like cell borders - , offset = that.wt.wtDom.offset($table[0]) + , offset = that.wt.wtDom.offset(table) , offsetTop = offset.top + tolerance , offsetLeft = offset.left + tolerance , width = that.containerWidth - that.wt.getSetting('scrollbarWidth') - 2 * tolerance @@ -2447,20 +2470,18 @@ Handsontable.TableView = function (instance) { }; var walkontableConfig = { - table: $table[0], - async: settings.asyncRendering, - stretchH: settings.stretchH, + table: table, + async: that.settings.asyncRendering, + stretchH: that.settings.stretchH, data: instance.getDataAtCell, totalRows: instance.countRows, totalColumns: instance.countCols, offsetRow: 0, offsetColumn: 0, - displayRows: null, - displayColumns: null, - width: this.containerWidth, - height: this.containerHeight, - frozenColumns: settings.rowHeaders ? [instance.getRowHeader] : null, - columnHeaders: settings.colHeaders ? instance.getColHeader : null, + width: this.getWidth(), + height: this.getHeight(), + rowHeaders: that.settings.rowHeaders ? [instance.getRowHeader] : null, + columnHeaders: that.settings.colHeaders ? instance.getColHeader : null, columnWidth: instance.getColWidth, cellRenderer: function (row, column, TD) { that.applyCellTypeMethod('renderer', TD, row, column); @@ -2468,27 +2489,27 @@ Handsontable.TableView = function (instance) { selections: { current: { className: 'current', - highlightRowClassName: settings.currentRowClassName, - highlightColumnClassName: settings.currentColClassName, + highlightRowClassName: that.settings.currentRowClassName, + highlightColumnClassName: that.settings.currentColClassName, border: { width: 2, color: '#5292F7', style: 'solid', cornerVisible: function () { - return settings.fillHandle && !that.isCellEdited() && !instance.selection.isMultiple() + return that.settings.fillHandle && !that.isCellEdited() && !instance.selection.isMultiple() } } }, area: { className: 'area', - highlightRowClassName: settings.currentRowClassName, - highlightColumnClassName: settings.currentColClassName, + highlightRowClassName: that.settings.currentRowClassName, + highlightColumnClassName: that.settings.currentColClassName, border: { width: 1, color: '#89AFF9', style: 'solid', cornerVisible: function () { - return settings.fillHandle && !that.isCellEdited() && instance.selection.isMultiple() + return that.settings.fillHandle && !that.isCellEdited() && instance.selection.isMultiple() } } }, @@ -2517,8 +2538,8 @@ Handsontable.TableView = function (instance) { event.preventDefault(); clearTextSelection(); - if (settings.afterOnCellMouseDown) { - settings.afterOnCellMouseDown.call(that.instance, event, coords, TD); + if (that.settings.afterOnCellMouseDown) { + that.settings.afterOnCellMouseDown.call(instance, event, coords, TD); } }, onCellMouseOver: function (event, coords, TD) { @@ -2540,28 +2561,22 @@ Handsontable.TableView = function (instance) { } }; - Handsontable.PluginHooks.run(this.instance, 'walkontableConfig', walkontableConfig); + Handsontable.PluginHooks.run(instance, 'walkontableConfig', walkontableConfig); this.wt = new Walkontable(walkontableConfig); - // this.instance.forceFullRender = true; //used when data was changed + // instance.forceFullRender = true; //used when data was changed // this.render(); - var lastContainerWidth = that.containerWidth; - var lastContainerHeight = that.containerHeight; - $window.on('resize.' + instance.guid, function () { - that.instance.registerTimeout('resizeTimeout', function () { - that.determineContainerSize(); - var newContainerWidth = that.containerWidth; - var newContainerHeight = that.containerHeight; - - if (lastContainerWidth !== newContainerWidth || lastContainerHeight !== newContainerHeight) { - that.wt.update('width', newContainerWidth); - that.wt.update('height', newContainerHeight); - that.instance.forceFullRender = true; + instance.registerTimeout('resizeTimeout', function () { + instance.parseSettingsFromDOM(); + var newWidth = that.getWidth(); + var newHeight = that.getHeight(); + if (walkontableConfig.width !== newWidth || walkontableConfig.height !== newHeight) { + instance.forceFullRender = true; that.render(); - lastContainerWidth = newContainerWidth; - lastContainerHeight = newContainerHeight; + walkontableConfig.width = newWidth; + walkontableConfig.height = newHeight; } }, 60); }); @@ -2572,44 +2587,28 @@ Handsontable.TableView = function (instance) { } }); - // $table[0].focus(); //otherwise TextEditor tests do not pass in IE8 + // table.focus(); //otherwise TextEditor tests do not pass in IE8 }; Handsontable.TableView.prototype.isCellEdited = function () { return (this.instance.textEditor && this.instance.textEditor.isCellEdited) || (this.instance.autocompleteEditor && this.instance.autocompleteEditor.isCellEdited) || (this.instance.handsontableEditor && this.instance.handsontableEditor.isCellEdited); }; -Handsontable.TableView.prototype.determineContainerSize = function () { - var settings = this.instance.getSettings(); - - this.containerWidth = typeof settings.width === 'function' ? settings.width() : settings.width; - this.containerHeight = typeof settings.height === 'function' ? settings.height() : settings.height; - - var computedWidth = this.instance.rootElement.width(); - var computedHeight = this.instance.rootElement.height(); - - if (settings.width === void 0 && computedWidth > 0) { - this.containerWidth = computedWidth; - } - - if (this.overflow === 'scroll' || this.overflow === 'auto') { - if (settings.height === void 0 && computedHeight > 0) { - this.containerHeight = computedHeight; - } +Handsontable.TableView.prototype.getWidth = function () { + var val = typeof this.settings.width !== void 0 ? this.settings.width : this.settingsFromDOM.width; + return typeof val === 'function' ? val() : val; +}; - if (this.instance.rootElement[0].style.height === '') { - if (this.wt && this.wt.wtScroll.wtScrollbarV.visible) { - if (typeof this.containerHeight === 'number') { //TODO move this to Handsontable, then this typeof can be removed - this.containerHeight += this.wt.getSetting('scrollbarHeight'); - } - } - } - } +Handsontable.TableView.prototype.getHeight = function () { + var val = this.settings.height !== void 0 ? this.settings.height : this.settingsFromDOM.height; + return typeof val === 'function' ? val() : val; }; Handsontable.TableView.prototype.render = function () { if (this.instance.forceFullRender) { Handsontable.PluginHooks.run(this.instance, 'beforeRender'); + this.wt.update('width', this.getWidth()); + this.wt.update('height', this.getHeight()); } this.wt.draw(!this.instance.forceFullRender); this.instance.rootElement.triggerHandler('render.handsontable'); @@ -2692,6 +2691,23 @@ Handsontable.helper.stringify = function (value) { } }; +/** + * Generates spreadsheet-like column names: A, B, C, ..., Z, AA, AB, etc + * @param index + * @returns {String} + */ +Handsontable.helper.spreadsheetColumnLabel = function (index) { + var dividend = index + 1; + var columnLabel = ''; + var modulo; + while (dividend > 0) { + modulo = (dividend - 1) % 26; + columnLabel = String.fromCharCode(65 + modulo) + columnLabel; + dividend = parseInt((dividend - modulo) / 26, 10); + } + return columnLabel; +}; + /** * Checks if child is a descendant of given parent node * http://stackoverflow.com/questions/2234979/how-to-check-in-javascript-if-one-element-is-a-child-of-another @@ -2735,6 +2751,7 @@ Handsontable.helper.randomString = function () { Handsontable.helper.inherit = function (Child, Parent) { function Bridge() { } + Bridge.prototype = Parent.prototype; Child.prototype = new Bridge(); Child.prototype.constructor = Child; @@ -2960,23 +2977,28 @@ Handsontable.CheckboxRenderer = function (instance, TD, row, col, prop, value, c //this is faster than innerHTML. See: https://github.com/warpech/jquery-handsontable/wiki/JavaScript-&-DOM-performance-tips } - if (!instance.checkboxInputMousedownListener) { - //not very elegant but easy and fast - instance.checkboxInputMousedownListener = function (event) { + var $input = $(INPUT); + + if (cellProperties.readOnly) { + $input.on('click', function (event) { + event.preventDefault(); + }); + } + else { + $input.on('mousedown', function (event) { if (!this.checked) { instance.setDataAtRowProp(row, prop, cellProperties.checkedTemplate); } else { instance.setDataAtRowProp(row, prop, cellProperties.uncheckedTemplate); } - event.stopPropagation(); //otherwise can confuse mousedown handler - }; - instance.rootElement.on('mousedown', '.htCheckboxRendererInput', instance.checkboxInputMousedownListener); //this way we don't bind event listener to each arrow. We rely on propagation instead - instance.checkboxInputMouseupListener = function (event) { - event.stopPropagation(); //otherwise can confuse dblclick handler - }; - instance.rootElement.on('mouseup', '.htCheckboxRendererInput', instance.checkboxInputMouseupListener); //this way we don't bind event listener to each arrow. We rely on propagation instead + event.stopPropagation(); //otherwise can confuse cell mousedown handler + }); + + $input.on('mouseup', function (event) { + event.stopPropagation(); //otherwise can confuse cell dblclick handler + }); } return TD; @@ -3577,17 +3599,18 @@ Handsontable.CheckboxEditor = function (instance, td, row, col, prop, value, cel } }); - function onDblClick() { + instance.view.wt.update('onCellDblClick', function () { toggleCheckboxCell(instance, row, prop, cellProperties); - } - - instance.view.wt.update('onCellDblClick', onDblClick); + }); return function () { instance.$table.off(".editor"); instance.view.wt.update('onCellDblClick', null); } }; + + + function HandsontableDateEditorClass(instance) { this.isCellEdited = false; this.instance = instance; @@ -5065,7 +5088,7 @@ WalkontableBorder.prototype.appear = function (corners) { var offsetRow = this.instance.getSetting('offsetRow') , offsetColumn = this.instance.getSetting('offsetColumn') , displayRows = this.instance.getSetting('displayRows') - , displayColumns = this.instance.getSetting('displayColumns'); + , lastColumn = this.instance.wtTable.getLastVisibleColumn(); var hideTop = false, hideLeft = false, hideBottom = false, hideRight = false; @@ -5085,19 +5108,17 @@ WalkontableBorder.prototype.appear = function (corners) { } } - if (displayColumns !== null) { - if (corners[1] > offsetColumn + displayColumns - 1 || corners[3] < offsetColumn) { - hideTop = hideLeft = hideBottom = hideRight = true; + if (corners[1] > lastColumn || corners[3] < offsetColumn) { + hideTop = hideLeft = hideBottom = hideRight = true; + } + else { + if (corners[1] < offsetColumn) { + corners[1] = offsetColumn; + hideLeft = true; } - else { - if (corners[1] < offsetColumn) { - corners[1] = offsetColumn; - hideLeft = true; - } - if (corners[3] > offsetColumn + displayColumns - 1) { - corners[3] = offsetColumn + displayColumns - 1; - hideRight = true; - } + if (corners[3] > lastColumn) { + corners[3] = lastColumn; + hideRight = true; } } @@ -5196,6 +5217,121 @@ WalkontableBorder.prototype.hasSetting = function (setting) { } return !!setting; }; +/** + * WalkontableColumnStrategy + * @param containerSizeFn + * @param cellRanges + * @param sizeAtIndex + * @param strategy - all, last, none + * @constructor + */ +function WalkontableColumnStrategy(containerSizeFn, cellRanges, sizeAtIndex, strategy) { + var low + , high + , cur + , size + , i + , ilen; + + this.containerSizeFn = containerSizeFn; + this.cacheLength = 0; + this.cacheTotalSize = 0; + this.cache = {}; + this.visibleCellRanges = []; + + if (!cellRanges) { + return; + } + else if (cellRanges % 2 === 1) { + throw new Error('cellRanges must have even number of elements'); + } + else if (!this.isSorted(cellRanges)) { + throw new Error('cellRanges must be in ascending order'); + } + + //step 1 - determine cells that fit containerSize and cache their widths + for (i = 0, ilen = cellRanges.length / 2; i < ilen; i++) { + low = cellRanges[2 * i]; + high = cellRanges[2 * i + 1]; + cur = low; + while (cur <= high) { + size = sizeAtIndex(cur); + if (this.cacheTotalSize >= this.getContainerSize(this.cacheTotalSize + size)) { + break; + } + this.cache[cur] = size; + this.cacheTotalSize += size; + this.cacheLength++; + + if (cur === low) { + this.visibleCellRanges[2 * i] = cur; + } + this.visibleCellRanges[2 * i + 1] = cur; + + cur++; + } + if (this.cacheTotalSize >= this.getContainerSize(this.cacheTotalSize)) { + break; + } + } + + var containerSize = this.getContainerSize(this.cacheTotalSize) + , remainingSize = containerSize - this.cacheTotalSize; + + //step 2 - apply stretching strategy + if (strategy === 'all') { + var ratio = containerSize / this.cacheTotalSize; + var newSize; + + if (ratio > 1) { //if the ratio is smaller than 1 then it means last cell is not completely visible (=there is nothing to stretch) + for (i = 0, ilen = this.visibleCellRanges.length / 2; i < ilen; i++) { + low = this.visibleCellRanges[2 * i]; + high = this.visibleCellRanges[2 * i + 1]; + cur = low; + while (cur <= high) { + if (i === ilen - 1 && cur === high) { + this.cache[cur] += remainingSize; + } + else { + newSize = Math.floor(ratio * this.cache[cur]); + remainingSize -= newSize - this.cache[cur]; + this.cache[cur] = newSize; + } + cur++; + } + } + + } + } + else if (strategy === 'last') { + if (remainingSize > 0) { + this.cache[this.visibleCellRanges[this.visibleCellRanges.length - 1]] += remainingSize; + } + } +} + +WalkontableColumnStrategy.prototype.getContainerSize = function (proposedWidth) { + var containerSize = typeof this.containerSizeFn === 'function' ? this.containerSizeFn(proposedWidth) : this.containerSizeFn; + if (containerSize === void 0 || containerSize === null || containerSize < 1) { + containerSize = Infinity; + } + return containerSize; +}; + +WalkontableColumnStrategy.prototype.getSize = function (index) { + return this.cache[index]; +}; + +WalkontableColumnStrategy.prototype.isSorted = function (cellRanges) { + for (var i = 0, ilen = cellRanges.length; i < ilen; i++) { + if (i > 0) { + if (cellRanges[i - 1] > cellRanges[i]) { + return false; + } + } + } + return true; +}; function Walkontable(settings) { var self = this, originalHeaders = []; @@ -6159,7 +6295,7 @@ WalkontableSelection.prototype.draw = function (selectionsOnly) { var offsetRow = this.instance.getSetting('offsetRow') , lastVisibleRow = offsetRow + this.instance.getSetting('displayRows') - 1 , offsetColumn = this.instance.getSetting('offsetColumn') - , lastVisibleColumn = offsetColumn + this.instance.getSetting('displayColumns') - 1; + , lastVisibleColumn = this.instance.wtTable.getLastVisibleColumn(); if (this.selected.length) { corners = this.getCorners(); @@ -6230,7 +6366,7 @@ function WalkontableSettings(instance, settings) { data: void 0, offsetRow: 0, offsetColumn: 0, - frozenColumns: null, + rowHeaders: null, columnHeaders: null, //this must be a function in format: function (col, TH) {} totalRows: void 0, totalColumns: void 0, @@ -6238,12 +6374,7 @@ function WalkontableSettings(instance, settings) { height: null, cellRenderer: function (row, column, TD) { var cellData = that.getSetting('data', row, column); - if (cellData !== void 0) { - that.instance.wtDom.avoidInnerHTML(TD, cellData); - } - else { - this.wtDom.empty(TD); - } + that.instance.wtDom.avoidInnerHTML(TD, cellData === void 0 || cellData === null ? '' : cellData); }, columnWidth: 50, selections: null, @@ -6361,29 +6492,6 @@ WalkontableSettings.prototype.displayRows = function () { } }; -WalkontableSettings.prototype.displayColumns = function () { - var estimated - , calculated; - - if (this.settings['width']) { - if (typeof this.settings['width'] !== 'number') { - throw new Error('Walkontable width parameter must be a number (' + typeof this.settings['width'] + ' given)'); - } - estimated = Math.ceil(this.settings['width'] / 50); //silly assumption but should be fine for now - calculated = this.getSetting('totalColumns') - this.getSetting('offsetColumn'); - if (calculated < 0) { - this.update('offsetColumn', Math.max(0, this.getSetting('totalColumns') - estimated)); - return estimated; - } - else { - return Math.min(estimated, calculated); - } - } - else { - return this.getSetting('totalColumns'); - } -}; - WalkontableSettings.prototype.viewportRows = function () { if (this.instance.wtTable.visibilityEdgeRow !== null) { return this.instance.wtTable.visibilityEdgeRow - this.instance.wtTable.visibilityStartRow; @@ -6395,7 +6503,7 @@ WalkontableSettings.prototype.viewportColumns = function () { if (this.instance.wtTable.visibilityEdgeColumn !== null) { return this.instance.wtTable.visibilityEdgeColumn - this.instance.wtTable.visibilityStartColumn; } - return this.getSetting('displayColumns'); + return this.instance.wtTable.getLastVisibleColumn(); }; var FLAG_VISIBLE_HORIZONTAL = 0x1; // 000001 var FLAG_VISIBLE_VERTICAL = 0x2; // 000010 @@ -6524,20 +6632,29 @@ WalkontableTable.prototype.refreshHiderDimensions = function () { }; WalkontableTable.prototype.refreshStretching = function () { - var stretchH = this.instance.getSetting('stretchH') - , totalColumns = this.instance.getSetting('totalColumns') - , displayColumns = this.instance.getSetting('displayColumns') - , displayTds = Math.min(displayColumns, totalColumns) - , offsetColumn = this.instance.getSetting('offsetColumn') - , frozenColumns = this.instance.getSetting('frozenColumns') - , frozenColumnsCount = frozenColumns ? frozenColumns.length : 0; + var instance = this.instance + , stretchH = instance.getSetting('stretchH') + , scrollH = instance.getSetting('scrollH') + , scrollbarWidth = instance.getSetting('scrollbarWidth') + , totalColumns = instance.getSetting('totalColumns') + , offsetColumn = instance.getSetting('offsetColumn') + , rowHeaders = instance.getSetting('rowHeaders') + , rowHeaderWidth = rowHeaders && rowHeaders.length ? 50 : 0 + , containerWidth = this.instance.getSetting('width') - rowHeaderWidth; - if (!this.instance.hasSetting('columnWidth')) { - return; - } + var containerWidthFn = function (cacheWidth) { + if (scrollH === 'scroll' || (scrollH === 'auto' && cacheWidth > containerWidth)) { + return containerWidth - scrollbarWidth; + } + return containerWidth; + }; + + var columnWidthFn = function (index) { + return instance.getSetting('columnWidth', index) + }; if (stretchH === 'hybrid') { - if (this.instance.wtScroll.wtScrollbarH.visible) { + if (offsetColumn > 0) { stretchH = 'last'; } else { @@ -6545,110 +6662,41 @@ WalkontableTable.prototype.refreshStretching = function () { } } - var TD; - if (this.instance.wtTable.TBODY.firstChild && this.instance.wtTable.TBODY.firstChild.firstChild) { - TD = this.instance.wtTable.TBODY.firstChild.firstChild; - } - else if (this.instance.wtTable.THEAD.firstChild && this.instance.wtTable.THEAD.firstChild.firstChild) { - TD = this.instance.wtTable.THEAD.firstChild.firstChild; - } - - if (frozenColumnsCount) { - TD = TD.nextSibling; - } - - if (!TD) { - return; - } - - var cellOffset = this.instance.wtDom.offset(TD) - , tableOffset = this.instance.wtTable.tableOffset - , rowHeaderWidth = cellOffset.left - tableOffset.left - , widths = [] - , widthSum = 0 - , c; - for (c = 0; c < displayTds; c++) { - widths.push(this.instance.getSetting('columnWidth', offsetColumn + c)); - widthSum += widths[c]; - } - var domWidth = widthSum + rowHeaderWidth; - - if (stretchH === 'all' || stretchH === 'last') { - var containerWidth = this.instance.getSetting('width'); - if (this.instance.wtScroll.wtScrollbarV.visible) { - containerWidth -= this.instance.getSetting('scrollbarWidth'); - } - - var diff = containerWidth - domWidth; - if (diff > 0) { - if (stretchH === 'all') { - var newWidth; - var remainingDiff = diff; - var ratio = diff / widthSum; - - for (c = 0; c < displayTds; c++) { - if (widths[c]) { - if (c === displayTds - 1) { - newWidth = widths[c] + remainingDiff; - } - else { - newWidth = widths[c] + Math.floor(ratio * widths[c]); - remainingDiff -= Math.floor(ratio * widths[c]); - } - } - widths[c] = newWidth; - } - } - else { - if (widths[widths.length - 1]) { - widths[widths.length - 1] = widths[widths.length - 1] + diff; - } - } - } - } - - for (c = 0; c < displayTds; c++) { - if (widths[c]) { - this.COLGROUP.childNodes[c + frozenColumnsCount].style.width = widths[c] + 'px'; - } - else { - this.COLGROUP.childNodes[c + frozenColumnsCount].style.width = ''; - } - } + this.columnStrategy = new WalkontableColumnStrategy(containerWidthFn, totalColumns ? [offsetColumn, totalColumns - 1] : null, columnWidthFn, stretchH); }; WalkontableTable.prototype.adjustAvailableNodes = function () { var instance = this.instance , totalRows = instance.getSetting('totalRows') - , totalColumns = instance.getSetting('totalColumns') , displayRows = instance.getSetting('displayRows') - , displayColumns = instance.getSetting('displayColumns') , displayTds - , frozenColumns = instance.getSetting('frozenColumns') - , frozenColumnsCount = frozenColumns ? frozenColumns.length : 0 + , rowHeaders = instance.getSetting('rowHeaders') + , rowHeadersCount = rowHeaders ? rowHeaders.length : 0 , TR , c; displayRows = Math.min(displayRows, totalRows); - displayTds = Math.min(displayColumns, totalColumns); + this.refreshStretching(); + displayTds = this.columnStrategy.cacheLength; + //adjust COLGROUP - while (this.colgroupChildrenLength < displayTds + frozenColumnsCount) { + while (this.colgroupChildrenLength < displayTds + rowHeadersCount) { this.COLGROUP.appendChild(document.createElement('COL')); this.colgroupChildrenLength++; } - while (this.colgroupChildrenLength > displayTds + frozenColumnsCount) { + while (this.colgroupChildrenLength > displayTds + rowHeadersCount) { this.COLGROUP.removeChild(this.COLGROUP.lastChild); this.colgroupChildrenLength--; } //adjust THEAD if (this.instance.hasSetting('columnHeaders')) { - while (this.theadChildrenLength < displayTds + frozenColumnsCount) { + while (this.theadChildrenLength < displayTds + rowHeadersCount) { this.THEAD.firstChild.appendChild(document.createElement('TH')); this.theadChildrenLength++; } - while (this.theadChildrenLength > displayTds + frozenColumnsCount) { + while (this.theadChildrenLength > displayTds + rowHeadersCount) { this.THEAD.firstChild.removeChild(this.THEAD.firstChild.lastChild); this.theadChildrenLength--; } @@ -6657,7 +6705,7 @@ WalkontableTable.prototype.adjustAvailableNodes = function () { //adjust TBODY while (this.tbodyChildrenLength < displayRows) { TR = document.createElement('TR'); - for (c = 0; c < frozenColumnsCount; c++) { + for (c = 0; c < rowHeadersCount; c++) { TR.appendChild(document.createElement('TH')); } this.TBODY.appendChild(TR); @@ -6672,13 +6720,13 @@ WalkontableTable.prototype.adjustAvailableNodes = function () { var trChildrenLength; for (var r = 0, rlen = TRs.length; r < rlen; r++) { trChildrenLength = TRs[r].childNodes.length; - while (trChildrenLength < displayTds + frozenColumnsCount) { + while (trChildrenLength < displayTds + rowHeadersCount) { var TD = document.createElement('TD'); TD.setAttribute('tabindex', 10000); //http://www.barryvan.com.au/2009/01/onfocus-and-onblur-for-divs-in-fx/; 32767 is max tabindex for IE7,8 TRs[r].appendChild(TD); trChildrenLength++; } - while (trChildrenLength > displayTds + frozenColumnsCount) { + while (trChildrenLength > displayTds + rowHeadersCount) { TRs[r].removeChild(TRs[r].lastChild); trChildrenLength--; } @@ -6706,26 +6754,23 @@ WalkontableTable.prototype._doDraw = function () { , offsetRow = this.instance.getSetting('offsetRow') , offsetColumn = this.instance.getSetting('offsetColumn') , totalRows = this.instance.getSetting('totalRows') - , totalColumns = this.instance.getSetting('totalColumns') , displayRows = this.instance.getSetting('displayRows') - , displayColumns = this.instance.getSetting('displayColumns') - , displayTds - , frozenColumns = this.instance.getSetting('frozenColumns') - , frozenColumnsCount = frozenColumns ? frozenColumns.length : 0 + , displayTds = this.columnStrategy.cacheLength + , rowHeaders = this.instance.getSetting('rowHeaders') + , rowHeadersCount = rowHeaders ? rowHeaders.length : 0 , TR , TH , TD , cellData; displayRows = Math.min(displayRows, totalRows); - displayTds = Math.min(displayColumns, totalColumns); //draw COLGROUP for (c = 0; c < this.colgroupChildrenLength; c++) { - if (c < frozenColumnsCount) { + if (c < rowHeadersCount) { this.wtDom.addClass(this.COLGROUP.childNodes[c], 'rowHeader'); - if (typeof frozenColumns[c] === "function") { - frozenColumns[c](null, this.COLGROUP.childNodes[c]) + if (typeof rowHeaders[c] === "function") { + rowHeaders[c](null, this.COLGROUP.childNodes[c]) } } else { @@ -6733,14 +6778,12 @@ WalkontableTable.prototype._doDraw = function () { } } - this.refreshStretching(); //needed here or otherwise scrollbarH is not shown - //draw THEAD - if (frozenColumnsCount && this.instance.hasSetting('columnHeaders')) { - for (c = 0; c < frozenColumnsCount; c++) { + if (rowHeadersCount && this.instance.hasSetting('columnHeaders')) { + for (c = 0; c < rowHeadersCount; c++) { TH = this.THEAD.childNodes[0].childNodes[c]; - if (typeof frozenColumns[c] === "function") { - frozenColumns[c](null, TH); + if (typeof rowHeaders[c] === "function") { + rowHeaders[c](null, TH); } else { this.wtDom.empty(TH); @@ -6751,10 +6794,12 @@ WalkontableTable.prototype._doDraw = function () { } } - if (this.instance.hasSetting('columnHeaders')) { - for (c = 0; c < displayTds; c++) { - this.instance.getSetting('columnHeaders', offsetColumn + c, this.THEAD.childNodes[0].childNodes[frozenColumnsCount + c]); + var columnHeaders = this.instance.hasSetting('columnHeaders'); + for (c = 0; c < displayTds; c++) { + if (columnHeaders) { + this.instance.getSetting('columnHeaders', offsetColumn + c, this.THEAD.childNodes[0].childNodes[rowHeadersCount + c]); } + this.COLGROUP.childNodes[c + rowHeadersCount].style.width = this.columnStrategy.getSize(offsetColumn + c) + 'px'; } //draw TBODY @@ -6763,14 +6808,14 @@ WalkontableTable.prototype._doDraw = function () { this.visibilityStartColumn = offsetColumn; for (r = 0; r < displayRows; r++) { TR = this.TBODY.childNodes[r]; - for (c = 0; c < frozenColumnsCount; c++) { //in future use nextSibling; http://jsperf.com/nextsibling-vs-indexed-childnodes + for (c = 0; c < rowHeadersCount; c++) { //in future use nextSibling; http://jsperf.com/nextsibling-vs-indexed-childnodes TH = TR.childNodes[c]; - cellData = typeof frozenColumns[c] === "function" ? frozenColumns[c](offsetRow + r, TH) : frozenColumns[c]; + cellData = typeof rowHeaders[c] === "function" ? rowHeaders[c](offsetRow + r, TH) : rowHeaders[c]; if (cellData !== void 0) { this.wtDom.avoidInnerHTML(TH, cellData); } /* - we can assume that frozenColumns[c] function took care of inserting content into TH + we can assume that rowHeaders[c] function took care of inserting content into TH else { TH.innerHTML = ''; }*/ @@ -6785,7 +6830,7 @@ WalkontableTable.prototype._doDraw = function () { break; } else { - TD = TR.childNodes[c + frozenColumnsCount]; + TD = TR.childNodes[c + rowHeadersCount]; TD.className = ''; TD.removeAttribute('style'); this.instance.getSetting('cellRenderer', offsetRow + r, offsetColumn + c, TD); @@ -6825,7 +6870,6 @@ WalkontableTable.prototype._doDraw = function () { WalkontableTable.prototype.refreshPositions = function (selectionsOnly) { this.instance.wtScroll.refreshScrollbars(); this.refreshHiderDimensions(); - this.refreshStretching(); this.refreshSelections(selectionsOnly); }; @@ -6853,8 +6897,6 @@ WalkontableTable.prototype.recalcViewportCells = function () { WalkontableTable.prototype.isCellVisible = function (r, c, TD) { var out = 0 - , scrollV = this.instance.getSetting('scrollV') - , scrollH = this.instance.getSetting('scrollH') , cellOffset = this.wtDom.offset(TD) , tableOffset = this.tableOffset , innerOffsetTop = cellOffset.top - tableOffset.top @@ -6924,12 +6966,12 @@ WalkontableTable.prototype.getCell = function (coords) { if (coords[1] < offsetColumn) { return -3; //column before viewport } - else if (coords[1] > offsetColumn + this.instance.getSetting('displayColumns') - 1) { + else if (coords[1] > this.getLastVisibleColumn()) { return -4; //column after viewport } else { - var frozenColumns = this.instance.getSetting('frozenColumns') - , frozenColumnsCount = (frozenColumns ? frozenColumns.length : 0) + var rowHeaders = this.instance.getSetting('rowHeaders') + , rowHeadersCount = (rowHeaders ? rowHeaders.length : 0) , tr = this.TBODY.childNodes[coords[0] - offsetRow]; if (typeof tr === "undefined") { //this block is only needed in async mode @@ -6937,19 +6979,23 @@ WalkontableTable.prototype.getCell = function (coords) { tr = this.TBODY.childNodes[coords[0] - offsetRow]; } - return tr.childNodes[coords[1] - offsetColumn + frozenColumnsCount]; + return tr.childNodes[coords[1] - offsetColumn + rowHeadersCount]; } } }; WalkontableTable.prototype.getCoords = function (TD) { - var frozenColumns = this.instance.getSetting('frozenColumns') - , frozenColumnsCount = frozenColumns ? frozenColumns.length : 0; + var rowHeaders = this.instance.getSetting('rowHeaders') + , rowHeadersCount = rowHeaders ? rowHeaders.length : 0; return [ this.wtDom.prevSiblings(TD.parentNode).length + this.instance.getSetting('offsetRow'), - TD.cellIndex + this.instance.getSetting('offsetColumn') - frozenColumnsCount + TD.cellIndex + this.instance.getSetting('offsetColumn') - rowHeadersCount ]; }; + +WalkontableTable.prototype.getLastVisibleColumn = function () { + return this.columnStrategy.visibleCellRanges[this.columnStrategy.visibleCellRanges.length - 1] +}; function WalkontableWheel(instance) { var that = this; diff --git a/handsontable.jquery.json b/handsontable.jquery.json index 82ccb08d5e7..492f8962013 100644 --- a/handsontable.jquery.json +++ b/handsontable.jquery.json @@ -1,7 +1,7 @@ { "name": "handsontable", "title": "Handsontable", - "version": "0.8.17", + "version": "0.8.18", "author": { "name": "Marcin Warpechowski", "email": "marcin@nextgen.pl", diff --git a/jquery.handsontable.css b/jquery.handsontable.css index 41c379d7516..681faa04780 100644 --- a/jquery.handsontable.css +++ b/jquery.handsontable.css @@ -1,12 +1,12 @@ /** - * Handsontable 0.8.17 + * Handsontable 0.8.18 * Handsontable is a simple jQuery plugin for editable tables with basic copy-paste compatibility with Excel and Google Docs * * Copyright 2012, Marcin Warpechowski * Licensed under the MIT license. * http://handsontable.com/ * - * Date: Sun Mar 31 2013 19:46:52 GMT+0200 (Central European Daylight Time) + * Date: Fri Apr 12 2013 11:44:21 GMT+0200 (Central European Daylight Time) */ .handsontable { diff --git a/jquery.handsontable.js b/jquery.handsontable.js index 7d7dcd01d90..fdf046b09e8 100644 --- a/jquery.handsontable.js +++ b/jquery.handsontable.js @@ -1,12 +1,12 @@ /** - * Handsontable 0.8.17 + * Handsontable 0.8.18 * Handsontable is a simple jQuery plugin for editable tables with basic copy-paste compatibility with Excel and Google Docs * * Copyright 2012, Marcin Warpechowski * Licensed under the MIT license. * http://handsontable.com/ * - * Date: Sun Mar 31 2013 19:46:52 GMT+0200 (Central European Daylight Time) + * Date: Fri Apr 12 2013 11:44:21 GMT+0200 (Central European Daylight Time) */ /*jslint white: true, browser: true, plusplus: true, indent: 4, maxerr: 50 */ @@ -35,6 +35,7 @@ Handsontable.Core = function (rootElement, settings) { priv = { settings: {}, + settingsFromDOM: {}, selStart: (new Handsontable.SelectionPoint()), selEnd: (new Handsontable.SelectionPoint()), editProxy: false, @@ -1197,6 +1198,7 @@ Handsontable.Core = function (rootElement, settings) { else { rangeModifier({row: priv.selStart.row(), col: 0}); } + event.preventDefault(); //don't scroll the window event.stopPropagation(); //required by HandsontableEditor break; @@ -1207,6 +1209,7 @@ Handsontable.Core = function (rootElement, settings) { else { rangeModifier({row: priv.selStart.row(), col: self.countCols() - 1}); } + event.preventDefault(); //don't scroll the window event.stopPropagation(); //required by HandsontableEditor break; @@ -1303,6 +1306,7 @@ Handsontable.Core = function (rootElement, settings) { bindEvents(); this.updateSettings(settings); + this.parseSettingsFromDOM(); this.view = new Handsontable.TableView(this); this.forceFullRender = true; //used when data was changed @@ -1580,6 +1584,38 @@ Handsontable.Core = function (rootElement, settings) { } }; + /** + * Parse settings from DOM and CSS + * @public + */ + this.parseSettingsFromDOM = function () { + var overflow = this.rootElement.css('overflow'); + if (overflow === 'scroll' || overflow === 'auto') { + this.rootElement[0].style.overflow = 'visible'; + priv.settingsFromDOM.overflow = overflow; + } + else if (priv.settings.width === void 0 || priv.settings.height === void 0) { + priv.settingsFromDOM.overflow = 'auto'; + } + + if (priv.settings.width === void 0) { + priv.settingsFromDOM.width = this.rootElement.width(); + } + else { + priv.settingsFromDOM.width = void 0; + } + + priv.settingsFromDOM.height = void 0; + if (priv.settings.height === void 0) { + if (priv.settingsFromDOM.overflow === 'scroll' || priv.settingsFromDOM.overflow === 'auto') { + var computedHeight = this.rootElement.height(); + if (computedHeight > 0) { + priv.settingsFromDOM.height = computedHeight; + } + } + } + }; + /** * Render visible data * @public @@ -1732,6 +1768,14 @@ Handsontable.Core = function (rootElement, settings) { return priv.settings; }; + /** + * Returns current settingsFromDOM object + * @return {Object} + */ + this.getSettingsFromDOM = function () { + return priv.settingsFromDOM; + }; + /** * Clears grid * @public @@ -1913,9 +1957,9 @@ Handsontable.Core = function (rootElement, settings) { */ this.getColHeader = function (col, TH) { col = Handsontable.PluginModifiers.run(self, 'col', col); - var DIV = document.createElement('DIV'), - SPAN = document.createElement('SPAN'), - avoidInnerHTML = self.view.wt.wtDom.avoidInnerHTML; + var DIV = document.createElement('DIV'), + SPAN = document.createElement('SPAN'), + avoidInnerHTML = self.view.wt.wtDom.avoidInnerHTML; DIV.className = 'relative'; SPAN.className = 'colHeader'; @@ -1930,15 +1974,7 @@ Handsontable.Core = function (rootElement, settings) { avoidInnerHTML(SPAN, priv.settings.colHeaders(col)); } else if (priv.settings.colHeaders && typeof priv.settings.colHeaders !== 'string' && typeof priv.settings.colHeaders !== 'number') { - var dividend = col + 1; - var columnLabel = ''; - var modulo; - while (dividend > 0) { - modulo = (dividend - 1) % 26; - columnLabel = String.fromCharCode(65 + modulo) + columnLabel; - dividend = parseInt((dividend - modulo) / 26, 10); - } - SPAN.appendChild(document.createTextNode(columnLabel)); + SPAN.appendChild(document.createTextNode(Handsontable.helper.spreadsheetColumnLabel(col))); } else { avoidInnerHTML(SPAN, priv.settings.colHeaders); @@ -2219,7 +2255,7 @@ Handsontable.Core = function (rootElement, settings) { /** * Handsontable version */ - this.version = '0.8.17'; //inserted by grunt from package.json + this.version = '0.8.18'; //inserted by grunt from package.json }; var settings = { @@ -2297,39 +2333,26 @@ $.fn.handsontable = function (action) { * @param {Object} instance */ Handsontable.TableView = function (instance) { - var that = this; - var $window = $(window); + var that = this + , $window = $(window) + , $documentElement = $(document.documentElement); this.instance = instance; - var settings = this.instance.getSettings(); + this.settings = instance.getSettings(); + this.settingsFromDOM = instance.getSettingsFromDOM(); instance.rootElement.data('originalStyle', instance.rootElement.attr('style')); //needed to retrieve original style in jsFiddle link generator in HT examples. may be removed in future versions instance.rootElement.addClass('handsontable'); var table = document.createElement('TABLE'); - table.className = 'htCore'; + table.className = 'htCore'; + table.appendChild(document.createElement('THEAD')); + table.appendChild(document.createElement('TBODY')); - table.appendChild(document.createElement('THEAD')); - table.appendChild(document.createElement('TBODY')); + instance.$table = $(table); + instance.rootElement.prepend(instance.$table); - var $table = $(table); - - instance.$table = $table; - instance.rootElement.prepend($table); - - this.overflow = instance.rootElement.css('overflow'); - if ((settings.width || settings.height) && !(this.overflow === 'scroll' || this.overflow === 'auto')) { - this.overflow = 'auto'; - } - if (this.overflow === 'scroll' || this.overflow === 'auto') { - instance.rootElement[0].style.overflow = 'visible'; - //instance.rootElement[0].style.overflow = 'hidden'; - } - this.determineContainerSize(); - //instance.rootElement[0].style.height = ''; - //instance.rootElement[0].style.width = ''; - - $(document.documentElement).on('keyup.' + instance.guid, function (event) { + $documentElement.on('keyup.' + instance.guid, function (event) { if (instance.selection.isInProgress() && !event.shiftKey) { instance.selection.finish(); } @@ -2338,7 +2361,7 @@ Handsontable.TableView = function (instance) { var isMouseDown , dragInterval; - $(document.documentElement).on('mouseup.' + instance.guid, function (event) { + $documentElement.on('mouseup.' + instance.guid, function (event) { if (instance.selection.isInProgress() && event.which === 1) { //is left mouse button instance.selection.finish(); } @@ -2355,7 +2378,7 @@ Handsontable.TableView = function (instance) { } }); - $(document.documentElement).on('mousedown.' + instance.guid, function (event) { + $documentElement.on('mousedown.' + instance.guid, function (event) { var next = event.target; if (next !== that.wt.wtTable.spreader) { //immediate click on "spreader" means click on the right side of vertical scrollbar @@ -2367,34 +2390,34 @@ Handsontable.TableView = function (instance) { } } - if (that.instance.getSettings().outsideClickDeselects) { - that.instance.deselectCell(); + if (that.settings.outsideClickDeselects) { + instance.deselectCell(); } else { - that.instance.destroyEditor(); + instance.destroyEditor(); } }); - $table.on('selectstart', function (event) { + instance.$table.on('selectstart', function (event) { //https://github.com/warpech/jquery-handsontable/issues/160 //selectstart is IE only event. Prevent text from being selected when performing drag down in IE8 event.preventDefault(); }); - $table.on('mouseenter', function () { + instance.$table.on('mouseenter', function () { if (dragInterval) { //if dragInterval was set (that means mouse was really outside of table, not over an element that is outside of
in DOM clearInterval(dragInterval); dragInterval = null; } }); - $table.on('mouseleave', function (event) { + instance.$table.on('mouseleave', function (event) { if (!(isMouseDown || (instance.autofill.handle && instance.autofill.handle.isDragged))) { return; } var tolerance = 1 //this is needed because width() and height() contains stuff like cell borders - , offset = that.wt.wtDom.offset($table[0]) + , offset = that.wt.wtDom.offset(table) , offsetTop = offset.top + tolerance , offsetLeft = offset.left + tolerance , width = that.containerWidth - that.wt.getSetting('scrollbarWidth') - 2 * tolerance @@ -2447,20 +2470,18 @@ Handsontable.TableView = function (instance) { }; var walkontableConfig = { - table: $table[0], - async: settings.asyncRendering, - stretchH: settings.stretchH, + table: table, + async: that.settings.asyncRendering, + stretchH: that.settings.stretchH, data: instance.getDataAtCell, totalRows: instance.countRows, totalColumns: instance.countCols, offsetRow: 0, offsetColumn: 0, - displayRows: null, - displayColumns: null, - width: this.containerWidth, - height: this.containerHeight, - frozenColumns: settings.rowHeaders ? [instance.getRowHeader] : null, - columnHeaders: settings.colHeaders ? instance.getColHeader : null, + width: this.getWidth(), + height: this.getHeight(), + rowHeaders: that.settings.rowHeaders ? [instance.getRowHeader] : null, + columnHeaders: that.settings.colHeaders ? instance.getColHeader : null, columnWidth: instance.getColWidth, cellRenderer: function (row, column, TD) { that.applyCellTypeMethod('renderer', TD, row, column); @@ -2468,27 +2489,27 @@ Handsontable.TableView = function (instance) { selections: { current: { className: 'current', - highlightRowClassName: settings.currentRowClassName, - highlightColumnClassName: settings.currentColClassName, + highlightRowClassName: that.settings.currentRowClassName, + highlightColumnClassName: that.settings.currentColClassName, border: { width: 2, color: '#5292F7', style: 'solid', cornerVisible: function () { - return settings.fillHandle && !that.isCellEdited() && !instance.selection.isMultiple() + return that.settings.fillHandle && !that.isCellEdited() && !instance.selection.isMultiple() } } }, area: { className: 'area', - highlightRowClassName: settings.currentRowClassName, - highlightColumnClassName: settings.currentColClassName, + highlightRowClassName: that.settings.currentRowClassName, + highlightColumnClassName: that.settings.currentColClassName, border: { width: 1, color: '#89AFF9', style: 'solid', cornerVisible: function () { - return settings.fillHandle && !that.isCellEdited() && instance.selection.isMultiple() + return that.settings.fillHandle && !that.isCellEdited() && instance.selection.isMultiple() } } }, @@ -2517,8 +2538,8 @@ Handsontable.TableView = function (instance) { event.preventDefault(); clearTextSelection(); - if (settings.afterOnCellMouseDown) { - settings.afterOnCellMouseDown.call(that.instance, event, coords, TD); + if (that.settings.afterOnCellMouseDown) { + that.settings.afterOnCellMouseDown.call(instance, event, coords, TD); } }, onCellMouseOver: function (event, coords, TD) { @@ -2540,28 +2561,22 @@ Handsontable.TableView = function (instance) { } }; - Handsontable.PluginHooks.run(this.instance, 'walkontableConfig', walkontableConfig); + Handsontable.PluginHooks.run(instance, 'walkontableConfig', walkontableConfig); this.wt = new Walkontable(walkontableConfig); - // this.instance.forceFullRender = true; //used when data was changed + // instance.forceFullRender = true; //used when data was changed // this.render(); - var lastContainerWidth = that.containerWidth; - var lastContainerHeight = that.containerHeight; - $window.on('resize.' + instance.guid, function () { - that.instance.registerTimeout('resizeTimeout', function () { - that.determineContainerSize(); - var newContainerWidth = that.containerWidth; - var newContainerHeight = that.containerHeight; - - if (lastContainerWidth !== newContainerWidth || lastContainerHeight !== newContainerHeight) { - that.wt.update('width', newContainerWidth); - that.wt.update('height', newContainerHeight); - that.instance.forceFullRender = true; + instance.registerTimeout('resizeTimeout', function () { + instance.parseSettingsFromDOM(); + var newWidth = that.getWidth(); + var newHeight = that.getHeight(); + if (walkontableConfig.width !== newWidth || walkontableConfig.height !== newHeight) { + instance.forceFullRender = true; that.render(); - lastContainerWidth = newContainerWidth; - lastContainerHeight = newContainerHeight; + walkontableConfig.width = newWidth; + walkontableConfig.height = newHeight; } }, 60); }); @@ -2572,44 +2587,28 @@ Handsontable.TableView = function (instance) { } }); - // $table[0].focus(); //otherwise TextEditor tests do not pass in IE8 + // table.focus(); //otherwise TextEditor tests do not pass in IE8 }; Handsontable.TableView.prototype.isCellEdited = function () { return (this.instance.textEditor && this.instance.textEditor.isCellEdited) || (this.instance.autocompleteEditor && this.instance.autocompleteEditor.isCellEdited) || (this.instance.handsontableEditor && this.instance.handsontableEditor.isCellEdited); }; -Handsontable.TableView.prototype.determineContainerSize = function () { - var settings = this.instance.getSettings(); - - this.containerWidth = typeof settings.width === 'function' ? settings.width() : settings.width; - this.containerHeight = typeof settings.height === 'function' ? settings.height() : settings.height; - - var computedWidth = this.instance.rootElement.width(); - var computedHeight = this.instance.rootElement.height(); - - if (settings.width === void 0 && computedWidth > 0) { - this.containerWidth = computedWidth; - } - - if (this.overflow === 'scroll' || this.overflow === 'auto') { - if (settings.height === void 0 && computedHeight > 0) { - this.containerHeight = computedHeight; - } +Handsontable.TableView.prototype.getWidth = function () { + var val = typeof this.settings.width !== void 0 ? this.settings.width : this.settingsFromDOM.width; + return typeof val === 'function' ? val() : val; +}; - if (this.instance.rootElement[0].style.height === '') { - if (this.wt && this.wt.wtScroll.wtScrollbarV.visible) { - if (typeof this.containerHeight === 'number') { //TODO move this to Handsontable, then this typeof can be removed - this.containerHeight += this.wt.getSetting('scrollbarHeight'); - } - } - } - } +Handsontable.TableView.prototype.getHeight = function () { + var val = this.settings.height !== void 0 ? this.settings.height : this.settingsFromDOM.height; + return typeof val === 'function' ? val() : val; }; Handsontable.TableView.prototype.render = function () { if (this.instance.forceFullRender) { Handsontable.PluginHooks.run(this.instance, 'beforeRender'); + this.wt.update('width', this.getWidth()); + this.wt.update('height', this.getHeight()); } this.wt.draw(!this.instance.forceFullRender); this.instance.rootElement.triggerHandler('render.handsontable'); @@ -2692,6 +2691,23 @@ Handsontable.helper.stringify = function (value) { } }; +/** + * Generates spreadsheet-like column names: A, B, C, ..., Z, AA, AB, etc + * @param index + * @returns {String} + */ +Handsontable.helper.spreadsheetColumnLabel = function (index) { + var dividend = index + 1; + var columnLabel = ''; + var modulo; + while (dividend > 0) { + modulo = (dividend - 1) % 26; + columnLabel = String.fromCharCode(65 + modulo) + columnLabel; + dividend = parseInt((dividend - modulo) / 26, 10); + } + return columnLabel; +}; + /** * Checks if child is a descendant of given parent node * http://stackoverflow.com/questions/2234979/how-to-check-in-javascript-if-one-element-is-a-child-of-another @@ -2735,6 +2751,7 @@ Handsontable.helper.randomString = function () { Handsontable.helper.inherit = function (Child, Parent) { function Bridge() { } + Bridge.prototype = Parent.prototype; Child.prototype = new Bridge(); Child.prototype.constructor = Child; @@ -2960,23 +2977,28 @@ Handsontable.CheckboxRenderer = function (instance, TD, row, col, prop, value, c //this is faster than innerHTML. See: https://github.com/warpech/jquery-handsontable/wiki/JavaScript-&-DOM-performance-tips } - if (!instance.checkboxInputMousedownListener) { - //not very elegant but easy and fast - instance.checkboxInputMousedownListener = function (event) { + var $input = $(INPUT); + + if (cellProperties.readOnly) { + $input.on('click', function (event) { + event.preventDefault(); + }); + } + else { + $input.on('mousedown', function (event) { if (!this.checked) { instance.setDataAtRowProp(row, prop, cellProperties.checkedTemplate); } else { instance.setDataAtRowProp(row, prop, cellProperties.uncheckedTemplate); } - event.stopPropagation(); //otherwise can confuse mousedown handler - }; - instance.rootElement.on('mousedown', '.htCheckboxRendererInput', instance.checkboxInputMousedownListener); //this way we don't bind event listener to each arrow. We rely on propagation instead - instance.checkboxInputMouseupListener = function (event) { - event.stopPropagation(); //otherwise can confuse dblclick handler - }; - instance.rootElement.on('mouseup', '.htCheckboxRendererInput', instance.checkboxInputMouseupListener); //this way we don't bind event listener to each arrow. We rely on propagation instead + event.stopPropagation(); //otherwise can confuse cell mousedown handler + }); + + $input.on('mouseup', function (event) { + event.stopPropagation(); //otherwise can confuse cell dblclick handler + }); } return TD; @@ -3577,17 +3599,18 @@ Handsontable.CheckboxEditor = function (instance, td, row, col, prop, value, cel } }); - function onDblClick() { + instance.view.wt.update('onCellDblClick', function () { toggleCheckboxCell(instance, row, prop, cellProperties); - } - - instance.view.wt.update('onCellDblClick', onDblClick); + }); return function () { instance.$table.off(".editor"); instance.view.wt.update('onCellDblClick', null); } }; + + + function HandsontableDateEditorClass(instance) { this.isCellEdited = false; this.instance = instance; @@ -5065,7 +5088,7 @@ WalkontableBorder.prototype.appear = function (corners) { var offsetRow = this.instance.getSetting('offsetRow') , offsetColumn = this.instance.getSetting('offsetColumn') , displayRows = this.instance.getSetting('displayRows') - , displayColumns = this.instance.getSetting('displayColumns'); + , lastColumn = this.instance.wtTable.getLastVisibleColumn(); var hideTop = false, hideLeft = false, hideBottom = false, hideRight = false; @@ -5085,19 +5108,17 @@ WalkontableBorder.prototype.appear = function (corners) { } } - if (displayColumns !== null) { - if (corners[1] > offsetColumn + displayColumns - 1 || corners[3] < offsetColumn) { - hideTop = hideLeft = hideBottom = hideRight = true; + if (corners[1] > lastColumn || corners[3] < offsetColumn) { + hideTop = hideLeft = hideBottom = hideRight = true; + } + else { + if (corners[1] < offsetColumn) { + corners[1] = offsetColumn; + hideLeft = true; } - else { - if (corners[1] < offsetColumn) { - corners[1] = offsetColumn; - hideLeft = true; - } - if (corners[3] > offsetColumn + displayColumns - 1) { - corners[3] = offsetColumn + displayColumns - 1; - hideRight = true; - } + if (corners[3] > lastColumn) { + corners[3] = lastColumn; + hideRight = true; } } @@ -5196,6 +5217,121 @@ WalkontableBorder.prototype.hasSetting = function (setting) { } return !!setting; }; +/** + * WalkontableColumnStrategy + * @param containerSizeFn + * @param cellRanges + * @param sizeAtIndex + * @param strategy - all, last, none + * @constructor + */ +function WalkontableColumnStrategy(containerSizeFn, cellRanges, sizeAtIndex, strategy) { + var low + , high + , cur + , size + , i + , ilen; + + this.containerSizeFn = containerSizeFn; + this.cacheLength = 0; + this.cacheTotalSize = 0; + this.cache = {}; + this.visibleCellRanges = []; + + if (!cellRanges) { + return; + } + else if (cellRanges % 2 === 1) { + throw new Error('cellRanges must have even number of elements'); + } + else if (!this.isSorted(cellRanges)) { + throw new Error('cellRanges must be in ascending order'); + } + + //step 1 - determine cells that fit containerSize and cache their widths + for (i = 0, ilen = cellRanges.length / 2; i < ilen; i++) { + low = cellRanges[2 * i]; + high = cellRanges[2 * i + 1]; + cur = low; + while (cur <= high) { + size = sizeAtIndex(cur); + if (this.cacheTotalSize >= this.getContainerSize(this.cacheTotalSize + size)) { + break; + } + this.cache[cur] = size; + this.cacheTotalSize += size; + this.cacheLength++; + + if (cur === low) { + this.visibleCellRanges[2 * i] = cur; + } + this.visibleCellRanges[2 * i + 1] = cur; + + cur++; + } + if (this.cacheTotalSize >= this.getContainerSize(this.cacheTotalSize)) { + break; + } + } + + var containerSize = this.getContainerSize(this.cacheTotalSize) + , remainingSize = containerSize - this.cacheTotalSize; + + //step 2 - apply stretching strategy + if (strategy === 'all') { + var ratio = containerSize / this.cacheTotalSize; + var newSize; + + if (ratio > 1) { //if the ratio is smaller than 1 then it means last cell is not completely visible (=there is nothing to stretch) + for (i = 0, ilen = this.visibleCellRanges.length / 2; i < ilen; i++) { + low = this.visibleCellRanges[2 * i]; + high = this.visibleCellRanges[2 * i + 1]; + cur = low; + while (cur <= high) { + if (i === ilen - 1 && cur === high) { + this.cache[cur] += remainingSize; + } + else { + newSize = Math.floor(ratio * this.cache[cur]); + remainingSize -= newSize - this.cache[cur]; + this.cache[cur] = newSize; + } + cur++; + } + } + + } + } + else if (strategy === 'last') { + if (remainingSize > 0) { + this.cache[this.visibleCellRanges[this.visibleCellRanges.length - 1]] += remainingSize; + } + } +} + +WalkontableColumnStrategy.prototype.getContainerSize = function (proposedWidth) { + var containerSize = typeof this.containerSizeFn === 'function' ? this.containerSizeFn(proposedWidth) : this.containerSizeFn; + if (containerSize === void 0 || containerSize === null || containerSize < 1) { + containerSize = Infinity; + } + return containerSize; +}; + +WalkontableColumnStrategy.prototype.getSize = function (index) { + return this.cache[index]; +}; + +WalkontableColumnStrategy.prototype.isSorted = function (cellRanges) { + for (var i = 0, ilen = cellRanges.length; i < ilen; i++) { + if (i > 0) { + if (cellRanges[i - 1] > cellRanges[i]) { + return false; + } + } + } + return true; +}; function Walkontable(settings) { var self = this, originalHeaders = []; @@ -6159,7 +6295,7 @@ WalkontableSelection.prototype.draw = function (selectionsOnly) { var offsetRow = this.instance.getSetting('offsetRow') , lastVisibleRow = offsetRow + this.instance.getSetting('displayRows') - 1 , offsetColumn = this.instance.getSetting('offsetColumn') - , lastVisibleColumn = offsetColumn + this.instance.getSetting('displayColumns') - 1; + , lastVisibleColumn = this.instance.wtTable.getLastVisibleColumn(); if (this.selected.length) { corners = this.getCorners(); @@ -6230,7 +6366,7 @@ function WalkontableSettings(instance, settings) { data: void 0, offsetRow: 0, offsetColumn: 0, - frozenColumns: null, + rowHeaders: null, columnHeaders: null, //this must be a function in format: function (col, TH) {} totalRows: void 0, totalColumns: void 0, @@ -6238,12 +6374,7 @@ function WalkontableSettings(instance, settings) { height: null, cellRenderer: function (row, column, TD) { var cellData = that.getSetting('data', row, column); - if (cellData !== void 0) { - that.instance.wtDom.avoidInnerHTML(TD, cellData); - } - else { - this.wtDom.empty(TD); - } + that.instance.wtDom.avoidInnerHTML(TD, cellData === void 0 || cellData === null ? '' : cellData); }, columnWidth: 50, selections: null, @@ -6361,29 +6492,6 @@ WalkontableSettings.prototype.displayRows = function () { } }; -WalkontableSettings.prototype.displayColumns = function () { - var estimated - , calculated; - - if (this.settings['width']) { - if (typeof this.settings['width'] !== 'number') { - throw new Error('Walkontable width parameter must be a number (' + typeof this.settings['width'] + ' given)'); - } - estimated = Math.ceil(this.settings['width'] / 50); //silly assumption but should be fine for now - calculated = this.getSetting('totalColumns') - this.getSetting('offsetColumn'); - if (calculated < 0) { - this.update('offsetColumn', Math.max(0, this.getSetting('totalColumns') - estimated)); - return estimated; - } - else { - return Math.min(estimated, calculated); - } - } - else { - return this.getSetting('totalColumns'); - } -}; - WalkontableSettings.prototype.viewportRows = function () { if (this.instance.wtTable.visibilityEdgeRow !== null) { return this.instance.wtTable.visibilityEdgeRow - this.instance.wtTable.visibilityStartRow; @@ -6395,7 +6503,7 @@ WalkontableSettings.prototype.viewportColumns = function () { if (this.instance.wtTable.visibilityEdgeColumn !== null) { return this.instance.wtTable.visibilityEdgeColumn - this.instance.wtTable.visibilityStartColumn; } - return this.getSetting('displayColumns'); + return this.instance.wtTable.getLastVisibleColumn(); }; var FLAG_VISIBLE_HORIZONTAL = 0x1; // 000001 var FLAG_VISIBLE_VERTICAL = 0x2; // 000010 @@ -6524,20 +6632,29 @@ WalkontableTable.prototype.refreshHiderDimensions = function () { }; WalkontableTable.prototype.refreshStretching = function () { - var stretchH = this.instance.getSetting('stretchH') - , totalColumns = this.instance.getSetting('totalColumns') - , displayColumns = this.instance.getSetting('displayColumns') - , displayTds = Math.min(displayColumns, totalColumns) - , offsetColumn = this.instance.getSetting('offsetColumn') - , frozenColumns = this.instance.getSetting('frozenColumns') - , frozenColumnsCount = frozenColumns ? frozenColumns.length : 0; + var instance = this.instance + , stretchH = instance.getSetting('stretchH') + , scrollH = instance.getSetting('scrollH') + , scrollbarWidth = instance.getSetting('scrollbarWidth') + , totalColumns = instance.getSetting('totalColumns') + , offsetColumn = instance.getSetting('offsetColumn') + , rowHeaders = instance.getSetting('rowHeaders') + , rowHeaderWidth = rowHeaders && rowHeaders.length ? 50 : 0 + , containerWidth = this.instance.getSetting('width') - rowHeaderWidth; - if (!this.instance.hasSetting('columnWidth')) { - return; - } + var containerWidthFn = function (cacheWidth) { + if (scrollH === 'scroll' || (scrollH === 'auto' && cacheWidth > containerWidth)) { + return containerWidth - scrollbarWidth; + } + return containerWidth; + }; + + var columnWidthFn = function (index) { + return instance.getSetting('columnWidth', index) + }; if (stretchH === 'hybrid') { - if (this.instance.wtScroll.wtScrollbarH.visible) { + if (offsetColumn > 0) { stretchH = 'last'; } else { @@ -6545,110 +6662,41 @@ WalkontableTable.prototype.refreshStretching = function () { } } - var TD; - if (this.instance.wtTable.TBODY.firstChild && this.instance.wtTable.TBODY.firstChild.firstChild) { - TD = this.instance.wtTable.TBODY.firstChild.firstChild; - } - else if (this.instance.wtTable.THEAD.firstChild && this.instance.wtTable.THEAD.firstChild.firstChild) { - TD = this.instance.wtTable.THEAD.firstChild.firstChild; - } - - if (frozenColumnsCount) { - TD = TD.nextSibling; - } - - if (!TD) { - return; - } - - var cellOffset = this.instance.wtDom.offset(TD) - , tableOffset = this.instance.wtTable.tableOffset - , rowHeaderWidth = cellOffset.left - tableOffset.left - , widths = [] - , widthSum = 0 - , c; - for (c = 0; c < displayTds; c++) { - widths.push(this.instance.getSetting('columnWidth', offsetColumn + c)); - widthSum += widths[c]; - } - var domWidth = widthSum + rowHeaderWidth; - - if (stretchH === 'all' || stretchH === 'last') { - var containerWidth = this.instance.getSetting('width'); - if (this.instance.wtScroll.wtScrollbarV.visible) { - containerWidth -= this.instance.getSetting('scrollbarWidth'); - } - - var diff = containerWidth - domWidth; - if (diff > 0) { - if (stretchH === 'all') { - var newWidth; - var remainingDiff = diff; - var ratio = diff / widthSum; - - for (c = 0; c < displayTds; c++) { - if (widths[c]) { - if (c === displayTds - 1) { - newWidth = widths[c] + remainingDiff; - } - else { - newWidth = widths[c] + Math.floor(ratio * widths[c]); - remainingDiff -= Math.floor(ratio * widths[c]); - } - } - widths[c] = newWidth; - } - } - else { - if (widths[widths.length - 1]) { - widths[widths.length - 1] = widths[widths.length - 1] + diff; - } - } - } - } - - for (c = 0; c < displayTds; c++) { - if (widths[c]) { - this.COLGROUP.childNodes[c + frozenColumnsCount].style.width = widths[c] + 'px'; - } - else { - this.COLGROUP.childNodes[c + frozenColumnsCount].style.width = ''; - } - } + this.columnStrategy = new WalkontableColumnStrategy(containerWidthFn, totalColumns ? [offsetColumn, totalColumns - 1] : null, columnWidthFn, stretchH); }; WalkontableTable.prototype.adjustAvailableNodes = function () { var instance = this.instance , totalRows = instance.getSetting('totalRows') - , totalColumns = instance.getSetting('totalColumns') , displayRows = instance.getSetting('displayRows') - , displayColumns = instance.getSetting('displayColumns') , displayTds - , frozenColumns = instance.getSetting('frozenColumns') - , frozenColumnsCount = frozenColumns ? frozenColumns.length : 0 + , rowHeaders = instance.getSetting('rowHeaders') + , rowHeadersCount = rowHeaders ? rowHeaders.length : 0 , TR , c; displayRows = Math.min(displayRows, totalRows); - displayTds = Math.min(displayColumns, totalColumns); + this.refreshStretching(); + displayTds = this.columnStrategy.cacheLength; + //adjust COLGROUP - while (this.colgroupChildrenLength < displayTds + frozenColumnsCount) { + while (this.colgroupChildrenLength < displayTds + rowHeadersCount) { this.COLGROUP.appendChild(document.createElement('COL')); this.colgroupChildrenLength++; } - while (this.colgroupChildrenLength > displayTds + frozenColumnsCount) { + while (this.colgroupChildrenLength > displayTds + rowHeadersCount) { this.COLGROUP.removeChild(this.COLGROUP.lastChild); this.colgroupChildrenLength--; } //adjust THEAD if (this.instance.hasSetting('columnHeaders')) { - while (this.theadChildrenLength < displayTds + frozenColumnsCount) { + while (this.theadChildrenLength < displayTds + rowHeadersCount) { this.THEAD.firstChild.appendChild(document.createElement('TH')); this.theadChildrenLength++; } - while (this.theadChildrenLength > displayTds + frozenColumnsCount) { + while (this.theadChildrenLength > displayTds + rowHeadersCount) { this.THEAD.firstChild.removeChild(this.THEAD.firstChild.lastChild); this.theadChildrenLength--; } @@ -6657,7 +6705,7 @@ WalkontableTable.prototype.adjustAvailableNodes = function () { //adjust TBODY while (this.tbodyChildrenLength < displayRows) { TR = document.createElement('TR'); - for (c = 0; c < frozenColumnsCount; c++) { + for (c = 0; c < rowHeadersCount; c++) { TR.appendChild(document.createElement('TH')); } this.TBODY.appendChild(TR); @@ -6672,13 +6720,13 @@ WalkontableTable.prototype.adjustAvailableNodes = function () { var trChildrenLength; for (var r = 0, rlen = TRs.length; r < rlen; r++) { trChildrenLength = TRs[r].childNodes.length; - while (trChildrenLength < displayTds + frozenColumnsCount) { + while (trChildrenLength < displayTds + rowHeadersCount) { var TD = document.createElement('TD'); TD.setAttribute('tabindex', 10000); //http://www.barryvan.com.au/2009/01/onfocus-and-onblur-for-divs-in-fx/; 32767 is max tabindex for IE7,8 TRs[r].appendChild(TD); trChildrenLength++; } - while (trChildrenLength > displayTds + frozenColumnsCount) { + while (trChildrenLength > displayTds + rowHeadersCount) { TRs[r].removeChild(TRs[r].lastChild); trChildrenLength--; } @@ -6706,26 +6754,23 @@ WalkontableTable.prototype._doDraw = function () { , offsetRow = this.instance.getSetting('offsetRow') , offsetColumn = this.instance.getSetting('offsetColumn') , totalRows = this.instance.getSetting('totalRows') - , totalColumns = this.instance.getSetting('totalColumns') , displayRows = this.instance.getSetting('displayRows') - , displayColumns = this.instance.getSetting('displayColumns') - , displayTds - , frozenColumns = this.instance.getSetting('frozenColumns') - , frozenColumnsCount = frozenColumns ? frozenColumns.length : 0 + , displayTds = this.columnStrategy.cacheLength + , rowHeaders = this.instance.getSetting('rowHeaders') + , rowHeadersCount = rowHeaders ? rowHeaders.length : 0 , TR , TH , TD , cellData; displayRows = Math.min(displayRows, totalRows); - displayTds = Math.min(displayColumns, totalColumns); //draw COLGROUP for (c = 0; c < this.colgroupChildrenLength; c++) { - if (c < frozenColumnsCount) { + if (c < rowHeadersCount) { this.wtDom.addClass(this.COLGROUP.childNodes[c], 'rowHeader'); - if (typeof frozenColumns[c] === "function") { - frozenColumns[c](null, this.COLGROUP.childNodes[c]) + if (typeof rowHeaders[c] === "function") { + rowHeaders[c](null, this.COLGROUP.childNodes[c]) } } else { @@ -6733,14 +6778,12 @@ WalkontableTable.prototype._doDraw = function () { } } - this.refreshStretching(); //needed here or otherwise scrollbarH is not shown - //draw THEAD - if (frozenColumnsCount && this.instance.hasSetting('columnHeaders')) { - for (c = 0; c < frozenColumnsCount; c++) { + if (rowHeadersCount && this.instance.hasSetting('columnHeaders')) { + for (c = 0; c < rowHeadersCount; c++) { TH = this.THEAD.childNodes[0].childNodes[c]; - if (typeof frozenColumns[c] === "function") { - frozenColumns[c](null, TH); + if (typeof rowHeaders[c] === "function") { + rowHeaders[c](null, TH); } else { this.wtDom.empty(TH); @@ -6751,10 +6794,12 @@ WalkontableTable.prototype._doDraw = function () { } } - if (this.instance.hasSetting('columnHeaders')) { - for (c = 0; c < displayTds; c++) { - this.instance.getSetting('columnHeaders', offsetColumn + c, this.THEAD.childNodes[0].childNodes[frozenColumnsCount + c]); + var columnHeaders = this.instance.hasSetting('columnHeaders'); + for (c = 0; c < displayTds; c++) { + if (columnHeaders) { + this.instance.getSetting('columnHeaders', offsetColumn + c, this.THEAD.childNodes[0].childNodes[rowHeadersCount + c]); } + this.COLGROUP.childNodes[c + rowHeadersCount].style.width = this.columnStrategy.getSize(offsetColumn + c) + 'px'; } //draw TBODY @@ -6763,14 +6808,14 @@ WalkontableTable.prototype._doDraw = function () { this.visibilityStartColumn = offsetColumn; for (r = 0; r < displayRows; r++) { TR = this.TBODY.childNodes[r]; - for (c = 0; c < frozenColumnsCount; c++) { //in future use nextSibling; http://jsperf.com/nextsibling-vs-indexed-childnodes + for (c = 0; c < rowHeadersCount; c++) { //in future use nextSibling; http://jsperf.com/nextsibling-vs-indexed-childnodes TH = TR.childNodes[c]; - cellData = typeof frozenColumns[c] === "function" ? frozenColumns[c](offsetRow + r, TH) : frozenColumns[c]; + cellData = typeof rowHeaders[c] === "function" ? rowHeaders[c](offsetRow + r, TH) : rowHeaders[c]; if (cellData !== void 0) { this.wtDom.avoidInnerHTML(TH, cellData); } /* - we can assume that frozenColumns[c] function took care of inserting content into TH + we can assume that rowHeaders[c] function took care of inserting content into TH else { TH.innerHTML = ''; }*/ @@ -6785,7 +6830,7 @@ WalkontableTable.prototype._doDraw = function () { break; } else { - TD = TR.childNodes[c + frozenColumnsCount]; + TD = TR.childNodes[c + rowHeadersCount]; TD.className = ''; TD.removeAttribute('style'); this.instance.getSetting('cellRenderer', offsetRow + r, offsetColumn + c, TD); @@ -6825,7 +6870,6 @@ WalkontableTable.prototype._doDraw = function () { WalkontableTable.prototype.refreshPositions = function (selectionsOnly) { this.instance.wtScroll.refreshScrollbars(); this.refreshHiderDimensions(); - this.refreshStretching(); this.refreshSelections(selectionsOnly); }; @@ -6853,8 +6897,6 @@ WalkontableTable.prototype.recalcViewportCells = function () { WalkontableTable.prototype.isCellVisible = function (r, c, TD) { var out = 0 - , scrollV = this.instance.getSetting('scrollV') - , scrollH = this.instance.getSetting('scrollH') , cellOffset = this.wtDom.offset(TD) , tableOffset = this.tableOffset , innerOffsetTop = cellOffset.top - tableOffset.top @@ -6924,12 +6966,12 @@ WalkontableTable.prototype.getCell = function (coords) { if (coords[1] < offsetColumn) { return -3; //column before viewport } - else if (coords[1] > offsetColumn + this.instance.getSetting('displayColumns') - 1) { + else if (coords[1] > this.getLastVisibleColumn()) { return -4; //column after viewport } else { - var frozenColumns = this.instance.getSetting('frozenColumns') - , frozenColumnsCount = (frozenColumns ? frozenColumns.length : 0) + var rowHeaders = this.instance.getSetting('rowHeaders') + , rowHeadersCount = (rowHeaders ? rowHeaders.length : 0) , tr = this.TBODY.childNodes[coords[0] - offsetRow]; if (typeof tr === "undefined") { //this block is only needed in async mode @@ -6937,19 +6979,23 @@ WalkontableTable.prototype.getCell = function (coords) { tr = this.TBODY.childNodes[coords[0] - offsetRow]; } - return tr.childNodes[coords[1] - offsetColumn + frozenColumnsCount]; + return tr.childNodes[coords[1] - offsetColumn + rowHeadersCount]; } } }; WalkontableTable.prototype.getCoords = function (TD) { - var frozenColumns = this.instance.getSetting('frozenColumns') - , frozenColumnsCount = frozenColumns ? frozenColumns.length : 0; + var rowHeaders = this.instance.getSetting('rowHeaders') + , rowHeadersCount = rowHeaders ? rowHeaders.length : 0; return [ this.wtDom.prevSiblings(TD.parentNode).length + this.instance.getSetting('offsetRow'), - TD.cellIndex + this.instance.getSetting('offsetColumn') - frozenColumnsCount + TD.cellIndex + this.instance.getSetting('offsetColumn') - rowHeadersCount ]; }; + +WalkontableTable.prototype.getLastVisibleColumn = function () { + return this.columnStrategy.visibleCellRanges[this.columnStrategy.visibleCellRanges.length - 1] +}; function WalkontableWheel(instance) { var that = this; diff --git a/package.json b/package.json index 84e10dc8b0b..0555c4a39d9 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "url": "https://github.com/warpech/jquery-handsontable/issues" }, "author": "Marcin Warpechowski ", - "version": "0.8.17", + "version": "0.8.18", "devDependencies": { "grunt": "~0.4.0", "grunt-replace": "~0.4.0",