Merge pull request #407 from israelito3000/feature/autocomplete2

Feature/autocomplete2
This commit is contained in:
Jos de Jong 2017-07-02 13:23:55 +02:00 committed by GitHub
commit 1f29f5912a
13 changed files with 802 additions and 27 deletions

3
.gitignore vendored
View File

@ -3,4 +3,5 @@ build
downloads
node_modules
*.zip
npm-debug.log
npm-debug.log
/.vs

View File

@ -114,6 +114,60 @@ Constructs a new JSONEditor.
Set the Ace editor theme, uses included 'ace/theme/jsoneditor' by default. Please note that only the default theme is included with jsoneditor, so if you specify another one you need to make sure it is loaded.
- `{Object} templates`
Array of templates that will appear in the context menu, Each template is a json object precreated that can be added as a object value to any node in your document.
The following example allow you can create a "Person" node and a "Address" node, each one will appear in your context menu, once you selected the whole json object will be created.
```js
var options = {
templates: [
{
text: 'Person',
title: 'Insert a Person Node',
className: 'jsoneditor-type-object',
field: 'PersonTemplate',
value: {
'firstName': 'John',
'lastName': 'Do',
'age': 28
}
},
{
text: 'Address',
title: 'Insert a Address Node',
field: 'AddressTemplate',
value: {
'street': "",
'city': "",
'state': "",
'ZIP code': ""
}
}
]
}
```
- `{Object} autocomplete`
*autocomplete* will enable this feature in your editor in tree mode, the object have the following **subelements**:
- `{Object} confirmKeys`
Indicate the KeyCodes for trigger confirm completion, by default those keys are: [39, 35, 9] which are the code for [right, end, tab]
- `{Function} getOptions (autocomplete, node, text, elementType)`
This function will return your possible options for create the autocomplete selection, you can control dynamically which options you want to display according to the current active editing node.
You can return a promise (for async) as well the options array (sync).
*Parameters:*
- text : The text in the current node part. (basically the text that the user is editing)
- path : The document json object that is being edited.
- input : Can be "field" or "value" depending if the user is editing a field name or a value of a node.
### Methods

View File

@ -1,37 +1,37 @@
<!DOCTYPE HTML>
<html>
<head>
<title>JSONEditor | Custom styling</title>
<title>JSONEditor | Custom styling</title>
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<style type="text/css">
#jsoneditor {
width: 500px;
height: 500px;
}
<style type="text/css">
#jsoneditor {
width: 500px;
height: 500px;
}
p {
width: 500px;
font-family: "DejaVu Sans", sans-serif;
}
</style>
p {
width: 500px;
font-family: "DejaVu Sans", sans-serif;
}
</style>
<link href="./css/darktheme.css" rel="stylesheet" type="text/css">
<link href="./css/darktheme.css" rel="stylesheet" type="text/css">
</head>
<body>
<p>
This example demonstrates how to customize the look of JSONEditor,
the editor below has a dark theme. Note that the example isn't worked
out for the mode <code>code</code>. To do that, you can load and configure
a custom theme for the Ace editor.
</p>
<p>
This example demonstrates how to customize the look of JSONEditor,
the editor below has a dark theme. Note that the example isn't worked
out for the mode <code>code</code>. To do that, you can load and configure
a custom theme for the Ace editor.
</p>
<div id="jsoneditor"></div>
<div id="jsoneditor"></div>
<script>
<script>
// create the editor
var container = document.getElementById('jsoneditor');
var options = {
@ -46,6 +46,6 @@
'string': 'Hello World'
};
var editor = new JSONEditor(container, options, json);
</script>
</script>
</body>
</html>

View File

@ -0,0 +1,56 @@
<!DOCTYPE HTML>
<html>
<head>
<title>JSONEditor | Auto Complete</title>
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<style type="text/css">
#jsoneditor {
width: 500px;
height: 500px;
}
p {
width: 500px;
font-family: "DejaVu Sans", sans-serif;
}
</style>
<!--<link href="./css/darktheme.css" rel="stylesheet" type="text/css">-->
</head>
<body>
<p>
This example demonstrates how to autocomplete works, options available are "apple","cranberry","raspberry","pie"
</p>
<div id="jsoneditor"></div>
<script>
// create the editor
var container = document.getElementById('jsoneditor');
var options = {
modes: ['text', 'tree'],
autocomplete: {
applyTo:['field','value'], // This indicates the autocomplete is going to be applied to json properties names and values.
getOptions: function () {
return ["apple","cranberry","raspberry","pie", "mango", "mandarine", "melon", "appleton"];
}
}
};
var json = {
'array': [{'field1':'v1', 'field2':'v2'}, 2, 3],
'boolean': true,
'null': null,
'number': 123,
'object': {'a': 'b', 'c': 'd'},
'string': 'Hello World'
};
var editor = new JSONEditor(container, options, json);
</script>
</body>
</html>

View File

@ -0,0 +1,71 @@
<!DOCTYPE HTML>
<html>
<head>
<title>JSONEditor | Auto Complete</title>
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<style type="text/css">
#jsoneditor {
width: 500px;
height: 500px;
}
p {
width: 500px;
font-family: "DejaVu Sans", sans-serif;
}
</style>
<!--<link href="./css/darktheme.css" rel="stylesheet" type="text/css">-->
</head>
<body>
<p>
This example demonstrates how to autocomplete works, options available are dynamics and consist in all the strings found in the json
</p>
<div id="jsoneditor"></div>
<script>
// create the editor
var container = document.getElementById('jsoneditor');
var options = {
modes: ['text', 'tree'],
autocomplete: {
applyTo:['value'],
getOptions: function (text, path) {
return new Promise(function (resolve, reject) {
var options = extractUniqueWords(path);
if (options.length > 0) resolve(options); else reject();
});
}
}
};
// ...
function extractUniqueWords (json) {
return _.uniq(_.flatMapDeep(json, function (value, key) {
return _.isObject(value)
? [key]
: [key, String(value)]
}))
}
var json = {
'array': [{'field1':'v1', 'field2':'v2'}, 2, 3],
'boolean': true,
'null': null,
'number': 123,
'object': {'a': 'b', 'c': 'd'},
'string': 'Hello World'
};
var editor = new JSONEditor(container, options, json);
</script>
</body>
</html>

View File

@ -0,0 +1,104 @@
<!DOCTYPE HTML>
<html>
<head>
<title>JSONEditor | Auto Complete</title>
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<script src="jsonpath.min.js"></script>
<style type="text/css">
#jsoneditor {
width: 500px;
height: 500px;
}
p {
width: 500px;
font-family: "DejaVu Sans", sans-serif;
}
</style>
<!--<link href="./css/darktheme.css" rel="stylesheet" type="text/css">-->
</head>
<body>
<p>
This example demonstrates how to autocomplete works with an ActivationChar option, press "*" in any value and continue with autocompletion.
The autocomplete returns the posible jsonpaths of the existing json document.
</p>
<div id="jsoneditor"></div>
<script>
// create the editor
var container = document.getElementById('jsoneditor');
var activationChar = '*';
var options = {
modes: ['text', 'tree'],
autocomplete: {
confirmKeys: [39, 35, 9, 190], // Confirm Autocomplete Keys: [right, end, tab, '.'] // By default are only [right, end, tab]
getOptions: function (text, path, input) {
if (!text.startsWith(activationChar) || input != 'value') return [];
var data = {};
var startFrom = 0;
var lastPoint = text.lastIndexOf('.');
if ((lastPoint > 0) && (text.length > 1)) {
data = jsonpath.query(path, '$.' + text.substring(activationChar.length, lastPoint));
if (data.length > 0) data = data[0]; else data = {};
// Indicate that autocompletion should start after the . (ignoring the first part)
startFrom = text.lastIndexOf('.') + 1;
}
else
data = path;
var optionsStr = YaskON.stringify(data, null, activationChar);
var options = optionsStr.split("\n");
return { startFrom: startFrom, options: options };
}
}
};
var YaskON = {
// Return first level json paths by the node 'o'
stringify: function (o, prefix, activationChar) {
prefix = prefix || '';
switch (typeof o) {
case 'object':
var output = "";
if (Array.isArray(o)) {
o.forEach(function (e, index) {
output += activationChar + prefix + '[' + index + ']' + '\n';
}.bind(this));
return output;
}
output = "";
for (var k in o) {
if (o.hasOwnProperty(k)) {
if (prefix == "") output += this.stringify(o[k], k, activationChar);
}
}
if (prefix != "") output += activationChar + prefix + '\n'
return output;
case 'function':
return "";
default:
return prefix + '\n';
}
}
};
var json = {
'array': [{ 'field1': 'v1', 'field2': 'v2' }, 2, 3],
'boolean': true,
'null': null,
'number': 123,
'object': { 'a': 'b', 'c': 'd' },
'string': 'Hello World'
};
var editor = new JSONEditor(container, options, json);
</script>
</body>
</html>

20
examples/jsonpath.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -130,7 +130,8 @@ gulp.task('bundle-css', ['mkdir'], function () {
'src/css/jsoneditor.css',
'src/css/contextmenu.css',
'src/css/menu.css',
'src/css/searchbox.css'
'src/css/searchbox.css',
'src/css/autocomplete.css'
])
.pipe(concatCss(NAME + '.css'))
.pipe(gulp.dest(DIST))

32
src/css/autocomplete.css Normal file
View File

@ -0,0 +1,32 @@
div.jsoneditor div.autocomplete.dropdown {
position: absolute;
background: white;
box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3);
border: 1px solid #d3d3d3;
z-index: 100;
overflow-x: hidden;
overflow-y: auto;
cursor: default;
margin: 0;
padding-left: 2pt;
padding-right: 5pt;
text-align: left;
outline: 0;
font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif;
font-size: 10pt;
}
div.jsoneditor div.autocomplete.dropdown .item {
color: #333;
}
div.jsoneditor div.autocomplete.dropdown .item.hover {
background-color: #ddd;
}
div.jsoneditor div.autocomplete.hint {
color: #aaa;
top:4px;
left:4px;
}

View File

@ -79,8 +79,8 @@ function JSONEditor (container, options, json) {
// validate options
if (options) {
var VALID_OPTIONS = [
'ace', 'theme',
'ajv', 'schema','templates',
'ace', 'theme','autocomplete',
'onChange', 'onEditable', 'onError', 'onModeChange',
'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation', 'sortObjectKeys'
];

367
src/js/autocomplete.js Normal file
View File

@ -0,0 +1,367 @@
'use strict';
function completely(config) {
config = config || {};
config.confirmKeys = config.confirmKeys || [39, 35, 9] // right, end, tab
var fontSize = '';
var fontFamily = '';
var wrapper = document.createElement('div');
wrapper.style.position = 'relative';
wrapper.style.outline = '0';
wrapper.style.border = '0';
wrapper.style.margin = '0';
wrapper.style.padding = '0';
var dropDown = document.createElement('div');
dropDown.className = 'autocomplete dropdown';
dropDown.style.position = 'absolute';
dropDown.style.visibility = 'hidden';
var spacer;
var leftSide; // <-- it will contain the leftSide part of the textfield (the bit that was already autocompleted)
var createDropDownController = function (elem, rs) {
var rows = [];
var ix = 0;
var oldIndex = -1;
var onMouseOver = function () { this.style.outline = '1px solid #ddd'; }
var onMouseOut = function () { this.style.outline = '0'; }
var onMouseDown = function () { p.hide(); p.onmouseselection(this.__hint, p.rs); }
var p = {
rs: rs,
hide: function () {
elem.style.visibility = 'hidden';
//rs.hideDropDown();
},
refresh: function (token, array) {
elem.style.visibility = 'hidden';
ix = 0;
elem.innerHTML = '';
var vph = (window.innerHeight || document.documentElement.clientHeight);
var rect = elem.parentNode.getBoundingClientRect();
var distanceToTop = rect.top - 6; // heuristic give 6px
var distanceToBottom = vph - rect.bottom - 6; // distance from the browser border.
rows = [];
for (var i = 0; i < array.length; i++) {
if (array[i].indexOf(token) !== 0) { continue; }
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 = token + '<b>' + array[i].substring(token.length) + '</b>';
rows.push(divRow);
elem.appendChild(divRow);
}
if (rows.length === 0) {
return; // nothing to show.
}
if (rows.length === 1 && token === rows[0].__hint) {
return; // do not show the dropDown if it has only one element which matches what we have just displayed.
}
if (rows.length < 2) return;
p.highlight(0);
if (distanceToTop > distanceToBottom * 3) { // Heuristic (only when the distance to the to top is 4 times more than distance to the bottom
elem.style.maxHeight = distanceToTop + 'px'; // we display the dropDown on the top of the input text
elem.style.top = '';
elem.style.bottom = '100%';
} else {
elem.style.top = '100%';
elem.style.bottom = '';
elem.style.maxHeight = distanceToBottom + 'px';
}
elem.style.visibility = 'visible';
},
highlight: function (index) {
if (oldIndex != -1 && rows[oldIndex]) {
rows[oldIndex].className = "item";
}
rows[index].className = "item hover";
oldIndex = index;
},
move: function (step) { // moves the selection either up or down (unless it's not possible) step is either +1 or -1.
if (elem.style.visibility === 'hidden') return ''; // nothing to move if there is no dropDown. (this happens if the user hits escape and then down or up)
if (ix + step === -1 || ix + step === rows.length) return rows[ix].__hint; // NO CIRCULAR SCROLLING.
ix += step;
p.highlight(ix);
return rows[ix].__hint;//txtShadow.value = uRows[uIndex].__hint ;
},
onmouseselection: function () { } // it will be overwritten.
};
return p;
}
function setEndOfContenteditable(contentEditableElement) {
var range, selection;
if (document.createRange)//Firefox, Chrome, Opera, Safari, IE 9+
{
range = document.createRange();//Create a range (a range is a like the selection but invisible)
range.selectNodeContents(contentEditableElement);//Select the entire contents of the element with the range
range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
selection = window.getSelection();//get the selection object (allows you to change selection)
selection.removeAllRanges();//remove any selections already made
selection.addRange(range);//make the range you have just created the visible selection
}
else if (document.selection)//IE 8 and lower
{
range = document.body.createTextRange();//Create a range (a range is a like the selection but invisible)
range.moveToElementText(contentEditableElement);//Select the entire contents of the element with the range
range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
range.select();//Select the range (make it the visible selection
}
}
function calculateWidthForText(text) {
if (spacer === undefined) { // on first call only.
spacer = document.createElement('span');
spacer.style.visibility = 'hidden';
spacer.style.position = 'fixed';
spacer.style.outline = '0';
spacer.style.margin = '0';
spacer.style.padding = '0';
spacer.style.border = '0';
spacer.style.left = '0';
spacer.style.whiteSpace = 'pre';
spacer.style.fontSize = fontSize;
spacer.style.fontFamily = fontFamily;
spacer.style.fontWeight = 'normal';
document.body.appendChild(spacer);
}
// Used to encode an HTML string into a plain text.
// taken from http://stackoverflow.com/questions/1219860/javascript-jquery-html-encoding
spacer.innerHTML = String(text).replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
return spacer.getBoundingClientRect().right;
}
var rs = {
onArrowDown: function () { }, // defaults to no action.
onArrowUp: function () { }, // defaults to no action.
onEnter: function () { }, // defaults to no action.
onTab: function () { }, // defaults to no action.
startFrom: 0,
options: [],
element: null,
elementHint: null,
elementStyle: null,
wrapper: wrapper, // Only to allow easy access to the HTML elements to the final user (possibly for minor customizations)
show: function (element, startPos, options) {
this.startFrom = startPos;
this.wrapper.remove();
if (this.elementHint) {
this.elementHint.remove();
this.elementHint = null;
}
if (fontSize == '') {
fontSize = window.getComputedStyle(element).getPropertyValue('font-size');
}
if (fontFamily == '') {
fontFamily = window.getComputedStyle(element).getPropertyValue('font-family');
}
var w = element.getBoundingClientRect().right - element.getBoundingClientRect().left;
dropDown.style.marginLeft = '0';
dropDown.style.marginTop = element.getBoundingClientRect().height + 'px';
this.options = options;
if (this.element != element) {
this.element = element;
this.elementStyle = {
zIndex: this.element.style.zIndex,
position: this.element.style.position,
backgroundColor: this.element.style.backgroundColor,
borderColor: this.element.style.borderColor
}
}
this.element.style.zIndex = 3;
this.element.style.position = 'relative';
this.element.style.backgroundColor = 'transparent';
this.element.style.borderColor = 'transparent';
this.elementHint = element.cloneNode();
this.elementHint.className = 'autocomplete hint';
this.elementHint.style.zIndex = 2;
this.elementHint.style.position = 'absolute';
this.elementHint.onfocus = function () { this.element.focus(); }.bind(this);
if (this.element.addEventListener) {
this.element.removeEventListener("keydown", keyDownHandler);
this.element.addEventListener("keydown", keyDownHandler, false);
this.element.removeEventListener("blur", onBlurHandler);
this.element.addEventListener("blur", onBlurHandler, false);
}
wrapper.appendChild(this.elementHint);
wrapper.appendChild(dropDown);
element.parentElement.appendChild(wrapper);
this.repaint(element);
},
setText: function (text) {
this.element.innerText = text;
},
getText: function () {
return this.element.innerText;
},
hideDropDown: function () {
this.wrapper.remove();
if (this.elementHint) {
this.elementHint.remove();
this.elementHint = null;
dropDownController.hide();
this.element.style.zIndex = this.elementStyle.zIndex;
this.element.style.position = this.elementStyle.position;
this.element.style.backgroundColor = this.elementStyle.backgroundColor;
this.element.style.borderColor = this.elementStyle.borderColor;
}
},
repaint: function (element) {
var text = element.innerText;
text = text.replace('\n', '');
var startFrom = this.startFrom;
var options = this.options;
var optionsLength = this.options.length;
// breaking text in leftSide and token.
var token = text.substring(this.startFrom);
leftSide = text.substring(0, this.startFrom);
for (var i = 0; i < optionsLength; i++) {
var opt = this.options[i];
if (opt.indexOf(token) === 0) { // <-- how about upperCase vs. lowercase
this.elementHint.innerText = leftSide + opt;
break;
}
}
// moving the dropDown and refreshing it.
dropDown.style.left = calculateWidthForText(leftSide) + 'px';
dropDownController.refresh(token, this.options);
this.elementHint.style.width = calculateWidthForText(this.elementHint.innerText) + 10 + 'px'
var wasDropDownHidden = (dropDown.style.visibility == 'hidden');
if (!wasDropDownHidden)
this.elementHint.style.width = calculateWidthForText(this.elementHint.innerText) + dropDown.clientWidth + 'px';
}
};
var dropDownController = createDropDownController(dropDown, rs);
var keyDownHandler = function (e) {
//console.log("Keydown:" + e.keyCode);
e = e || window.event;
var keyCode = e.keyCode;
if (this.elementHint == null) return;
if (keyCode == 33) { return; } // page up (do nothing)
if (keyCode == 34) { return; } // page down (do nothing);
if (keyCode == 27) { //escape
rs.hideDropDown();
rs.element.focus();
e.preventDefault();
e.stopPropagation();
return;
}
if (config.confirmKeys.indexOf(keyCode) >= 0) { // (autocomplete triggered)
if (keyCode == 9) {
if (this.elementHint.innerText.length == 0) {
rs.onTab();
}
}
if (this.elementHint.innerText.length > 0) { // if there is a hint
if (this.element.innerText != this.elementHint.innerText) {
this.element.innerText = this.elementHint.innerText;
rs.hideDropDown();
setEndOfContenteditable(this.element);
if (keyCode == 9) {
rs.element.focus();
e.preventDefault();
e.stopPropagation();
}
}
}
return;
}
if (keyCode == 13) { // enter (autocomplete triggered)
if (this.elementHint.innerText.length == 0) { // if there is a hint
rs.onEnter();
} else {
var wasDropDownHidden = (dropDown.style.visibility == 'hidden');
dropDownController.hide();
if (wasDropDownHidden) {
rs.hideDropDown();
rs.element.focus();
rs.onEnter();
return;
}
this.element.innerText = this.elementHint.innerText;
rs.hideDropDown();
setEndOfContenteditable(this.element);
e.preventDefault();
e.stopPropagation();
}
return;
}
if (keyCode == 40) { // down
var m = dropDownController.move(+1);
if (m == '') { rs.onArrowDown(); }
this.elementHint.innerText = leftSide + m;
e.preventDefault();
e.stopPropagation();
return;
}
if (keyCode == 38) { // up
var m = dropDownController.move(-1);
if (m == '') { rs.onArrowUp(); }
this.elementHint.innerText = leftSide + m;
e.preventDefault();
e.stopPropagation();
return;
}
}.bind(rs);
var onBlurHandler = function (e) {
rs.hideDropDown();
//console.log("Lost focus.");
}.bind(rs);
dropDownController.onmouseselection = function (text, rs) {
rs.element.innerText = rs.elementHint.innerText = leftSide + text;
rs.hideDropDown();
window.setTimeout(function () {
rs.element.focus();
setEndOfContenteditable(rs.element);
}, 1);
};
return rs;
}
module.exports = completely;

View File

@ -8,6 +8,7 @@ var ContextMenu = require('./ContextMenu');
var Node = require('./Node');
var ModeSwitcher = require('./ModeSwitcher');
var util = require('./util');
var autocomplete = require('./autocomplete');
// create a mixin with the functions for tree mode
var treemode = {};
@ -51,6 +52,9 @@ treemode.create = function (container, options) {
this._setOptions(options);
if (options.autocomplete)
this.autocomplete = new autocomplete(options.autocomplete);
if (this.options.history && this.options.mode !== 'view') {
this.history = new History(this);
}
@ -107,7 +111,8 @@ treemode._setOptions = function (options) {
history: true,
mode: 'tree',
name: undefined, // field name of root node
schema: null
schema: null,
autocomplete: null
};
// copy all options
@ -1051,7 +1056,9 @@ treemode._findTopLevelNodes = function (start, end) {
*/
treemode._onKeyDown = function (event) {
var keynum = event.which || event.keyCode;
var altKey = event.altKey;
var ctrlKey = event.ctrlKey;
var metaKey = event.metaKey;
var shiftKey = event.shiftKey;
var handled = false;
@ -1097,6 +1104,41 @@ 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";
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, editor.get(), jsonElementType);
if (typeof result.then === 'function') {
// probably a promise
if (result.then(function (obj) {
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);
}
}
if (handled) {
event.preventDefault();
event.stopPropagation();

View File

@ -776,3 +776,30 @@ exports.textDiff = function textDiff(oldText, newText) {
return {start: start, end: newEnd};
};
// Polyfill for array remove
(function (arr) {
arr.forEach(function (item) {
if (item.hasOwnProperty('remove')) {
return;
}
Object.defineProperty(item, 'remove', {
configurable: true,
enumerable: true,
writable: true,
value: function remove() {
if (this.parentNode != null)
this.parentNode.removeChild(this);
}
});
});
})([Element.prototype, CharacterData.prototype, DocumentType.prototype]);
// Polyfill for startsWith
if (!String.prototype.startsWith) {
String.prototype.startsWith = function (searchString, position) {
position = position || 0;
return this.substr(position, searchString.length) === searchString;
};
}