Fixed #278: Implemented method `editor.destroy()` to properly cleanup the editor
This commit is contained in:
parent
61c6f21f90
commit
a78b2ae57a
|
@ -5,6 +5,7 @@ https://github.com/josdejong/jsoneditor
|
||||||
|
|
||||||
## not yet released, version 5.2.0
|
## not yet released, version 5.2.0
|
||||||
|
|
||||||
|
- Implemented method `editor.destroy()` to properly cleanup the editor (#278).
|
||||||
- Fixed #268: JSONEditor now trims text in fields and values.
|
- Fixed #268: JSONEditor now trims text in fields and values.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,10 @@ Constructs a new JSONEditor.
|
||||||
|
|
||||||
Collapse all fields. Only applicable for mode 'tree', 'view', and 'form'.
|
Collapse all fields. Only applicable for mode 'tree', 'view', and 'form'.
|
||||||
|
|
||||||
|
#### `JSONEditor.destroy()`
|
||||||
|
|
||||||
|
Destroy the editor. Clean up DOM, event listeners, and web workers.
|
||||||
|
|
||||||
#### `JSONEditor.expandAll()`
|
#### `JSONEditor.expandAll()`
|
||||||
|
|
||||||
Expand all fields. Only applicable for mode 'tree', 'view', and 'form'.
|
Expand all fields. Only applicable for mode 'tree', 'view', and 'form'.
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
"bugs": "https://github.com/josdejong/jsoneditor/issues",
|
"bugs": "https://github.com/josdejong/jsoneditor/issues",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "gulp",
|
"build": "gulp",
|
||||||
"watch": "watch",
|
"watch": "gulp watch",
|
||||||
"test": "mocha test"
|
"test": "mocha test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -7,6 +7,9 @@ var util = require('./util');
|
||||||
*/
|
*/
|
||||||
function History (editor) {
|
function History (editor) {
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
|
this.history = [];
|
||||||
|
this.index = -1;
|
||||||
|
|
||||||
this.clear();
|
this.clear();
|
||||||
|
|
||||||
// map with all supported actions
|
// map with all supported actions
|
||||||
|
@ -249,4 +252,14 @@ History.prototype.redo = function () {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy history
|
||||||
|
*/
|
||||||
|
History.prototype.destroy = function () {
|
||||||
|
this.editor = null;
|
||||||
|
|
||||||
|
this.history = [];
|
||||||
|
this.index = -1;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = History;
|
module.exports = History;
|
||||||
|
|
|
@ -130,10 +130,9 @@ JSONEditor.prototype._create = function (container, options, json) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detach the editor from the DOM
|
* Destroy the editor. Clean up DOM, event listeners, and web workers.
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
JSONEditor.prototype._delete = function () {};
|
JSONEditor.prototype.destroy = function () {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set JSON object in editor
|
* Set JSON object in editor
|
||||||
|
@ -207,7 +206,7 @@ JSONEditor.prototype.setMode = function (mode) {
|
||||||
name = this.getName();
|
name = this.getName();
|
||||||
data = this[asText ? 'getText' : 'get'](); // get text or json
|
data = this[asText ? 'getText' : 'get'](); // get text or json
|
||||||
|
|
||||||
this._delete();
|
this.destroy();
|
||||||
util.clear(this);
|
util.clear(this);
|
||||||
util.extend(this, config.mixin);
|
util.extend(this, config.mixin);
|
||||||
this.create(container, options);
|
this.create(container, options);
|
||||||
|
|
|
@ -2,64 +2,48 @@ var ContextMenu = require('./ContextMenu');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a select box to be used in the editor menu's, which allows to switch mode
|
* Create a select box to be used in the editor menu's, which allows to switch mode
|
||||||
* @param {Object} editor
|
* @param {HTMLElement} container
|
||||||
* @param {String[]} modes Available modes: 'code', 'form', 'text', 'tree', 'view'
|
* @param {String[]} modes Available modes: 'code', 'form', 'text', 'tree', 'view'
|
||||||
* @param {String} current Available modes: 'code', 'form', 'text', 'tree', 'view'
|
* @param {String} current Available modes: 'code', 'form', 'text', 'tree', 'view'
|
||||||
* @returns {HTMLElement} box
|
* @param {function(mode: string)} onSwitch Callback invoked on switch
|
||||||
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function createModeSwitcher(editor, modes, current) {
|
function ModeSwitcher(container, modes, current, onSwitch) {
|
||||||
// TODO: decouple mode switcher from editor
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switch the mode of the editor
|
|
||||||
* @param {String} mode
|
|
||||||
*/
|
|
||||||
function switchMode(mode) {
|
|
||||||
// switch mode
|
|
||||||
editor.setMode(mode);
|
|
||||||
|
|
||||||
// restore focus on mode box
|
|
||||||
var modeBox = editor.dom && editor.dom.modeBox;
|
|
||||||
if (modeBox) {
|
|
||||||
modeBox.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// available modes
|
// available modes
|
||||||
var availableModes = {
|
var availableModes = {
|
||||||
code: {
|
code: {
|
||||||
'text': 'Code',
|
'text': 'Code',
|
||||||
'title': 'Switch to code highlighter',
|
'title': 'Switch to code highlighter',
|
||||||
'click': function () {
|
'click': function () {
|
||||||
switchMode('code')
|
onSwitch('code')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
'text': 'Form',
|
'text': 'Form',
|
||||||
'title': 'Switch to form editor',
|
'title': 'Switch to form editor',
|
||||||
'click': function () {
|
'click': function () {
|
||||||
switchMode('form');
|
onSwitch('form');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
'text': 'Text',
|
'text': 'Text',
|
||||||
'title': 'Switch to plain text editor',
|
'title': 'Switch to plain text editor',
|
||||||
'click': function () {
|
'click': function () {
|
||||||
switchMode('text');
|
onSwitch('text');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tree: {
|
tree: {
|
||||||
'text': 'Tree',
|
'text': 'Tree',
|
||||||
'title': 'Switch to tree editor',
|
'title': 'Switch to tree editor',
|
||||||
'click': function () {
|
'click': function () {
|
||||||
switchMode('tree');
|
onSwitch('tree');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
view: {
|
view: {
|
||||||
'text': 'View',
|
'text': 'View',
|
||||||
'title': 'Switch to tree view',
|
'title': 'Switch to tree view',
|
||||||
'click': function () {
|
'click': function () {
|
||||||
switchMode('view');
|
onSwitch('view');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -94,12 +78,35 @@ function createModeSwitcher(editor, modes, current) {
|
||||||
menu.show(box);
|
menu.show(box);
|
||||||
};
|
};
|
||||||
|
|
||||||
var div = document.createElement('div');
|
var frame = document.createElement('div');
|
||||||
div.className = 'jsoneditor-modes';
|
frame.className = 'jsoneditor-modes';
|
||||||
div.style.position = 'relative';
|
frame.style.position = 'relative';
|
||||||
div.appendChild(box);
|
frame.appendChild(box);
|
||||||
|
|
||||||
return div;
|
container.appendChild(frame);
|
||||||
|
|
||||||
|
this.dom = {
|
||||||
|
container: container,
|
||||||
|
box: box,
|
||||||
|
frame: frame
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.create = createModeSwitcher;
|
/**
|
||||||
|
* Set focus to switcher
|
||||||
|
*/
|
||||||
|
ModeSwitcher.prototype.focus = function () {
|
||||||
|
this.dom.box.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the ModeSwitcher, remove from DOM
|
||||||
|
*/
|
||||||
|
ModeSwitcher.prototype.destroy = function () {
|
||||||
|
if (this.dom && this.dom.frame && this.dom.frame.parentNode) {
|
||||||
|
this.dom.frame.parentNode.removeChild(this.dom.frame);
|
||||||
|
}
|
||||||
|
this.dom = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = ModeSwitcher;
|
|
@ -2073,7 +2073,6 @@ Node.prototype.onEvent = function (event) {
|
||||||
target = event.target || event.srcElement,
|
target = event.target || event.srcElement,
|
||||||
dom = this.dom,
|
dom = this.dom,
|
||||||
node = this,
|
node = this,
|
||||||
focusNode,
|
|
||||||
expandable = this._hasChilds();
|
expandable = this._hasChilds();
|
||||||
|
|
||||||
// check if mouse is on menu or on dragarea.
|
// check if mouse is on menu or on dragarea.
|
||||||
|
@ -2123,7 +2122,7 @@ Node.prototype.onEvent = function (event) {
|
||||||
//noinspection FallthroughInSwitchStatementJS
|
//noinspection FallthroughInSwitchStatementJS
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'focus':
|
case 'focus':
|
||||||
focusNode = this;
|
this.editor.focusNode = this;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'blur':
|
case 'blur':
|
||||||
|
@ -2176,7 +2175,7 @@ Node.prototype.onEvent = function (event) {
|
||||||
if (target == domField) {
|
if (target == domField) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'focus':
|
case 'focus':
|
||||||
focusNode = this;
|
this.editor.focusNode = this;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'blur':
|
case 'blur':
|
||||||
|
|
|
@ -292,4 +292,19 @@ SearchBox.prototype.clear = function () {
|
||||||
this._onSearch();
|
this._onSearch();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the search box
|
||||||
|
*/
|
||||||
|
SearchBox.prototype.destroy = function () {
|
||||||
|
this.editor = null;
|
||||||
|
this.dom.container.removeChild(this.dom.table);
|
||||||
|
this.dom = null;
|
||||||
|
|
||||||
|
this.results = null;
|
||||||
|
this.activeResult = null;
|
||||||
|
|
||||||
|
this._clearDelay();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = SearchBox;
|
module.exports = SearchBox;
|
||||||
|
|
|
@ -6,7 +6,7 @@ catch (err) {
|
||||||
// failed to load ace, no problem, we will fall back to plain text
|
// failed to load ace, no problem, we will fall back to plain text
|
||||||
}
|
}
|
||||||
|
|
||||||
var modeswitcher = require('./modeswitcher');
|
var ModeSwitcher = require('./ModeSwitcher');
|
||||||
var util = require('./util');
|
var util = require('./util');
|
||||||
|
|
||||||
// create a mixin with the functions for text mode
|
// create a mixin with the functions for text mode
|
||||||
|
@ -123,9 +123,13 @@ textmode.create = function (container, options) {
|
||||||
|
|
||||||
// create mode box
|
// create mode box
|
||||||
if (this.options && this.options.modes && this.options.modes.length) {
|
if (this.options && this.options.modes && this.options.modes.length) {
|
||||||
var modeBox = modeswitcher.create(this, this.options.modes, this.options.mode);
|
this.modeSwitcher = new ModeSwitcher(this.menu, this.options.modes, this.options.mode, function onSwitch(mode) {
|
||||||
this.menu.appendChild(modeBox);
|
me.modeSwitcher.destroy();
|
||||||
this.dom.modeBox = modeBox;
|
|
||||||
|
// switch mode and restore focus
|
||||||
|
me.setMode(mode);
|
||||||
|
me.modeSwitcher.focus();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.content = document.createElement('div');
|
this.content = document.createElement('div');
|
||||||
|
@ -253,18 +257,27 @@ textmode._onKeyDown = function (event) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detach the editor from the DOM
|
* Destroy the editor. Clean up DOM, event listeners, and web workers.
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
textmode._delete = function () {
|
textmode.destroy = function () {
|
||||||
// remove old ace editor
|
// remove old ace editor
|
||||||
if (this.aceEditor) {
|
if (this.aceEditor) {
|
||||||
this.aceEditor.destroy();
|
this.aceEditor.destroy();
|
||||||
|
this.aceEditor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.frame && this.container && this.frame.parentNode == this.container) {
|
if (this.frame && this.container && this.frame.parentNode == this.container) {
|
||||||
this.container.removeChild(this.frame);
|
this.container.removeChild(this.frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.modeSwitcher) {
|
||||||
|
this.modeSwitcher.destroy();
|
||||||
|
this.modeSwitcher = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.textarea = null;
|
||||||
|
|
||||||
|
this._debouncedValidate = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,7 +3,7 @@ var History = require('./History');
|
||||||
var SearchBox = require('./SearchBox');
|
var SearchBox = require('./SearchBox');
|
||||||
var ContextMenu = require('./ContextMenu');
|
var ContextMenu = require('./ContextMenu');
|
||||||
var Node = require('./Node');
|
var Node = require('./Node');
|
||||||
var modeswitcher = require('./modeswitcher');
|
var ModeSwitcher = require('./ModeSwitcher');
|
||||||
var util = require('./util');
|
var util = require('./util');
|
||||||
|
|
||||||
// create a mixin with the functions for tree mode
|
// create a mixin with the functions for tree mode
|
||||||
|
@ -43,6 +43,8 @@ treemode.create = function (container, options) {
|
||||||
this.validateSchema = null; // will be set in .setSchema(schema)
|
this.validateSchema = null; // will be set in .setSchema(schema)
|
||||||
this.errorNodes = [];
|
this.errorNodes = [];
|
||||||
|
|
||||||
|
this.node = null;
|
||||||
|
this.focusNode = null;
|
||||||
|
|
||||||
this._setOptions(options);
|
this._setOptions(options);
|
||||||
|
|
||||||
|
@ -55,12 +57,39 @@ treemode.create = function (container, options) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detach the editor from the DOM
|
* Destroy the editor. Clean up DOM, event listeners, and web workers.
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
treemode._delete = function () {
|
treemode.destroy = function () {
|
||||||
if (this.frame && this.container && this.frame.parentNode == this.container) {
|
if (this.frame && this.container && this.frame.parentNode == this.container) {
|
||||||
this.container.removeChild(this.frame);
|
this.container.removeChild(this.frame);
|
||||||
|
this.frame = null;
|
||||||
|
}
|
||||||
|
this.container = null;
|
||||||
|
|
||||||
|
this.dom = null;
|
||||||
|
|
||||||
|
this.clear();
|
||||||
|
this.node = null;
|
||||||
|
this.focusNode = null;
|
||||||
|
this.selection = null;
|
||||||
|
this.multiselection = null;
|
||||||
|
this.errorNodes = null;
|
||||||
|
this.validateSchema = null;
|
||||||
|
this._debouncedValidate = null;
|
||||||
|
|
||||||
|
if (this.history) {
|
||||||
|
this.history.destroy();
|
||||||
|
this.history = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.searchBox) {
|
||||||
|
this.searchBox.destroy();
|
||||||
|
this.searchBox = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.modeSwitcher) {
|
||||||
|
this.modeSwitcher.destroy();
|
||||||
|
this.modeSwitcher = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -94,12 +123,6 @@ treemode._setOptions = function (options) {
|
||||||
this._debouncedValidate = util.debounce(this.validate.bind(this), this.DEBOUNCE_INTERVAL);
|
this._debouncedValidate = util.debounce(this.validate.bind(this), this.DEBOUNCE_INTERVAL);
|
||||||
};
|
};
|
||||||
|
|
||||||
// node currently being edited
|
|
||||||
var focusNode = undefined;
|
|
||||||
|
|
||||||
// dom having focus
|
|
||||||
var domFocus = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set JSON object in editor
|
* Set JSON object in editor
|
||||||
* @param {Object | undefined} json JSON data
|
* @param {Object | undefined} json JSON data
|
||||||
|
@ -156,8 +179,8 @@ treemode.set = function (json, name) {
|
||||||
*/
|
*/
|
||||||
treemode.get = function () {
|
treemode.get = function () {
|
||||||
// remove focus from currently edited node
|
// remove focus from currently edited node
|
||||||
if (focusNode) {
|
if (this.focusNode) {
|
||||||
focusNode.blur();
|
this.focusNode.blur();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.node) {
|
if (this.node) {
|
||||||
|
@ -520,7 +543,7 @@ treemode.getSelection = function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dom: domFocus,
|
dom: this.dom.focus,
|
||||||
range: range,
|
range: range,
|
||||||
nodes: this.multiselection.nodes.slice(0),
|
nodes: this.multiselection.nodes.slice(0),
|
||||||
scrollTop: this.content ? this.content.scrollTop : 0
|
scrollTop: this.content ? this.content.scrollTop : 0
|
||||||
|
@ -686,9 +709,14 @@ treemode._createFrame = function () {
|
||||||
|
|
||||||
// create mode box
|
// create mode box
|
||||||
if (this.options && this.options.modes && this.options.modes.length) {
|
if (this.options && this.options.modes && this.options.modes.length) {
|
||||||
var modeBox = modeswitcher.create(this, this.options.modes, this.options.mode);
|
var me = this;
|
||||||
this.menu.appendChild(modeBox);
|
this.modeSwitcher = new ModeSwitcher(this.menu, this.options.modes, this.options.mode, function onSwitch(mode) {
|
||||||
this.dom.modeBox = modeBox;
|
me.modeSwitcher.destroy();
|
||||||
|
|
||||||
|
// switch mode and restore focus
|
||||||
|
me.setMode(mode);
|
||||||
|
me.modeSwitcher.focus();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// create search box
|
// create search box
|
||||||
|
@ -736,7 +764,7 @@ treemode._onEvent = function (event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == 'focus') {
|
if (event.type == 'focus') {
|
||||||
domFocus = event.target;
|
this.dom.focus = event.target;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == 'mousedown') {
|
if (event.type == 'mousedown') {
|
||||||
|
@ -1011,7 +1039,7 @@ treemode._onKeyDown = function (event) {
|
||||||
if (keynum == 9) { // Tab or Shift+Tab
|
if (keynum == 9) { // Tab or Shift+Tab
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
// select all text when moving focus to an editable div
|
// select all text when moving focus to an editable div
|
||||||
util.selectContentEditable(domFocus);
|
util.selectContentEditable(this.dom.focus);
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue