Last active
December 2, 2015 13:07
-
-
Save optilude/9864800 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Autocomplete editor that can store a value separately to its displayed | |
* value. Uses a "handsontable-in-handsontable" editor. | |
* | |
* Requires the following configuration: | |
* | |
* - `source`, an array of arrays or a function that takes `query` and | |
* `callback` parameters like the one used by `AutocompleteEditor`. | |
* The data passed should be an array of objects or array of arrays, | |
* akin to the `data` of a Handsontable instance. | |
* - `handsontable`, an object with settings for the inner table. Typical | |
* settings include `colWidths`, `colHeaders`, `dataSchema` and | |
* `columns`. | |
* - `extractValue`, a function that will be passed a selected row from | |
* the `source` data and which should return the *value* to store in | |
* the underlying handsontable. | |
* - `extractTitle`, a function that will be passed a selected row from | |
* the `source` data and which should return the *title* to show when | |
* opening the editor | |
* | |
* For example: | |
* | |
* { | |
* data: "user", | |
* editor: KeyValueAutocompleteEditor | |
* handsontable: { | |
* colWidths: [140, 196], | |
* colHeaders: [Name", "Email"], | |
* dataSchema: {name: null, email: null}, | |
* columns: [{ | |
* data: 'name' | |
* }, { | |
* data: 'email' | |
* }] | |
* }, | |
* extractValue: function(row) { return row.userId; }, | |
* extractTitle: function(row) { return row.name; }, | |
* source: function(query, callback) { | |
* | |
* if(query.length < 2) { | |
* return callback([]); | |
* } | |
* | |
* // This would more likely use an AJAX call or some other | |
* // mechanism to build an array of arrays of possible values | |
* callback([{userId: 1, name: 'User 1', email: '[email protected]'}, | |
* {userId: 2, name: 'User 2', email: '[email protected]'}]); | |
* | |
* }, | |
* } | |
* | |
* When a row is chosen in the editor the value returned by `extractValue()` | |
* is saved in the underlying handsontable, but a "hint" is also stored in | |
* the relevant row of the underlying handsontable data array. The hint has | |
* a key of `_<prop>` where <prop> is the name of the column, so in the | |
* example above, that would be `_user`. The hint contains the full row of | |
* the inner handsontable. In the example above, that would be a single | |
* object with keys `userId`, `name` and `email`. | |
* | |
* You can load the hint object into the data array before the editor is | |
* invoked for the first time to let the editor start with the correct | |
* value. You can also use this in a cell renderer to render the title | |
* rather than the data: | |
* | |
* renderer: function(instance, td, row, col, prop, value, cellProperties) { | |
* if(value !== undefined && value !== null) { | |
* var hint = instance.getDataAtRow(row)['_' + prop]; | |
* | |
* if(hint !== undefined) { | |
* value = cellProperties.extractTitle(hint); | |
* } | |
* } | |
* | |
* Handsontable.AutocompleteRenderer(instance, td, row, col, prop, value, cellProperties); | |
* } | |
* | |
*/ | |
var KeyValueAutocompleteEditor = (function() { | |
var KeyValueAutocompleteEditor = Handsontable.editors.HandsontableEditor.prototype.extend(); | |
KeyValueAutocompleteEditor.prototype.init = function () { | |
Handsontable.editors.HandsontableEditor.prototype.init.apply(this, arguments); | |
this.query = null; | |
this.choices = []; | |
}; | |
KeyValueAutocompleteEditor.prototype.prepare = function () { | |
Handsontable.editors.HandsontableEditor.prototype.prepare.apply(this, arguments); | |
this.hintProp = '_' + this.prop; | |
// Make the prototype behave as we want | |
this.cellProperties.filter = false; | |
this.cellProperties.strict = true; | |
}; | |
KeyValueAutocompleteEditor.prototype.beginEditing = function (initialValue) { | |
// Set initial editor value based on the title that was last used | |
var hint = this.instance.getDataAtRow(this.row)[this.hintProp]; | |
if(hint !== undefined && this.cellProperties.extractTitle !== undefined) { | |
initialValue = this.cellProperties.extractTitle(hint); | |
} | |
Handsontable.editors.HandsontableEditor.prototype.beginEditing.apply(this, [initialValue]); | |
}; | |
KeyValueAutocompleteEditor.prototype.createElements = function(){ | |
Handsontable.editors.HandsontableEditor.prototype.createElements.apply(this, arguments); | |
this.$htContainer.addClass('keyValueAutocompleteEditor'); | |
}; | |
KeyValueAutocompleteEditor.prototype.bindEvents = function(){ | |
var that = this, | |
keyTimeout = 250, | |
timer = null; | |
this.$textarea.on('keydown.autocompleteEditor', function(event){ | |
if(event.altKey || event.ctrlKey || event.metaKey || | |
(Handsontable.helper.isMetaKey(event.keyCode) && [Handsontable.helper.keyCode.BACKSPACE, Handsontable.helper.keyCode.DELETE].indexOf(event.keyCode) === -1)) { | |
return; | |
} | |
// Don't run query until user stops typing | |
if(timer !== null) { | |
clearTimeout(timer); | |
} | |
timer = setTimeout(function () { | |
that.queryChoices(that.$textarea.val()); | |
}, keyTimeout); | |
}); | |
this.$htContainer.on('mouseleave', function () { | |
that.highlightBestMatchingChoice(); | |
}); | |
this.$htContainer.on('mouseenter', function () { | |
that.$htContainer.handsontable('deselectCell'); | |
}); | |
Handsontable.editors.HandsontableEditor.prototype.bindEvents.apply(this, arguments); | |
}; | |
var onBeforeKeyDownInner; | |
KeyValueAutocompleteEditor.prototype.open = function () { | |
Handsontable.editors.HandsontableEditor.prototype.open.apply(this, arguments); | |
this.$textarea[0].style.visibility = 'visible'; | |
this.focus(); | |
var choicesListHot = this.$htContainer.handsontable('getInstance'); | |
var that = this; | |
choicesListHot.updateSettings({ | |
afterRenderer: function (TD, row, col, prop, value) { | |
if(!_.isString(value)){ | |
return; | |
} | |
var caseSensitive = this.getCellMeta(row, col).filteringCaseSensitive === true; | |
var indexOfMatch = caseSensitive ? value.indexOf(this.query) : value.toLowerCase().indexOf(that.query.toLowerCase()); | |
if(indexOfMatch != -1){ | |
var match = value.substr(indexOfMatch, that.query.length); | |
TD.innerHTML = value.replace(match, '<strong>' + match + '</strong>'); | |
} | |
} | |
}); | |
onBeforeKeyDownInner = function (event) { | |
var instance = this; | |
if (event.keyCode == Handsontable.helper.keyCode.ARROW_UP){ | |
if (instance.getSelected() && instance.getSelected()[0] === 0){ | |
that.instance.listen(); | |
that.focus(); | |
event.preventDefault(); | |
event.stopImmediatePropagation(); | |
} | |
} | |
}; | |
choicesListHot.addHook('beforeKeyDown', onBeforeKeyDownInner); | |
this.queryChoices(this.TEXTAREA.value); | |
}; | |
KeyValueAutocompleteEditor.prototype.close = function () { | |
this.$htContainer.handsontable('getInstance').removeHook('beforeKeyDown', onBeforeKeyDownInner); | |
// Clear hint if we're not going to have one | |
if(!this.$htContainer.handsontable('getInstance').getSelected()) { | |
this.saveValue([[null]]); | |
this.instance.getDataAtRow(this.row)[this.hintProp] = {}; | |
} | |
Handsontable.editors.HandsontableEditor.prototype.close.apply(this, arguments); | |
}; | |
KeyValueAutocompleteEditor.prototype.queryChoices = function(query){ | |
this.query = query; | |
if (typeof this.cellProperties.source == 'function'){ | |
var that = this; | |
this.cellProperties.source(query, function(choices){ | |
that.updateChoicesList(choices); | |
}); | |
} else if (Handsontable.helper.isArray(this.cellProperties.source)) { | |
this.updateChoicesList(this.cellProperties.source); | |
} else { | |
this.updateChoicesList([]); | |
} | |
}; | |
KeyValueAutocompleteEditor.prototype.updateChoicesList = function (choices) { | |
this.choices = choices; | |
this.$htContainer.handsontable('loadData', choices); | |
this.highlightBestMatchingChoice(); | |
this.focus(); | |
}; | |
KeyValueAutocompleteEditor.prototype.highlightBestMatchingChoice = function () { | |
var bestMatchingChoice = this.findBestMatchingChoice(); | |
if ( typeof bestMatchingChoice == 'undefined' && this.cellProperties.allowInvalid === false){ | |
bestMatchingChoice = 0; | |
} | |
if(typeof bestMatchingChoice == 'undefined'){ | |
this.$htContainer.handsontable('deselectCell'); | |
} else { | |
var endCol = this.$htContainer.handsontable('countCols') - 1; | |
this.$htContainer.handsontable('selectCell', bestMatchingChoice, 0, bestMatchingChoice, endCol, true); | |
} | |
}; | |
KeyValueAutocompleteEditor.prototype.findBestMatchingChoice = function(){ | |
var bestMatch = {}, | |
hot = this.$htContainer.handsontable('getInstance'), | |
valueLength = this.getValue().length, | |
currentItem, | |
indexOfValue, | |
charsLeft; | |
for(var i = 0, len = this.choices.length; i < len; i++){ | |
currentItem = this.cellProperties.extractTitle(this.choices[i]); | |
if(valueLength > 0){ | |
indexOfValue = currentItem.indexOf(this.getValue()); | |
} else { | |
indexOfValue = currentItem === this.getValue() ? 0 : -1; | |
} | |
if(indexOfValue == -1) continue; | |
charsLeft = currentItem.length - indexOfValue - valueLength; | |
if( typeof bestMatch.indexOfValue == 'undefined' || bestMatch.indexOfValue > indexOfValue || | |
( bestMatch.indexOfValue == indexOfValue && bestMatch.charsLeft > charsLeft ) ){ | |
bestMatch.indexOfValue = indexOfValue; | |
bestMatch.charsLeft = charsLeft; | |
bestMatch.index = i; | |
} | |
} | |
return bestMatch.index; | |
}; | |
KeyValueAutocompleteEditor.prototype.finishEditing = function (isCancelled, ctrlDown) { | |
var hot = this.$htContainer.handsontable('getInstance'), | |
selection = hot.getSelected(); | |
if (hot.isListening()) { //if focus is still in the HOT editor | |
this.instance.listen(); //return the focus to the parent HOT instance | |
} | |
if (selection) { | |
var selectedRow = selection[0], | |
value = this.cellProperties.extractValue(hot.getDataAtRow(selectedRow)), | |
title = this.cellProperties.extractTitle(hot.getDataAtRow(selectedRow)); | |
// Value stored in the table | |
if (value !== void 0) { | |
this.saveValue([[value]]); | |
// Store rest of data in a hint for future rendering | |
this.instance.getDataAtRow(this.row)[this.hintProp] = hot.getDataAtRow(selectedRow); | |
} | |
// Value of the editor text box | |
if (title !== void 0) { | |
this.setValue(title); | |
} | |
} | |
return Handsontable.editors.TextEditor.prototype.finishEditing.apply(this, arguments); | |
}; | |
return KeyValueAutocompleteEditor; | |
})(Handsontable); |
radusl :
you can replace
!_.isString(value)
by:
typeof(value)!='string'
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
i tried your example and i get an error "Uncaught ReferenceError: _ is not defined" at KeyValueAutocompleteEditor.js at line 164