/** * Handsontable 0.7.0-beta * 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/ */ /*jslint white: true, browser: true, plusplus: true, indent: 4, maxerr: 50 */ var Handsontable = { //class namespace extension: {}, //extenstion namespace helper: {} //helper namespace }; (function ($, window, Handsontable) { "use strict"; /** * Handsontable constructor * @param rootElement The jQuery element in which Handsontable DOM will be inserted * @param settings * @constructor */ Handsontable.Core = function (rootElement, settings) { this.rootElement = rootElement; var priv, datamap, grid, selection, editproxy, highlight, autofill, self = this; priv = { settings: {}, selStart: null, selEnd: null, editProxy: false, isPopulated: null, scrollable: null, undoRedo: null, extensions: {}, colToProp: [], propToCol: {}, dataSchema: null, dataType: 'array' }; var hasMinWidthProblem = ($.browser.msie && (parseInt($.browser.version, 10) <= 7)); /** * Used to get over IE7 not respecting CSS min-width (and also not showing border around empty cells) * @param {Element} td */ this.minWidthFix = function (td) { if (hasMinWidthProblem) { if (td.className) { td.innerHTML = '
' + td.innerHTML + '
'; } else { td.innerHTML = '
' + td.innerHTML + '
'; } } }; var hasPositionProblem = ($.browser.msie && (parseInt($.browser.version, 10) <= 7)); /** * Used to get over IE7 returning negative position in demo/buttons.html * @param {Object} position */ this.positionFix = function (position) { if (hasPositionProblem) { if (position.top < 0) { position.top = 0; } if (position.left < 0) { position.left = 0; } } }; datamap = { recursiveDuckSchema: function (obj) { var schema; if ($.isPlainObject(obj)) { schema = {}; for (var i in obj) { if (obj.hasOwnProperty(i)) { if ($.isPlainObject(obj[i])) { schema[i] = datamap.recursiveDuckSchema(obj[i]); } else { schema[i] = null; } } } } else { schema = []; } return schema; }, recursiveDuckColumns: function (schema, lastCol, parent) { var prop, i; if (typeof lastCol === 'undefined') { lastCol = 0; parent = ''; } if ($.isPlainObject(schema)) { for (i in schema) { if (schema.hasOwnProperty(i)) { if (schema[i] === null) { prop = parent + i; priv.colToProp.push(prop); priv.propToCol[prop] = lastCol; lastCol++; } else { lastCol = datamap.recursiveDuckColumns(schema[i], lastCol, i + '.'); } } } } return lastCol; }, createMap: function () { if (typeof datamap.getSchema() === "undefined") { throw new Error("trying to create `columns` definition but you didnt' provide `schema` nor `data`"); } var i, ilen, schema = datamap.getSchema(); priv.colToProp = []; priv.propToCol = {}; if (priv.settings.columns) { for (i = 0, ilen = priv.settings.columns.length; i < ilen; i++) { priv.colToProp[i] = priv.settings.columns[i].data; priv.propToCol[priv.settings.columns[i].data] = i; } } else { datamap.recursiveDuckColumns(schema); } }, colToProp: function (col) { if (typeof priv.colToProp[col] !== 'undefined') { return priv.colToProp[col]; } else { return col; } }, propToCol: function (prop) { if (typeof priv.propToCol[prop] !== 'undefined') { return priv.propToCol[prop]; } else { return prop; } }, getSchema: function () { return priv.settings.dataSchema || priv.duckDataSchema; }, /** * Creates row at the bottom of the data array * @param {Object} [coords] Optional. Coords of the cell before which the new row will be inserted */ createRow: function (coords) { var row; if (priv.dataType === 'array') { row = []; for (var c = 0; c < self.colCount; c++) { row.push(null); } } else { row = $.extend(true, {}, datamap.getSchema()); } if (!coords || coords.row >= self.rowCount) { priv.settings.data.push(row); } else { priv.settings.data.splice(coords.row, 0, row); } }, /** * Creates col at the right of the data array * @param {Object} [coords] Optional. Coords of the cell before which the new column will be inserted */ createCol: function (coords) { if (priv.dataType === 'object' || priv.settings.columns) { throw new Error("cannot create column with object data source or columns option specified"); } var r = 0; if (!coords || coords.col >= self.colCount) { for (; r < self.rowCount; r++) { if (typeof priv.settings.data[r] === 'undefined') { priv.settings.data[r] = []; } priv.settings.data[r].push(''); } } else { for (; r < self.rowCount; r++) { priv.settings.data[r].splice(coords.col, 0, ''); } } }, /** * Removes row at the bottom of the data array * @param {Object} [coords] Optional. Coords of the cell which row will be removed * @param {Object} [toCoords] Required if coords is defined. Coords of the cell until which all rows will be removed */ removeRow: function (coords, toCoords) { if (!coords || coords.row === self.rowCount - 1) { priv.settings.data.pop(); } else { priv.settings.data.splice(coords.row, toCoords.row - coords.row + 1); } }, /** * Removes col at the right of the data array * @param {Object} [coords] Optional. Coords of the cell which col will be removed * @param {Object} [toCoords] Required if coords is defined. Coords of the cell until which all cols will be removed */ removeCol: function (coords, toCoords) { if (priv.dataType === 'object' || priv.settings.columns) { throw new Error("cannot remove column with object data source or columns option specified"); } var r = 0; if (!coords || coords.col === self.colCount - 1) { for (; r < self.rowCount; r++) { priv.settings.data[r].pop(); } } else { var howMany = toCoords.col - coords.col + 1; for (; r < self.rowCount; r++) { priv.settings.data[r].splice(coords.col, howMany); } } }, /** * Returns single value from the data array * @param {Number} row * @param {Number} prop */ get: function (row, prop) { if (typeof prop === 'string' && prop.indexOf('.') > -1) { var sliced = prop.split("."); var out = priv.settings.data[row]; for (var i = 0, ilen = sliced.length; i < ilen; i++) { out = out[sliced[i]]; if (typeof out === 'undefined') { return null; } } return out; } else { return priv.settings.data[row] ? priv.settings.data[row][prop] : null; } }, /** * Saves single value to the data array * @param {Number} row * @param {Number} prop * @param {String} value */ set: function (row, prop, value) { if (typeof prop === 'string' && prop.indexOf('.') > -1) { var sliced = prop.split("."); var out = priv.settings.data[row]; for (var i = 0, ilen = sliced.length - 1; i < ilen; i++) { out = out[sliced[i]]; } out[sliced[i]] = value; } else { priv.settings.data[row][prop] = value; } }, /** * Clears the data array */ clear: function () { for (var r = 0; r < self.rowCount; r++) { for (var c = 0; c < self.colCount; c++) { datamap.set(r, datamap.colToProp(c), ''); } } }, /** * Returns the data array * @return {Array} */ getAll: function () { return priv.settings.data; }, /** * Returns data range as array * @param {Object} start Start selection position * @param {Object} end End selection position * @return {Array} */ getRange: function (start, end) { var r, rlen, c, clen, output = [], row; rlen = Math.max(start.row, end.row); clen = Math.max(start.col, end.col); for (r = Math.min(start.row, end.row); r <= rlen; r++) { row = []; for (c = Math.min(start.col, end.col); c <= clen; c++) { row.push(datamap.get(r, datamap.colToProp(c))); } output.push(row); } return output; }, /** * Return data as text (tab separated columns) * @param {Object} start (Optional) Start selection position * @param {Object} end (Optional) End selection position * @return {String} */ getText: function (start, end) { return SheetClip.stringify(datamap.getRange(start, end)); } }; grid = { /** * Alter grid * @param {String} action Possible values: "insert_row", "insert_col", "remove_row", "remove_col" * @param {Object} coords * @param {Object} [toCoords] Required only for actions "remove_row" and "remove_col" */ alter: function (action, coords, toCoords) { var oldData, newData, changes, r, rlen, c, clen, result; oldData = $.extend(true, [], datamap.getAll()); switch (action) { case "insert_row": datamap.createRow(coords); self.view.createRow(coords); self.blockedCols.refresh(); if (priv.selStart && priv.selStart.row >= coords.row) { priv.selStart.row = priv.selStart.row + 1; selection.transformEnd(1, 0); } else { selection.transformEnd(0, 0); //refresh selection, otherwise arrow movement does not work } break; case "insert_col": datamap.createCol(coords); self.view.createCol(coords); self.blockedRows.refresh(); if (priv.selStart && priv.selStart.col >= coords.col) { priv.selStart.col = priv.selStart.col + 1; selection.transformEnd(0, 1); } else { selection.transformEnd(0, 0); //refresh selection, otherwise arrow movement does not work } break; case "remove_row": datamap.removeRow(coords, toCoords); self.view.removeRow(coords, toCoords); result = grid.keepEmptyRows(); if (!result) { self.blockedCols.refresh(); } selection.transformEnd(0, 0); //refresh selection, otherwise arrow movement does not work break; case "remove_col": datamap.removeCol(coords, toCoords); self.view.removeCol(coords, toCoords); result = grid.keepEmptyRows(); if (!result) { self.blockedRows.refresh(); } selection.transformEnd(0, 0); //refresh selection, otherwise arrow movement does not work break; } changes = []; newData = datamap.getAll(); for (r = 0, rlen = newData.length; r < rlen; r++) { for (c = 0, clen = newData[r].length; c < clen; c++) { changes.push([r, c, oldData[r] ? oldData[r][c] : null, newData[r][c]]); } } self.rootElement.triggerHandler("datachange.handsontable", [changes, 'alter']); }, /** * Makes sure there are empty rows at the bottom of the table * @return recreate {Boolean} TRUE if row or col was added or removed */ keepEmptyRows: function () { var r, c, rlen, clen, emptyRows = 0, emptyCols = 0, recreateRows = false, recreateCols = false, val; var $tbody = $(priv.tableBody); //count currently empty rows rows : for (r = self.countRows() - 1; r >= 0; r--) { for (c = 0, clen = self.colCount; c < clen; c++) { val = datamap.get(r, datamap.colToProp(c)); if (val !== '' && val !== null && typeof val !== 'undefined') { break rows; } } emptyRows++; } //should I add empty rows to data source to meet startRows? rlen = self.countRows(); if (rlen < priv.settings.startRows) { for (r = 0; r < priv.settings.startRows - rlen; r++) { datamap.createRow(); } } //should I add empty rows to table view to meet startRows? if (self.rowCount < priv.settings.startRows) { for (; self.rowCount < priv.settings.startRows; emptyRows++) { self.view.createRow(); recreateRows = true; } } //should I add empty rows to meet minSpareRows? if (emptyRows < priv.settings.minSpareRows) { for (; emptyRows < priv.settings.minSpareRows; emptyRows++) { datamap.createRow(); self.view.createRow(); recreateRows = true; } } //should I add empty rows to meet minHeight //WARNING! jQuery returns 0 as height() for container which is not :visible. this will lead to a infinite loop if (priv.settings.minHeight) { if ($tbody.height() > 0 && $tbody.height() <= priv.settings.minHeight) { while ($tbody.height() <= priv.settings.minHeight) { datamap.createRow(); self.view.createRow(); recreateRows = true; } } } //count currently empty cols if (self.countRows() - 1 > 0) { cols : for (c = self.colCount - 1; c >= 0; c--) { for (r = 0; r < self.countRows(); r++) { val = datamap.get(r, datamap.colToProp(c)); if (val !== '' && val !== null && typeof val !== 'undefined') { break cols; } } emptyCols++; } } //should I add empty cols to meet startCols? if (self.colCount < priv.settings.startCols) { for (; self.colCount < priv.settings.startCols; emptyCols++) { if (!priv.settings.columns) { datamap.createCol(); } self.view.createCol(); recreateCols = true; } } //should I add empty cols to meet minSpareCols? if (priv.dataType === 'array' && emptyCols < priv.settings.minSpareCols) { for (; emptyCols < priv.settings.minSpareCols; emptyCols++) { if (!priv.settings.columns) { datamap.createCol(); } self.view.createCol(); recreateCols = true; } } //should I add empty rows to meet minWidth //WARNING! jQuery returns 0 as width() for container which is not :visible. this will lead to a infinite loop if (priv.settings.minWidth) { if ($tbody.width() > 0 && $tbody.width() <= priv.settings.minWidth) { while ($tbody.width() <= priv.settings.minWidth) { if (!priv.settings.columns) { datamap.createCol(); } self.view.createCol(); recreateCols = true; } } } if (!recreateRows && priv.settings.enterBeginsEditing) { for (; ((priv.settings.startRows && self.rowCount > priv.settings.startRows) && (priv.settings.minSpareRows && emptyRows > priv.settings.minSpareRows) && (!priv.settings.minHeight || $tbody.height() - $tbody.find('tr:last').height() - 4 > priv.settings.minHeight)); emptyRows--) { self.view.removeRow(); datamap.removeRow(); recreateRows = true; } } if (recreateRows && priv.selStart) { //if selection is outside, move selection to last row if (priv.selStart.row > self.rowCount - 1) { priv.selStart.row = self.rowCount - 1; if (priv.selEnd.row > priv.selStart.row) { priv.selEnd.row = priv.selStart.row; } } else if (priv.selEnd.row > self.rowCount - 1) { priv.selEnd.row = self.rowCount - 1; if (priv.selStart.row > priv.selEnd.row) { priv.selStart.row = priv.selEnd.row; } } } if (priv.settings.columns && priv.settings.columns.length) { clen = priv.settings.columns.length; if (self.colCount !== clen) { while (self.colCount > clen) { self.view.removeCol(); } while (self.colCount < clen) { self.view.createCol(); } recreateCols = true; } } else if (!recreateCols && priv.settings.enterBeginsEditing) { for (; ((priv.settings.startCols && self.colCount > priv.settings.startCols) && (priv.settings.minSpareCols && emptyCols > priv.settings.minSpareCols) && (!priv.settings.minWidth || $tbody.width() - $tbody.find('tr:last').find('td:last').width() - 4 > priv.settings.minWidth)); emptyCols--) { if (!priv.settings.columns) { datamap.removeCol(); } self.view.removeCol(); recreateCols = true; } } if (recreateCols && priv.selStart) { //if selection is outside, move selection to last row if (priv.selStart.col > self.colCount - 1) { priv.selStart.col = self.colCount - 1; if (priv.selEnd.col > priv.selStart.col) { priv.selEnd.col = priv.selStart.col; } } else if (priv.selEnd.col > self.colCount - 1) { priv.selEnd.col = self.colCount - 1; if (priv.selStart.col > priv.selEnd.col) { priv.selStart.col = priv.selEnd.col; } } } if (recreateRows || recreateCols) { selection.refreshBorders(); self.blockedCols.refresh(); self.blockedRows.refresh(); } return (recreateRows || recreateCols); }, /** * Is cell writable */ isCellWritable: function ($td, cellProperties) { if (priv.isPopulated) { var data = $td.data('readOnly'); if (typeof data === 'undefined') { return !cellProperties.readOnly; } else { return data; } } return true; }, /** * Populate cells at position with 2d array * @param {Object} start Start selection position * @param {Array} input 2d array * @param {Object} [end] End selection position (only for drag-down mode) * @param {String} [source="populateFromArray"] * @return {Object|undefined} ending td in pasted area (only if any cell was changed) */ populateFromArray: function (start, input, end, source) { var r, rlen, c, clen, td, endTd, setData = [], current = {}; rlen = input.length; if (rlen === 0) { return false; } current.row = start.row; current.col = start.col; for (r = 0; r < rlen; r++) { if ((end && current.row > end.row) || (!priv.settings.minSpareRows && current.row > self.rowCount - 1)) { break; } current.col = start.col; clen = input[r] ? input[r].length : 0; for (c = 0; c < clen; c++) { if ((end && current.col > end.col) || (!priv.settings.minSpareCols && current.col > self.colCount - 1)) { break; } td = self.view.getCellAtCoords(current); if (self.getCellMeta(current.row, current.col).isWritable) { var p = datamap.colToProp(current.col); setData.push([current.row, p, input[r][c]]); } current.col++; if (end && c === clen - 1) { c = -1; } } current.row++; if (end && r === rlen - 1) { r = -1; } } endTd = self.setDataAtCell(setData, null, null, source || 'populateFromArray'); return endTd; }, /** * Clears all cells in the grid */ clear: function () { var tds = self.view.getAllCells(); for (var i = 0, ilen = tds.length; i < ilen; i++) { $(tds[i]).empty(); self.minWidthFix(tds[i]); } }, /** * Returns the top left (TL) and bottom right (BR) selection coordinates * @param {Object[]} coordsArr * @returns {Object} */ getCornerCoords: function (coordsArr) { function mapProp(func, array, prop) { function getProp(el) { return el[prop]; } if (Array.prototype.map) { return func.apply(Math, array.map(getProp)); } return func.apply(Math, $.map(array, getProp)); } return { TL: { row: mapProp(Math.min, coordsArr, "row"), col: mapProp(Math.min, coordsArr, "col") }, BR: { row: mapProp(Math.max, coordsArr, "row"), col: mapProp(Math.max, coordsArr, "col") } }; }, /** * Returns array of td objects given start and end coordinates */ getCellsAtCoords: function (start, end) { var corners = grid.getCornerCoords([start, end]); var r, c, output = []; for (r = corners.TL.row; r <= corners.BR.row; r++) { for (c = corners.TL.col; c <= corners.BR.col; c++) { output.push(self.view.getCellAtCoords({ row: r, col: c })); } } return output; } }; this.selection = selection = { //this public assignment is only temporary /** * Starts selection range on given td object * @param td element */ setRangeStart: function (td) { selection.deselect(); priv.selStart = self.view.getCellCoords(td); selection.setRangeEnd(td); }, /** * Ends selection range on given td object * @param {Element} td * @param {Boolean} [scrollToCell=true] If true, viewport will be scrolled to range end */ setRangeEnd: function (td, scrollToCell) { var coords = self.view.getCellCoords(td); selection.end(coords); if (!priv.settings.multiSelect) { priv.selStart = coords; } self.rootElement.triggerHandler("selection.handsontable", [priv.selStart.row, priv.selStart.col, priv.selEnd.row, priv.selEnd.col]); self.rootElement.triggerHandler("selectionbyprop.handsontable", [priv.selStart.row, datamap.colToProp(priv.selStart.col), priv.selEnd.row, datamap.colToProp(priv.selEnd.col)]); selection.refreshBorders(); if (scrollToCell !== false) { self.view.scrollViewport(td); } }, /** * Redraws borders around cells */ refreshBorders: function () { editproxy.destroy(); if (!selection.isSelected()) { return; } if (autofill.handle) { autofill.showHandle(); } priv.currentBorder.appear([priv.selStart]); highlight.on(); editproxy.prepare(); }, /** * Setter/getter for selection start */ start: function (coords) { if (typeof coords !== 'undefined') { priv.selStart = coords; } return priv.selStart; }, /** * Setter/getter for selection end */ end: function (coords) { if (typeof coords !== 'undefined') { priv.selEnd = coords; } return priv.selEnd; }, /** * Returns information if we have a multiselection * @return {Boolean} */ isMultiple: function () { return !(priv.selEnd.col === priv.selStart.col && priv.selEnd.row === priv.selStart.row); }, /** * Selects cell relative to current cell (if possible) */ transformStart: function (rowDelta, colDelta, force) { if (priv.selStart.row + rowDelta > self.rowCount - 1) { if (force && priv.settings.minSpareRows > 0) { self.alter("insert_row", self.rowCount); } else if (priv.settings.autoWrapCol && priv.selStart.col + colDelta < self.colCount - 1) { rowDelta = 1 - self.rowCount; colDelta = 1; } } else if (priv.settings.autoWrapCol && priv.selStart.row + rowDelta < 0 && priv.selStart.col + colDelta >= 0) { rowDelta = self.rowCount - 1; colDelta = -1; } if (priv.selStart.col + colDelta > self.colCount - 1) { if (force && priv.settings.minSpareCols > 0) { self.alter("insert_col", self.colCount); } else if (priv.settings.autoWrapRow && priv.selStart.row + rowDelta < self.rowCount - 1) { rowDelta = 1; colDelta = 1 - self.colCount; } } else if (priv.settings.autoWrapRow && priv.selStart.col + colDelta < 0 && priv.selStart.row + rowDelta >= 0) { rowDelta = -1; colDelta = self.colCount - 1; } var td = self.view.getCellAtCoords({ row: (priv.selStart.row + rowDelta), col: priv.selStart.col + colDelta }); if (td) { selection.setRangeStart(td); } else { selection.setRangeStart(self.view.getCellAtCoords(priv.selStart)); //rerun some routines } }, /** * Sets selection end cell relative to current selection end cell (if possible) */ transformEnd: function (rowDelta, colDelta) { if (priv.selEnd) { var td = self.view.getCellAtCoords({ row: (priv.selEnd.row + rowDelta), col: priv.selEnd.col + colDelta }); if (td) { selection.setRangeEnd(td); } } }, /** * Returns true if currently there is a selection on screen, false otherwise * @return {Boolean} */ isSelected: function () { var selEnd = selection.end(); if (!selEnd || typeof selEnd.row === "undefined") { return false; } return true; }, /** * Returns true if coords is within current selection coords * @return {Boolean} */ inInSelection: function (coords) { if (!selection.isSelected()) { return false; } var sel = grid.getCornerCoords([priv.selStart, priv.selEnd]); return (sel.TL.row <= coords.row && sel.BR.row >= coords.row && sel.TL.col <= coords.col && sel.BR.col >= coords.col); }, /** * Deselects all selected cells */ deselect: function () { if (!selection.isSelected()) { return; } highlight.off(); priv.currentBorder.disappear(); if (autofill.handle) { autofill.hideHandle(); } selection.end(false); editproxy.destroy(); self.rootElement.triggerHandler('deselect.handsontable'); }, /** * Select all cells */ selectAll: function () { if (!priv.settings.multiSelect) { return; } var tds = self.view.getAllCells(); if (tds.length) { selection.setRangeStart(tds[0]); selection.setRangeEnd(tds[tds.length - 1], false); } }, /** * Deletes data from selected cells */ empty: function () { if (!selection.isSelected()) { return; } var corners = grid.getCornerCoords([priv.selStart, selection.end()]); var r, c, changes = []; for (r = corners.TL.row; r <= corners.BR.row; r++) { for (c = corners.TL.col; c <= corners.BR.col; c++) { if (self.getCellMeta(r, c).isWritable) { changes.push([r, datamap.colToProp(c), '']); } } } self.setDataAtCell(changes); } }; highlight = { /** * Create highlight border */ init: function () { priv.selectionBorder = new Handsontable.Border(self, { className: 'selection', bg: true }); }, /** * Show border around selected cells */ on: function () { if (!selection.isSelected()) { return false; } if (selection.isMultiple()) { priv.selectionBorder.appear([priv.selStart, selection.end()]); } else { priv.selectionBorder.disappear(); } }, /** * Hide border around selected cells */ off: function () { if (!selection.isSelected()) { return false; } priv.selectionBorder.disappear(); } }; this.autofill = autofill = { //this public assignment is only temporary handle: null, fillBorder: null, /** * Create fill handle and fill border objects */ init: function () { if (!autofill.handle) { autofill.handle = new Handsontable.FillHandle(self); autofill.fillBorder = new Handsontable.Border(self, { className: 'htFillBorder' }); $(autofill.handle.handle).on('dblclick', autofill.selectAdjacent); } else { autofill.handle.disabled = false; autofill.fillBorder.disabled = false; } self.rootElement.on('beginediting.handsontable', function () { autofill.hideHandle(); }); self.rootElement.on('finishediting.handsontable', function () { if (selection.isSelected()) { autofill.showHandle(); } }); }, /** * Hide fill handle and fill border permanently */ disable: function () { autofill.handle.disabled = true; autofill.fillBorder.disabled = true; }, /** * Selects cells down to the last row in the left column, then fills down to that cell */ selectAdjacent: function () { var select, data, r, maxR, c; if (selection.isMultiple()) { select = priv.selectionBorder.corners; } else { select = priv.currentBorder.corners; } autofill.fillBorder.disappear(); data = datamap.getAll(); rows : for (r = select.BR.row + 1; r < self.rowCount; r++) { for (c = select.TL.col; c <= select.BR.col; c++) { if (data[r][c]) { break rows; } } if (!!data[r][select.TL.col - 1] || !!data[r][select.BR.col + 1]) { maxR = r; } } if (maxR) { autofill.showBorder(self.view.getCellAtCoords({row: maxR, col: select.BR.col})); autofill.apply(); } }, /** * Apply fill values to the area in fill border, omitting the selection border */ apply: function () { var drag, select, start, end; autofill.handle.isDragged = 0; drag = autofill.fillBorder.corners; if (!drag) { return; } autofill.fillBorder.disappear(); if (selection.isMultiple()) { select = priv.selectionBorder.corners; } else { select = priv.currentBorder.corners; } if (drag.TL.row === select.TL.row && drag.TL.col < select.TL.col) { start = drag.TL; end = { row: drag.BR.row, col: select.TL.col - 1 }; } else if (drag.TL.row === select.TL.row && drag.BR.col > select.BR.col) { start = { row: drag.TL.row, col: select.BR.col + 1 }; end = drag.BR; } else if (drag.TL.row < select.TL.row && drag.TL.col === select.TL.col) { start = drag.TL; end = { row: select.TL.row - 1, col: drag.BR.col }; } else if (drag.BR.row > select.BR.row && drag.TL.col === select.TL.col) { start = { row: select.BR.row + 1, col: drag.TL.col }; end = drag.BR; } if (start) { grid.populateFromArray(start, SheetClip.parse(priv.editProxy.val()), end, 'autofill'); selection.setRangeStart(self.view.getCellAtCoords(drag.TL)); selection.setRangeEnd(self.view.getCellAtCoords(drag.BR)); } else { //reset to avoid some range bug selection.refreshBorders(); } }, /** * Show fill handle */ showHandle: function () { autofill.handle.appear([priv.selStart, priv.selEnd]); }, /** * Hide fill handle */ hideHandle: function () { autofill.handle.disappear(); }, /** * Show fill border */ showBorder: function (td) { var coords = self.view.getCellCoords(td); var corners = grid.getCornerCoords([priv.selStart, priv.selEnd]); if (priv.settings.fillHandle !== 'horizontal' && (corners.BR.row < coords.row || corners.TL.row > coords.row)) { coords = {row: coords.row, col: corners.BR.col}; } else if (priv.settings.fillHandle !== 'vertical') { coords = {row: corners.BR.row, col: coords.col}; } else { return; //wrong direction } autofill.fillBorder.appear([priv.selStart, priv.selEnd, coords]); } }; this.editproxy = editproxy = { //this public assignment is only temporary /** * Create input field */ init: function () { priv.editProxy = $('