Feat: Added filter and trigger options for autocomplete (#713)

* Feat: Added filter options for autocomplete

* Feat: added trigger option for autocomplete

* Fix: const/let => var
This commit is contained in:
Gcaufy 2019-06-08 17:37:52 +08:00 committed by Jos de Jong
parent 59290dd6c2
commit 85e0710097
4 changed files with 101 additions and 50 deletions

View File

@ -274,6 +274,22 @@ Constructs a new JSONEditor.
*autocomplete* will enable this feature in your editor in tree mode, the object have the following **subelements**:
- `{string} filter`
- `{Function} filter`
Indicate the filter method of the autocomplete. Default to `start`.
- `start` : Match your input from the start, e.g. `ap` match `apple` but `pl` does not.
- `contain` : Contain your input or not, e.g. `pl` match `apple` too.
- Custom Function : Define custom filter rule, return `true` will match you input.
- `{string} trigger`
Indicate the way to trigger autocomplete menu. Default to `keydown`
- `keydown` : When you type something in the field or value, it will trigger autocomplete.
- `focus` : When you focus in the field or value, it will trigger the autocomplete.
- `{number[]} confirmKeys`
Indicate the KeyCodes for trigger confirm completion, by default those keys are: [39, 35, 9] which are the code for [right, end, tab]

View File

@ -34,6 +34,8 @@
var options = {
autocomplete: {
applyTo:['value'],
filter: 'contain',
trigger: 'focus',
getOptions: function (text, path, input, editor) {
return new Promise(function (resolve, reject) {
var options = extractUniqueWords(editor.get());

View File

@ -1,7 +1,18 @@
'use strict';
var defaultFilterFunction = {
start: function (token, match, config) {
return match.indexOf(token) === 0;
},
contain: function (token, match, config) {
return match.indexOf(token) > -1;
}
};
function completely(config) {
config = config || {};
config.filter = config.filter || 'start';
config.trigger = config.trigger || 'keydown';
config.confirmKeys = config.confirmKeys || [39, 35, 9] // right, end, tab
config.caseSensitive = config.caseSensitive || false // autocomplete case sensitive
@ -47,22 +58,25 @@ function completely(config) {
var distanceToBottom = vph - rect.bottom - 6; // distance from the browser border.
rows = [];
for (var i = 0; i < array.length; i++) {
var filterFn = typeof config.filter === 'function' ? config.filter : defaultFilterFunction[config.filter];
if ( (config.caseSensitive && array[i].indexOf(token) !== 0)
||(!config.caseSensitive && array[i].toLowerCase().indexOf(token.toLowerCase()) !== 0)) { continue; }
var filtered = !filterFn ? [] : array.filter(function (match) {
return filterFn(config.caseSensitive ? token : token.toLowerCase(), config.caseSensitive ? match : match.toLowerCase(), config);
});
rows = filtered.map(function (row) {
var divRow = document.createElement('div');
divRow.className = 'item';
//divRow.style.color = config.color;
divRow.onmouseover = onMouseOver;
divRow.onmouseout = onMouseOut;
divRow.onmousedown = onMouseDown;
divRow.__hint = row;
divRow.innerHTML = row.substring(0, token.length) + '<b>' + row.substring(token.length) + '</b>';
elem.appendChild(divRow);
return divRow;
});
var divRow = document.createElement('div');
divRow.className = 'item';
//divRow.style.color = config.color;
divRow.onmouseover = onMouseOver;
divRow.onmouseout = onMouseOut;
divRow.onmousedown = onMouseDown;
divRow.__hint = array[i];
divRow.innerHTML = array[i].substring(0, token.length) + '<b>' + array[i].substring(token.length) + '</b>';
rows.push(divRow);
elem.appendChild(divRow);
}
if (rows.length === 0) {
return; // nothing to show.
}

View File

@ -1140,12 +1140,17 @@ treemode._onEvent = function (event) {
return;
}
var node = Node.getNodeFromTarget(event.target);
if (event.type === 'keydown') {
this._onKeyDown(event);
}
if (event.type === 'focus') {
this.focusTarget = event.target;
if (this.options.autocomplete && this.options.autocomplete.trigger === 'focus') {
this._showAutoComplete(event.target);
}
}
if (event.type === 'mousedown') {
@ -1155,7 +1160,6 @@ treemode._onEvent = function (event) {
this._updateDragDistance(event);
}
var node = Node.getNodeFromTarget(event.target);
if (node && this.options && this.options.navigationBar && node && (event.type === 'keydown' || event.type === 'mousedown')) {
// apply on next tick, right after the new key press is applied
@ -1505,6 +1509,51 @@ treemode._findTopLevelNodes = function (start, end) {
}
};
/**
* Show autocomplete menu
* @param {Node} node
* @param {HTMLElement} element
* @private
*/
treemode._showAutoComplete = function (element) {
var node = Node.getNodeFromTarget(element);
var jsonElementType = "";
if (event.target.className.indexOf("jsoneditor-value") >= 0) jsonElementType = "value";
if (event.target.className.indexOf("jsoneditor-field") >= 0) jsonElementType = "field";
var self = this;
setTimeout(function () {
if (self.options.autocomplete.trigger === 'focus' || element.innerText.length > 0) {
var result = self.options.autocomplete.getOptions(element.innerText, node.getPath(), jsonElementType, node.editor);
if (result === null) {
self.autocomplete.hideDropDown();
} else if (typeof result.then === 'function') {
// probably a promise
if (result.then(function (obj) {
if (obj === null) {
self.autocomplete.hideDropDown();
} else if (obj.options) {
self.autocomplete.show(element, obj.startFrom, obj.options);
} else {
self.autocomplete.show(element, 0, obj);
}
}.bind(self)));
} else {
// definitely not a promise
if (result.options)
self.autocomplete.show(element, result.startFrom, result.options);
else
self.autocomplete.show(element, 0, result);
}
}
else
self.autocomplete.hideDropDown();
}, 50);
}
/**
* Event handler for keydown. Handles shortcut keys
* @param {Event} event
@ -1562,41 +1611,11 @@ treemode._onKeyDown = function (event) {
if ((this.options.autocomplete) && (!handled)) {
if (!ctrlKey && !altKey && !metaKey && (event.key.length == 1 || keynum == 8 || keynum == 46)) {
handled = false;
var jsonElementType = "";
if (event.target.className.indexOf("jsoneditor-value") >= 0) jsonElementType = "value";
if (event.target.className.indexOf("jsoneditor-field") >= 0) jsonElementType = "field";
handled = false;
var node = Node.getNodeFromTarget(event.target);
var node = Node.getNodeFromTarget(event.target);
// Activate autocomplete
setTimeout(function (hnode, element) {
if (element.innerText.length > 0) {
var result = this.options.autocomplete.getOptions(element.innerText, hnode.getPath(), jsonElementType, hnode.editor);
if (result === null) {
this.autocomplete.hideDropDown();
} else if (typeof result.then === 'function') {
// probably a promise
if (result.then(function (obj) {
if (obj === null) {
this.autocomplete.hideDropDown();
} else if (obj.options) {
this.autocomplete.show(element, obj.startFrom, obj.options);
} else {
this.autocomplete.show(element, 0, obj);
}
}.bind(this)));
} else {
// definitely not a promise
if (result.options)
this.autocomplete.show(element, result.startFrom, result.options);
else
this.autocomplete.show(element, 0, result);
}
}
else
this.autocomplete.hideDropDown();
}.bind(this, node, event.target), 50);
// Activate autocomplete
this._showAutoComplete(event.target);
}
}