Autocomplete final refactory

This commit is contained in:
Israel Garcia 2017-05-16 02:20:30 -04:00
parent 131e8f106d
commit 1393b148db
10 changed files with 249 additions and 205 deletions

View File

@ -25,7 +25,7 @@
*
* @author Jos de Jong, <wjosdejong@gmail.com>
* @version 5.6.0
* @date 2017-04-15
* @date 2017-05-16
*/
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
@ -164,7 +164,7 @@ return /******/ (function(modules) { // webpackBootstrap
// validate options
if (options) {
var VALID_OPTIONS = [
'ace', 'theme',
'ace', 'theme','autocomplete',
'ajv', 'schema',
'onChange', 'onEditable', 'onError', 'onModeChange',
'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation', 'sortObjectKeys'
@ -485,6 +485,7 @@ return /******/ (function(modules) { // webpackBootstrap
var ModeSwitcher = __webpack_require__(11);
var util = __webpack_require__(4);
// create a mixin with the functions for tree mode
var treemode = {};
@ -583,7 +584,8 @@ return /******/ (function(modules) { // webpackBootstrap
history: true,
mode: 'tree',
name: undefined, // field name of root node
schema: null
schema: null,
autocomplete: null
};
// copy all options
@ -1527,7 +1529,9 @@ return /******/ (function(modules) { // webpackBootstrap
*/
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;
@ -1573,6 +1577,25 @@ return /******/ (function(modules) { // webpackBootstrap
}
}
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('values') >= 0 && event.target.className.indexOf("jsoneditor-value") >= 0) ||
(this.options.autocomplete.ApplyTo.indexOf('name') >= 0 && event.target.className.indexOf("jsoneditor-field") >= 0)) {
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)
this.options.autocomplete.Show(hnode, element);
else
this.options.autocomplete.Hide();
}.bind(this, node, event.target), 100);
}
}
}
}
if (handled) {
event.preventDefault();
event.stopPropagation();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

29
dist/jsoneditor.js vendored
View File

@ -25,7 +25,7 @@
*
* @author Jos de Jong, <wjosdejong@gmail.com>
* @version 5.6.0
* @date 2017-04-15
* @date 2017-05-16
*/
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
@ -164,7 +164,7 @@ return /******/ (function(modules) { // webpackBootstrap
// validate options
if (options) {
var VALID_OPTIONS = [
'ace', 'theme',
'ace', 'theme','autocomplete',
'ajv', 'schema',
'onChange', 'onEditable', 'onError', 'onModeChange',
'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation', 'sortObjectKeys'
@ -8514,6 +8514,7 @@ return /******/ (function(modules) { // webpackBootstrap
var ModeSwitcher = __webpack_require__(12);
var util = __webpack_require__(5);
// create a mixin with the functions for tree mode
var treemode = {};
@ -8612,7 +8613,8 @@ return /******/ (function(modules) { // webpackBootstrap
history: true,
mode: 'tree',
name: undefined, // field name of root node
schema: null
schema: null,
autocomplete: null
};
// copy all options
@ -9556,7 +9558,9 @@ return /******/ (function(modules) { // webpackBootstrap
*/
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;
@ -9602,6 +9606,25 @@ return /******/ (function(modules) { // webpackBootstrap
}
}
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('values') >= 0 && event.target.className.indexOf("jsoneditor-value") >= 0) ||
(this.options.autocomplete.ApplyTo.indexOf('name') >= 0 && event.target.className.indexOf("jsoneditor-field") >= 0)) {
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)
this.options.autocomplete.Show(hnode, element);
else
this.options.autocomplete.Hide();
}.bind(this, node, event.target), 100);
}
}
}
}
if (handled) {
event.preventDefault();
event.stopPropagation();

2
dist/jsoneditor.map vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,106 +1,41 @@
<!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">
<!--<link href="css/horsey.css" rel="stylesheet" type="text/css">-->
<script src="../dist/jsoneditor.js"></script>
<!--<script src="horsey.js"></script>-->
<script src="../src/js/autocomplete.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 pv = completely({
fontSize: '10pt',
fontFamily: 'droid sans mono, consolas, monospace, courier new, courier, sans-serif'
});
var container = document.getElementById('jsoneditor');
var options = {
modes: ['text', 'tree'],
autocomplete: {
trigerOn: ['*'],
GetOptions: function (node, element, key) {
var YaskON = {
stringify: function (o, prefix) {
prefix = prefix || '';
switch (typeof o) {
case 'object':
var output = "";
if (Array.isArray(o)) {
o.forEach(function (e, index) {
output += prefix + '[' + index + ']' + '\n';
if (typeof e == 'object') output += this.stringify(e, prefix + '[' + index + ']');
}.bind(this));
return output;
}
output = "";
for (var k in o) {
if (o.hasOwnProperty(k)) {
if (prefix == "") output += this.stringify(o[k], k);
//else output += this.stringify(o[k], prefix + '.' + k);
}
}
if (prefix != "") output += prefix + '\n'
return output;
case 'function':
return "";
default:
return prefix + '\n';
}
}
};
var data = {};
pv.startFrom = 0;
var text = element.innerText;//.substring(1);
var lastPoint = text.lastIndexOf('.');
if ((lastPoint > 0) && (text.length > 1)) {
var fnode = node.editor.node.findNode('.' + text.substring(0, lastPoint));
if (fnode && typeof fnode.getValue() == 'object') {
data = fnode.getValue();
pv.startFrom = text.lastIndexOf('.') + 1;
}
}
else
data = node.editor.get();
var optionsStr = YaskON.stringify(data);
var options = optionsStr.split("\n");
if (options.length > 0)
pv.Show(element, options);
return options;
},
Hide: function () {
pv.hideDropDown();
}
}
modes: ['text', 'tree']
};
var json = {
'array': [1, 2, 3],
@ -111,6 +46,6 @@
'string': 'Hello World'
};
var editor = new JSONEditor(container, options, json);
</script>
</script>
</body>
</html>

View File

@ -0,0 +1,112 @@
<!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="autocomplete.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 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>
<script>
// create the editor
var container = document.getElementById('jsoneditor');
var pv = autocomplete({
fontSize: '10pt',
fontFamily: 'droid sans mono, consolas, monospace, courier new, courier, sans-serif'
});
var options = {
modes: ['text', 'tree'],
autocomplete: {
ActivationChar: '',
ApplyTo:['values'],
Show: function (node, element) {
var YaskON = {
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 data = {};
pv.startFrom = 0;
var text = element.innerText;
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();
pv.startFrom = text.lastIndexOf('.') + 1;
}
}
else
data = node.editor.get();
var optionsStr = YaskON.stringify(data, null, this.ActivationChar);
var options = optionsStr.split("\n");
if (options.length > 0)
pv.Show(element, options);
},
Hide: function () {
pv.hideDropDown();
}
}
};
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

@ -1,6 +1,6 @@
'use strict';
function completely(config) {
function autocomplete(config) {
config = config || {};
config.fontSize = config.fontSize || '16px';
config.fontFamily = config.fontFamily || 'sans-serif';
@ -167,11 +167,10 @@ function completely(config) {
}
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.
onChange: function () { }, // defaults to repainting.
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,
@ -213,11 +212,6 @@ function completely(config) {
this.elementHint.style.color = config.hintColor;
this.elementHint.onfocus = function () { this.element.focus(); }.bind(this);
/*
registerOnTextChange(this.element, function (text) { // note the function needs to be wrapped as API-users will define their onChange
rs.onChange(text);
});*/
if (this.element.addEventListener) {
this.element.removeEventListener("keydown", keyDownHandler);
this.element.addEventListener("keydown", keyDownHandler, false);
@ -243,12 +237,13 @@ function completely(config) {
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;
}
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;
@ -270,7 +265,6 @@ function completely(config) {
break;
}
}
// moving the dropDown and refreshing it.
dropDown.style.left = calculateWidthForText(leftSide) + 'px';
dropDownController.refresh(token, this.options);
@ -284,7 +278,7 @@ function completely(config) {
var dropDownController = createDropDownController(dropDown, rs);
var keyDownHandler = function (e) {
console.log("Keydown:" + e.keyCode);
//console.log("Keydown:" + e.keyCode);
e = e || window.event;
var keyCode = e.keyCode;
@ -294,32 +288,28 @@ function completely(config) {
if (keyCode == 34) { return; } // page down (do nothing);
if (keyCode == 27) { //escape
dropDownController.hide();
this.elementHint.innerText = this.element.innerText; // ensure that no hint is left.
this.element.focus();
rs.hideDropDown();
rs.element.focus();
e.preventDefault();
e.stopPropagation();
return;
}
if (keyCode == 39 || keyCode == 35 || keyCode == 9) { // right, end, tab (autocomplete triggered)
if (keyCode == 9) { // for tabs we need to ensure that we override the default behaviour: move to the next focusable HTML-element
e.preventDefault();
e.stopPropagation();
if (keyCode == 39 || keyCode == 35 || keyCode == 9 || keyCode == 190) { // right, end, tab, '.' (autocomplete triggered)
if (keyCode == 9) {
if (this.elementHint.innerText.length == 0) {
rs.onTab(); // tab was called with no action.
// users might want to re-enable its default behaviour or handle the call somehow.
rs.onTab();
}
}
if (this.elementHint.innerText.length > 0) { // if there is a hint
dropDownController.hide();
if (this.element.innerText != this.elementHint.innerText) {
this.element.innerText = this.elementHint.innerText;
rs.hideDropDown();
setEndOfContenteditable(this.element);
var hasTextChanged = registerOnTextChangeOldValue != this.element.innerText
registerOnTextChangeOldValue = this.element.innerText; // <-- to avoid dropDown to appear again.
// for example imagine the array contains the following words: bee, beef, beetroot
// user has hit enter to get 'bee' it would be prompted with the dropDown again (as beef and beetroot also match)
if (hasTextChanged) {
rs.onChange(this.element.innerText); // <-- forcing it.
if (keyCode == 9) {
rs.element.focus();
e.preventDefault();
e.stopPropagation();
}
}
}
@ -334,24 +324,17 @@ function completely(config) {
dropDownController.hide();
if (wasDropDownHidden) {
this.elementHint.innerText = this.element.innerText; // ensure that no hint is left.
this.element.focus();
rs.hideDropDown();
rs.element.focus();
rs.onEnter();
return;
}
this.element.innerText = this.elementHint.innerText;
var hasTextChanged = registerOnTextChangeOldValue != this.element.innerText
registerOnTextChangeOldValue = this.element.innerText; // <-- to avoid dropDown to appear again.
// for example imagine the array contains the following words: bee, beef, beetroot
// user has hit enter to get 'bee' it would be prompted with the dropDown again (as beef and beetroot also match)
if (hasTextChanged) {
rs.onChange(this.element.innerText); // <-- forcing it.
}
rs.hideDropDown();
setEndOfContenteditable(this.element);
e.preventDefault();
e.stopPropagation();
setEndOfContenteditable(this.element);
}
return;
}
@ -378,51 +361,18 @@ function completely(config) {
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);
};
var registerOnTextChangeOldValue;
/**
* Register a callback function to detect changes to the content of the input-type-text.
* Those changes are typically followed by user's action: a key-stroke event but sometimes it might be a mouse click.
**/
var registerOnTextChange = function (txt, callback) {
registerOnTextChangeOldValue = txt.value;
var handler = function () {
var value = txt.value;
if (registerOnTextChangeOldValue !== value) {
registerOnTextChangeOldValue = value;
callback(value);
}
};
//
// For user's actions, we listen to both input events and key up events
// It appears that input events are not enough so we defensively listen to key up events too.
// source: http://help.dottoro.com/ljhxklln.php
//
// The cost of listening to three sources should be negligible as the handler will invoke callback function
// only if the text.value was effectively changed.
//
//
if (txt.addEventListener) {
txt.addEventListener("input", handler, false);
txt.addEventListener('keyup', handler, false);
txt.addEventListener('change', handler, false);
} else { // is this a fair assumption: that attachEvent will exist ?
txt.attachEvent('oninput', handler); // IE<9
txt.attachEvent('onkeyup', handler); // IE<9
txt.attachEvent('onchange', handler); // IE<9
}
};
return rs;
}

View File

@ -8,7 +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 = {};
@ -1102,19 +1102,20 @@ treemode._onKeyDown = function (event) {
}
if ((this.options.autocomplete) && (!handled)) {
if (!ctrlKey && !altKey && !metaKey) {
if (!ctrlKey && !altKey && !metaKey && (event.key.length == 1 || keynum == 8 || keynum == 46)) {
handled = false;
if (event.target.className.indexOf("jsoneditor-field") < 0) {
if ((this.options.autocomplete.ApplyTo.indexOf('values') >= 0 && event.target.className.indexOf("jsoneditor-value") >= 0) ||
(this.options.autocomplete.ApplyTo.indexOf('name') >= 0 && event.target.className.indexOf("jsoneditor-field") >= 0)) {
var node = Node.getNodeFromTarget(event.target);
//if (event.target.innerText.startsWith('*')) {
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)
this.options.autocomplete.GetOptions(hnode, element, keynum);
this.options.autocomplete.Show(hnode, element);
else
this.options.autocomplete.Hide();
}.bind(this, node, event.target), 100);
//}
}
}
}
}