Fixed #278: Implemented method `editor.destroy()` to properly cleanup the editor

This commit is contained in:
jos 2016-03-20 14:54:47 +01:00
parent 61c6f21f90
commit a78b2ae57a
10 changed files with 143 additions and 64 deletions

View File

@ -5,6 +5,7 @@ https://github.com/josdejong/jsoneditor
## 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.

View File

@ -111,6 +111,10 @@ Constructs a new JSONEditor.
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()`
Expand all fields. Only applicable for mode 'tree', 'view', and 'form'.

View File

@ -19,7 +19,7 @@
"bugs": "https://github.com/josdejong/jsoneditor/issues",
"scripts": {
"build": "gulp",
"watch": "watch",
"watch": "gulp watch",
"test": "mocha test"
},
"dependencies": {

View File

@ -7,6 +7,9 @@ var util = require('./util');
*/
function History (editor) {
this.editor = editor;
this.history = [];
this.index = -1;
this.clear();
// 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;

View File

@ -130,10 +130,9 @@ JSONEditor.prototype._create = function (container, options, json) {
};
/**
* Detach the editor from the DOM
* @private
* Destroy the editor. Clean up DOM, event listeners, and web workers.
*/
JSONEditor.prototype._delete = function () {};
JSONEditor.prototype.destroy = function () {};
/**
* Set JSON object in editor
@ -207,7 +206,7 @@ JSONEditor.prototype.setMode = function (mode) {
name = this.getName();
data = this[asText ? 'getText' : 'get'](); // get text or json
this._delete();
this.destroy();
util.clear(this);
util.extend(this, config.mixin);
this.create(container, options);

View File

@ -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
* @param {Object} editor
* @param {HTMLElement} container
* @param {String[]} modes 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) {
// 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();
}
}
function ModeSwitcher(container, modes, current, onSwitch) {
// available modes
var availableModes = {
code: {
'text': 'Code',
'title': 'Switch to code highlighter',
'click': function () {
switchMode('code')
onSwitch('code')
}
},
form: {
'text': 'Form',
'title': 'Switch to form editor',
'click': function () {
switchMode('form');
onSwitch('form');
}
},
text: {
'text': 'Text',
'title': 'Switch to plain text editor',
'click': function () {
switchMode('text');
onSwitch('text');
}
},
tree: {
'text': 'Tree',
'title': 'Switch to tree editor',
'click': function () {
switchMode('tree');
onSwitch('tree');
}
},
view: {
'text': 'View',
'title': 'Switch to tree view',
'click': function () {
switchMode('view');
onSwitch('view');
}
}
};
@ -94,12 +78,35 @@ function createModeSwitcher(editor, modes, current) {
menu.show(box);
};
var div = document.createElement('div');
div.className = 'jsoneditor-modes';
div.style.position = 'relative';
div.appendChild(box);
var frame = document.createElement('div');
frame.className = 'jsoneditor-modes';
frame.style.position = 'relative';
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;

View File

@ -2073,7 +2073,6 @@ Node.prototype.onEvent = function (event) {
target = event.target || event.srcElement,
dom = this.dom,
node = this,
focusNode,
expandable = this._hasChilds();
// check if mouse is on menu or on dragarea.
@ -2123,7 +2122,7 @@ Node.prototype.onEvent = function (event) {
//noinspection FallthroughInSwitchStatementJS
switch (type) {
case 'focus':
focusNode = this;
this.editor.focusNode = this;
break;
case 'blur':
@ -2176,7 +2175,7 @@ Node.prototype.onEvent = function (event) {
if (target == domField) {
switch (type) {
case 'focus':
focusNode = this;
this.editor.focusNode = this;
break;
case 'blur':

View File

@ -292,4 +292,19 @@ SearchBox.prototype.clear = function () {
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;

View File

@ -6,7 +6,7 @@ catch (err) {
// 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');
// create a mixin with the functions for text mode
@ -123,9 +123,13 @@ textmode.create = function (container, options) {
// create mode box
if (this.options && this.options.modes && this.options.modes.length) {
var modeBox = modeswitcher.create(this, this.options.modes, this.options.mode);
this.menu.appendChild(modeBox);
this.dom.modeBox = modeBox;
this.modeSwitcher = new ModeSwitcher(this.menu, this.options.modes, this.options.mode, function onSwitch(mode) {
me.modeSwitcher.destroy();
// switch mode and restore focus
me.setMode(mode);
me.modeSwitcher.focus();
});
}
this.content = document.createElement('div');
@ -253,18 +257,27 @@ textmode._onKeyDown = function (event) {
};
/**
* Detach the editor from the DOM
* @private
* Destroy the editor. Clean up DOM, event listeners, and web workers.
*/
textmode._delete = function () {
textmode.destroy = function () {
// remove old ace editor
if (this.aceEditor) {
this.aceEditor.destroy();
this.aceEditor = null;
}
if (this.frame && this.container && this.frame.parentNode == this.container) {
this.container.removeChild(this.frame);
}
if (this.modeSwitcher) {
this.modeSwitcher.destroy();
this.modeSwitcher = null;
}
this.textarea = null;
this._debouncedValidate = null;
};
/**

View File

@ -3,7 +3,7 @@ var History = require('./History');
var SearchBox = require('./SearchBox');
var ContextMenu = require('./ContextMenu');
var Node = require('./Node');
var modeswitcher = require('./modeswitcher');
var ModeSwitcher = require('./ModeSwitcher');
var util = require('./util');
// 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.errorNodes = [];
this.node = null;
this.focusNode = null;
this._setOptions(options);
@ -55,12 +57,39 @@ treemode.create = function (container, options) {
};
/**
* Detach the editor from the DOM
* @private
* Destroy the editor. Clean up DOM, event listeners, and web workers.
*/
treemode._delete = function () {
treemode.destroy = function () {
if (this.frame && this.container && this.frame.parentNode == this.container) {
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);
};
// node currently being edited
var focusNode = undefined;
// dom having focus
var domFocus = null;
/**
* Set JSON object in editor
* @param {Object | undefined} json JSON data
@ -156,8 +179,8 @@ treemode.set = function (json, name) {
*/
treemode.get = function () {
// remove focus from currently edited node
if (focusNode) {
focusNode.blur();
if (this.focusNode) {
this.focusNode.blur();
}
if (this.node) {
@ -520,7 +543,7 @@ treemode.getSelection = function () {
}
return {
dom: domFocus,
dom: this.dom.focus,
range: range,
nodes: this.multiselection.nodes.slice(0),
scrollTop: this.content ? this.content.scrollTop : 0
@ -686,9 +709,14 @@ treemode._createFrame = function () {
// create mode box
if (this.options && this.options.modes && this.options.modes.length) {
var modeBox = modeswitcher.create(this, this.options.modes, this.options.mode);
this.menu.appendChild(modeBox);
this.dom.modeBox = modeBox;
var me = this;
this.modeSwitcher = new ModeSwitcher(this.menu, this.options.modes, this.options.mode, function onSwitch(mode) {
me.modeSwitcher.destroy();
// switch mode and restore focus
me.setMode(mode);
me.modeSwitcher.focus();
});
}
// create search box
@ -736,7 +764,7 @@ treemode._onEvent = function (event) {
}
if (event.type == 'focus') {
domFocus = event.target;
this.dom.focus = event.target;
}
if (event.type == 'mousedown') {
@ -1011,7 +1039,7 @@ treemode._onKeyDown = function (event) {
if (keynum == 9) { // Tab or Shift+Tab
setTimeout(function () {
// select all text when moving focus to an editable div
util.selectContentEditable(domFocus);
util.selectContentEditable(this.dom.focus);
}, 0);
}