');
priv.editProxyHolder.append(priv.editProxy);
function onClick(event) {
event.stopPropagation();
}
function onCut() {
setTimeout(function () {
selection.empty();
}, 100);
}
function onPaste() {
setTimeout(function () {
self.rootElement.one("datachange.handsontable", function (event, changes, source) {
if (changes.length) {
var last = changes[changes.length - 1];
selection.setRangeEnd({row: last[0], col: self.propToCol(last[1])});
}
});
var input = priv.editProxy.val().replace(/^[\r\n]*/g, '').replace(/[\r\n]*$/g, ''), //remove newline from the start and the end of the input
inputArray = SheetClip.parse(input),
coords = grid.getCornerCoords([priv.selStart.coords(), priv.selEnd.coords()]);
grid.populateFromArray(coords.TL, inputArray, {
row: Math.max(coords.BR.row, inputArray.length - 1 + coords.TL.row),
col: Math.max(coords.BR.col, inputArray[0].length - 1 + coords.TL.col)
}, 'paste');
}, 100);
}
var $body = $(document.body);
function onKeyDown(event) {
if ($body.children('.context-menu-list:visible').length) {
return;
}
priv.lastKeyCode = event.keyCode;
if (selection.isSelected()) {
var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey; //catch CTRL but not right ALT (which in some systems triggers ALT+CTRL)
if (Handsontable.helper.isPrintableChar(event.keyCode) && ctrlDown) {
if (event.keyCode === 65) { //CTRL + A
selection.selectAll(); //select all cells
}
else if (event.keyCode === 88 && $.browser.opera) { //CTRL + X
priv.editProxyHolder.triggerHandler('cut'); //simulate oncut for Opera
}
else if (event.keyCode === 86 && $.browser.opera) { //CTRL + V
priv.editProxyHolder.triggerHandler('paste'); //simulate onpaste for Opera
}
else if (event.keyCode === 89 || (event.shiftKey && event.keyCode === 90)) { //CTRL + Y or CTRL + SHIFT + Z
priv.undoRedo && priv.undoRedo.redo();
}
else if (event.keyCode === 90) { //CTRL + Z
priv.undoRedo && priv.undoRedo.undo();
}
return;
}
var rangeModifier = event.shiftKey ? selection.setRangeEnd : selection.setRangeStart;
switch (event.keyCode) {
case 38: /* arrow up */
if (event.shiftKey) {
selection.transformEnd(-1, 0);
}
else {
selection.transformStart(-1, 0);
}
event.preventDefault();
break;
case 9: /* tab */
var tabMoves = typeof priv.settings.tabMoves === 'function' ? priv.settings.tabMoves(event) : priv.settings.tabMoves;
if (event.shiftKey) {
selection.transformStart(-tabMoves.row, -tabMoves.col);
}
else {
selection.transformStart(tabMoves.row, tabMoves.col);
}
event.preventDefault();
break;
case 39: /* arrow right */
if (event.shiftKey) {
selection.transformEnd(0, 1);
}
else {
selection.transformStart(0, 1);
}
event.preventDefault();
break;
case 37: /* arrow left */
if (event.shiftKey) {
selection.transformEnd(0, -1);
}
else {
selection.transformStart(0, -1);
}
event.preventDefault();
break;
case 8: /* backspace */
case 46: /* delete */
selection.empty(event);
event.preventDefault();
break;
case 40: /* arrow down */
if (event.shiftKey) {
selection.transformEnd(1, 0); //expanding selection down with shift
}
else {
selection.transformStart(1, 0); //move selection down
}
event.preventDefault();
break;
case 113: /* F2 */
event.preventDefault(); //prevent Opera from opening Go to Page dialog
break;
case 13: /* return/enter */
var enterMoves = typeof priv.settings.enterMoves === 'function' ? priv.settings.enterMoves(event) : priv.settings.enterMoves;
if (event.shiftKey) {
selection.transformStart(-enterMoves.row, -enterMoves.col); //move selection up
}
else {
selection.transformStart(enterMoves.row, enterMoves.col); //move selection down
}
event.preventDefault(); //don't add newline to field
break;
case 36: /* home */
if (event.ctrlKey || event.metaKey) {
rangeModifier({row: 0, col: priv.selStart.col()});
}
else {
rangeModifier({row: priv.selStart.row(), col: 0});
}
break;
case 35: /* end */
if (event.ctrlKey || event.metaKey) {
rangeModifier({row: self.countRows() - 1, col: priv.selStart.col()});
}
else {
rangeModifier({row: priv.selStart.row(), col: self.countCols() - 1});
}
break;
case 33: /* pg up */
rangeModifier({row: 0, col: priv.selStart.col()});
break;
case 34: /* pg dn */
rangeModifier({row: self.countRows() - 1, col: priv.selStart.col()});
break;
default:
break;
}
}
}
priv.editProxy.on('click', onClick);
priv.editProxyHolder.on('cut', onCut);
priv.editProxyHolder.on('paste', onPaste);
priv.editProxyHolder.on('keydown', onKeyDown);
self.rootElement.append(priv.editProxyHolder);
},
/**
* Destroy current editor, if exists
* @param {Boolean} revertOriginal
*/
destroy: function (revertOriginal) {
if (typeof priv.editorDestroyer === "function") {
priv.editorDestroyer(revertOriginal);
priv.editorDestroyer = null;
}
},
/**
* Prepare text input to be displayed at given grid cell
*/
prepare: function () {
priv.editProxy.height(priv.editProxy.parent().innerHeight() - 4);
priv.editProxy.val(datamap.getText(priv.selStart.coords(), priv.selEnd.coords()));
setTimeout(editproxy.focus, 1);
priv.editorDestroyer = self.view.applyCellTypeMethod('editor', self.view.getCellAtCoords(priv.selStart.coords()), priv.selStart.coords(), priv.editProxy);
},
/**
* Sets focus to textarea
*/
focus: function () {
priv.editProxy[0].select();
}
};
this.init = function () {
editproxy.init();
bindEvents();
this.updateSettings(settings);
this.view = new Handsontable.TableView(this);
Handsontable.PluginHooks.run(self, 'afterInit');
};
validate = function (changes, source) {
var validated = $.Deferred();
var deferreds = [];
if (source === 'paste') {
//validate strict autocompletes
var process = function (i) {
var deferred = $.Deferred();
deferreds.push(deferred);
var originalVal = changes[i][3];
var lowercaseVal = typeof originalVal === 'string' ? originalVal.toLowerCase() : null;
return function (source) {
var found = false;
for (var s = 0, slen = source.length; s < slen; s++) {
if (originalVal === source[s]) {
found = true; //perfect match
break;
}
else if (lowercaseVal === source[s].toLowerCase()) {
changes[i][3] = source[s]; //good match, fix the case
found = true;
break;
}
}
if (!found) {
changes[i] = null;
}
deferred.resolve();
}
};
for (var i = changes.length - 1; i >= 0; i--) {
var cellProperties = self.getCellMeta(changes[i][0], changes[i][1]);
if (cellProperties.strict && cellProperties.source) {
var items = $.isFunction(cellProperties.source) ? cellProperties.source(changes[i][3], process(i)) : cellProperties.source;
if (items) {
process(i)(items)
}
}
}
}
$.when(deferreds).then(function () {
for (var i = changes.length - 1; i >= 0; i--) {
if (changes[i] === null) {
changes.splice(i, 1);
}
}
if (priv.settings.onBeforeChange && changes.length) {
var result = priv.settings.onBeforeChange.apply(self.rootElement[0], [changes, source]);
if (typeof result === 'function') {
$.when(result).then(function () {
validated.resolve();
});
}
else {
if (result === false) {
changes.splice(0, changes.length); //invalidate all changes (remove everything from array)
}
validated.resolve();
}
}
else {
validated.resolve();
}
});
return $.when(validated);
};
var bindEvents = function () {
self.rootElement.on("datachange.handsontable", function (event, changes, source) {
if (priv.settings.onChange) {
priv.settings.onChange.apply(self.rootElement[0], [changes, source]);
}
});
self.rootElement.on("selection.handsontable", function (event, row, col, endRow, endCol) {
if (priv.settings.onSelection) {
priv.settings.onSelection.apply(self.rootElement[0], [row, col, endRow, endCol]);
}
});
self.rootElement.on("selectionbyprop.handsontable", function (event, row, prop, endRow, endProp) {
if (priv.settings.onSelectionByProp) {
priv.settings.onSelectionByProp.apply(self.rootElement[0], [row, prop, endRow, endProp]);
}
});
};
/**
* Set data at given cell
* @public
* @param {Number|Array} row or array of changes in format [[row, col, value], ...]
* @param {Number} prop
* @param {String} value
* @param {String} [source='edit'] String that identifies how this change will be described in changes array (useful in onChange callback)
*/
this.setDataAtCell = function (row, prop, value, source) {
var changes, i, ilen;
if (typeof row === "object") { //is it an array of changes
changes = row;
}
else if ($.isPlainObject(value)) { //backwards compatibility
changes = value;
}
else {
changes = [
[row, prop, value]
];
}
for (i = 0, ilen = changes.length; i < ilen; i++) {
changes[i].splice(2, 0, datamap.get(changes[i][0], changes[i][1])); //add old value at index 2
}
validate(changes, source).then(function () { //when validate is resolved...
for (i = 0, ilen = changes.length; i < ilen; i++) {
row = changes[i][0];
prop = changes[i][1];
var col = datamap.propToCol(prop);
value = changes[i][3];
if (priv.settings.minSpareRows) {
while (row > self.countRows() - 1) {
datamap.createRow();
}
}
if (priv.dataType === 'array' && priv.settings.minSpareCols) {
while (col > self.countCols() - 1) {
datamap.createCol();
}
}
datamap.set(row, prop, value);
}
var recreated = grid.keepEmptyRows();
if (!recreated) {
selection.refreshBorders();
}
self.view.render();
self.rootElement.triggerHandler("datachange.handsontable", [changes, source || 'edit']);
});
};
/**
* Destroys current editor, renders and selects current cell. If revertOriginal != true, edited data is saved
* @param {Boolean} revertOriginal
*/
this.destroyEditor = function (revertOriginal) {
selection.refreshBorders(revertOriginal);
};
/**
* 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)
*/
this.populateFromArray = function (start, input, end, source) {
return grid.populateFromArray(start, input, end, source);
};
/**
* Returns the top left (TL) and bottom right (BR) selection coordinates
* @param {Object[]} coordsArr
* @returns {Object}
*/
this.getCornerCoords = function (coordsArr) {
return grid.getCornerCoords(coordsArr);
};
/**
* Returns current selection. Returns undefined if there is no selection.
* @public
* @return {Array} [topLeftRow, topLeftCol, bottomRightRow, bottomRightCol]
*/
this.getSelected = function () { //https://github.com/warpech/jquery-handsontable/issues/44 //cjl
if (selection.isSelected()) {
var coords = grid.getCornerCoords([priv.selStart.coords(), priv.selEnd.coords()]);
return [coords.TL.row, coords.TL.col, coords.BR.row, coords.BR.col];
}
};
/**
* Render visible data
* @public
* @param {Array} changes (Optional) If not given, all visible grid will be rerendered
* @param {String} source (Optional)
*/
this.render = function (changes, source) {
/*if (typeof changes === "undefined") {
changes = [];
var r
, c
, p
, val
, rlen = self.countRows()
, clen = (priv.settings.columns && priv.settings.columns.length) || priv.settings.startCols;
for (r = 0; r < rlen; r++) {
for (c = 0; c < clen; c++) {
p = datamap.colToProp(c);
val = datamap.get(r, p);
changes.push([r, p, val, val]);
}
}
}*/
if (self.view) {
self.view.render();
}
selection.refreshBorderDimensions();
priv.editProxy.triggerHandler('refreshBorder');
};
/**
* Load data from array
* @public
* @param {Array} data
*/
this.loadData = function (data, isInitial) {
priv.isPopulated = false;
priv.settings.data = data;
if ($.isPlainObject(priv.settings.dataSchema) || $.isPlainObject(data[0])) {
priv.dataType = 'object';
}
else {
priv.dataType = 'array';
}
if (data[0]) {
priv.duckDataSchema = datamap.recursiveDuckSchema(data[0]);
}
else {
priv.duckDataSchema = {};
}
datamap.createMap();
if (isInitial) {
var rlen = priv.settings.data.length;
if (priv.settings.startRows) {
while (priv.settings.startRows > rlen) {
datamap.createRow();
rlen++;
}
}
}
grid.keepEmptyRows();
grid.clear();
var changes = [];
var rlen = priv.settings.data.length; //recount number of rows in case some row was removed by keepEmptyRows
var clen = self.countCols();
for (var r = 0; r < rlen; r++) {
for (var c = 0; c < clen; c++) {
var p = datamap.colToProp(c);
changes.push([r, p, "", datamap.get(r, p)])
}
}
self.rootElement.triggerHandler('datachange.handsontable', [changes, 'loadData']);
self.render(changes, 'loadData');
priv.isPopulated = true;
self.clearUndo();
};
/**
* Return the current data object (the same that was passed by `data` configuration option or `loadData` method). Optionally you can provide cell range `r`, `c`, `r2`, `c2` to get only a fragment of grid data
* @public
* @param {Number} r (Optional) From row
* @param {Number} c (Optional) From col
* @param {Number} r2 (Optional) To row
* @param {Number} c2 (Optional) To col
* @return {Array|Object}
*/
this.getData = function (r, c, r2, c2) {
if (typeof r === 'undefined') {
return datamap.getAll();
}
else {
return datamap.getRange({row: r, col: c}, {row: r2, col: c2});
}
};
/**
* Update settings
* @public
*/
this.updateSettings = function (settings) {
var i, recreated;
if (typeof settings.rows !== "undefined") {
throw new Error("'rows' setting is no longer supported. do you mean startRows, minRows or maxRows?");
}
if (typeof settings.cols !== "undefined") {
throw new Error("'cols' setting is no longer supported. do you mean startCols, minCols or maxCols?");
}
if (typeof settings.undo !== "undefined") {
if (priv.undoRedo && settings.undo === false) {
priv.undoRedo = null;
}
else if (!priv.undoRedo && settings.undo === true) {
priv.undoRedo = new Handsontable.UndoRedo(self);
}
}
for (i in settings) {
if (i === 'data') {
continue; //loadData will be triggered later
}
else if (settings.hasOwnProperty(i)) {
priv.settings[i] = settings[i];
//launch extensions
if (Handsontable.extension[i]) {
priv.extensions[i] = new Handsontable.extension[i](self, settings[i]);
}
}
}
if (priv.settings.data === void 0) {
if (settings.data === void 0) {
settings.data = [];
var row;
for (var r = 0, rlen = priv.settings.startRows; r < rlen; r++) {
row = [];
for (var c = 0, clen = priv.settings.startCols; c < clen; c++) {
row.push(null);
}
settings.data.push(row);
}
}
}
if (settings.data !== void 0) {
self.loadData(settings.data, true);
}
else if (settings.columns !== void 0) {
datamap.createMap();
}
/*
TODO implement it in 0.8.0
if (typeof settings.fillHandle !== "undefined") {
if (autofill.handle && settings.fillHandle === false) {
autofill.disable();
}
else if (!autofill.handle && settings.fillHandle !== false) {
autofill.init();
}
}*/
recreated = grid.keepEmptyRows();
if (!recreated) {
selection.refreshBorders(null, true);
}
};
/**
* Returns current settings object
* @return {Object}
*/
this.getSettings = function () {
return priv.settings;
};
/**
* Clears grid
* @public
*/
this.clear = function () {
selection.selectAll();
selection.empty();
};
/**
* Return true if undo can be performed, false otherwise
* @public
*/
this.isUndoAvailable = function () {
return priv.undoRedo && priv.undoRedo.isUndoAvailable();
};
/**
* Return true if redo can be performed, false otherwise
* @public
*/
this.isRedoAvailable = function () {
return priv.undoRedo && priv.undoRedo.isRedoAvailable();
};
/**
* Undo last edit
* @public
*/
this.undo = function () {
priv.undoRedo && priv.undoRedo.undo();
};
/**
* Redo edit (used to reverse an undo)
* @public
*/
this.redo = function () {
priv.undoRedo && priv.undoRedo.redo();
};
/**
* Clears undo history
* @public
*/
this.clearUndo = function () {
priv.undoRedo && priv.undoRedo.clear();
};
/**
* Alters the grid
* @param {String} action See grid.alter for possible values
* @param {Number} from
* @param {Number} [to] Optional. Used only for actions "remove_row" and "remove_col"
* @public
*/
this.alter = function (action, from, to) {
if (typeof to === "undefined") {
to = from;
}
switch (action) {
case "insert_row":
case "remove_row":
grid.alter(action, {row: from, col: 0}, {row: to, col: 0});
break;
case "insert_col":
case "remove_col":
grid.alter(action, {row: 0, col: from}, {row: 0, col: to});
break;
default:
throw Error('There is no such action "' + action + '"');
break;
}
};
/**
* Returns
element corresponding to params row, col
* @param {Number} row
* @param {Number} col
* @public
* @return {Element}
*/
this.getCell = function (row, col) {
return self.view.getCellAtCoords({row: row, col: col});
};
/**
* Returns property name associated with column number
* @param {Number} col
* @public
* @return {String}
*/
this.colToProp = function (col) {
return datamap.colToProp(col);
};
/**
* Returns column number associated with property name
* @param {String} prop
* @public
* @return {Number}
*/
this.propToCol = function (prop) {
return datamap.propToCol(prop);
};
/**
* Return cell value at `row`, `col`
* @param {Number} row
* @param {Number} col
* @public
* @return {string}
*/
this.getDataAtCell = function (row, col) {
return datamap.get(row, datamap.colToProp(col));
};
/**
* Returns cell meta data object corresponding to params row, col
* @param {Number} row
* @param {Number} col
* @public
* @return {Object}
*/
this.getCellMeta = function (row, col) {
var cellProperites = {}
, prop = datamap.colToProp(col);
if (priv.settings.columns) {
cellProperites = $.extend(true, cellProperites, priv.settings.columns[col] || {});
}
if (priv.settings.cells) {
cellProperites = $.extend(true, cellProperites, priv.settings.cells(row, col, prop) || {});
}
cellProperites.isWritable = !cellProperites.readOnly;
Handsontable.PluginHooks.run(self, 'afterGetCellMeta', [row, col, cellProperites]);
return cellProperites;
};
/**
* Sets cell to be readonly
* @param {Number} row
* @param {Number} col
* @public
*/
this.setCellReadOnly = function (row, col) {
throw new Error('not implemented yet (Handsontable 0.8.0)');
};
/**
* Sets cell to be editable (removes readonly)
* @param {Number} row
* @param {Number} col
* @public
*/
this.setCellEditable = function (row, col) {
throw new Error('not implemented yet (Handsontable 0.8.0)');
};
/**
* Return array of row headers (if they are enabled). If param `row` given, return header at given row as string
* @param {Number} row (Optional)
* @return {Array|String}
*/
this.getRowHeader = function (row) {
if (priv.settings.rowHeaders === true) {
return row + 1;
}
else if (typeof priv.settings.rowHeaders === 'function') {
return priv.settings.colHeaders(row);
}
else if (Object.prototype.toString.call(priv.settings.rowHeaders) === '[object Array]') {
return priv.settings.colHeaders[row];
}
else {
return priv.settings.colHeaders;
}
};
/**
* Return array of col headers (if they are enabled). If param `col` given, return header at given col as string
* @param {Number} col (Optional)
* @return {Array|String}
*/
this.getColHeader = function (col) {
if (priv.settings.colHeaders === true) {
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);
}
return columnLabel;
}
else if (typeof priv.settings.colHeaders === 'function') {
return priv.settings.colHeaders(col);
}
else if (Object.prototype.toString.call(priv.settings.colHeaders) === '[object Array]') {
return priv.settings.colHeaders[col];
}
else {
return priv.settings.colHeaders;
}
};
/**
* Return total number of rows in grid
* @return {Number}
*/
this.countRows = function () {
return priv.settings.data.length;
};
/**
* Return total number of columns in grid
* @return {Number}
*/
this.countCols = function () {
if (priv.dataType === 'object') {
if (priv.settings.columns && priv.settings.columns.length) {
return priv.settings.columns.length;
}
else {
return priv.colToProp.length;
}
}
else if (priv.dataType === 'array') {
return Math.max((priv.settings.columns && priv.settings.columns.length) || 0, (priv.settings.data && priv.settings.data[0] && priv.settings.data[0].length) || 0);
}
};
/**
* Selects cell on grid. Optionally selects range to another cell
* @param {Number} row
* @param {Number} col
* @param {Number} [endRow]
* @param {Number} [endCol]
* @param {Boolean} [scrollToCell=true] If true, viewport will be scrolled to the selection
* @public
*/
this.selectCell = function (row, col, endRow, endCol, scrollToCell) {
if (typeof row !== 'number' || row < 0 || row >= self.countRows()) {
return false;
}
if (typeof col !== 'number' || col < 0 || col >= self.countCols()) {
return false;
}
if (typeof endRow !== "undefined") {
if (typeof endRow !== 'number' || endRow < 0 || endRow >= self.countRows()) {
return false;
}
if (typeof endCol !== 'number' || endCol < 0 || endCol >= self.countCols()) {
return false;
}
}
priv.selStart.coords({row: row, col: col});
if (typeof endRow === "undefined") {
selection.setRangeEnd({row: row, col: col}, scrollToCell);
}
else {
selection.setRangeEnd({row: row, col: col}, scrollToCell);
}
};
this.selectCellByProp = function (row, prop, endRow, endProp, scrollToCell) {
arguments[1] = datamap.propToCol(arguments[1]);
if (typeof arguments[3] !== "undefined") {
arguments[3] = datamap.propToCol(arguments[3]);
}
return self.selectCell.apply(self, arguments);
};
/**
* Deselects current sell selection on grid
* @public
*/
this.deselectCell = function () {
selection.deselect();
};
/**
* Remove grid from DOM
* @public
*/
this.destroy = function () {
self.rootElement.empty();
self.rootElement.removeData('handsontable');
};
/**
* Handsontable version
*/
this.version = '0.8.0-beta1'; //inserted by grunt from package.json
};
var settings = {
'data': void 0,
'startRows': 5,
'startCols': 5,
'minRows': 0,
'minCols': 0,
'maxRows': Infinity,
'maxCols': Infinity,
'minSpareRows': 0,
'minSpareCols': 0,
'minHeight': 0,
'minWidth': 0,
'multiSelect': true,
'fillHandle': true,
'undo': true,
'outsideClickDeselects': true,
'enterBeginsEditing': true,
'enterMoves': {row: 1, col: 0},
'tabMoves': {row: 0, col: 1},
'autoWrapRow': false,
'autoWrapCol': false,
'viewEngine': 'walkontable'
};
$.fn.handsontable = function (action) {
var i, ilen, args, output = [], userSettings;
if (typeof action !== 'string') { //init
userSettings = action || {};
return this.each(function () {
var $this = $(this);
if ($this.data("handsontable")) {
instance = $this.data("handsontable");
instance.updateSettings(userSettings);
}
else {
var currentSettings = $.extend(true, {}, settings), instance;
for (i in userSettings) {
if (userSettings.hasOwnProperty(i)) {
currentSettings[i] = userSettings[i];
}
}
instance = new Handsontable.Core($this, currentSettings);
$this.data("handsontable", instance);
instance.init();
}
});
}
else {
args = [];
if (arguments.length > 1) {
for (i = 1, ilen = arguments.length; i < ilen; i++) {
args.push(arguments[i]);
}
}
this.each(function () {
output = $(this).data("handsontable")[action].apply(this, args);
});
return output;
}
};
/**
* Handsontable TableView constructor
* @param {Object} instance
*/
Handsontable.TableView = function (instance) {
var that = this;
this.instance = instance;
var settings = this.instance.getSettings();
instance.rootElement.addClass('handsontable');
var $table = $('');
instance.rootElement.prepend($table);
var overflow = instance.rootElement.css('overflow');
var myWidth = settings.width;
var myHeight = settings.height;
if (overflow === 'scroll' || overflow === 'auto') {
instance.rootElement.css('overflow', '');
if (settings.width === void 0 && parseInt(instance.rootElement.css('width')) > 0) {
myWidth = parseInt(instance.rootElement.css('width'));
instance.rootElement[0].style.width = '';
}
if (settings.height === void 0 && parseInt(instance.rootElement.css('height')) > 0) {
myHeight = parseInt(instance.rootElement.css('height'));
instance.rootElement[0].style.height = '';
}
}
var isMouseDown
, dragInterval;
$(document.body).on('mouseup', function () {
isMouseDown = false;
clearInterval(dragInterval);
dragInterval = null;
});
$table.on('mouseenter', function () {
if (dragInterval) { //if dragInterval was set (that means mouse was really outide of table, not over an element that is outside of in DOM
clearInterval(dragInterval);
dragInterval = null;
}
});
$table.on('mouseleave', function (event) {
var tolerance = 1 //this is needed because width() and height() contains stuff like cell borders
, offset = that.wt.wtDom.offset($table[0])
, offsetTop = offset.top + tolerance
, offsetLeft = offset.left + tolerance
, width = $table.width() - 2 * tolerance
, height = $table.height() - 2 * tolerance
, method
, row = 0
, col = 0
, dragFn;
if (event.pageY < offsetTop) { //top edge crossed
row = -1;
method = 'scrollVertical';
}
else if (event.pageY >= offsetTop + height) { //bottom edge crossed
row = 1;
method = 'scrollVertical';
}
else if (event.pageX < offsetLeft) { //left edge crossed
col = -1;
method = 'scrollHorizontal';
}
else if (event.pageX >= offsetLeft + width) { //right edge crossed
col = 1;
method = 'scrollHorizontal';
}
if (method) {
dragFn = function () {
if (isMouseDown) {
instance.selection.transformEnd(row, col);
that.wt[method](row + col).draw();
}
};
dragFn();
dragInterval = setInterval(dragFn, 100);
}
});
var walkontableConfig = {
table: $table[0],
data: instance.getDataAtCell,
totalRows: instance.countRows,
totalColumns: instance.countCols,
offsetRow: 0,
offsetColumn: 0,
displayRows: null,
displayColumns: null,
width: myWidth,
height: myHeight,
frozenColumns: settings.rowHeaders ? [instance.getRowHeader] : null,
columnHeaders: settings.colHeaders ? instance.getColHeader : null,
columnWidth: settings.colWidths ? settings.colWidths : null,
cellRenderer: function (row, column, TD) {
that.applyCellTypeMethod('renderer', TD, {row: row, col: column}, instance.getDataAtCell(row, column));
},
selections: {
current: {
className: 'current',
border: {
width: 2,
color: '#5292F7',
style: 'solid'
}
},
area: {
className: 'area',
border: {
width: 1,
color: '#89AFF9',
style: 'solid'
}
}
},
onCellMouseDown: function (event, coords, TD) {
isMouseDown = true;
var coordsObj = {row: coords[0], col: coords[1]};
if (event.button === 2 && instance.selection.inInSelection(coordsObj)) { //right mouse button
//do nothing
}
else if (event.shiftKey) {
instance.selection.setRangeEnd(coordsObj);
}
else {
instance.selection.setRangeStart(coordsObj);
}
},
onCellMouseOver: function (event, coords, TD) {
var coordsObj = {row: coords[0], col: coords[1]};
if (isMouseDown) {
instance.selection.setRangeEnd(coordsObj);
}
else if (that.instance.autofill.handle && that.instance.autofill.handle.isDragged) {
that.instance.autofill.handle.isDragged++;
that.instance.autofill.showBorder(this);
}
}
};
Handsontable.PluginHooks.run(this.instance, 'walkontableConfig', [walkontableConfig]);
this.wt = new Walkontable(walkontableConfig);
this.wt.draw();
};
Handsontable.TableView.prototype.render = function (row, col, prop, value) {
this.wt.draw();
this.instance.rootElement.triggerHandler('render.handsontable');
};
Handsontable.TableView.prototype.applyCellTypeMethod = function (methodName, td, coords, extraParam) {
var prop = this.instance.colToProp(coords.col)
, method
, cellProperties = this.instance.getCellMeta(coords.row, coords.col);
if (cellProperties.type && typeof cellProperties.type[methodName] === "function") {
method = cellProperties.type[methodName];
}
if (typeof method !== "function") {
method = Handsontable.TextCell[methodName];
}
return method(this.instance, td, coords.row, coords.col, prop, extraParam, cellProperties);
};
/**
* Returns td object given coordinates
*/
Handsontable.TableView.prototype.getCellAtCoords = function (coords) {
return this.wt.wtTable.getCell([coords.row, coords.col]);
};
/**
* Scroll viewport to selection
* @param coords
*/
Handsontable.TableView.prototype.scrollViewport = function (coords) {
this.wt.scrollViewport([coords.row, coords.col]);
};
/**
* Returns true if keyCode represents a printable character
* @param {Number} keyCode
* @return {Boolean}
*/
Handsontable.helper.isPrintableChar = function (keyCode) {
return ((keyCode == 32) || //space
(keyCode >= 48 && keyCode <= 57) || //0-9
(keyCode >= 96 && keyCode <= 111) || //numpad
(keyCode >= 186 && keyCode <= 192) || //;=,-./`
(keyCode >= 219 && keyCode <= 222) || //[]{}\|"'
keyCode >= 226 || //special chars (229 for Asian chars)
(keyCode >= 65 && keyCode <= 90)); //a-z
};
/**
* Converts a value to string
* @param value
* @return {String}
*/
Handsontable.helper.stringify = function (value) {
switch (typeof value) {
case 'string':
case 'number':
return value + '';
break;
case 'object':
if (value === null) {
return '';
}
else {
return value.toString();
}
break;
case 'undefined':
return '';
break;
default:
return value.toString();
}
};
/**
* Create DOM element for drag-down handle
* @constructor
* @param {Object} instance Handsontable instance
*/
Handsontable.FillHandle = function (instance) {
return;
this.instance = instance;
this.rootElement = instance.rootElement;
var container = this.rootElement[0];
this.handle = document.createElement("div");
this.handle.className = "htFillHandle";
this.disappear();
container.appendChild(this.handle);
var that = this;
$(this.handle).mousedown(function () {
that.isDragged = 1;
});
this.rootElement.find('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();
});
};
Handsontable.FillHandle.prototype = {
/**
* Show handle in cell cornerł
* @param {Object[]} coordsArr
*/
appear: function (coordsArr) {
return;
if (this.disabled) {
return;
}
var $td, tdOffset, containerOffset, top, left, height, width;
var corners = this.instance.getCornerCoords(coordsArr);
$td = $(this.instance.getCell(corners.BR.row, corners.BR.col));
tdOffset = $td.offset();
containerOffset = this.$container.offset();
top = tdOffset.top - containerOffset.top + this.rootElement.scrollTop() - 1;
left = tdOffset.left - containerOffset.left + this.rootElement.scrollLeft() - 1;
height = $td.outerHeight();
width = $td.outerWidth();
this.handle.style.top = top + height - 3 + 'px';
this.handle.style.left = left + width - 3 + 'px';
this.handle.style.display = 'block';
},
/**
* Hide handle
*/
disappear: function () {
return;
this.handle.style.display = 'none';
}
};
/**
* Handsontable UndoRedo class
*/
Handsontable.UndoRedo = function (instance) {
var that = this;
this.instance = instance;
this.clear();
instance.rootElement.on("datachange.handsontable", function (event, changes, origin) {
if (origin !== 'undo' && origin !== 'redo') {
that.add(changes);
}
});
};
/**
* Undo operation from current revision
*/
Handsontable.UndoRedo.prototype.undo = function () {
var i, ilen;
if (this.isUndoAvailable()) {
var setData = $.extend(true, [], this.data[this.rev]);
for (i = 0, ilen = setData.length; i < ilen; i++) {
setData[i].splice(3, 1);
}
this.instance.setDataAtCell(setData, null, null, 'undo');
this.rev--;
}
};
/**
* Redo operation from current revision
*/
Handsontable.UndoRedo.prototype.redo = function () {
var i, ilen;
if (this.isRedoAvailable()) {
this.rev++;
var setData = $.extend(true, [], this.data[this.rev]);
for (i = 0, ilen = setData.length; i < ilen; i++) {
setData[i].splice(2, 1);
}
this.instance.setDataAtCell(setData, null, null, 'redo');
}
};
/**
* Returns true if undo point is available
* @return {Boolean}
*/
Handsontable.UndoRedo.prototype.isUndoAvailable = function () {
return (this.rev >= 0);
};
/**
* Returns true if redo point is available
* @return {Boolean}
*/
Handsontable.UndoRedo.prototype.isRedoAvailable = function () {
return (this.rev < this.data.length - 1);
};
/**
* Add new history poins
* @param changes
*/
Handsontable.UndoRedo.prototype.add = function (changes) {
this.rev++;
this.data.splice(this.rev); //if we are in point abcdef(g)hijk in history, remove everything after (g)
this.data.push(changes);
};
/**
* Clears undo history
*/
Handsontable.UndoRedo.prototype.clear = function () {
this.data = [];
this.rev = -1;
};
Handsontable.SelectionPoint = function () {
this._row = null; //private use intended
this._col = null;
};
Handsontable.SelectionPoint.prototype.exists = function () {
return (this._row !== null);
};
Handsontable.SelectionPoint.prototype.row = function (val) {
if (val !== void 0) {
this._row = val;
}
return this._row;
};
Handsontable.SelectionPoint.prototype.col = function (val) {
if (val !== void 0) {
this._col = val;
}
return this._col;
};
Handsontable.SelectionPoint.prototype.coords = function (coords) {
if (coords !== void 0) {
this._row = coords.row;
this._col = coords.col;
}
return {
row: this._row,
col: this._col
}
};
Handsontable.SelectionPoint.prototype.arr = function (arr) {
if (arr !== void 0) {
this._row = arr[0];
this._col = arr[1];
}
return [this._row, this._col]
};
/**
* Default text renderer
* @param {Object} instance Handsontable instance
* @param {Element} td Table cell where to render
* @param {Number} row
* @param {Number} col
* @param {String|Number} prop Row object property name
* @param value Value to render (remember to escape unsafe HTML before inserting to DOM!)
* @param {Object} cellProperties Cell properites (shared by cell renderer and editor)
*/
Handsontable.TextRenderer = function (instance, td, row, col, prop, value, cellProperties) {
var escaped = Handsontable.helper.stringify(value);
escaped = escaped.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); //escape html special chars
td.innerHTML = escaped.replace(/\n/g, ' ');
};
/**
* Autocomplete renderer
* @param {Object} instance Handsontable instance
* @param {Element} td Table cell where to render
* @param {Number} row
* @param {Number} col
* @param {String|Number} prop Row object property name
* @param value Value to render (remember to escape unsafe HTML before inserting to DOM!)
* @param {Object} cellProperties Cell properites (shared by cell renderer and editor)
*/
Handsontable.AutocompleteRenderer = function (instance, td, row, col, prop, value, cellProperties) {
var $td = $(td);
var $text = $('');
var $arrow = $('▼ ');
$arrow.mouseup(function (event) {
instance.view.wt.getSetting('onCellDblClick');
});
Handsontable.TextCell.renderer(instance, $text[0], row, col, prop, value, cellProperties);
if ($text.html() === '') {
$text.html(' ');
}
$text.append($arrow);
$td.empty().append($text);
};
/**
* Checkbox renderer
* @param {Object} instance Handsontable instance
* @param {Element} td Table cell where to render
* @param {Number} row
* @param {Number} col
* @param {String|Number} prop Row object property name
* @param value Value to render (remember to escape unsafe HTML before inserting to DOM!)
* @param {Object} cellProperties Cell properites (shared by cell renderer and editor)
*/
Handsontable.CheckboxRenderer = function (instance, td, row, col, prop, value, cellProperties) {
if (typeof cellProperties.checkedTemplate === "undefined") {
cellProperties.checkedTemplate = true;
}
if (typeof cellProperties.uncheckedTemplate === "undefined") {
cellProperties.uncheckedTemplate = false;
}
if (value === cellProperties.checkedTemplate || value === Handsontable.helper.stringify(cellProperties.checkedTemplate)) {
td.innerHTML = "";
}
else if (value === cellProperties.uncheckedTemplate || value === Handsontable.helper.stringify(cellProperties.uncheckedTemplate)) {
td.innerHTML = "";
}
else if (value === null) { //default value
td.innerHTML = "";
}
else {
td.innerHTML = "#bad value#";
}
var $input = $(td).find('input:first');
$input.mousedown(function (event) {
if (!$(this).is(':checked')) {
instance.setDataAtCell(row, prop, cellProperties.checkedTemplate);
}
else {
instance.setDataAtCell(row, prop, cellProperties.uncheckedTemplate);
}
event.stopPropagation(); //otherwise can confuse mousedown handler
});
$input.mouseup(function (event) {
event.stopPropagation(); //otherwise can confuse dblclick handler
});
return td;
};
var texteditor = {
isCellEdited: false,
/**
* Returns caret position in edit proxy
* @author http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea
* @return {Number}
*/
getCaretPosition: function (keyboardProxy) {
var el = keyboardProxy[0];
if (el.selectionStart) {
return el.selectionStart;
}
else if (document.selection) {
el.focus();
var r = document.selection.createRange();
if (r == null) {
return 0;
}
var re = el.createTextRange(),
rc = re.duplicate();
re.moveToBookmark(r.getBookmark());
rc.setEndPoint('EndToStart', re);
return rc.text.length;
}
return 0;
},
/**
* Sets caret position in edit proxy
* @author http://blog.vishalon.net/index.php/javascript-getting-and-setting-caret-position-in-textarea/
* @param {Number}
*/
setCaretPosition: function (keyboardProxy, pos) {
var el = keyboardProxy[0];
if (el.setSelectionRange) {
el.focus();
el.setSelectionRange(pos, pos);
}
else if (el.createTextRange) {
var range = el.createTextRange();
range.collapse(true);
range.moveEnd('character', pos);
range.moveStart('character', pos);
range.select();
}
},
/**
* Shows text input in grid cell
*/
beginEditing: function (instance, td, row, col, prop, keyboardProxy, useOriginalValue, suffix) {
if (texteditor.isCellEdited) {
return;
}
var coords = {row: row, col: col};
instance.view.scrollViewport(coords);
instance.view.render();
texteditor.$td = $(instance.getCell(row, col)); //because old td may have been scrolled out with scrollViewport
keyboardProxy.on('cut.editor', function (event) {
event.stopPropagation();
});
keyboardProxy.on('paste.editor', function (event) {
event.stopPropagation();
});
if (!instance.getCellMeta(row, col).isWritable) {
return;
}
texteditor.isCellEdited = true;
if (useOriginalValue) {
var original = instance.getDataAtCell(row, prop);
original = Handsontable.helper.stringify(original) + (suffix || '');
keyboardProxy.val(original);
texteditor.setCaretPosition(keyboardProxy, original.length);
}
else {
keyboardProxy.val('');
}
texteditor.refreshDimensions(instance, keyboardProxy);
keyboardProxy.parent().removeClass('htHidden');
instance.rootElement.triggerHandler('beginediting.handsontable');
setTimeout(function () {
//async fix for Firefox 3.6.28 (needs manual testing)
keyboardProxy.parent().css({
overflow: 'visible'
});
}, 1);
},
refreshDimensions: function (instance, keyboardProxy) {
if (!texteditor.isCellEdited) {
return;
}
///start prepare textarea position
var currentOffset = texteditor.$td.offset();
var containerOffset = instance.rootElement.offset();
var scrollTop = instance.rootElement.scrollTop();
var scrollLeft = instance.rootElement.scrollLeft();
var editTop = currentOffset.top - containerOffset.top + scrollTop - 1;
var editLeft = currentOffset.left - containerOffset.left + scrollLeft - 1;
var settings = instance.getSettings();
var rowHeadersCount = settings.rowHeaders === false ? 0 : 1;
var colHeadersCount = settings.colHeaders === false ? 0 : 1;
if (editTop < 0) {
editTop = 0;
}
if (editLeft < 0) {
editLeft = 0;
}
if (rowHeadersCount > 0 && parseInt(texteditor.$td.css('border-top-width')) > 0) {
editTop += 1;
}
if (colHeadersCount > 0 && parseInt(texteditor.$td.css('border-left-width')) > 0) {
editLeft += 1;
}
if ($.browser.msie && parseInt($.browser.version, 10) <= 7) {
editTop -= 1;
}
keyboardProxy.parent().addClass('htHidden').css({
top: editTop,
left: editLeft
});
///end prepare textarea position
var width = texteditor.$td.width()
, height = texteditor.$td.outerHeight() - 4;
if (parseInt(texteditor.$td.css('border-top-width')) > 0) {
height -= 1;
}
if (parseInt(texteditor.$td.css('border-left-width')) > 0) {
if (rowHeadersCount > 0) {
width -= 1;
}
}
keyboardProxy.autoResize({
maxHeight: 200,
minHeight: height,
minWidth: width,
maxWidth: Math.max(168, width),
animate: false,
extraSpace: 0
});
},
/**
* Finishes text input in selected cells
*/
finishEditing: function (instance, td, row, col, prop, keyboardProxy, isCancelled, ctrlDown) {
if (texteditor.triggerOnlyByDestroyer) {
return;
}
if (texteditor.isCellEdited) {
texteditor.isCellEdited = false;
var val;
if (isCancelled) {
val = [
[texteditor.originalValue]
];
}
else {
val = [
[$.trim(keyboardProxy.val())]
];
}
if (ctrlDown) { //if ctrl+enter and multiple cells selected, behave like Excel (finish editing and apply to all cells)
var sel = instance.handsontable('getSelected');
instance.populateFromArray({row: sel[0], col: sel[1]}, val, {row: sel[2], col: sel[3]}, false, 'edit');
}
else {
instance.populateFromArray({row: row, col: col}, val, null, false, 'edit');
}
}
keyboardProxy.off(".editor");
instance.view.wt.update('onCellDblClick', null);
keyboardProxy.css({
width: 0,
height: 0
});
keyboardProxy.parent().addClass('htHidden').css({
overflow: 'hidden'
});
instance.rootElement.triggerHandler('finishediting.handsontable');
}
};
/**
* Default text editor
* @param {Object} instance Handsontable instance
* @param {Element} td Table cell where to render
* @param {Number} row
* @param {Number} col
* @param {String|Number} prop Row object property name
* @param {Object} keyboardProxy jQuery element of keyboard proxy that contains current editing value
* @param {Object} cellProperties Cell properites (shared by cell renderer and editor)
*/
Handsontable.TextEditor = function (instance, td, row, col, prop, keyboardProxy, cellProperties) {
texteditor.isCellEdited = false;
texteditor.$td = $(td);
texteditor.originalValue = instance.getDataAtCell(row, prop);
texteditor.triggerOnlyByDestroyer = cellProperties.strict;
keyboardProxy.parent().addClass('htHidden').css({
top: 0,
left: 0,
overflow: 'hidden'
});
keyboardProxy.css({
width: 0,
height: 0
});
keyboardProxy.on('refreshBorder.editor', function () {
setTimeout(function () {
if (texteditor.isCellEdited) {
texteditor.refreshDimensions(instance, keyboardProxy);
}
}, 0);
});
keyboardProxy.on("keydown.editor", function (event) {
var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey; //catch CTRL but not right ALT (which in some systems triggers ALT+CTRL)
if (Handsontable.helper.isPrintableChar(event.keyCode)) {
if (!texteditor.isCellEdited && !ctrlDown) { //disregard CTRL-key shortcuts
texteditor.beginEditing(instance, null, row, col, prop, keyboardProxy);
event.stopImmediatePropagation();
}
else if (ctrlDown) {
if (texteditor.isCellEdited && event.keyCode === 65) { //CTRL + A
event.stopPropagation();
}
else if (texteditor.isCellEdited && event.keyCode === 88 && $.browser.opera) { //CTRL + X
event.stopPropagation();
}
else if (texteditor.isCellEdited && event.keyCode === 86 && $.browser.opera) { //CTRL + V
event.stopPropagation();
}
}
return;
}
switch (event.keyCode) {
case 38: /* arrow up */
if (texteditor.isCellEdited) {
texteditor.finishEditing(instance, null, row, col, prop, keyboardProxy, false);
}
break;
case 9: /* tab */
if (texteditor.isCellEdited) {
texteditor.finishEditing(instance, null, row, col, prop, keyboardProxy, false);
}
event.preventDefault();
break;
case 39: /* arrow right */
if (texteditor.isCellEdited) {
if (texteditor.getCaretPosition(keyboardProxy) === keyboardProxy.val().length) {
texteditor.finishEditing(instance, null, row, col, prop, keyboardProxy, false);
}
else {
event.stopPropagation();
}
}
break;
case 37: /* arrow left */
if (texteditor.isCellEdited) {
if (texteditor.getCaretPosition(keyboardProxy) === 0) {
texteditor.finishEditing(instance, null, row, col, prop, keyboardProxy, false);
}
else {
event.stopPropagation();
}
}
break;
case 8: /* backspace */
case 46: /* delete */
if (texteditor.isCellEdited) {
event.stopPropagation();
}
break;
case 40: /* arrow down */
if (texteditor.isCellEdited) {
texteditor.finishEditing(instance, null, row, col, prop, keyboardProxy, false);
}
break;
case 27: /* ESC */
if (texteditor.isCellEdited) {
instance.destroyEditor(true);
event.stopPropagation();
}
break;
case 113: /* F2 */
if (!texteditor.isCellEdited) {
texteditor.beginEditing(instance, null, row, col, prop, keyboardProxy, true); //show edit field
event.stopPropagation();
event.preventDefault(); //prevent Opera from opening Go to Page dialog
}
break;
case 13: /* return/enter */
if (texteditor.isCellEdited) {
var selected = instance.getSelected();
var isMultipleSelection = !(selected[0] === selected[2] && selected[1] === selected[3]);
if ((event.ctrlKey && !isMultipleSelection) || event.altKey) { //if ctrl+enter or alt+enter, add new line
keyboardProxy.val(keyboardProxy.val() + '\n');
keyboardProxy[0].focus();
event.stopPropagation();
}
else {
texteditor.finishEditing(instance, null, row, col, prop, keyboardProxy, false, ctrlDown);
}
}
else if (instance.getSettings().enterBeginsEditing) {
if ((ctrlDown && !selection.isMultiple()) || event.altKey) { //if ctrl+enter or alt+enter, add new line
texteditor.beginEditing(instance, null, row, col, prop, keyboardProxy, true, '\n'); //show edit field
}
else {
texteditor.beginEditing(instance, null, row, col, prop, keyboardProxy, true); //show edit field
}
event.stopPropagation();
}
event.preventDefault(); //don't add newline to field
break;
case 36: /* home */
if (texteditor.isCellEdited) {
event.stopPropagation();
}
break;
case 35: /* end */
if (texteditor.isCellEdited) {
event.stopPropagation();
}
break;
}
});
function onDblClick() {
keyboardProxy[0].focus();
texteditor.beginEditing(instance, null, row, col, prop, keyboardProxy, true);
}
instance.view.wt.update('onCellDblClick', onDblClick);
return function (isCancelled) {
texteditor.triggerOnlyByDestroyer = false;
texteditor.finishEditing(instance, null, row, col, prop, keyboardProxy, isCancelled);
}
};
function isAutoComplete(keyboardProxy) {
var typeahead = keyboardProxy.data("typeahead");
if (typeahead && typeahead.$menu.is(":visible")) {
return typeahead;
}
else {
return false;
}
}
/**
* Autocomplete editor
* @param {Object} instance Handsontable instance
* @param {Element} td Table cell where to render
* @param {Number} row
* @param {Number} col
* @param {String|Number} prop Row object property name
* @param {Object} keyboardProxy jQuery element of keyboard proxy that contains current editing value
* @param {Object} cellProperties Cell properites (shared by cell renderer and editor)
*/
Handsontable.AutocompleteEditor = function (instance, td, row, col, prop, keyboardProxy, cellProperties) {
var typeahead = keyboardProxy.data('typeahead')
, i
, dontHide = false;
if (!typeahead) {
keyboardProxy.typeahead(cellProperties.options || {});
typeahead = keyboardProxy.data('typeahead');
typeahead._show = typeahead.show;
typeahead._hide = typeahead.hide;
typeahead._render = typeahead.render;
typeahead._highlighter = typeahead.highlighter;
}
else {
if (cellProperties.options) {
/* overwrite typeahead options (most importantly `items`) */
for (i in cellProperties) {
if (cellProperties.hasOwnProperty(i)) {
typeahead.options[i] = cellProperties.options[i];
}
}
}
typeahead.$menu.off(); //remove previous typeahead bindings
keyboardProxy.off(); //remove previous typeahead bindings. Removing this will cause prepare to register 2 keydown listeners in typeahead
typeahead.listen(); //add typeahead bindings
}
typeahead.minLength = 0;
typeahead.highlighter = typeahead._highlighter;
typeahead.show = function () {
if (keyboardProxy.parent().hasClass('htHidden')) {
return;
}
return typeahead._show.call(this);
};
typeahead.hide = function () {
if (!dontHide) {
dontHide = false; //set to true by dblclick handler, otherwise appears and disappears immediately after double click
return typeahead._hide.call(this);
}
};
typeahead.lookup = function () {
var items;
this.query = this.$element.val();
items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source;
return items ? this.process(items) : this;
};
typeahead.matcher = function () {
return true;
};
typeahead.select = function () {
var val = this.$menu.find('.active').attr('data-value') || keyboardProxy.val();
destroyer(true);
instance.setDataAtCell(row, prop, typeahead.updater(val));
return this.hide();
};
typeahead.render = function (items) {
typeahead._render.call(this, items);
if (!cellProperties.strict) {
this.$menu.find('li:eq(0)').removeClass('active');
}
return this;
};
/* overwrite typeahead methods (matcher, sorter, highlighter, updater, etc) if provided in cellProperties */
for (i in cellProperties) {
if (cellProperties.hasOwnProperty(i)) {
typeahead[i] = cellProperties[i];
}
}
var wasDestroyed = false;
keyboardProxy.on("keydown.editor", function (event) {
switch (event.keyCode) {
case 27: /* ESC */
dontHide = false;
break;
case 37: /* arrow left */
case 39: /* arrow right */
case 38: /* arrow up */
case 40: /* arrow down */
case 9: /* tab */
case 13: /* return/enter */
if (!keyboardProxy.parent().hasClass('htHidden')) {
event.stopImmediatePropagation();
}
event.preventDefault();
}
});
keyboardProxy.on("keyup.editor", function (event) {
if (wasDestroyed) {
return;
}
switch (event.keyCode) {
case 9: /* tab */
case 13: /* return/enter */
if (!isAutoComplete(keyboardProxy)) {
var ev = $.Event('keyup');
ev.keyCode = 113; //113 triggers lookup, in contrary to 13 or 9 which only trigger hide
keyboardProxy.trigger(ev);
}
else {
setTimeout(function () { //so pressing enter will move one row down after change is applied by 'select' above
var ev = $.Event('keydown');
ev.keyCode = event.keyCode;
keyboardProxy.parent().trigger(ev);
}, 10);
}
break;
default:
if (!Handsontable.helper.isPrintableChar(event.keyCode)) { //otherwise Del or F12 would open suggestions list
event.stopImmediatePropagation();
}
}
}
);
var textDestroyer = Handsontable.TextEditor(instance, td, row, col, prop, keyboardProxy, cellProperties);
function onDblClick() {
keyboardProxy[0].focus();
texteditor.beginEditing(instance, null, row, col, prop, keyboardProxy, true);
dontHide = true;
setTimeout(function () { //otherwise is misaligned in IE9
keyboardProxy.data('typeahead').lookup();
}, 1);
}
instance.view.wt.update('onCellDblClick', onDblClick); //no need to destroy it here because it will be destroyed by TextEditor destroyer
var destroyer = function (isCancelled) {
wasDestroyed = true;
keyboardProxy.off(); //remove typeahead bindings
textDestroyer(isCancelled);
dontHide = false;
if (isAutoComplete(keyboardProxy)) {
isAutoComplete(keyboardProxy).hide();
}
};
return destroyer;
};
function toggleCheckboxCell(instance, row, prop, cellProperties) {
if (Handsontable.helper.stringify(instance.getDataAtCell(row, prop)) === Handsontable.helper.stringify(cellProperties.checkedTemplate)) {
instance.setDataAtCell(row, prop, cellProperties.uncheckedTemplate);
}
else {
instance.setDataAtCell(row, prop, cellProperties.checkedTemplate);
}
}
/**
* Checkbox editor
* @param {Object} instance Handsontable instance
* @param {Element} td Table cell where to render
* @param {Number} row
* @param {Number} col
* @param {String|Number} prop Row object property name
* @param {Object} keyboardProxy jQuery element of keyboard proxy that contains current editing value
* @param {Object} cellProperties Cell properites (shared by cell renderer and editor)
*/
Handsontable.CheckboxEditor = function (instance, td, row, col, prop, keyboardProxy, cellProperties) {
if (typeof cellProperties === "undefined") {
cellProperties = {};
}
if (typeof cellProperties.checkedTemplate === "undefined") {
cellProperties.checkedTemplate = true;
}
if (typeof cellProperties.uncheckedTemplate === "undefined") {
cellProperties.uncheckedTemplate = false;
}
keyboardProxy.on("keydown.editor", function (event) {
var ctrlDown = (event.ctrlKey || event.metaKey) && !event.altKey; //catch CTRL but not right ALT (which in some systems triggers ALT+CTRL)
if (!ctrlDown && Handsontable.helper.isPrintableChar(event.keyCode)) {
toggleCheckboxCell(instance, row, prop, cellProperties);
event.stopPropagation();
}
});
function onDblClick() {
toggleCheckboxCell(instance, row, prop, cellProperties);
}
instance.view.wt.update('onCellDblClick', onDblClick);
return function () {
keyboardProxy.off(".editor");
instance.view.wt.update('onCellDblClick', null);
}
};
Handsontable.AutocompleteCell = {
renderer: Handsontable.AutocompleteRenderer,
editor: Handsontable.AutocompleteEditor
};
Handsontable.CheckboxCell = {
renderer: Handsontable.CheckboxRenderer,
editor: Handsontable.CheckboxEditor
};
Handsontable.TextCell = {
renderer: Handsontable.TextRenderer,
editor: Handsontable.TextEditor
};
Handsontable.PluginHooks = {
hooks: {
afterInit: [],
afterGetCellMeta: [],
walkontableConfig: []
},
push: function (hook, fn) {
this.hooks[hook].push(fn);
},
unshift: function (hook, fn) {
this.hooks[hook].unshift(fn);
},
run: function (instance, hook, args) {
for (var i = 0, ilen = this.hooks[hook].length; i < ilen; i++) {
if (args) {
this.hooks[hook][i].apply(instance, args);
}
else {
this.hooks[hook][i].call(instance);
}
}
}
};
function createContextMenu() {
var instance = this
, defaultOptions = {
selector: "#" + instance.rootElement.attr('id') + ' table, #' + instance.rootElement.attr('id') + ' div',
trigger: 'right',
callback: onContextClick
},
allItems = {
"row_above": {name: "Insert row above", disabled: isDisabled},
"row_below": {name: "Insert row below", disabled: isDisabled},
"hsep1": "---------",
"col_left": {name: "Insert column on the left", disabled: isDisabled},
"col_right": {name: "Insert column on the right", disabled: isDisabled},
"hsep2": "---------",
"remove_row": {name: "Remove row", disabled: isDisabled},
"remove_col": {name: "Remove column", disabled: isDisabled},
"hsep3": "---------",
"undo": {name: "Undo", disabled: function () {
return !instance.isUndoAvailable();
}},
"redo": {name: "Redo", disabled: function () {
return !instance.isRedoAvailable();
}}
}
, options = {}
, i
, ilen
, settings = instance.getSettings();
function onContextClick(key) {
var corners = instance.getSelected(); //[top left row, top left col, bottom right row, bottom right col]
switch (key) {
case "row_above":
instance.alter("insert_row", corners[0]);
break;
case "row_below":
instance.alter("insert_row", corners[2] + 1);
break;
case "col_left":
instance.alter("insert_col", corners[1]);
break;
case "col_right":
instance.alter("insert_col", corners[3] + 1);
break;
case "remove_row":
instance.alter(key, corners[0], corners[2]);
break;
case "remove_col":
instance.alter(key, corners[1], corners[3]);
break;
case "undo":
instance.undo();
break;
case "redo":
instance.redo();
break;
}
}
function isDisabled(key) {
//TODO rewrite
/*if (instance.blockedCols.main.find('th.htRowHeader.active').length && (key === "remove_col" || key === "col_left" || key === "col_right")) {
return true;
}
else if (instance.blockedRows.main.find('th.htColHeader.active').length && (key === "remove_row" || key === "row_above" || key === "row_below")) {
return true;
}
else*/
if (instance.countRows() >= instance.getSettings().maxRows && (key === "row_above" || key === "row_below")) {
return true;
}
else if (instance.countCols() >= instance.getSettings().maxCols && (key === "col_left" || key === "col_right")) {
return true;
}
else {
return false;
}
}
if (!settings.contextMenu) {
return;
}
else if (settings.contextMenu === true) { //contextMenu is true
options.items = allItems;
}
else if (Object.prototype.toString.apply(settings.contextMenu) === '[object Array]') { //contextMenu is an array
options.items = {};
for (i = 0, ilen = settings.contextMenu.length; i < ilen; i++) {
var key = settings.contextMenu[i];
if (typeof allItems[key] === 'undefined') {
throw new Error('Context menu key "' + key + '" is not recognised');
}
options.items[key] = allItems[key];
}
}
else if (Object.prototype.toString.apply(settings.contextMenu) === '[object Object]') { //contextMenu is an options object as defined in http://medialize.github.com/jQuery-contextMenu/docs.html
options = settings.contextMenu;
if (options.items) {
for (i in options.items) {
if (options.items.hasOwnProperty(i) && allItems[i]) {
if (typeof options.items[i] === 'string') {
options.items[i] = allItems[i];
}
else {
options.items[i] = $.extend(true, allItems[i], options.items[i]);
}
}
}
}
else {
options.items = allItems;
}
if (options.callback) {
var handsontableCallback = defaultOptions.callback;
var customCallback = options.callback;
options.callback = function (key, options) {
handsontableCallback(key, options);
customCallback(key, options);
}
}
}
if (!instance.rootElement.attr('id')) {
throw new Error("Handsontable container must have an id");
}
$.contextMenu($.extend(true, defaultOptions, options));
}
Handsontable.PluginHooks.push('afterInit', createContextMenu);
/**
* This plugin adds support for legacy features, deprecated APIs, etc.
*/
/**
* Support for old autocomplete syntax
* For old syntax, see: https://github.com/warpech/jquery-handsontable/blob/8c9e701d090ea4620fe08b6a1a048672fadf6c7e/README.md#defining-autocomplete
*/
Handsontable.PluginHooks.push('afterGetCellMeta', function (row, col, cellProperties) {
var settings = this.getSettings(), data = this.getData(), i, ilen, a;
if (settings.autoComplete) {
for (i = 0, ilen = settings.autoComplete.length; i < ilen; i++) {
if (settings.autoComplete[i].match(row, col, data)) {
if (typeof cellProperties.type === 'undefined') {
cellProperties.type = Handsontable.AutocompleteCell;
}
else {
if (typeof cellProperties.type.renderer === 'undefined') {
cellProperties.type.renderer = Handsontable.AutocompleteCell.renderer;
}
if (typeof cellProperties.type.editor === 'undefined') {
cellProperties.type.editor = Handsontable.AutocompleteCell.editor;
}
}
for (a in settings.autoComplete[i]) {
if (settings.autoComplete[i].hasOwnProperty(a) && a !== 'match' && typeof cellProperties[i] === 'undefined') {
if(a === 'source') {
cellProperties[a] = settings.autoComplete[i][a](row, col);
}
else {
cellProperties[a] = settings.autoComplete[i][a];
}
}
}
break;
}
}
}
});
/*
* jQuery.fn.autoResize 1.1+
* --
* https://github.com/warpech/jQuery.fn.autoResize
*
* This fork differs from others in a way that it autoresizes textarea in 2-dimensions (horizontally and vertically).
* It was originally forked from alexbardas's repo but maybe should be merged with dpashkevich's repo in future.
*
* originally forked from:
* https://github.com/jamespadolsey/jQuery.fn.autoResize
* which is now located here:
* https://github.com/alexbardas/jQuery.fn.autoResize
* though the mostly maintained for is here:
* https://github.com/dpashkevich/jQuery.fn.autoResize/network
*
* --
* This program is free software. It comes without any warranty, to
* the extent permitted by applicable law. You can redistribute it
* and/or modify it under the terms of the Do What The Fuck You Want
* To Public License, Version 2, as published by Sam Hocevar. See
* http://sam.zoy.org/wtfpl/COPYING for more details. */
(function($){
autoResize.defaults = {
onResize: function(){},
animate: {
duration: 200,
complete: function(){}
},
extraSpace: 50,
minHeight: 'original',
maxHeight: 500,
minWidth: 'original',
maxWidth: 500
};
autoResize.cloneCSSProperties = [
'lineHeight', 'textDecoration', 'letterSpacing',
'fontSize', 'fontFamily', 'fontStyle', 'fontWeight',
'textTransform', 'textAlign', 'direction', 'wordSpacing', 'fontSizeAdjust',
'padding'
];
autoResize.cloneCSSValues = {
position: 'absolute',
top: -9999,
left: -9999,
opacity: 0,
overflow: 'hidden',
border: '1px solid black',
padding: '0.49em' //this must be about the width of caps W character
};
autoResize.resizableFilterSelector = 'textarea,input:not(input[type]),input[type=text],input[type=password]';
autoResize.AutoResizer = AutoResizer;
$.fn.autoResize = autoResize;
function autoResize(config) {
this.filter(autoResize.resizableFilterSelector).each(function(){
new AutoResizer( $(this), config );
});
return this;
}
function AutoResizer(el, config) {
if(this.clones) return;
this.config = $.extend({}, autoResize.defaults, config);
this.el = el;
this.nodeName = el[0].nodeName.toLowerCase();
this.previousScrollTop = null;
if (config.maxWidth === 'original') config.maxWidth = el.width();
if (config.minWidth === 'original') config.minWidth = el.width();
if (config.maxHeight === 'original') config.maxHeight = el.height();
if (config.minHeight === 'original') config.minHeight = el.height();
if (this.nodeName === 'textarea') {
el.css({
resize: 'none',
overflowY: 'hidden'
});
}
el.data('AutoResizer', this);
this.createClone();
this.injectClone();
this.bind();
}
AutoResizer.prototype = {
bind: function() {
var check = $.proxy(function(){
this.check();
return true;
}, this);
this.unbind();
this.el
.bind('keyup.autoResize', check)
//.bind('keydown.autoResize', check)
.bind('change.autoResize', check);
this.check(null, true);
},
unbind: function() {
this.el.unbind('.autoResize');
},
createClone: function() {
var el = this.el,
self = this,
config = this.config;
this.clones = $();
if (config.minHeight !== 'original' || config.maxHeight !== 'original') {
this.hClone = el.clone().height('auto');
this.clones = this.clones.add(this.hClone);
}
if (config.minWidth !== 'original' || config.maxWidth !== 'original') {
this.wClone = $('').width('auto').css({
whiteSpace: 'nowrap',
'float': 'left'
});
this.clones = this.clones.add(this.wClone);
}
$.each(autoResize.cloneCSSProperties, function(i, p){
self.clones.css(p, el.css(p));
});
this.clones
.removeAttr('name')
.removeAttr('id')
.attr('tabIndex', -1)
.css(autoResize.cloneCSSValues);
},
check: function(e, immediate) {
var config = this.config,
wClone = this.wClone,
hClone = this.hClone,
el = this.el,
value = el.val();
if (wClone) {
wClone.text(value);
// Calculate new width + whether to change
var cloneWidth = wClone.outerWidth(),
newWidth = (cloneWidth + config.extraSpace) >= config.minWidth ?
cloneWidth + config.extraSpace : config.minWidth,
currentWidth = el.width();
newWidth = Math.min(newWidth, config.maxWidth);
if (
(newWidth < currentWidth && newWidth >= config.minWidth) ||
(newWidth >= config.minWidth && newWidth <= config.maxWidth)
) {
config.onResize.call(el);
el.scrollLeft(0);
config.animate && !immediate ?
el.stop(1,1).animate({
width: newWidth
}, config.animate)
: el.width(newWidth);
}
}
if (hClone) {
if (newWidth) {
hClone.width(newWidth);
}
hClone.height(0).val(value).scrollTop(10000);
var scrollTop = hClone[0].scrollTop + config.extraSpace;
// Don't do anything if scrollTop hasen't changed:
if (this.previousScrollTop === scrollTop) {
return;
}
this.previousScrollTop = scrollTop;
if (scrollTop >= config.maxHeight) {
el.css('overflowY', '');
return;
}
el.css('overflowY', 'hidden');
if (scrollTop < config.minHeight) {
scrollTop = config.minHeight;
}
config.onResize.call(el);
// Either animate or directly apply height:
config.animate && !immediate ?
el.stop(1,1).animate({
height: scrollTop
}, config.animate)
: el.height(scrollTop);
}
},
destroy: function() {
this.unbind();
this.el.removeData('AutoResizer');
this.clones.remove();
delete this.el;
delete this.hClone;
delete this.wClone;
delete this.clones;
},
injectClone: function() {
(
autoResize.cloneContainer ||
(autoResize.cloneContainer = $('').appendTo('body'))
).append(this.clones);
}
};
})(jQuery);
/**
* SheetClip - Spreadsheet Clipboard Parser
* version 0.2
*
* This tiny library transforms JavaScript arrays to strings that are pasteable by LibreOffice, OpenOffice,
* Google Docs and Microsoft Excel.
*
* Copyright 2012, Marcin Warpechowski
* Licensed under the MIT license.
* http://github.com/warpech/sheetclip/
*/
/*jslint white: true*/
(function (global) {
"use strict";
function countQuotes(str) {
return str.split('"').length - 1;
}
global.SheetClip = {
parse: function (str) {
var r, rlen, rows, arr = [], a = 0, c, clen, multiline, last;
rows = str.split('\n');
if (rows.length > 1 && rows[rows.length - 1] === '') {
rows.pop();
}
for (r = 0, rlen = rows.length; r < rlen; r += 1) {
rows[r] = rows[r].split('\t');
for (c = 0, clen = rows[r].length; c < clen; c += 1) {
if (!arr[a]) {
arr[a] = [];
}
if (multiline && c === 0) {
last = arr[a].length - 1;
arr[a][last] = arr[a][last] + '\n' + rows[r][0];
if (multiline && countQuotes(rows[r][0]) % 2 === 1) {
multiline = false;
arr[a][last] = arr[a][last].substring(0, arr[a][last].length - 1).replace(/""/g, '"');
}
}
else {
if (c === clen - 1 && rows[r][c].indexOf('"') === 0) {
arr[a].push(rows[r][c].substring(1).replace(/""/g, '"'));
multiline = true;
}
else {
arr[a].push(rows[r][c].replace(/""/g, '"'));
multiline = false;
}
}
}
if (!multiline) {
a += 1;
}
}
return arr;
},
stringify: function (arr) {
var r, rlen, c, clen, str = '', val;
for (r = 0, rlen = arr.length; r < rlen; r += 1) {
for (c = 0, clen = arr[r].length; c < clen; c += 1) {
if (c > 0) {
str += '\t';
}
val = arr[r][c];
if (typeof val === 'string') {
if (val.indexOf('\n') > -1) {
str += '"' + val.replace(/"/g, '""') + '"';
}
else {
str += val;
}
}
else if (val === null || val === void 0) { //void 0 resolves to undefined
str += '';
}
else {
str += val;
}
}
str += '\n';
}
return str;
}
};
}(window));
/**
* walkontable 0.1
*
* Date: Tue Dec 11 2012 11:27:15 GMT+0100 (Central European Standard Time)
*/
function WalkontableBorder(instance, settings) {
//reference to instance
this.instance = instance;
this.settings = settings;
this.wtDom = new WalkontableDom();
this.main = document.createElement("div");
this.main.style.position = 'absolute';
this.main.style.top = 0;
this.main.style.left = 0;
for (var i = 0; i < 4; i++) {
var DIV = document.createElement('DIV');
DIV.className = 'wtBorder ' + settings.className;
DIV.style.backgroundColor = settings.border.color;
DIV.style.height = settings.border.width + 'px';
DIV.style.width = settings.border.width + 'px';
this.main.appendChild(DIV);
}
this.top = this.main.childNodes[0];
this.left = this.main.childNodes[1];
this.bottom = this.main.childNodes[2];
this.right = this.main.childNodes[3];
this.disappear();
instance.wtTable.parent.appendChild(this.main);
}
/**
* Show border around one or many cells
* @param {Array} corners
*/
WalkontableBorder.prototype.appear = function (corners) {
var isMultiple, $from, $to, fromOffset, toOffset, containerOffset, top, minTop, left, minLeft, height, width;
if (this.disabled) {
return;
}
var offsetRow = this.instance.getSetting('offsetRow')
, offsetColumn = this.instance.getSetting('offsetColumn')
, displayRows = this.instance.getSetting('displayRows')
, displayColumns = this.instance.getSetting('displayColumns');
var hideTop, hideLeft, hideBottom, hideRight;
if (displayRows !== null) {
if (corners[0] > offsetRow + displayRows - 1 || corners[2] < offsetRow) {
hideTop = hideLeft = hideBottom = hideRight = true;
}
else {
if (corners[0] < offsetRow) {
corners[0] = offsetRow;
hideTop = true;
}
if (corners[2] > offsetRow + displayRows - 1) {
corners[2] = offsetRow + displayRows - 1;
hideBottom = true;
}
}
}
if (displayColumns !== null) {
if (corners[1] > offsetColumn + displayColumns - 1 || corners[3] < offsetColumn) {
hideTop = hideLeft = hideBottom = hideRight = 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 (!(hideTop == hideLeft == hideBottom == hideRight == true)) {
isMultiple = (corners[0] !== corners[2] || corners[1] !== corners[3]);
$from = $(this.instance.wtTable.getCell([corners[0], corners[1]]));
$to = isMultiple ? $(this.instance.wtTable.getCell([corners[2], corners[3]])) : $from;
fromOffset = this.wtDom.offset($from[0]);
toOffset = isMultiple ? this.wtDom.offset($to[0]) : fromOffset;
containerOffset = this.wtDom.offset(this.instance.wtTable.TABLE);
minTop = fromOffset.top;
height = toOffset.top + $to.outerHeight() - minTop;
minLeft = fromOffset.left;
width = toOffset.left + $to.outerWidth() - minLeft;
top = minTop - containerOffset.top - 1;
left = minLeft - containerOffset.left - 1;
if (parseInt($from.css('border-top-width')) > 0) {
top += 1;
height -= 1;
}
if (parseInt($from.css('border-left-width')) > 0) {
left += 1;
width -= 1;
}
}
if (hideTop) {
this.top.style.display = 'none';
}
else {
this.top.style.top = top + 'px';
this.top.style.left = left + 'px';
this.top.style.width = width + 'px';
this.top.style.display = 'block';
}
if (hideLeft) {
this.left.style.display = 'none';
}
else {
this.left.style.top = top + 'px';
this.left.style.left = left + 'px';
this.left.style.height = height + 'px';
this.left.style.display = 'block';
}
var delta = Math.floor(this.settings.border.width / 2);
if (hideBottom) {
this.bottom.style.display = 'none';
}
else {
this.bottom.style.top = top + height - delta + 'px';
this.bottom.style.left = left + 'px';
this.bottom.style.width = width + 'px';
this.bottom.style.display = 'block';
}
if (hideRight) {
this.right.style.display = 'none';
}
else {
this.right.style.top = top + 'px';
this.right.style.left = left + width - delta + 'px';
this.right.style.height = height + 1 + 'px';
this.right.style.display = 'block';
}
};
/**
* Hide border
*/
WalkontableBorder.prototype.disappear = function () {
this.top.style.display = 'none';
this.left.style.display = 'none';
this.bottom.style.display = 'none';
this.right.style.display = 'none';
};
function Walkontable(settings) {
var that = this;
var originalHeaders = [];
//default settings. void 0 means it is required, null means it can be empty
var defaults = {
table: void 0,
data: void 0,
offsetRow: 0,
offsetColumn: 0,
frozenColumns: null,
columnHeaders: false,
totalRows: void 0,
totalColumns: void 0,
width: null,
height: null,
displayRows: null,
displayColumns: null,
cellRenderer: function (row, column, TD) {
var cellData = that.getSetting('data', row, column);
if (cellData !== void 0) {
TD.innerHTML = cellData;
}
else {
TD.innerHTML = '';
}
},
columnWidth: null,
selections: null,
onCellMouseDown: null,
onCellMouseOver: null,
onCellDblClick: null
};
//reference to settings
this.settings = {};
for (var i in defaults) {
if (defaults.hasOwnProperty(i)) {
if (settings[i] !== void 0) {
this.settings[i] = settings[i];
}
else if (defaults[i] === void 0) {
throw new Error('A required setting "' + i + '" was not provided');
}
else {
this.settings[i] = defaults[i];
}
}
}
//bootstrap from settings
this.wtTable = new WalkontableTable(this);
this.wtScroll = new WalkontableScroll(this);
this.wtWheel = new WalkontableWheel(this);
this.wtEvent = new WalkontableEvent(this);
this.wtDom = new WalkontableDom();
//find original headers
if (this.wtTable.THEAD.childNodes.length && this.wtTable.THEAD.childNodes[0].childNodes.length) {
for (var c = 0, clen = this.wtTable.THEAD.childNodes[0].childNodes.length; c < clen; c++) {
originalHeaders.push(this.wtTable.THEAD.childNodes[0].childNodes[c].innerHTML);
}
if (!this.hasSetting('columnHeaders')) {
this.settings.columnHeaders = function (column) {
return originalHeaders[column];
}
}
}
//initialize selections
this.selections = {};
if (this.settings.selections) {
for (i in this.settings.selections) {
if (this.settings.selections.hasOwnProperty(i)) {
this.selections[i] = new WalkontableSelection(this, this.settings.selections[i]);
}
}
}
this.drawn = false;
}
Walkontable.prototype.draw = function () {
this.wtTable.draw();
this.wtScroll.refreshScrollbars();
this.drawn = true;
return this;
};
Walkontable.prototype.update = function (settings, value) {
if (value === void 0) { //settings is object
for (var i in settings) {
if (settings.hasOwnProperty(i)) {
this.settings[i] = settings[i];
}
}
}
else { //if value is defined then settings is the key
this.settings[settings] = value;
}
return this;
};
Walkontable.prototype.scrollVertical = function (delta) {
return this.wtScroll.scrollVertical(delta);
};
Walkontable.prototype.scrollHorizontal = function (delta) {
return this.wtScroll.scrollHorizontal(delta);
};
Walkontable.prototype.scrollViewport = function (coords) {
return this.wtScroll.scrollViewport(coords);
};
Walkontable.prototype.getSetting = function (key, param1, param2, param3) {
if (key === 'displayRows' && this.settings['height']) {
return this.settings['height'] / 20; //silly assumption but should be fine for now
}
else if (key === 'displayColumns' && this.settings['width']) {
return this.settings['width'] / 50; //silly assumption but should be fine for now
}
else if (key === 'displayRows' && this.settings['displayRows'] === null) {
return this.getSetting('totalRows');
}
else if (key === 'displayColumns' && this.settings['displayColumns'] === null) {
return this.settings['rowHeaders'] ? this.getSetting('totalColumns') + 1 : this.getSetting('totalColumns');
}
if (typeof this.settings[key] === 'function') {
return this.settings[key](param1, param2, param3);
}
else if (param1 !== void 0 && typeof this.settings[key] === 'object' && this.settings[key][param1] !== void 0) {
return this.settings[key][param1];
}
else {
return this.settings[key];
}
};
Walkontable.prototype.hasSetting = function (key) {
return !!this.settings[key]
};
function WalkontableDom() {
}
//goes up the DOM tree (including given element) until it finds an element that matches the nodeName
WalkontableDom.prototype.closest = function (elem, nodeNames) {
while (elem != null) {
if (elem.nodeType === 1 && nodeNames.indexOf(elem.nodeName) > -1) {
return elem;
}
elem = elem.parentNode;
}
return null;
};
WalkontableDom.prototype.prevSiblings = function (elem) {
var out = [];
while ((elem = elem.previousSibling) != null) {
if (elem.nodeType === 1) {
out.push(elem);
}
}
return out;
};
//http://snipplr.com/view/3561/addclass-removeclass-hasclass/
WalkontableDom.prototype.hasClass = function (ele, cls) {
return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
};
WalkontableDom.prototype.addClass = function (ele, cls) {
if (!this.hasClass(ele, cls)) ele.className += " " + cls;
};
WalkontableDom.prototype.removeClass = function (ele, cls) {
if (this.hasClass(ele, cls)) { //is this really needed?
var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
ele.className = ele.className.replace(reg, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, ''); //last 2 replaces do right trim (see http://blog.stevenlevithan.com/archives/faster-trim-javascript)
}
};
/*//http://net.tutsplus.com/tutorials/javascript-ajax/javascript-from-null-cross-browser-event-binding/
WalkontableDom.prototype.addEvent = (function () {
var that = this;
if (document.addEventListener) {
return function (elem, type, cb) {
if ((elem && !elem.length) || elem === window) {
elem.addEventListener(type, cb, false);
}
else if (elem && elem.length) {
var len = elem.length;
for (var i = 0; i < len; i++) {
that.addEvent(elem[i], type, cb);
}
}
};
}
else {
return function (elem, type, cb) {
if ((elem && !elem.length) || elem === window) {
elem.attachEvent('on' + type, function () {
//normalize
//http://stackoverflow.com/questions/4643249/cross-browser-event-object-normalization
var e = window['event'];
e.target = e.srcElement;
//e.offsetX = e.layerX;
//e.offsetY = e.layerY;
e.relatedTarget = e.relatedTarget || e.type == 'mouseover' ? e.fromElement : e.toElement;
if (e.target.nodeType === 3) e.target = e.target.parentNode; //Safari bug
return cb.call(elem, e)
});
}
else if (elem.length) {
var len = elem.length;
for (var i = 0; i < len; i++) {
that.addEvent(elem[i], type, cb);
}
}
};
}
})();
WalkontableDom.prototype.triggerEvent = function (element, eventName, target) {
var event;
if (document.createEvent) {
event = document.createEvent("MouseEvents");
event.initEvent(eventName, true, true);
} else {
event = document.createEventObject();
event.eventType = eventName;
}
event.eventName = eventName;
event.target = target;
console.log("próbujem", event, element, target);
if (document.createEvent) {
target.dispatchEvent(event);
} else {
target.fireEvent("on" + event.eventType, event);
}
};*/
WalkontableDom.prototype.removeTextNodes = function (elem, parent) {
if (elem.nodeType === 3) {
parent.removeChild(elem); //bye text nodes!
}
else if (['TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR'].indexOf(elem.nodeName) > -1) {
var childs = elem.childNodes;
for (var i = childs.length - 1; i >= 0; i--) {
this.removeTextNodes(childs[i], elem);
}
}
};
/**
* seems getBounding is usually faster: http://jsperf.com/offset-vs-getboundingclientrect/4
* but maybe offset + cache would work?
*/
WalkontableDom.prototype.offset = function (elem) {
var rect = elem.getBoundingClientRect();
return {
top: rect.top + document.documentElement.scrollTop,
left: rect.left + document.documentElement.scrollLeft
};
};
/*
WalkontableDom.prototype.offsetLeft = function (elem) {
var offset = elem.offsetLeft;
while (elem = elem.offsetParent) {
offset += elem.offsetLeft;
}
return offset;
};
WalkontableDom.prototype.offsetTop = function (elem) {
var offset = elem.offsetTop;
while (elem = elem.offsetParent) {
offset += elem.offsetTop;
}
return offset;
};
WalkontableDom.prototype.offset = function (elem) {
var offsetLeft = elem.offsetLeft
, offsetTop = elem.offsetTop;
while (elem = elem.offsetParent) {
offsetLeft += elem.offsetLeft;
offsetTop += elem.offsetTop;
}
return {
left: offsetLeft,
top: offsetTop
};
};
*/
function WalkontableEvent(instance) {
var that = this;
//reference to instance
this.instance = instance;
this.wtDom = new WalkontableDom();
var onMouseDown = function (event) {
if (that.instance.settings.onCellMouseDown) {
var coords
, TD = that.wtDom.closest(event.target, ['TD', 'TH']);
if (TD) {
coords = that.instance.wtTable.getCoords(TD);
}
else if (!TD && that.wtDom.hasClass(event.target, 'wtBorder') && that.wtDom.hasClass(event.target, 'current')) {
coords = that.instance.selections.current.selected[0];
TD = that.instance.wtTable.getCell(coords);
}
if(TD) {
that.instance.getSetting('onCellMouseDown', event, coords, TD);
}
}
};
var lastMouseOver;
var onMouseOver = function (event) {
if (that.instance.settings.onCellMouseOver) {
var TD = that.wtDom.closest(event.target, ['TD', 'TH']);
if (TD !== lastMouseOver) {
lastMouseOver = TD;
that.instance.getSetting('onCellMouseOver', event, that.instance.wtTable.getCoords(TD), TD);
}
}
};
var dblClickOrigin
, dblClickTimeout;
var onMouseUp = function (event) {
if (event.button !== 2 && that.instance.settings.onCellDblClick) { //if not right mouse button
var coords
, TD = that.wtDom.closest(event.target, ['TD', 'TH']);
if (TD) {
coords = that.instance.wtTable.getCoords(TD);
}
else if (!TD && that.wtDom.hasClass(event.target, 'wtBorder') && that.wtDom.hasClass(event.target, 'current')) {
coords = that.instance.selections.current.selected[0];
TD = that.instance.wtTable.getCell(coords);
}
if (TD && dblClickOrigin === TD) {
that.instance.getSetting('onCellDblClick', event, coords, TD);
dblClickOrigin = null;
}
else {
dblClickOrigin = TD;
clearTimeout(dblClickTimeout);
dblClickTimeout = setTimeout(function () {
dblClickOrigin = null;
}, 500);
}
}
};
$(this.instance.wtTable.parent).on('mousedown', onMouseDown);
$(this.instance.settings.table).on('mouseover', onMouseOver);
$(this.instance.wtTable.parent).on('mouseup', onMouseUp);
}
//http://stackoverflow.com/questions/3629183/why-doesnt-indexof-work-on-an-array-ie8
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (elt /*, from*/) {
var len = this.length >>> 0;
var from = Number(arguments[1]) || 0;
from = (from < 0)
? Math.ceil(from)
: Math.floor(from);
if (from < 0)
from += len;
for (; from < len; from++) {
if (from in this &&
this[from] === elt)
return from;
}
return -1;
};
}
function WalkontableScroll(instance) {
this.instance = instance;
this.wtScrollbarV = new WalkontableScrollbar(instance, 'vertical');
this.wtScrollbarH = new WalkontableScrollbar(instance, 'horizontal');
}
WalkontableScroll.prototype.refreshScrollbars = function () {
this.wtScrollbarV.refresh();
this.wtScrollbarH.refresh();
};
WalkontableScroll.prototype.scrollVertical = function (delta) {
var offsetRow = this.instance.getSetting('offsetRow')
, max = this.instance.getSetting('totalRows') - this.instance.getSetting('displayRows');
if (max < 0) {
max = 0;
}
offsetRow = offsetRow + delta;
if (offsetRow < 0) {
offsetRow = 0;
}
else if (offsetRow >= max) {
offsetRow = max;
}
this.instance.update('offsetRow', offsetRow);
return this.instance;
};
WalkontableScroll.prototype.scrollHorizontal = function (delta) {
var displayColumns = this.instance.getSetting('displayColumns');
if (displayColumns !== null) {
var offsetColumn = this.instance.getSetting('offsetColumn')
, max = this.instance.getSetting('totalColumns') - displayColumns;
if (max < 0) {
max = 0;
}
offsetColumn = offsetColumn + delta;
if (offsetColumn < 0) {
offsetColumn = 0;
}
else if (offsetColumn >= max) {
offsetColumn = max;
}
this.instance.update('offsetColumn', offsetColumn);
}
return this.instance;
};
/**
* Scrolls viewport to a cell by minimum number of cells
*/
WalkontableScroll.prototype.scrollViewport = function (coords) {
var offsetRow = this.instance.getSetting('offsetRow')
, offsetColumn = this.instance.getSetting('offsetColumn')
, displayRows = this.instance.getSetting('displayRows')
, displayColumns = this.instance.getSetting('displayColumns')
, totalRows = this.instance.getSetting('totalRows')
, totalColumns = this.instance.getSetting('totalColumns');
if (coords[0] < 0 || coords[0] > totalRows - 1) {
throw new Error('row ' + coords[0] + ' does not exist');
}
else if (coords[1] < 0 || coords[1] > totalColumns - 1) {
throw new Error('column ' + coords[1] + ' does not exist');
}
if (displayRows < totalRows) {
if (coords[0] > offsetRow + displayRows - 1) {
this.scrollVertical(coords[0] - (offsetRow + displayRows - 1));
}
else if (coords[0] < offsetRow) {
this.scrollVertical(coords[0] - offsetRow);
}
}
if (displayColumns < totalColumns) {
if (coords[1] > offsetColumn + displayColumns - 1) {
this.scrollHorizontal(coords[1] - (offsetColumn + displayColumns - 1));
}
else if (coords[1] < offsetColumn) {
this.scrollHorizontal(coords[1] - offsetColumn);
}
}
return this.instance;
};
function WalkontableScrollbar(instance, type) {
var that = this;
//reference to instance
this.instance = instance;
this.type = type;
this.$table = $(this.instance.wtTable.TABLE);
//create elements
this.slider = document.createElement('DIV');
this.slider.style.position = 'absolute';
this.slider.style.top = '0';
this.slider.style.left = '0';
this.slider.className = 'dragdealer ' + type;
this.handle = document.createElement('DIV');
this.handle.className = 'handle';
this.slider.appendChild(this.handle);
this.instance.wtTable.parent.appendChild(this.slider);
this.dragdealer = new Dragdealer(this.slider, {
vertical: (type === 'vertical'),
horizontal: (type === 'horizontal'),
speed: 100,
yPrecision: 100,
animationCallback: function (x, y) {
that.onScroll(type === 'vertical' ? y : x);
}
});
}
WalkontableScrollbar.prototype.onScroll = function (delta) {
if (this.instance.drawn) {
var keys = this.type === 'vertical' ? ['offsetRow', 'totalRows', 'displayRows'] : ['offsetColumn', 'totalColumns', 'displayColumns'];
var total = this.instance.getSetting(keys[1]);
var display = this.instance.getSetting(keys[2]);
if (total > display) {
var newOffset = Math.max(0, Math.round((total - display) * delta));
if (newOffset !== this.instance.getSetting(keys[0])) { //is new offset different than old offset
this.instance.update(keys[0], newOffset);
this.instance.draw();
}
}
}
};
WalkontableScrollbar.prototype.refresh = function () {
var ratio = 1
, handleSize
, handlePosition
, offsetRow = this.instance.getSetting('offsetRow')
, offsetColumn = this.instance.getSetting('offsetColumn')
, totalRows = this.instance.getSetting('totalRows')
, totalColumns = this.instance.getSetting('totalColumns')
, tableWidth = this.$table.outerWidth()
, tableHeight = this.$table.outerHeight()
, displayRows = Math.min(this.instance.getSetting('displayRows'), totalRows)
, displayColumns = Math.min(this.instance.getSetting('displayColumns'), totalColumns);
if (!tableWidth) {
throw new Error("I could not compute table width. Is the element attached to the DOM?");
}
if (!tableHeight) {
throw new Error("I could not compute table height. Is the element attached to the DOM?");
}
if (this.type === 'vertical') {
this.slider.style.top = this.$table.position().top + 'px';
this.slider.style.left = tableWidth - 1 + 'px'; //1 is sliders border-width
this.slider.style.height = tableHeight - 2 + 'px'; //2 is sliders border-width
if (totalRows) {
ratio = displayRows / totalRows;
}
handleSize = Math.round($(this.slider).height() * ratio);
if (handleSize < 10) {
handleSize = 30;
}
this.handle.style.height = handleSize + 'px';
handlePosition = tableHeight * (offsetRow / totalRows);
if (handlePosition > tableHeight - handleSize) {
handlePosition = tableHeight - handleSize;
}
this.handle.style.top = handlePosition + 'px';
}
else if (this.type === 'horizontal') {
this.slider.style.left = this.$table.position().left + 'px';
this.slider.style.top = tableHeight - 1 + 'px'; //1 is sliders border-width
this.slider.style.width = tableWidth - 2 + 'px'; //2 is sliders border-width
if (totalColumns) {
ratio = displayColumns / totalColumns;
}
handleSize = Math.round($(this.slider).width() * ratio);
if (handleSize < 10) {
handleSize = 30;
}
this.handle.style.width = handleSize + 'px';
handlePosition = tableWidth * (offsetColumn / totalColumns);
if (handlePosition > tableWidth - handleSize) {
handlePosition = tableWidth - handleSize;
}
else if (handlePosition < 0) {
handlePosition = 0;
}
this.handle.style.left = handlePosition + 'px';
}
this.dragdealer.setWrapperOffset();
//this.dragdealer.setBoundsPadding();
this.dragdealer.setBounds();
//this.dragdealer.setSteps();
};
function WalkontableSelection(instance, settings) {
this.instance = instance;
this.selected = [];
if (settings.border) {
this.border = new WalkontableBorder(instance, settings);
}
this.onAdd = function (coords) {
var TD = instance.wtTable.getCell(coords);
if (TD) {
if (settings.className) {
instance.wtDom.addClass(TD, settings.className);
}
}
};
/*this.onRemove = function (coords) {
var TD = instance.wtTable.getCell(coords);
if (TD) {
if (settings.className) {
instance.wtDom.removeClass(TD, settings.className);
}
}
};*/
}
WalkontableSelection.prototype.add = function (coords) {
this.selected.push(coords);
};
WalkontableSelection.prototype.remove = function (coords) {
var index = this.isSelected(coords);
if (index > -1) {
this.selected.splice(index, 1);
}
};
WalkontableSelection.prototype.clear = function () {
for (var i = this.selected.length - 1; i >= 0; i--) {
this.remove(this.selected[i]);
}
};
WalkontableSelection.prototype.isSelected = function (coords) {
for (var i = 0, ilen = this.selected.length; i < ilen; i++) {
if (this.selected[i][0] === coords[0] && this.selected[i][1] === coords[1]) {
return i;
}
}
return -1;
};
WalkontableSelection.prototype.getSelected = function () {
return this.selected;
};
/**
* Returns the top left (TL) and bottom right (BR) selection coordinates
* @returns {Object}
*/
WalkontableSelection.prototype.getCorners = function () {
var minRow
, minColumn
, maxRow
, maxColumn
, i
, ilen = this.selected.length;
if (ilen > 0) {
minRow = maxRow = this.selected[0][0];
minColumn = maxColumn = this.selected[0][1];
if (ilen > 1) {
for (i = 1; i < ilen; i++) {
if (this.selected[i][0] < minRow) {
minRow = this.selected[i][0];
}
else if (this.selected[i][0] > maxRow) {
maxRow = this.selected[i][0];
}
if (this.selected[i][1] < minColumn) {
minColumn = this.selected[i][1];
}
else if (this.selected[i][1] > maxColumn) {
maxColumn = this.selected[i][1];
}
}
}
}
return [minRow, minColumn, maxRow, maxColumn];
};
WalkontableSelection.prototype.draw = function () {
var TD;
for (var i = 0, ilen = this.selected.length; i < ilen; i++) {
TD = this.instance.wtTable.getCell(this.selected[i]);
if (TD) {
this.onAdd(this.selected[i], TD);
}
}
if (this.border) {
if (ilen > 0) {
this.border.appear(this.getCorners());
}
else {
this.border.disappear(this.getCorners());
}
}
};
/*WalkontableSelection.prototype.rectangleSize = function () {
var that = this
, rowLengths = {}
, rowBegins = {}
, rowEnds = {}
, row
, col
, rowSpan
, colSpan
, lastRow
, i
, ilen
, j
, height = 0
, tableSection
, lastTableSection;
this.selected.sort(function (a, b) {
return that.wtCell.colIndex(a) - that.wtCell.colIndex(b);
});
this.selected.sort(function (a, b) {
return that.wtCell.rowIndex(a) - that.wtCell.rowIndex(b);
});
for (i = 0, ilen = this.selected.length; i < ilen; i++) {
tableSection = this.wtDom.closestParent(this.selected[i], ['THEAD', 'TBODY', 'TFOOT', 'TABLE']);
if(lastTableSection && lastTableSection !== tableSection) {
return null; //can only select cells that are in the same section (thead, tbody, tfoot or table if none of them is defined)
}
lastTableSection = tableSection;
row = this.wtCell.rowIndex(this.selected[i]);
col = this.wtCell.colIndex(this.selected[i]);
rowSpan = this.selected[i].rowSpan;
colSpan = this.selected[i].colSpan;
for (j = 0; j < rowSpan; j++) {
if (typeof rowBegins[row + j] === 'undefined' || col < rowBegins[row + j]) {
rowBegins[row + j] = col;
}
if (typeof rowEnds[row + j] === 'undefined' || col + colSpan - 1 > rowEnds[row + j]) {
rowEnds[row + j] = col + colSpan - 1;
}
if (typeof rowLengths[row + j] === 'undefined') {
rowLengths[row + j] = 0;
height++;
}
rowLengths[row + j] += colSpan;
}
}
if (!ilen) {
return null; //empty selection
}
lastRow = -1;
for (i in rowBegins) {
if (rowBegins.hasOwnProperty(i)) {
if (lastRow !== -1 && rowBegins[i] !== lastRow) {
return null; //selected rows begin in different column
}
lastRow = rowBegins[i];
}
}
lastRow = -1;
for (i in rowEnds) {
if (rowEnds.hasOwnProperty(i)) {
if (lastRow !== -1 && rowEnds[i] !== lastRow) {
return null; //selected rows end in different column
}
if (rowEnds[i] !== rowBegins[i] + rowLengths[i] - 1) {
return null; //selected rows end does not match begin + length
}
lastRow = rowEnds[i];
}
}
lastRow = -1;
for (i in rowLengths) {
if (rowLengths.hasOwnProperty(i)) {
if (lastRow !== -1 && rowLengths[i] !== lastRow) {
return null; //selected rows have different length
}
if (lastRow !== -1 && !rowLengths.hasOwnProperty(i - 1)) {
return null; //there is a row gap in selection
}
lastRow = rowLengths[i];
}
}
return {width: lastRow, height: height};
};*/
function WalkontableTable(instance) {
//reference to instance
this.instance = instance;
this.TABLE = this.instance.getSetting('table');
this.wtDom = new WalkontableDom();
this.wtDom.removeTextNodes(this.TABLE);
//wtHolder
var parent = this.TABLE.parentNode;
if (!parent || parent.nodeType !== 1 || !this.wtDom.hasClass(parent, 'wtHolder')) {
var holder = document.createElement('DIV');
holder.style.position = 'relative';
holder.className = 'wtHolder';
if (parent) {
parent.insertBefore(holder, this.TABLE); //if TABLE is detached (e.g. in Jasmine test), it has no parentNode so we cannot attach holder to it
}
holder.appendChild(this.TABLE);
this.parent = holder;
}
//bootstrap from settings
this.TBODY = this.TABLE.getElementsByTagName('TBODY')[0];
if (!this.TBODY) {
this.TBODY = document.createElement('TBODY');
this.TABLE.appendChild(this.TBODY);
}
this.THEAD = this.TABLE.getElementsByTagName('THEAD')[0];
if (!this.THEAD) {
this.THEAD = document.createElement('THEAD');
this.TABLE.insertBefore(this.THEAD, this.TBODY);
}
this.COLGROUP = this.TABLE.getElementsByTagName('COLGROUP')[0];
if (!this.COLGROUP) {
this.COLGROUP = document.createElement('COLGROUP');
this.TABLE.insertBefore(this.COLGROUP, this.THEAD);
}
if (this.instance.hasSetting('columnHeaders')) {
if (!this.THEAD.childNodes.length) {
var TR = document.createElement('TR');
this.THEAD.appendChild(TR);
}
}
this.colgroupChildrenLength = this.COLGROUP.childNodes.length;
this.theadChildrenLength = this.THEAD.firstChild ? this.THEAD.firstChild.childNodes.length : 0;
this.tbodyChildrenLength = this.TBODY.childNodes.length;
}
WalkontableTable.prototype.adjustAvailableNodes = function () {
var 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
, TR
, c;
displayRows = Math.min(displayRows, totalRows);
displayTds = Math.min(displayColumns, totalColumns);
//adjust COLGROUP
while (this.colgroupChildrenLength < displayTds + frozenColumnsCount) {
this.COLGROUP.appendChild(document.createElement('COL'));
this.colgroupChildrenLength++;
}
while (this.colgroupChildrenLength > displayTds + frozenColumnsCount) {
this.COLGROUP.removeChild(this.COLGROUP.lastChild);
this.colgroupChildrenLength--;
}
//adjust THEAD
if (this.instance.hasSetting('columnHeaders')) {
while (this.theadChildrenLength < displayTds + frozenColumnsCount) {
this.THEAD.firstChild.appendChild(document.createElement('TH'));
this.theadChildrenLength++;
}
while (this.theadChildrenLength > displayTds + frozenColumnsCount) {
this.THEAD.firstChild.removeChild(this.THEAD.firstChild.lastChild);
this.theadChildrenLength--;
}
}
//adjust TBODY
while (this.tbodyChildrenLength < displayRows) {
TR = document.createElement('TR');
for (c = 0; c < frozenColumnsCount; c++) {
TR.appendChild(document.createElement('TH'));
}
for (c = 0; c < displayTds; c++) {
TR.appendChild(document.createElement('TD'));
}
this.TBODY.appendChild(TR);
this.tbodyChildrenLength++;
}
while (this.tbodyChildrenLength > displayRows) {
this.TBODY.removeChild(this.TBODY.lastChild);
this.tbodyChildrenLength--;
}
var TRs = this.TBODY.childNodes;
var trChildrenLength;
for (var r = 0, rlen = TRs.length; r < rlen; r++) {
trChildrenLength = TRs[r].childNodes.length;
while (trChildrenLength < displayTds + frozenColumnsCount) {
TRs[r].appendChild(document.createElement('TD'));
trChildrenLength++;
}
while (trChildrenLength > displayTds + frozenColumnsCount) {
TRs[r].removeChild(TRs[r].lastChild);
trChildrenLength--;
}
}
};
WalkontableTable.prototype.draw = function () {
var r
, c
, 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
, TR
, TH
, TD
, cellData;
this.adjustAvailableNodes();
this.tableOffset = this.wtDom.offset(this.TABLE);
displayRows = Math.min(displayRows, totalRows);
displayTds = Math.min(displayColumns, totalColumns);
//draw COLGROUP
for (c = 0; c < this.colgroupChildrenLength; c++) {
if (c < frozenColumnsCount) {
this.wtDom.addClass(this.COLGROUP.childNodes[c], 'rowHeader');
if (typeof frozenColumns[c] === "function") {
frozenColumns[c](null, this.COLGROUP.childNodes[c])
}
}
else {
this.wtDom.removeClass(this.COLGROUP.childNodes[c], 'rowHeader');
}
}
if (this.instance.hasSetting('columnWidth')) {
for (c = 0; c < displayTds; c++) {
this.COLGROUP.childNodes[c + frozenColumnsCount].style.width = this.instance.getSetting('columnWidth', offsetColumn + c) + 'px';
}
}
//draw THEAD
if (frozenColumnsCount && this.instance.hasSetting('columnHeaders')) {
for (c = 0; c < frozenColumnsCount; c++) {
if (typeof frozenColumns[c] === "function") {
frozenColumns[c](null, this.THEAD.childNodes[0].childNodes[c])
}
else {
this.THEAD.childNodes[0].childNodes[c].innerHTML = '';
}
}
}
if (this.instance.hasSetting('columnHeaders')) {
for (c = 0; c < displayTds; c++) {
this.THEAD.childNodes[0].childNodes[frozenColumnsCount + c].innerHTML = this.instance.getSetting('columnHeaders', offsetColumn + c);
}
}
//draw TBODY
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
TH = TR.childNodes[c];
cellData = typeof frozenColumns[c] === "function" ? frozenColumns[c](offsetRow + r, TH) : frozenColumns[c];
if (cellData !== void 0) {
TH.innerHTML = cellData;
}
/*
we can assume that frozenColumns[c] function took care of inserting content into TH
else {
TH.innerHTML = '';
}*/
}
for (c = 0; c < displayTds; c++) { //in future use nextSibling; http://jsperf.com/nextsibling-vs-indexed-childnodes
TD = TR.childNodes[c + frozenColumnsCount];
TD.className = '';
this.instance.getSetting('cellRenderer', offsetRow + r, offsetColumn + c, TD);
}
}
//redraw selections
if (this.instance.selections) {
for (r in this.instance.selections) {
if (this.instance.selections.hasOwnProperty(r)) {
this.instance.selections[r].draw();
}
}
}
return this;
};
WalkontableTable.prototype.getCell = function (coords) {
var offsetRow = this.instance.getSetting('offsetRow')
, offsetColumn = this.instance.getSetting('offsetColumn')
, displayRows = this.instance.getSetting('displayRows')
, displayColumns = this.instance.getSetting('displayColumns')
, frozenColumns = this.instance.getSetting('frozenColumns')
, frozenColumnsCount = frozenColumns ? frozenColumns.length : 0;
if (coords[0] >= offsetRow && coords[0] <= offsetRow + displayRows - 1) {
if (coords[1] >= offsetColumn && coords[1] < offsetColumn + displayColumns) {
return this.TBODY.childNodes[coords[0] - offsetRow].childNodes[coords[1] - offsetColumn + frozenColumnsCount];
}
}
return null;
};
WalkontableTable.prototype.getCoords = function (TD) {
var frozenColumns = this.instance.getSetting('frozenColumns')
, frozenColumnsCount = frozenColumns ? frozenColumns.length : 0;
return [
this.wtDom.prevSiblings(TD.parentNode).length + this.instance.getSetting('offsetRow'),
TD.cellIndex + this.instance.getSetting('offsetColumn') - frozenColumnsCount
];
};
function WalkontableWheel(instance) {
var that = this;
//reference to instance
this.instance = instance;
var wheelTimeout;
$(this.instance.settings.table).on('mousewheel', function (event, delta, deltaX, deltaY) {
clearTimeout(wheelTimeout);
wheelTimeout = setTimeout(function () { //timeout is needed because with fast-wheel scrolling mousewheel event comes dozen times per second
if (deltaY) {
that.instance.scrollVertical(-deltaY).draw();
}
else if (deltaX) {
that.instance.scrollHorizontal(deltaX).draw();
}
}, 0);
event.preventDefault();
});
}
/**
* Dragdealer JS v0.9.5 - patched by Walkontable at line 66
* http://code.ovidiu.ch/dragdealer-js
*
* Copyright (c) 2010, Ovidiu Chereches
* MIT License
* http://legal.ovidiu.ch/licenses/MIT
*/
/* Cursor */
var Cursor =
{
x: 0, y: 0,
init: function()
{
this.setEvent('mouse');
this.setEvent('touch');
},
setEvent: function(type)
{
var moveHandler = document['on' + type + 'move'] || function(){};
document['on' + type + 'move'] = function(e)
{
moveHandler(e);
Cursor.refresh(e);
}
},
refresh: function(e)
{
if(!e)
{
e = window.event;
}
if(e.type == 'mousemove')
{
this.set(e);
}
else if(e.touches)
{
this.set(e.touches[0]);
}
},
set: function(e)
{
if(e.pageX || e.pageY)
{
this.x = e.pageX;
this.y = e.pageY;
}
else if(e.clientX || e.clientY)
{
this.x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
this.y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
}
};
Cursor.init();
/* Position */
var Position =
{
get: function(obj)
{
var curtop = 0, curleft = 0; //Walkontable patch. Original (var curleft = curtop = 0;) created curtop in global scope
if(obj.offsetParent)
{
do
{
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
}
while((obj = obj.offsetParent));
}
return [curleft, curtop];
}
};
/* Dragdealer */
var Dragdealer = function(wrapper, options)
{
if(typeof(wrapper) == 'string')
{
wrapper = document.getElementById(wrapper);
}
if(!wrapper)
{
return;
}
var handle = wrapper.getElementsByTagName('div')[0];
if(!handle || handle.className.search(/(^|\s)handle(\s|$)/) == -1)
{
return;
}
this.init(wrapper, handle, options || {});
this.setup();
};
Dragdealer.prototype =
{
init: function(wrapper, handle, options)
{
this.wrapper = wrapper;
this.handle = handle;
this.options = options;
this.disabled = this.getOption('disabled', false);
this.horizontal = this.getOption('horizontal', true);
this.vertical = this.getOption('vertical', false);
this.slide = this.getOption('slide', true);
this.steps = this.getOption('steps', 0);
this.snap = this.getOption('snap', false);
this.loose = this.getOption('loose', false);
this.speed = this.getOption('speed', 10) / 100;
this.xPrecision = this.getOption('xPrecision', 0);
this.yPrecision = this.getOption('yPrecision', 0);
this.callback = options.callback || null;
this.animationCallback = options.animationCallback || null;
this.bounds = {
left: options.left || 0, right: -(options.right || 0),
top: options.top || 0, bottom: -(options.bottom || 0),
x0: 0, x1: 0, xRange: 0,
y0: 0, y1: 0, yRange: 0
};
this.value = {
prev: [-1, -1],
current: [options.x || 0, options.y || 0],
target: [options.x || 0, options.y || 0]
};
this.offset = {
wrapper: [0, 0],
mouse: [0, 0],
prev: [-999999, -999999],
current: [0, 0],
target: [0, 0]
};
this.change = [0, 0];
this.activity = false;
this.dragging = false;
this.tapping = false;
},
getOption: function(name, defaultValue)
{
return this.options[name] !== undefined ? this.options[name] : defaultValue;
},
setup: function()
{
this.setWrapperOffset();
this.setBoundsPadding();
this.setBounds();
this.setSteps();
this.addListeners();
},
setWrapperOffset: function()
{
this.offset.wrapper = Position.get(this.wrapper);
},
setBoundsPadding: function()
{
if(!this.bounds.left && !this.bounds.right)
{
this.bounds.left = Position.get(this.handle)[0] - this.offset.wrapper[0];
this.bounds.right = -this.bounds.left;
}
if(!this.bounds.top && !this.bounds.bottom)
{
this.bounds.top = Position.get(this.handle)[1] - this.offset.wrapper[1];
this.bounds.bottom = -this.bounds.top;
}
},
setBounds: function()
{
this.bounds.x0 = this.bounds.left;
this.bounds.x1 = this.wrapper.offsetWidth + this.bounds.right;
this.bounds.xRange = (this.bounds.x1 - this.bounds.x0) - this.handle.offsetWidth;
this.bounds.y0 = this.bounds.top;
this.bounds.y1 = this.wrapper.offsetHeight + this.bounds.bottom;
this.bounds.yRange = (this.bounds.y1 - this.bounds.y0) - this.handle.offsetHeight;
this.bounds.xStep = 1 / (this.xPrecision || Math.max(this.wrapper.offsetWidth, this.handle.offsetWidth));
this.bounds.yStep = 1 / (this.yPrecision || Math.max(this.wrapper.offsetHeight, this.handle.offsetHeight));
},
setSteps: function()
{
if(this.steps > 1)
{
this.stepRatios = [];
for(var i = 0; i <= this.steps - 1; i++)
{
this.stepRatios[i] = i / (this.steps - 1);
}
}
},
addListeners: function()
{
var self = this;
this.wrapper.onselectstart = function()
{
return false;
}
this.handle.onmousedown = this.handle.ontouchstart = function(e)
{
self.handleDownHandler(e);
};
this.wrapper.onmousedown = this.wrapper.ontouchstart = function(e)
{
self.wrapperDownHandler(e);
};
var mouseUpHandler = document.onmouseup || function(){};
document.onmouseup = function(e)
{
mouseUpHandler(e);
self.documentUpHandler(e);
};
var touchEndHandler = document.ontouchend || function(){};
document.ontouchend = function(e)
{
touchEndHandler(e);
self.documentUpHandler(e);
};
var resizeHandler = window.onresize || function(){};
window.onresize = function(e)
{
resizeHandler(e);
self.documentResizeHandler(e);
};
this.wrapper.onmousemove = function(e)
{
self.activity = true;
}
this.wrapper.onclick = function(e)
{
return !self.activity;
}
this.interval = setInterval(function(){ self.animate() }, 25);
self.animate(false, true);
},
handleDownHandler: function(e)
{
this.activity = false;
Cursor.refresh(e);
this.preventDefaults(e, true);
this.startDrag();
this.cancelEvent(e);
},
wrapperDownHandler: function(e)
{
Cursor.refresh(e);
this.preventDefaults(e, true);
this.startTap();
},
documentUpHandler: function(e)
{
this.stopDrag();
this.stopTap();
//this.cancelEvent(e);
},
documentResizeHandler: function(e)
{
this.setWrapperOffset();
this.setBounds();
this.update();
},
enable: function()
{
this.disabled = false;
this.handle.className = this.handle.className.replace(/\s?disabled/g, '');
},
disable: function()
{
this.disabled = true;
this.handle.className += ' disabled';
},
setStep: function(x, y, snap)
{
this.setValue(
this.steps && x > 1 ? (x - 1) / (this.steps - 1) : 0,
this.steps && y > 1 ? (y - 1) / (this.steps - 1) : 0,
snap
);
},
setValue: function(x, y, snap)
{
this.setTargetValue([x, y || 0]);
if(snap)
{
this.groupCopy(this.value.current, this.value.target);
}
},
startTap: function(target)
{
if(this.disabled)
{
return;
}
this.tapping = true;
if(target === undefined)
{
target = [
Cursor.x - this.offset.wrapper[0] - (this.handle.offsetWidth / 2),
Cursor.y - this.offset.wrapper[1] - (this.handle.offsetHeight / 2)
];
}
this.setTargetOffset(target);
},
stopTap: function()
{
if(this.disabled || !this.tapping)
{
return;
}
this.tapping = false;
this.setTargetValue(this.value.current);
this.result();
},
startDrag: function()
{
if(this.disabled)
{
return;
}
this.offset.mouse = [
Cursor.x - Position.get(this.handle)[0],
Cursor.y - Position.get(this.handle)[1]
];
this.dragging = true;
},
stopDrag: function()
{
if(this.disabled || !this.dragging)
{
return;
}
this.dragging = false;
var target = this.groupClone(this.value.current);
if(this.slide)
{
var ratioChange = this.change;
target[0] += ratioChange[0] * 4;
target[1] += ratioChange[1] * 4;
}
this.setTargetValue(target);
this.result();
},
feedback: function()
{
var value = this.value.current;
if(this.snap && this.steps > 1)
{
value = this.getClosestSteps(value);
}
if(!this.groupCompare(value, this.value.prev))
{
if(typeof(this.animationCallback) == 'function')
{
this.animationCallback(value[0], value[1]);
}
this.groupCopy(this.value.prev, value);
}
},
result: function()
{
if(typeof(this.callback) == 'function')
{
this.callback(this.value.target[0], this.value.target[1]);
}
},
animate: function(direct, first)
{
if(direct && !this.dragging)
{
return;
}
if(this.dragging)
{
var prevTarget = this.groupClone(this.value.target);
var offset = [
Cursor.x - this.offset.wrapper[0] - this.offset.mouse[0],
Cursor.y - this.offset.wrapper[1] - this.offset.mouse[1]
];
this.setTargetOffset(offset, this.loose);
this.change = [
this.value.target[0] - prevTarget[0],
this.value.target[1] - prevTarget[1]
];
}
if(this.dragging || first)
{
this.groupCopy(this.value.current, this.value.target);
}
if(this.dragging || this.glide() || first)
{
this.update();
this.feedback();
}
},
glide: function()
{
var diff = [
this.value.target[0] - this.value.current[0],
this.value.target[1] - this.value.current[1]
];
if(!diff[0] && !diff[1])
{
return false;
}
if(Math.abs(diff[0]) > this.bounds.xStep || Math.abs(diff[1]) > this.bounds.yStep)
{
this.value.current[0] += diff[0] * this.speed;
this.value.current[1] += diff[1] * this.speed;
}
else
{
this.groupCopy(this.value.current, this.value.target);
}
return true;
},
update: function()
{
if(!this.snap)
{
this.offset.current = this.getOffsetsByRatios(this.value.current);
}
else
{
this.offset.current = this.getOffsetsByRatios(
this.getClosestSteps(this.value.current)
);
}
this.show();
},
show: function()
{
if(!this.groupCompare(this.offset.current, this.offset.prev))
{
if(this.horizontal)
{
this.handle.style.left = String(this.offset.current[0]) + 'px';
}
if(this.vertical)
{
this.handle.style.top = String(this.offset.current[1]) + 'px';
}
this.groupCopy(this.offset.prev, this.offset.current);
}
},
setTargetValue: function(value, loose)
{
var target = loose ? this.getLooseValue(value) : this.getProperValue(value);
this.groupCopy(this.value.target, target);
this.offset.target = this.getOffsetsByRatios(target);
},
setTargetOffset: function(offset, loose)
{
var value = this.getRatiosByOffsets(offset);
var target = loose ? this.getLooseValue(value) : this.getProperValue(value);
this.groupCopy(this.value.target, target);
this.offset.target = this.getOffsetsByRatios(target);
},
getLooseValue: function(value)
{
var proper = this.getProperValue(value);
return [
proper[0] + ((value[0] - proper[0]) / 4),
proper[1] + ((value[1] - proper[1]) / 4)
];
},
getProperValue: function(value)
{
var proper = this.groupClone(value);
proper[0] = Math.max(proper[0], 0);
proper[1] = Math.max(proper[1], 0);
proper[0] = Math.min(proper[0], 1);
proper[1] = Math.min(proper[1], 1);
if((!this.dragging && !this.tapping) || this.snap)
{
if(this.steps > 1)
{
proper = this.getClosestSteps(proper);
}
}
return proper;
},
getRatiosByOffsets: function(group)
{
return [
this.getRatioByOffset(group[0], this.bounds.xRange, this.bounds.x0),
this.getRatioByOffset(group[1], this.bounds.yRange, this.bounds.y0)
];
},
getRatioByOffset: function(offset, range, padding)
{
return range ? (offset - padding) / range : 0;
},
getOffsetsByRatios: function(group)
{
return [
this.getOffsetByRatio(group[0], this.bounds.xRange, this.bounds.x0),
this.getOffsetByRatio(group[1], this.bounds.yRange, this.bounds.y0)
];
},
getOffsetByRatio: function(ratio, range, padding)
{
return Math.round(ratio * range) + padding;
},
getClosestSteps: function(group)
{
return [
this.getClosestStep(group[0]),
this.getClosestStep(group[1])
];
},
getClosestStep: function(value)
{
var k = 0;
var min = 1;
for(var i = 0; i <= this.steps - 1; i++)
{
if(Math.abs(this.stepRatios[i] - value) < min)
{
min = Math.abs(this.stepRatios[i] - value);
k = i;
}
}
return this.stepRatios[k];
},
groupCompare: function(a, b)
{
return a[0] == b[0] && a[1] == b[1];
},
groupCopy: function(a, b)
{
a[0] = b[0];
a[1] = b[1];
},
groupClone: function(a)
{
return [a[0], a[1]];
},
preventDefaults: function(e, selection)
{
if(!e)
{
e = window.event;
}
if(e.preventDefault)
{
e.preventDefault();
}
e.returnValue = false;
if(selection && document.selection)
{
document.selection.empty();
}
},
cancelEvent: function(e)
{
if(!e)
{
e = window.event;
}
if(e.stopPropagation)
{
e.stopPropagation();
}
e.cancelBubble = true;
}
};
/*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
* Licensed under the MIT License (LICENSE.txt).
*
* Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
* Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
* Thanks to: Seamus Leahy for adding deltaX and deltaY
*
* Version: 3.0.6
*
* Requires: 1.2.2+
*/
(function($) {
var types = ['DOMMouseScroll', 'mousewheel'];
if ($.event.fixHooks) {
for ( var i=types.length; i; ) {
$.event.fixHooks[ types[--i] ] = $.event.mouseHooks;
}
}
$.event.special.mousewheel = {
setup: function() {
if ( this.addEventListener ) {
for ( var i=types.length; i; ) {
this.addEventListener( types[--i], handler, false );
}
} else {
this.onmousewheel = handler;
}
},
teardown: function() {
if ( this.removeEventListener ) {
for ( var i=types.length; i; ) {
this.removeEventListener( types[--i], handler, false );
}
} else {
this.onmousewheel = null;
}
}
};
$.fn.extend({
mousewheel: function(fn) {
return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
},
unmousewheel: function(fn) {
return this.unbind("mousewheel", fn);
}
});
function handler(event) {
var orgEvent = event || window.event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0;
event = $.event.fix(orgEvent);
event.type = "mousewheel";
// Old school scrollwheel delta
if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta/120; }
if ( orgEvent.detail ) { delta = -orgEvent.detail/3; }
// New school multidimensional scroll (touchpads) deltas
deltaY = delta;
// Gecko
if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
deltaY = 0;
deltaX = -1*delta;
}
// Webkit
if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; }
if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; }
// Add event and delta to the front of the arguments
args.unshift(event, delta, deltaX, deltaY);
return ($.event.dispatch || $.event.handle).apply(this, args);
}
})(jQuery);
})(jQuery, window, Handsontable);
/* =============================================================
* bootstrap-typeahead.js v2.1.1
* http://twitter.github.com/bootstrap/javascript.html#typeahead
* =============================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================ */
!function($){
"use strict"; // jshint ;_;
/* TYPEAHEAD PUBLIC CLASS DEFINITION
* ================================= */
var Typeahead = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, $.fn.typeahead.defaults, options)
this.matcher = this.options.matcher || this.matcher
this.sorter = this.options.sorter || this.sorter
this.highlighter = this.options.highlighter || this.highlighter
this.updater = this.options.updater || this.updater
this.$menu = $(this.options.menu).appendTo('body')
this.source = this.options.source
this.shown = false
this.listen()
}
Typeahead.prototype = {
constructor: Typeahead
, select: function () {
var val = this.$menu.find('.active').attr('data-value')
this.$element
.val(this.updater(val))
.change()
return this.hide()
}
, updater: function (item) {
return item
}
, show: function () {
var pos = $.extend({}, this.$element.offset(), {
height: this.$element[0].offsetHeight
})
this.$menu.css({
top: pos.top + pos.height
, left: pos.left
})
this.$menu.show()
this.shown = true
return this
}
, hide: function () {
this.$menu.hide()
this.shown = false
return this
}
, lookup: function (event) {
var items
this.query = this.$element.val()
if (!this.query || this.query.length < this.options.minLength) {
return this.shown ? this.hide() : this
}
items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
return items ? this.process(items) : this
}
, process: function (items) {
var that = this
items = $.grep(items, function (item) {
return that.matcher(item)
})
items = this.sorter(items)
if (!items.length) {
return this.shown ? this.hide() : this
}
return this.render(items.slice(0, this.options.items)).show()
}
, matcher: function (item) {
return ~item.toLowerCase().indexOf(this.query.toLowerCase())
}
, sorter: function (items) {
var beginswith = []
, caseSensitive = []
, caseInsensitive = []
, item
while (item = items.shift()) {
if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
else if (~item.indexOf(this.query)) caseSensitive.push(item)
else caseInsensitive.push(item)
}
return beginswith.concat(caseSensitive, caseInsensitive)
}
, highlighter: function (item) {
var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
return '' + match + ''
})
}
, render: function (items) {
var that = this
items = $(items).map(function (i, item) {
i = $(that.options.item).attr('data-value', item)
i.find('a').html(that.highlighter(item))
return i[0]
})
items.first().addClass('active')
this.$menu.html(items)
return this
}
, next: function (event) {
var active = this.$menu.find('.active').removeClass('active')
, next = active.next()
if (!next.length) {
next = $(this.$menu.find('li')[0])
}
next.addClass('active')
}
, prev: function (event) {
var active = this.$menu.find('.active').removeClass('active')
, prev = active.prev()
if (!prev.length) {
prev = this.$menu.find('li').last()
}
prev.addClass('active')
}
, listen: function () {
this.$element
.on('blur', $.proxy(this.blur, this))
.on('keypress', $.proxy(this.keypress, this))
.on('keyup', $.proxy(this.keyup, this))
if ($.browser.chrome || $.browser.webkit || $.browser.msie) {
this.$element.on('keydown', $.proxy(this.keydown, this))
}
this.$menu
.on('click', $.proxy(this.click, this))
.on('mouseenter', 'li', $.proxy(this.mouseenter, this))
}
, move: function (e) {
if (!this.shown) return
switch(e.keyCode) {
case 9: // tab
case 13: // enter
case 27: // escape
e.preventDefault()
break
case 38: // up arrow
e.preventDefault()
this.prev()
break
case 40: // down arrow
e.preventDefault()
this.next()
break
}
e.stopPropagation()
}
, keydown: function (e) {
this.suppressKeyPressRepeat = !~$.inArray(e.keyCode, [40,38,9,13,27])
this.move(e)
}
, keypress: function (e) {
if (this.suppressKeyPressRepeat) return
this.move(e)
}
, keyup: function (e) {
switch(e.keyCode) {
case 40: // down arrow
case 38: // up arrow
break
case 9: // tab
case 13: // enter
if (!this.shown) return
this.select()
break
case 27: // escape
if (!this.shown) return
this.hide()
break
default:
this.lookup()
}
e.stopPropagation()
e.preventDefault()
}
, blur: function (e) {
var that = this
setTimeout(function () { that.hide() }, 150)
}
, click: function (e) {
e.stopPropagation()
e.preventDefault()
this.select()
}
, mouseenter: function (e) {
this.$menu.find('.active').removeClass('active')
$(e.currentTarget).addClass('active')
}
}
/* TYPEAHEAD PLUGIN DEFINITION
* =========================== */
$.fn.typeahead = function (option) {
return this.each(function () {
var $this = $(this)
, data = $this.data('typeahead')
, options = typeof option == 'object' && option
if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.typeahead.defaults = {
source: []
, items: 8
, menu: ''
, item: ''
, minLength: 1
}
$.fn.typeahead.Constructor = Typeahead
/* TYPEAHEAD DATA-API
* ================== */
$(function () {
$('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
var $this = $(this)
if ($this.data('typeahead')) return
e.preventDefault()
$this.typeahead($this.data())
})
})
}(window.jQuery);
/*!
* jQuery contextMenu - Plugin for simple contextMenu handling
*
* Version: 1.5.25
*
* Authors: Rodney Rehm, Addy Osmani (patches for FF)
* Web: http://medialize.github.com/jQuery-contextMenu/
*
* Licensed under
* MIT License http://www.opensource.org/licenses/mit-license
* GPL v3 http://opensource.org/licenses/GPL-3.0
*
*/
(function($, undefined){
// TODO: -
// ARIA stuff: menuitem, menuitemcheckbox und menuitemradio
// create |