diff --git a/docs/api.md b/docs/api.md
index c4a890a..8761f38 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -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]
diff --git a/examples/12_autocomplete_dynamic.html b/examples/12_autocomplete_dynamic.html
index fd7351e..e6c008f 100644
--- a/examples/12_autocomplete_dynamic.html
+++ b/examples/12_autocomplete_dynamic.html
@@ -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());
diff --git a/src/js/autocomplete.js b/src/js/autocomplete.js
index b53253a..e4e2640 100644
--- a/src/js/autocomplete.js
+++ b/src/js/autocomplete.js
@@ -1,8 +1,19 @@
'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.confirmKeys = config.confirmKeys || [39, 35, 9] // right, end, tab
+ 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
var fontSize = '';
@@ -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) + '' + row.substring(token.length) + '';
+ 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) + '' + array[i].substring(token.length) + '';
- rows.push(divRow);
- elem.appendChild(divRow);
- }
if (rows.length === 0) {
return; // nothing to show.
}
diff --git a/src/js/treemode.js b/src/js/treemode.js
index 69685dd..f02c8ff 100644
--- a/src/js/treemode.js
+++ b/src/js/treemode.js
@@ -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);
}
}