Implemented debouncing of keyboard input, resulting in much less history actions whilst typing

This commit is contained in:
jos 2016-01-12 18:11:56 +01:00
parent c0077250ef
commit f45fefe38f
7 changed files with 81 additions and 15 deletions

View File

@ -10,6 +10,8 @@ https://github.com/josdejong/jsoneditor
- Implemented #197: display an error in case of duplicate keys in an object.
- Implemented #183: display a checkbox left from boolean values, so you can
easily switch between true/false.
- Implemented debouncing of keyboard input, resulting in much less history
actions whilst typing.
- Added a minimalist bundle to the `dist` folder, excluding `ace` and `ajv`.
- Fixed #222: editor throwing `onChange` events when switching mode.
- Fixed an error throw when switching to mode "code" via the menu.
@ -17,6 +19,7 @@ https://github.com/josdejong/jsoneditor
from `Shift+Arrow Up/Down` to `Ctrl+Shift+Arrow Up/Down`.
## 2015-12-31, version 5.0.1
- Fixed a bug in positioning of the context menu for multiple selected nodes.

View File

@ -75,7 +75,7 @@ function JSONEditor (container, options, json) {
if (options) {
var VALID_OPTIONS = [
'ace', 'theme',
'ajv', 'schema', 'debounceInterval',
'ajv', 'schema',
'onChange', 'onEditable', 'onError', 'onModeChange',
'escapeUnicode', 'history', 'mode', 'modes', 'name', 'indentation'
];
@ -110,6 +110,9 @@ function JSONEditor (container, options, json) {
*/
JSONEditor.modes = {};
// debounce interval for JSON schema vaidation in milliseconds
JSONEditor.prototype.DEBOUNCE_INTERVAL = 150;
/**
* Create the JSONEditor
* @param {Element} container Container element

View File

@ -28,9 +28,12 @@ function Node (editor, params) {
this.setValue(null);
}
this._debouncedGetDomValue = util.debounce(this._getDomValue.bind(this), 100);
this._debouncedGetDomValue = util.debounce(this._getDomValue.bind(this), Node.prototype.DEBOUNCE_INTERVAL);
}
// debounce interval for keyboard input in milliseconds
Node.prototype.DEBOUNCE_INTERVAL = 150;
/**
* Determine whether the field and/or value of this node are editable
* @private
@ -1115,12 +1118,28 @@ Node.prototype._getDomValue = function(silent) {
if (value !== this.value) {
var oldValue = this.value;
this.value = value;
var selection = this.editor.getSelection();
var undoDiff = util.textDiff(value, oldValue);
var redoDiff = util.textDiff(oldValue, value);
console.log('selection', selection, oldValue, value, util.textDiff(oldValue, value), util.textDiff(value, oldValue))
this.editor._onAction('editValue', {
'node': this,
'oldValue': oldValue,
'newValue': value,
'oldSelection': this.editor.selection,
'newSelection': this.editor.getSelection()
'oldSelection': util.extend({}, selection, {
range: {
container: selection.range.container,
startOffset: undoDiff.start,
endOffset: undoDiff.end
}
}),
'newSelection': util.extend({}, selection, {
range: {
container: selection.range.container,
startOffset: redoDiff.start,
endOffset: redoDiff.end
}
})
});
}
}
@ -2080,7 +2099,7 @@ Node.prototype.onEvent = function (event) {
break;
case 'input':
this._getDomValue(true);
this._debouncedGetDomValue(true);
this._updateDomValue();
break;
@ -2098,7 +2117,7 @@ Node.prototype.onEvent = function (event) {
break;
case 'keyup':
this._getDomValue(true);
this._debouncedGetDomValue(true);
this._updateDomValue();
break;

View File

@ -71,9 +71,7 @@ textmode.create = function (container, options) {
this.validateSchema = null;
// create a debounced validate function
var wait = this.options.debounceInterval;
var immediate = true;
this._debouncedValidate = util.debounce(this.validate.bind(this), wait, immediate);
this._debouncedValidate = util.debounce(this.validate.bind(this), this.DEBOUNCE_INTERVAL);
this.width = container.clientWidth;
this.height = container.clientHeight;

View File

@ -75,8 +75,7 @@ treemode._setOptions = function (options) {
history: true,
mode: 'tree',
name: undefined, // field name of root node
schema: null,
debounceInterval: 100 // debounce interval for schema validation in milliseconds
schema: null
};
// copy all options
@ -92,9 +91,7 @@ treemode._setOptions = function (options) {
this.setSchema(this.options.schema);
// create a debounced validate function
var wait = this.options.debounceInterval;
var immediate = true;
this._debouncedValidate = util.debounce(this.validate.bind(this), wait, immediate);
this._debouncedValidate = util.debounce(this.validate.bind(this), this.DEBOUNCE_INTERVAL);
};
// node currently being edited

View File

@ -695,3 +695,50 @@ exports.debounce = function debounce(func, wait, immediate) {
if (callNow) func.apply(context, args);
};
};
/**
* Determines the difference between two texts.
* Can only detect one removed or inserted block of characters.
* @param {string} oldText
* @param {string} newText
* @return {{start: number, end: number}} Returns the start and end
* of the changed part in newText.
*/
exports.textDiff = function textDiff(oldText, newText) {
var len = newText.length;
var start = 0;
var oldEnd = oldText.length;
var newEnd = newText.length;
while (newText.charAt(start) === oldText.charAt(start)
&& start < len) {
start++;
}
while (newText.charAt(newEnd - 1) === oldText.charAt(oldEnd - 1)
&& newEnd > start && oldEnd > 0) {
newEnd--;
oldEnd--;
}
return {start: start, end: newEnd};
};
/**
* Extend object a with the properties of object b or a series of objects
* Only properties with defined values are copied
* @param {Object} a
* @param {... Object} b
* @return {Object} a
*/
exports.extend = function (a, b) {
for (var i = 1; i < arguments.length; i++) {
var other = arguments[i];
for (var prop in other) {
if (other.hasOwnProperty(prop)) {
a[prop] = other[prop];
}
}
}
return a;
};

View File

@ -87,7 +87,6 @@
console.log('json', json);
console.log('schema', schema);
console.log('string', JSON.stringify(json));
</script>
</body>
</html>