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); } }