- polyfills moved to utils.js

- lowercase "Show" function
- refine autocomplete examples.
- style improvements to match context menu.
- set auto to scrollbar in autocomplete dropdownbox
- Added docs for Templates and Autocomplete
- Promisify getOptions function
- Add elementType to getOptions function to return which element "field" or "value" is being edited.
- applyTo option now accept ['field', 'value'] instead of ['name', 'value']
- optimize confirmKeys to be part of the autocomplete option instead of autocomplete.config
This commit is contained in:
Israel Garcia 2017-06-16 20:19:56 -04:00
parent fc4562fea1
commit 00ba443acf
8 changed files with 173 additions and 116 deletions

View File

@ -114,6 +114,69 @@ 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]
- `{Object} applyTo`
Indicate where the autocomplete is going to be activated, under field in json node or/and under value in a json node, default is: ['field','value']
- `{string} activationChar`
This is a single char string, indicates that the text should starts with this char in order to process and display the autocompletion, a typical example is in some editors you type '@' and then you see a dropdownbox where you can link to an specific author.
- `{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:*
- autocomplete : The instance of the autocomplete engine.
- node : The current active editing node. (node include field and value)
- text : The text in the current node part. (basically the text that the user is editing)
- elementType : Can be "field" or "value" depending if the user is editing a field name or a value of a node.
### Methods

View File

@ -36,9 +36,9 @@
var options = {
modes: ['text', 'tree'],
autocomplete: {
applyTo:['name','value'], // This indicates the autocomplete is going to be applied to json properties names and values.
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"];
return ["apple","cranberry","raspberry","pie", "mango", "mandarine", "melon", "appleton"];
}
}
};

View File

@ -5,6 +5,7 @@
<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">
@ -38,46 +39,27 @@
autocomplete: {
applyTo:['value'],
getOptions: function (autocomplete, node, text) {
// Return all strings in json
function stringify(o, prefix) {
prefix = prefix || '';
switch (typeof o) {
case 'object':
var output = "";
if (Array.isArray(o)) {
if ((prefix != "") && (prefix != text)) output += prefix + '\n';
o.forEach(function (e, index) {
output += stringify(e, null);
}.bind(this));
return output;
}
output = "";
for (var k in o) {
if (o.hasOwnProperty(k)) {
if (prefix == "") output += stringify(o[k], k);
}
}
if ((prefix != "") && (prefix != text)) output += prefix + '\n';
return output;
case 'function':
return "";
default:
var output = "";
if ((prefix != "") && (prefix != text)) output += prefix + '\n';
if ((o != "") && (o != text)) output += o + '\n';
return output;
}
}
return new Promise(function (resolve, reject) {
var data = node.editor.get();
var optionsStr = stringify(data, null);
var options = optionsStr.split("\n");
return options;
var options = extractUniqueWords(data);
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,

View File

@ -36,13 +36,31 @@
var options = {
modes: ['text', 'tree'],
autocomplete: {
config: {
confirmKeys: [39, 35, 9, 190] // Confirm Autocomplete Keys: [right, end, tab, '.'] // By default are only [right, end, tab]
},
confirmKeys: [39, 35, 9, 190], // Confirm Autocomplete Keys: [right, end, tab, '.'] // By default are only [right, end, tab]
activationChar: '*', // ActivationChar indicate if the value starts with this Char then autocompletion will be activated. ('' will be ignored).
applyTo:['value'],
getOptions: function (autocomplete, node, text) {
var data = {};
autocomplete.startFrom = 0;
var lastPoint = text.lastIndexOf('.');
if ((lastPoint > 0) && (text.length > 1)) {
var fnode = node.editor.node.findNode('.' + text.substring(this.activationChar.length, lastPoint));
if (fnode && typeof fnode.getValue() == 'object') {
data = fnode.getValue();
// Indicate that autocompletion should start after the . (ignoring the first part)
autocomplete.startFrom = text.lastIndexOf('.') + 1;
}
}
else
data = node.editor.get();
var optionsStr = YaskON.stringify(data, null, this.activationChar);
var options = optionsStr.split("\n");
return options;
}
}
};
var YaskON = {
// Return first level json paths by the node 'o'
stringify: function (o, prefix, activationChar) {
@ -71,27 +89,6 @@
}
}
};
var data = {};
autocomplete.startFrom = 0;
var lastPoint = text.lastIndexOf('.');
if ((lastPoint > 0) && (text.length > 1)) {
var fnode = node.editor.node.findNode('.' + text.substring(this.activationChar.length, lastPoint));
if (fnode && typeof fnode.getValue() == 'object') {
data = fnode.getValue();
// Indicate that autocompletion should start after the . (ignoring the first part)
autocomplete.startFrom = text.lastIndexOf('.') + 1;
}
}
else
data = node.editor.get();
var optionsStr = YaskON.stringify(data, null, this.activationChar);
var options = optionsStr.split("\n");
return options;
}
}
};
var json = {
'array': [{ 'field1': 'v1', 'field2': 'v2' }, 2, 3],
'boolean': true,

View File

@ -1,15 +1,16 @@
div.jsoneditor div.autocomplete.dropdown {
position: absolute;
background-color: #fff;
border: 1px solid #aaa;
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: scroll;
overflow-y: auto;
cursor: default;
margin: 0;
padding-left: 2pt;
padding-right: 10pt;
padding-right: 5pt;
text-align: left;
outline: 0;
font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif;

View File

@ -1,29 +1,5 @@
'use strict';
(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]);
if (!String.prototype.startsWith) {
String.prototype.startsWith = function (searchString, position) {
position = position || 0;
return this.substr(position, searchString.length) === searchString;
};
}
function completely(config) {
config = config || {};
config.confirmKeys = config.confirmKeys || [39, 35, 9] // right, end, tab
@ -181,7 +157,7 @@ function completely(config) {
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, options) {
show: function (element, options) {
this.wrapper.remove();
if (this.elementHint) {
this.elementHint.remove();

View File

@ -53,7 +53,7 @@ treemode.create = function (container, options) {
this._setOptions(options);
if (options.autocomplete)
this.autocomplete = new autocomplete(options.autocomplete.config);
this.autocomplete = new autocomplete(options.autocomplete);
if (this.options.history && this.options.mode !== 'view') {
this.history = new History(this);
@ -1107,15 +1107,26 @@ treemode._onKeyDown = function (event) {
if ((this.options.autocomplete) && (!handled)) {
if (!ctrlKey && !altKey && !metaKey && (event.key.length == 1 || keynum == 8 || keynum == 46)) {
handled = false;
if ((this.options.autocomplete.applyTo.indexOf('value') >= 0 && event.target.className.indexOf("jsoneditor-value") >= 0) ||
(this.options.autocomplete.applyTo.indexOf('name') >= 0 && event.target.className.indexOf("jsoneditor-field") >= 0)) {
var jsonElementType = "";
if (event.target.className.indexOf("jsoneditor-value") >= 0) jsonElementType = "value";
if (event.target.className.indexOf("jsoneditor-field") >= 0) jsonElementType = "field";
if ((this.options.autocomplete.applyTo.indexOf('value') >= 0 && jsonElementType == "value") ||
(this.options.autocomplete.applyTo.indexOf('field') >= 0 && jsonElementType == "field")) {
var node = Node.getNodeFromTarget(event.target);
if (this.options.autocomplete.activationChar == null || event.target.innerText.startsWith(this.options.autocomplete.activationChar)) { // Activate autocomplete
setTimeout(function (hnode, element) {
if (element.innerText.length > 0) {
var options = this.options.autocomplete.getOptions(this.autocomplete, hnode, element.innerText);
if (options.length > 0)
this.autocomplete.Show(element, options);
var result = this.options.autocomplete.getOptions(this.autocomplete, hnode, element.innerText, jsonElementType);
if (typeof result.then === 'function') {
// probably a promise
if (result.then(function (options) {
this.autocomplete.show(element, options);
}.bind(this)));
} else {
// definitely not a promise
this.autocomplete.show(element, result);
}
}
else
this.autocomplete.hideDropDown();

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