3898 lines
114 KiB
JavaScript
3898 lines
114 KiB
JavaScript
|
/**
|
|||
|
* @file jsoneditor.js
|
|||
|
*
|
|||
|
* @brief
|
|||
|
* JSONEditor is a web-based tool to view, edit, and format JSON.
|
|||
|
* It shows data a clear, editable treeview.
|
|||
|
*
|
|||
|
* Supported browsers: Chrome, Firefox, Safari, Opera, Internet Explorer 8+
|
|||
|
*
|
|||
|
* @license
|
|||
|
* This json editor is open sourced with the intention to use the editor as
|
|||
|
* a component in your own application. Not to just copy and monetize the editor
|
|||
|
* as it is.
|
|||
|
*
|
|||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|||
|
* use this file except in compliance with the License. You may obtain a copy
|
|||
|
* of the License at
|
|||
|
*
|
|||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
|
*
|
|||
|
* Unless required by applicable law or agreed to in writing, software
|
|||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|||
|
* License for the specific language governing permissions and limitations under
|
|||
|
* the License.
|
|||
|
*
|
|||
|
* Copyright (c) 2011-2012 Jos de Jong, http://jsoneditoronline.org
|
|||
|
*
|
|||
|
* @author Jos de Jong, <wjosdejong@gmail.com>
|
|||
|
* @date 2012-12-08
|
|||
|
*/
|
|||
|
// Internet Explorer 8 and older does not support Array.indexOf,
|
|||
|
// so we define it here in that case
|
|||
|
// http://soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer/
|
|||
|
if (!Array.prototype.indexOf) {
|
|||
|
Array.prototype.indexOf = function(obj) {
|
|||
|
for (var i = 0; i < this.length; i++) {
|
|||
|
if (this[i] == obj) {
|
|||
|
return i;
|
|||
|
}
|
|||
|
}
|
|||
|
return -1;
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
// Internet Explorer 8 and older does not support Array.forEach,
|
|||
|
// so we define it here in that case
|
|||
|
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach
|
|||
|
if (!Array.prototype.forEach) {
|
|||
|
Array.prototype.forEach = function(fn, scope) {
|
|||
|
for (var i = 0, len = this.length; i < len; ++i) {
|
|||
|
fn.call(scope || this, this[i], i, this);
|
|||
|
}
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
// define variable JSON, needed for correct error handling on IE7 and older
|
|||
|
var JSON;
|
|||
|
|
|||
|
/**
|
|||
|
* JSONEditor
|
|||
|
* @param {Element} container Container element
|
|||
|
* @param {Object} [options] Object with options. available options:
|
|||
|
* {String} mode Editor mode. Available values:
|
|||
|
* 'editor' (default), 'viewer'.
|
|||
|
* {Boolean} search Enable search box.
|
|||
|
* True by default
|
|||
|
* {Boolean} history Enable history (undo/redo).
|
|||
|
* True by default
|
|||
|
* {function} change Callback method, triggered
|
|||
|
* on change of contents
|
|||
|
* {String} name Field name for the root node.
|
|||
|
* @param {Object | undefined} json JSON object
|
|||
|
*/
|
|||
|
JSONEditor = function(container, options, json) {
|
|||
|
// check availability of JSON parser (not available in IE7 and older)
|
|||
|
if (!JSON) {
|
|||
|
throw new Error(
|
|||
|
"您当前使用的浏览器不支持 JSON. \n\n" +
|
|||
|
"请下载安装最新版本的浏览, 本站推荐Google Chrome.\n" +
|
|||
|
"(PS: 当前主流浏览器都支持JSON)."
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
if (!container) {
|
|||
|
throw new Error("没有提供容器元素.");
|
|||
|
}
|
|||
|
this.container = container;
|
|||
|
this.dom = {};
|
|||
|
|
|||
|
this._setOptions(options);
|
|||
|
|
|||
|
if (this.options.history && this.editable) {
|
|||
|
this.history = new JSONEditor.History(this);
|
|||
|
}
|
|||
|
|
|||
|
this._createFrame();
|
|||
|
this._createTable();
|
|||
|
|
|||
|
this.set(json || {});
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Initialize and set default options
|
|||
|
* @param {Object} [options] Object with options. available options:
|
|||
|
* {String} mode Editor mode. Available values:
|
|||
|
* 'editor' (default), 'viewer'.
|
|||
|
* {Boolean} search Enable search box.
|
|||
|
* True by default.
|
|||
|
* {Boolean} history Enable history (undo/redo).
|
|||
|
* True by default.
|
|||
|
* {function} change Callback method, triggered
|
|||
|
* on change of contents.
|
|||
|
* {String} name Field name for the root node.
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.prototype._setOptions = function(options) {
|
|||
|
this.options = {
|
|||
|
search: true,
|
|||
|
history: true,
|
|||
|
mode: "editor",
|
|||
|
name: undefined // field name of root node
|
|||
|
};
|
|||
|
|
|||
|
// copy all options
|
|||
|
if (options) {
|
|||
|
for (var prop in options) {
|
|||
|
if (options.hasOwnProperty(prop)) {
|
|||
|
this.options[prop] = options[prop];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// check for deprecated options
|
|||
|
if (options.enableSearch) {
|
|||
|
// deprecated since version 1.6.0, 2012-11-03
|
|||
|
this.options.search = options.enableSearch;
|
|||
|
// console.log('WARNING: Option "enableSearch" is deprecated. Use "search" instead.');
|
|||
|
}
|
|||
|
if (options.enableHistory) {
|
|||
|
// deprecated since version 1.6.0, 2012-11-03
|
|||
|
this.options.search = options.enableHistory;
|
|||
|
// console.log('WARNING: Option "enableHistory" is deprecated. Use "history" instead.');
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// interpret the options
|
|||
|
this.editable = this.options.mode != "viewer";
|
|||
|
};
|
|||
|
|
|||
|
// node currently being edited
|
|||
|
JSONEditor.focusNode = undefined;
|
|||
|
|
|||
|
/**
|
|||
|
* Set JSON object in editor
|
|||
|
* @param {Object | undefined} json JSON data
|
|||
|
* @param {String} [name] Optional field name for the root node.
|
|||
|
* Can also be set using setName(name).
|
|||
|
*/
|
|||
|
JSONEditor.prototype.set = function(json, name) {
|
|||
|
// adjust field name for root node
|
|||
|
if (name) {
|
|||
|
this.options.name = name;
|
|||
|
}
|
|||
|
|
|||
|
// verify if json is valid JSON, ignore when a function
|
|||
|
if (json instanceof Function || json === undefined) {
|
|||
|
this.clear();
|
|||
|
} else {
|
|||
|
this.content.removeChild(this.table); // Take the table offline
|
|||
|
|
|||
|
// replace the root node
|
|||
|
var params = {
|
|||
|
field: this.options.name,
|
|||
|
value: json
|
|||
|
};
|
|||
|
var node = new JSONEditor.Node(this, params);
|
|||
|
this._setRoot(node);
|
|||
|
|
|||
|
// expand
|
|||
|
var recurse = false;
|
|||
|
this.node.expand(recurse);
|
|||
|
|
|||
|
this.content.appendChild(this.table); // Put the table online again
|
|||
|
}
|
|||
|
|
|||
|
// TODO: maintain history, store last state and previous document
|
|||
|
if (this.history) {
|
|||
|
this.history.clear();
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Get JSON object from editor
|
|||
|
* @return {Object | undefined} json
|
|||
|
*/
|
|||
|
JSONEditor.prototype.get = function() {
|
|||
|
// remove focus from currently edited node
|
|||
|
if (JSONEditor.focusNode) {
|
|||
|
JSONEditor.focusNode.blur();
|
|||
|
}
|
|||
|
|
|||
|
if (this.node) {
|
|||
|
return this.node.getValue();
|
|||
|
} else {
|
|||
|
return undefined;
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Set a field name for the root node.
|
|||
|
* @param {String | undefined} name
|
|||
|
*/
|
|||
|
JSONEditor.prototype.setName = function(name) {
|
|||
|
this.options.name = name;
|
|||
|
if (this.node) {
|
|||
|
this.node.updateField(this.options.name);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Get the field name for the root node.
|
|||
|
* @return {String | undefined} name
|
|||
|
*/
|
|||
|
JSONEditor.prototype.getName = function() {
|
|||
|
return this.options.name;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Remove the root node from the editor
|
|||
|
*/
|
|||
|
JSONEditor.prototype.clear = function() {
|
|||
|
if (this.node) {
|
|||
|
this.node.collapse();
|
|||
|
this.tbody.removeChild(this.node.getDom());
|
|||
|
delete this.node;
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Set the root node for the json editor
|
|||
|
* @param {JSONEditor.Node} node
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.prototype._setRoot = function(node) {
|
|||
|
this.clear();
|
|||
|
|
|||
|
this.node = node;
|
|||
|
|
|||
|
// append to the dom
|
|||
|
this.tbody.appendChild(node.getDom());
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Search text in all nodes
|
|||
|
* The nodes will be expanded when the text is found one of its childs,
|
|||
|
* else it will be collapsed. Searches are case insensitive.
|
|||
|
* @param {String} text
|
|||
|
* @return {Object[]} results Array with nodes containing the search results
|
|||
|
* The result objects contains fields:
|
|||
|
* - {JSONEditor.Node} node,
|
|||
|
* - {String} elem the dom element name where
|
|||
|
* the result is found ('field' or
|
|||
|
* 'value')
|
|||
|
*/
|
|||
|
JSONEditor.prototype.search = function(text) {
|
|||
|
var results;
|
|||
|
if (this.node) {
|
|||
|
this.content.removeChild(this.table); // Take the table offline
|
|||
|
results = this.node.search(text);
|
|||
|
this.content.appendChild(this.table); // Put the table online again
|
|||
|
} else {
|
|||
|
results = [];
|
|||
|
}
|
|||
|
|
|||
|
return results;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Expand all nodes
|
|||
|
*/
|
|||
|
JSONEditor.prototype.expandAll = function() {
|
|||
|
if (this.node) {
|
|||
|
this.content.removeChild(this.table); // Take the table offline
|
|||
|
this.node.expand();
|
|||
|
this.content.appendChild(this.table); // Put the table online again
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Collapse all nodes
|
|||
|
*/
|
|||
|
JSONEditor.prototype.collapseAll = function() {
|
|||
|
if (this.node) {
|
|||
|
this.content.removeChild(this.table); // Take the table offline
|
|||
|
this.node.collapse();
|
|||
|
this.content.appendChild(this.table); // Put the table online again
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* The method onChange is called whenever a field or value is changed, created,
|
|||
|
* deleted, duplicated, etc.
|
|||
|
* @param {String} action Change action. Available values: "editField",
|
|||
|
* "editValue", "changeType", "appendNode",
|
|||
|
* "removeNode", "duplicateNode", "moveNode", "expand",
|
|||
|
* "collapse".
|
|||
|
* @param {Object} params Object containing parameters describing the change.
|
|||
|
* The parameters in params depend on the action (for
|
|||
|
* example for "editValue" the Node, old value, and new
|
|||
|
* value are provided). params contains all information
|
|||
|
* needed to undo or redo the action.
|
|||
|
*/
|
|||
|
JSONEditor.prototype.onAction = function(action, params) {
|
|||
|
// add an action to the history
|
|||
|
if (this.history) {
|
|||
|
this.history.add(action, params);
|
|||
|
}
|
|||
|
|
|||
|
// trigger the onChange callback
|
|||
|
if (this.options.change) {
|
|||
|
try {
|
|||
|
this.options.change();
|
|||
|
} catch (err) {
|
|||
|
//console.log('Error in change callback: ', err);
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Set the focus to the JSONEditor. A hidden input field will be created
|
|||
|
* which captures key events
|
|||
|
*/
|
|||
|
// TODO: use the focus method?
|
|||
|
JSONEditor.prototype.focus = function() {
|
|||
|
/*
|
|||
|
if (!this.dom.focus) {
|
|||
|
this.dom.focus = document.createElement('input');
|
|||
|
this.dom.focus.className = 'jsoneditor-hidden-focus';
|
|||
|
|
|||
|
var editor = this;
|
|||
|
this.dom.focus.onblur = function () {
|
|||
|
// remove itself
|
|||
|
if (editor.dom.focus) {
|
|||
|
var focus = editor.dom.focus;
|
|||
|
delete editor.dom.focus;
|
|||
|
editor.frame.removeChild(focus);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
// attach the hidden input box to the DOM
|
|||
|
if (this.frame.firstChild) {
|
|||
|
this.frame.insertBefore(this.dom.focus, this.frame.firstChild);
|
|||
|
}
|
|||
|
else {
|
|||
|
this.frame.appendChild(this.dom.focus);
|
|||
|
}
|
|||
|
}
|
|||
|
this.dom.focus.focus();
|
|||
|
*/
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Adjust the scroll position such that given top position is shown at 1/4
|
|||
|
* of the window height.
|
|||
|
* @param {Number} top
|
|||
|
*/
|
|||
|
JSONEditor.prototype.scrollTo = function(top) {
|
|||
|
var content = this.content;
|
|||
|
if (content) {
|
|||
|
// cancel any running animation
|
|||
|
var editor = this;
|
|||
|
if (editor.animateTimeout) {
|
|||
|
clearTimeout(editor.animateTimeout);
|
|||
|
delete editor.animateTimeout;
|
|||
|
}
|
|||
|
|
|||
|
// calculate final scroll position
|
|||
|
var height = content.clientHeight;
|
|||
|
var bottom = content.scrollHeight - height;
|
|||
|
var finalScrollTop = Math.min(Math.max(top - height / 4, 0), bottom);
|
|||
|
|
|||
|
// animate towards the new scroll position
|
|||
|
var animate = function() {
|
|||
|
var scrollTop = content.scrollTop;
|
|||
|
var diff = finalScrollTop - scrollTop;
|
|||
|
if (Math.abs(diff) > 3) {
|
|||
|
content.scrollTop += diff / 3;
|
|||
|
editor.animateTimeout = setTimeout(animate, 50);
|
|||
|
}
|
|||
|
};
|
|||
|
animate();
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* @constructor JSONEditor.History
|
|||
|
* Store action history, enables undo and redo
|
|||
|
* @param {JSONEditor} editor
|
|||
|
*/
|
|||
|
JSONEditor.History = function(editor) {
|
|||
|
this.editor = editor;
|
|||
|
this.clear();
|
|||
|
|
|||
|
// map with all supported actions
|
|||
|
this.actions = {
|
|||
|
editField: {
|
|||
|
undo: function(obj) {
|
|||
|
obj.params.node.updateField(obj.params.oldValue);
|
|||
|
},
|
|||
|
redo: function(obj) {
|
|||
|
obj.params.node.updateField(obj.params.newValue);
|
|||
|
}
|
|||
|
},
|
|||
|
editValue: {
|
|||
|
undo: function(obj) {
|
|||
|
obj.params.node.updateValue(obj.params.oldValue);
|
|||
|
},
|
|||
|
redo: function(obj) {
|
|||
|
obj.params.node.updateValue(obj.params.newValue);
|
|||
|
}
|
|||
|
},
|
|||
|
appendNode: {
|
|||
|
undo: function(obj) {
|
|||
|
obj.params.parent.removeChild(obj.params.node);
|
|||
|
},
|
|||
|
redo: function(obj) {
|
|||
|
obj.params.parent.appendChild(obj.params.node);
|
|||
|
}
|
|||
|
},
|
|||
|
removeNode: {
|
|||
|
undo: function(obj) {
|
|||
|
var parent = obj.params.parent;
|
|||
|
var beforeNode = parent.childs[obj.params.index] || parent.append;
|
|||
|
parent.insertBefore(obj.params.node, beforeNode);
|
|||
|
},
|
|||
|
redo: function(obj) {
|
|||
|
obj.params.parent.removeChild(obj.params.node);
|
|||
|
}
|
|||
|
},
|
|||
|
duplicateNode: {
|
|||
|
undo: function(obj) {
|
|||
|
obj.params.parent.removeChild(obj.params.clone);
|
|||
|
},
|
|||
|
redo: function(obj) {
|
|||
|
// TODO: insert after instead of insert before
|
|||
|
obj.params.parent.insertBefore(obj.params.clone, obj.params.node);
|
|||
|
}
|
|||
|
},
|
|||
|
changeType: {
|
|||
|
undo: function(obj) {
|
|||
|
obj.params.node.changeType(obj.params.oldType);
|
|||
|
},
|
|||
|
redo: function(obj) {
|
|||
|
obj.params.node.changeType(obj.params.newType);
|
|||
|
}
|
|||
|
},
|
|||
|
moveNode: {
|
|||
|
undo: function(obj) {
|
|||
|
obj.params.startParent.moveTo(obj.params.node, obj.params.startIndex);
|
|||
|
},
|
|||
|
redo: function(obj) {
|
|||
|
obj.params.endParent.moveTo(obj.params.node, obj.params.endIndex);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// TODO: restore the original caret position and selection with each undo
|
|||
|
// TODO: implement history for actions "expand", "collapse", "scroll", "setDocument"
|
|||
|
};
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* The method onChange is executed when the History is changed, and can
|
|||
|
* be overloaded.
|
|||
|
*/
|
|||
|
JSONEditor.History.prototype.onChange = function() {};
|
|||
|
|
|||
|
/**
|
|||
|
* Add a new action to the history
|
|||
|
* @param {String} action The executed action. Available actions: "editField",
|
|||
|
* "editValue", "changeType", "appendNode",
|
|||
|
* "removeNode", "duplicateNode", "moveNode"
|
|||
|
* @param {Object} params Object containing parameters describing the change.
|
|||
|
* The parameters in params depend on the action (for
|
|||
|
* example for "editValue" the Node, old value, and new
|
|||
|
* value are provided). params contains all information
|
|||
|
* needed to undo or redo the action.
|
|||
|
*/
|
|||
|
JSONEditor.History.prototype.add = function(action, params) {
|
|||
|
this.index++;
|
|||
|
this.history[this.index] = {
|
|||
|
action: action,
|
|||
|
params: params,
|
|||
|
timestamp: new Date()
|
|||
|
};
|
|||
|
|
|||
|
// remove redo actions which are invalid now
|
|||
|
if (this.index < this.history.length - 1) {
|
|||
|
this.history.splice(this.index + 1, this.history.length - this.index - 1);
|
|||
|
}
|
|||
|
|
|||
|
// fire onchange event
|
|||
|
this.onChange();
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Clear history
|
|||
|
*/
|
|||
|
JSONEditor.History.prototype.clear = function() {
|
|||
|
this.history = [];
|
|||
|
this.index = -1;
|
|||
|
|
|||
|
// fire onchange event
|
|||
|
this.onChange();
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Check if there is an action available for undo
|
|||
|
* @return {Boolean} canUndo
|
|||
|
*/
|
|||
|
JSONEditor.History.prototype.canUndo = function() {
|
|||
|
return this.index >= 0;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Check if there is an action available for redo
|
|||
|
* @return {Boolean} canRedo
|
|||
|
*/
|
|||
|
JSONEditor.History.prototype.canRedo = function() {
|
|||
|
return this.index < this.history.length - 1;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Undo the last action
|
|||
|
*/
|
|||
|
JSONEditor.History.prototype.undo = function() {
|
|||
|
if (this.canUndo()) {
|
|||
|
var obj = this.history[this.index];
|
|||
|
if (obj) {
|
|||
|
var action = this.actions[obj.action];
|
|||
|
if (action && action.undo) {
|
|||
|
action.undo(obj);
|
|||
|
} else {
|
|||
|
//console.log('Error: unknown action "' + obj.action + '"');
|
|||
|
}
|
|||
|
}
|
|||
|
this.index--;
|
|||
|
|
|||
|
// fire onchange event
|
|||
|
this.onChange();
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Redo the last action
|
|||
|
*/
|
|||
|
JSONEditor.History.prototype.redo = function() {
|
|||
|
if (this.canRedo()) {
|
|||
|
this.index++;
|
|||
|
|
|||
|
var obj = this.history[this.index];
|
|||
|
if (obj) {
|
|||
|
if (obj) {
|
|||
|
var action = this.actions[obj.action];
|
|||
|
if (action && action.redo) {
|
|||
|
action.redo(obj);
|
|||
|
} else {
|
|||
|
//console.log('Error: unknown action "' + obj.action + '"');
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// fire onchange event
|
|||
|
this.onChange();
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* @constructor JSONEditor.Node
|
|||
|
* Create a new Node
|
|||
|
* @param {JSONEditor} editor
|
|||
|
* @param {Object} params Can contain parameters: field, fieldEditable, value.
|
|||
|
*/
|
|||
|
JSONEditor.Node = function(editor, params) {
|
|||
|
this.editor = editor;
|
|||
|
this.dom = {};
|
|||
|
this.expanded = false;
|
|||
|
|
|||
|
if (params && params instanceof Object) {
|
|||
|
this.setField(params.field, params.fieldEditable);
|
|||
|
this.setValue(params.value);
|
|||
|
} else {
|
|||
|
this.setField();
|
|||
|
this.setValue();
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Set parent node
|
|||
|
* @param {JSONEditor.Node} parent
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.setParent = function(parent) {
|
|||
|
this.parent = parent;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Get parent node. Returns undefined when no parent node is set.
|
|||
|
* @return {JSONEditor.Node} parent
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.getParent = function() {
|
|||
|
return this.parent;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Set field
|
|||
|
* @param {String} field
|
|||
|
* @param {boolean} fieldEditable
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.setField = function(field, fieldEditable) {
|
|||
|
this.field = field;
|
|||
|
this.fieldEditable = fieldEditable == true;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Get field
|
|||
|
* @return {String}
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.getField = function() {
|
|||
|
if (this.field === undefined) {
|
|||
|
this._getDomField();
|
|||
|
}
|
|||
|
|
|||
|
return this.field;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Set value. Value is a JSON structure or an element String, Boolean, etc.
|
|||
|
* @param {*} value
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.setValue = function(value) {
|
|||
|
var childValue, child;
|
|||
|
|
|||
|
// first clear all current childs (if any)
|
|||
|
var childs = this.childs;
|
|||
|
if (childs) {
|
|||
|
while (childs.length) {
|
|||
|
this.removeChild(childs[0]);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// TODO: remove the DOM of this Node
|
|||
|
|
|||
|
this.type = this._getType(value);
|
|||
|
if (this.type == "array") {
|
|||
|
// array
|
|||
|
this.childs = [];
|
|||
|
for (var i = 0, iMax = value.length; i < iMax; i++) {
|
|||
|
childValue = value[i];
|
|||
|
if (childValue !== undefined && !(childValue instanceof Function)) {
|
|||
|
// ignore undefined and functions
|
|||
|
child = new JSONEditor.Node(this.editor, {
|
|||
|
value: childValue
|
|||
|
});
|
|||
|
this.appendChild(child);
|
|||
|
}
|
|||
|
}
|
|||
|
this.value = "";
|
|||
|
} else if (this.type == "object") {
|
|||
|
// object
|
|||
|
this.childs = [];
|
|||
|
for (var childField in value) {
|
|||
|
if (value.hasOwnProperty(childField)) {
|
|||
|
childValue = value[childField];
|
|||
|
if (childValue !== undefined && !(childValue instanceof Function)) {
|
|||
|
// ignore undefined and functions
|
|||
|
child = new JSONEditor.Node(this.editor, {
|
|||
|
field: childField,
|
|||
|
value: childValue
|
|||
|
});
|
|||
|
this.appendChild(child);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
this.value = "";
|
|||
|
} else {
|
|||
|
// value
|
|||
|
this.childs = undefined;
|
|||
|
this.value = value;
|
|||
|
/* TODO
|
|||
|
if (typeof(value) == 'string') {
|
|||
|
var escValue = JSON.stringify(value);
|
|||
|
this.value = escValue.substring(1, escValue.length - 1);
|
|||
|
console.log('check', value, this.value);
|
|||
|
}
|
|||
|
else {
|
|||
|
this.value = value;
|
|||
|
}
|
|||
|
*/
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Get value. Value is a JSON structure
|
|||
|
* @return {*} value
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.getValue = function() {
|
|||
|
//var childs, i, iMax;
|
|||
|
|
|||
|
if (this.type == "array") {
|
|||
|
var arr = [];
|
|||
|
this.childs.forEach(function(child) {
|
|||
|
arr.push(child.getValue());
|
|||
|
});
|
|||
|
return arr;
|
|||
|
} else if (this.type == "object") {
|
|||
|
var obj = {};
|
|||
|
this.childs.forEach(function(child) {
|
|||
|
obj[child.getField()] = child.getValue();
|
|||
|
});
|
|||
|
return obj;
|
|||
|
} else {
|
|||
|
if (this.value === undefined) {
|
|||
|
this._getDomValue();
|
|||
|
}
|
|||
|
|
|||
|
return this.value;
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Get the nesting level of this node
|
|||
|
* @return {Number} level
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.getLevel = function() {
|
|||
|
return this.parent ? this.parent.getLevel() + 1 : 0;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Create a clone of a node
|
|||
|
* The complete state of a clone is copied, including whether it is expanded or
|
|||
|
* not. The DOM elements are not cloned.
|
|||
|
* @return {JSONEditor.Node} clone
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.clone = function() {
|
|||
|
var clone = new JSONEditor.Node(this.editor);
|
|||
|
clone.type = this.type;
|
|||
|
clone.field = this.field;
|
|||
|
clone.fieldInnerText = this.fieldInnerText;
|
|||
|
clone.fieldEditable = this.fieldEditable;
|
|||
|
clone.value = this.value;
|
|||
|
clone.valueInnerText = this.valueInnerText;
|
|||
|
clone.expanded = this.expanded;
|
|||
|
|
|||
|
if (this.childs) {
|
|||
|
// an object or array
|
|||
|
var cloneChilds = [];
|
|||
|
this.childs.forEach(function(child) {
|
|||
|
var childClone = child.clone();
|
|||
|
childClone.setParent(clone);
|
|||
|
cloneChilds.push(childClone);
|
|||
|
});
|
|||
|
clone.childs = cloneChilds;
|
|||
|
} else {
|
|||
|
// a value
|
|||
|
clone.childs = undefined;
|
|||
|
}
|
|||
|
|
|||
|
return clone;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Expand this node and optionally its childs.
|
|||
|
* @param {boolean} recurse Optional recursion, true by default. When
|
|||
|
* true, all childs will be expanded recursively
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.expand = function(recurse) {
|
|||
|
if (!this.childs) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// set this node expanded
|
|||
|
this.expanded = true;
|
|||
|
if (this.dom.expand) {
|
|||
|
this.dom.expand.className = "jsoneditor-expanded";
|
|||
|
}
|
|||
|
|
|||
|
this.showChilds();
|
|||
|
|
|||
|
if (recurse != false) {
|
|||
|
this.childs.forEach(function(child) {
|
|||
|
child.expand(recurse);
|
|||
|
});
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Collapse this node and optionally its childs.
|
|||
|
* @param {Number} recurse Optional recursion, true by default. When
|
|||
|
* true, all childs will be collapsed recursively
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.collapse = function(recurse) {
|
|||
|
if (!this.childs) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
this.hideChilds();
|
|||
|
|
|||
|
// collapse childs in case of recurse
|
|||
|
if (recurse != false) {
|
|||
|
this.childs.forEach(function(child) {
|
|||
|
child.collapse(recurse);
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
// make this node collapsed
|
|||
|
if (this.dom.expand) {
|
|||
|
this.dom.expand.className = "jsoneditor-collapsed";
|
|||
|
}
|
|||
|
this.expanded = false;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Recursively show all childs when they are expanded
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.showChilds = function() {
|
|||
|
var childs = this.childs;
|
|||
|
if (!childs) {
|
|||
|
return;
|
|||
|
}
|
|||
|
if (!this.expanded) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
var tr = this.dom.tr;
|
|||
|
var table = tr ? tr.parentNode : undefined;
|
|||
|
if (table) {
|
|||
|
// show row with append button
|
|||
|
var append = this.getAppend();
|
|||
|
var nextTr = tr.nextSibling;
|
|||
|
if (nextTr) {
|
|||
|
table.insertBefore(append, nextTr);
|
|||
|
} else {
|
|||
|
table.appendChild(append);
|
|||
|
}
|
|||
|
|
|||
|
// show childs
|
|||
|
this.childs.forEach(function(child) {
|
|||
|
table.insertBefore(child.getDom(), append);
|
|||
|
child.showChilds();
|
|||
|
});
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Hide the node with all its childs
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.hide = function() {
|
|||
|
var tr = this.dom.tr;
|
|||
|
var table = tr ? tr.parentNode : undefined;
|
|||
|
if (table) {
|
|||
|
table.removeChild(tr);
|
|||
|
}
|
|||
|
this.hideChilds();
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Recursively hide all childs
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.hideChilds = function() {
|
|||
|
var childs = this.childs;
|
|||
|
if (!childs) {
|
|||
|
return;
|
|||
|
}
|
|||
|
if (!this.expanded) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// hide append row
|
|||
|
var append = this.getAppend();
|
|||
|
if (append.parentNode) {
|
|||
|
append.parentNode.removeChild(append);
|
|||
|
}
|
|||
|
|
|||
|
// hide childs
|
|||
|
this.childs.forEach(function(child) {
|
|||
|
child.hide();
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Add a new child to the node.
|
|||
|
* Only applicable when Node value is of type array or object
|
|||
|
* @param {JSONEditor.Node} node
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.appendChild = function(node) {
|
|||
|
if (this.type == "array" || this.type == "object") {
|
|||
|
// adjust the link to the parent
|
|||
|
node.setParent(this);
|
|||
|
node.fieldEditable = this.type == "object";
|
|||
|
if (this.type == "array") {
|
|||
|
node.index = this.childs.length;
|
|||
|
}
|
|||
|
this.childs.push(node);
|
|||
|
|
|||
|
if (this.expanded) {
|
|||
|
// insert into the DOM, before the appendRow
|
|||
|
var newtr = node.getDom();
|
|||
|
var appendTr = this.getAppend();
|
|||
|
var table = appendTr ? appendTr.parentNode : undefined;
|
|||
|
if (appendTr && table) {
|
|||
|
table.insertBefore(newtr, appendTr);
|
|||
|
}
|
|||
|
|
|||
|
node.showChilds();
|
|||
|
}
|
|||
|
|
|||
|
this.updateDom({
|
|||
|
updateIndexes: true
|
|||
|
});
|
|||
|
node.updateDom({
|
|||
|
recurse: true
|
|||
|
});
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Move a node from its current parent to this node
|
|||
|
* Only applicable when Node value is of type array or object
|
|||
|
* @param {JSONEditor.Node} node
|
|||
|
* @param {JSONEditor.Node} beforeNode
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.moveBefore = function(node, beforeNode) {
|
|||
|
if (this.type == "array" || this.type == "object") {
|
|||
|
// create a temporary row, to prevent the scroll position from jumping
|
|||
|
// when removing the node
|
|||
|
var tbody = this.dom.tr ? this.dom.tr.parentNode : undefined;
|
|||
|
if (tbody) {
|
|||
|
var trTemp = document.createElement("tr");
|
|||
|
trTemp.style.height = tbody.clientHeight + "px";
|
|||
|
tbody.appendChild(trTemp);
|
|||
|
}
|
|||
|
|
|||
|
var parent = node.getParent();
|
|||
|
if (parent) {
|
|||
|
parent.removeChild(node);
|
|||
|
}
|
|||
|
if (beforeNode instanceof JSONEditor.AppendNode) {
|
|||
|
this.appendChild(node);
|
|||
|
} else {
|
|||
|
this.insertBefore(node, beforeNode);
|
|||
|
}
|
|||
|
|
|||
|
if (tbody) {
|
|||
|
tbody.removeChild(trTemp);
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Move a node from its current parent to this node
|
|||
|
* Only applicable when Node value is of type array or object.
|
|||
|
* If index is out of range, the node will be appended to the end
|
|||
|
* @param {JSONEditor.Node} node
|
|||
|
* @param {Number} index
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.moveTo = function(node, index) {
|
|||
|
if (node.parent == this) {
|
|||
|
// same parent
|
|||
|
var currentIndex = this.childs.indexOf(node);
|
|||
|
if (currentIndex < index) {
|
|||
|
// compensate the index for removal of the node itself
|
|||
|
index++;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
var beforeNode = this.childs[index] || this.append;
|
|||
|
this.moveBefore(node, beforeNode);
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Insert a new child before a given node
|
|||
|
* Only applicable when Node value is of type array or object
|
|||
|
* @param {JSONEditor.Node} node
|
|||
|
* @param {JSONEditor.Node} beforeNode
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.insertBefore = function(node, beforeNode) {
|
|||
|
if (this.type == "array" || this.type == "object") {
|
|||
|
if (beforeNode == this.append) {
|
|||
|
// append to the child nodes
|
|||
|
|
|||
|
// adjust the link to the parent
|
|||
|
node.setParent(this);
|
|||
|
node.fieldEditable = this.type == "object";
|
|||
|
this.childs.push(node);
|
|||
|
} else {
|
|||
|
// insert before a child node
|
|||
|
var index = this.childs.indexOf(beforeNode);
|
|||
|
if (index == -1) {
|
|||
|
throw new Error("节点未找到.");
|
|||
|
}
|
|||
|
|
|||
|
// adjust the link to the parent
|
|||
|
node.setParent(this);
|
|||
|
node.fieldEditable = this.type == "object";
|
|||
|
this.childs.splice(index, 0, node);
|
|||
|
}
|
|||
|
|
|||
|
if (this.expanded) {
|
|||
|
// insert into the DOM
|
|||
|
var newTr = node.getDom();
|
|||
|
var nextTr = beforeNode.getDom();
|
|||
|
var table = nextTr ? nextTr.parentNode : undefined;
|
|||
|
if (nextTr && table) {
|
|||
|
table.insertBefore(newTr, nextTr);
|
|||
|
}
|
|||
|
|
|||
|
node.showChilds();
|
|||
|
}
|
|||
|
|
|||
|
this.updateDom({
|
|||
|
updateIndexes: true
|
|||
|
});
|
|||
|
node.updateDom({
|
|||
|
recurse: true
|
|||
|
});
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Search in this node
|
|||
|
* The node will be expanded when the text is found one of its childs, else
|
|||
|
* it will be collapsed. Searches are case insensitive.
|
|||
|
* @param {String} text
|
|||
|
* @return {JSONEditor.Node[]} results Array with nodes containing the search text
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.search = function(text) {
|
|||
|
var results = [];
|
|||
|
var index;
|
|||
|
var search = text ? text.toLowerCase() : undefined;
|
|||
|
|
|||
|
// delete old search data
|
|||
|
delete this.searchField;
|
|||
|
delete this.searchValue;
|
|||
|
|
|||
|
// search in field
|
|||
|
if (this.field != undefined) {
|
|||
|
var field = String(this.field).toLowerCase();
|
|||
|
index = field.indexOf(search);
|
|||
|
if (index != -1) {
|
|||
|
this.searchField = true;
|
|||
|
results.push({
|
|||
|
node: this,
|
|||
|
elem: "field"
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
// update dom
|
|||
|
this._updateDomField();
|
|||
|
}
|
|||
|
|
|||
|
// search in value
|
|||
|
if (this.type == "array" || this.type == "object") {
|
|||
|
// array, object
|
|||
|
|
|||
|
// search the nodes childs
|
|||
|
if (this.childs) {
|
|||
|
var childResults = [];
|
|||
|
this.childs.forEach(function(child) {
|
|||
|
childResults = childResults.concat(child.search(text));
|
|||
|
});
|
|||
|
results = results.concat(childResults);
|
|||
|
}
|
|||
|
|
|||
|
// update dom
|
|||
|
if (search != undefined) {
|
|||
|
var recurse = false;
|
|||
|
if (childResults.length == 0) {
|
|||
|
this.collapse(recurse);
|
|||
|
} else {
|
|||
|
this.expand(recurse);
|
|||
|
}
|
|||
|
}
|
|||
|
} else {
|
|||
|
// string, auto
|
|||
|
if (this.value != undefined) {
|
|||
|
var value = String(this.value).toLowerCase();
|
|||
|
index = value.indexOf(search);
|
|||
|
if (index != -1) {
|
|||
|
this.searchValue = true;
|
|||
|
results.push({
|
|||
|
node: this,
|
|||
|
elem: "value"
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// update dom
|
|||
|
this._updateDomValue();
|
|||
|
}
|
|||
|
|
|||
|
return results;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Move the scroll position such that this node is in the visible area.
|
|||
|
* The node will not get the focus
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.scrollTo = function() {
|
|||
|
if (!this.dom.tr || !this.dom.tr.parentNode) {
|
|||
|
// if the node is not visible, expand its parents
|
|||
|
var parent = this.parent;
|
|||
|
var recurse = false;
|
|||
|
while (parent) {
|
|||
|
parent.expand(recurse);
|
|||
|
parent = parent.parent;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (this.dom.tr && this.dom.tr.parentNode) {
|
|||
|
this.editor.scrollTo(this.dom.tr.offsetTop);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Set focus to the value of this node
|
|||
|
* @param {String} [field] The field name of the element to get the focus
|
|||
|
* available values: 'field', 'value'
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.focus = function(field) {
|
|||
|
if (this.dom.tr && this.dom.tr.parentNode) {
|
|||
|
if (field != "value" && this.fieldEditable) {
|
|||
|
var domField = this.dom.field;
|
|||
|
if (domField) {
|
|||
|
domField.focus();
|
|||
|
}
|
|||
|
} else {
|
|||
|
var domValue = this.dom.value;
|
|||
|
if (domValue) {
|
|||
|
domValue.focus();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Update the values from the DOM field and value of this node
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.blur = function() {
|
|||
|
// retrieve the actual field and value from the DOM.
|
|||
|
this._getDomValue(false);
|
|||
|
this._getDomField(false);
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Duplicate given child node
|
|||
|
* new structure will be added right before the cloned node
|
|||
|
* @param {JSONEditor.Node} node the childNode to be duplicated
|
|||
|
* @return {JSONEditor.Node} clone the clone of the node
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._duplicate = function(node) {
|
|||
|
var clone = node.clone();
|
|||
|
|
|||
|
/* TODO: adjust the field name (to prevent equal field names)
|
|||
|
if (this.type == 'object') {
|
|||
|
}
|
|||
|
*/
|
|||
|
|
|||
|
// TODO: insert after instead of insert before
|
|||
|
this.insertBefore(clone, node);
|
|||
|
|
|||
|
return clone;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Check if given node is a child. The method will check recursively to find
|
|||
|
* this node.
|
|||
|
* @param {JSONEditor.Node} node
|
|||
|
* @return {boolean} containsNode
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.containsNode = function(node) {
|
|||
|
if (this == node) {
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
var childs = this.childs;
|
|||
|
if (childs) {
|
|||
|
// TOOD: use the js5 Array.some() here?
|
|||
|
for (var i = 0, iMax = childs.length; i < iMax; i++) {
|
|||
|
if (childs[i].containsNode(node)) {
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return false;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Move given node into this node
|
|||
|
* @param {JSONEditor.Node} node the childNode to be moved
|
|||
|
* @param {JSONEditor.Node} beforeNode node will be inserted before given
|
|||
|
* node. If no beforeNode is given,
|
|||
|
* the node is appended at the end
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._move = function(node, beforeNode) {
|
|||
|
if (node == beforeNode) {
|
|||
|
// nothing to do...
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// check if this node is not a child of the node to be moved here
|
|||
|
if (node.containsNode(this)) {
|
|||
|
throw new Error("不能把区域移动到自身的子节点.");
|
|||
|
}
|
|||
|
|
|||
|
// remove the original node
|
|||
|
if (node.parent) {
|
|||
|
node.parent.removeChild(node);
|
|||
|
}
|
|||
|
|
|||
|
// create a clone of the node
|
|||
|
var clone = node.clone();
|
|||
|
node.clearDom();
|
|||
|
|
|||
|
// insert or append the node
|
|||
|
if (beforeNode) {
|
|||
|
this.insertBefore(clone, beforeNode);
|
|||
|
} else {
|
|||
|
this.appendChild(clone);
|
|||
|
}
|
|||
|
|
|||
|
/* TODO: adjust the field name (to prevent equal field names)
|
|||
|
if (this.type == 'object') {
|
|||
|
}
|
|||
|
*/
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Remove a child from the node.
|
|||
|
* Only applicable when Node value is of type array or object
|
|||
|
* @param {JSONEditor.Node} node The child node to be removed;
|
|||
|
* @return {JSONEditor.Node | undefined} node The removed node on success,
|
|||
|
* else undefined
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.removeChild = function(node) {
|
|||
|
if (this.childs) {
|
|||
|
var index = this.childs.indexOf(node);
|
|||
|
|
|||
|
if (index != -1) {
|
|||
|
node.hide();
|
|||
|
|
|||
|
// delete old search results
|
|||
|
delete node.searchField;
|
|||
|
delete node.searchValue;
|
|||
|
|
|||
|
var removedNode = this.childs.splice(index, 1)[0];
|
|||
|
|
|||
|
this.updateDom({
|
|||
|
updateIndexes: true
|
|||
|
});
|
|||
|
|
|||
|
return removedNode;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return undefined;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Remove a child node node from this node
|
|||
|
* This method is equal to Node.removeChild, except that _remove firex an
|
|||
|
* onChange event.
|
|||
|
* @param {JSONEditor.Node} node
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._remove = function(node) {
|
|||
|
this.removeChild(node);
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Change the type of the value of this Node
|
|||
|
* @param {String} newType
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.changeType = function(newType) {
|
|||
|
var oldType = this.type;
|
|||
|
|
|||
|
if (
|
|||
|
(newType == "string" || newType == "auto") &&
|
|||
|
(oldType == "string" || oldType == "auto")
|
|||
|
) {
|
|||
|
// this is an easy change
|
|||
|
this.type = newType;
|
|||
|
} else {
|
|||
|
// change from array to object, or from string/auto to object/array
|
|||
|
|
|||
|
var table = this.dom.tr ? this.dom.tr.parentNode : undefined;
|
|||
|
var lastTr;
|
|||
|
if (this.expanded) {
|
|||
|
lastTr = this.getAppend();
|
|||
|
} else {
|
|||
|
lastTr = this.getDom();
|
|||
|
}
|
|||
|
var nextTr = lastTr && lastTr.parentNode ? lastTr.nextSibling : undefined;
|
|||
|
|
|||
|
// hide current field and all its childs
|
|||
|
this.hide();
|
|||
|
this.clearDom();
|
|||
|
|
|||
|
// adjust the field and the value
|
|||
|
this.type = newType;
|
|||
|
|
|||
|
// adjust childs
|
|||
|
if (newType == "object") {
|
|||
|
if (!this.childs) {
|
|||
|
this.childs = [];
|
|||
|
}
|
|||
|
|
|||
|
this.childs.forEach(function(child, index) {
|
|||
|
child.clearDom();
|
|||
|
delete child.index;
|
|||
|
child.fieldEditable = true;
|
|||
|
if (child.field == undefined) {
|
|||
|
child.field = index;
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
if (oldType == "string" || oldType == "auto") {
|
|||
|
this.expanded = true;
|
|||
|
}
|
|||
|
} else if (newType == "array") {
|
|||
|
if (!this.childs) {
|
|||
|
this.childs = [];
|
|||
|
}
|
|||
|
|
|||
|
this.childs.forEach(function(child, index) {
|
|||
|
child.clearDom();
|
|||
|
child.fieldEditable = false;
|
|||
|
child.index = index;
|
|||
|
});
|
|||
|
|
|||
|
if (oldType == "string" || oldType == "auto") {
|
|||
|
this.expanded = true;
|
|||
|
}
|
|||
|
} else {
|
|||
|
this.expanded = false;
|
|||
|
}
|
|||
|
|
|||
|
// create new DOM
|
|||
|
if (table) {
|
|||
|
if (nextTr) {
|
|||
|
table.insertBefore(this.getDom(), nextTr);
|
|||
|
} else {
|
|||
|
table.appendChild(this.getDom());
|
|||
|
}
|
|||
|
}
|
|||
|
this.showChilds();
|
|||
|
}
|
|||
|
|
|||
|
if (newType == "auto" || newType == "string") {
|
|||
|
// cast value to the correct type
|
|||
|
if (newType == "string") {
|
|||
|
this.value = String(this.value);
|
|||
|
} else {
|
|||
|
this.value = this._stringCast(String(this.value));
|
|||
|
}
|
|||
|
|
|||
|
this.focus();
|
|||
|
}
|
|||
|
|
|||
|
this.updateDom({
|
|||
|
updateIndexes: true
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Retrieve value from DOM
|
|||
|
* @param {boolean} silent. If true (default), no errors will be thrown in
|
|||
|
* case of invalid data
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._getDomValue = function(silent) {
|
|||
|
if (this.dom.value && this.type != "array" && this.type != "object") {
|
|||
|
this.valueInnerText = JSONEditor.getInnerText(this.dom.value);
|
|||
|
}
|
|||
|
|
|||
|
if (this.valueInnerText != undefined) {
|
|||
|
try {
|
|||
|
// retrieve the value
|
|||
|
var value;
|
|||
|
if (this.type == "string") {
|
|||
|
value = this._unescapeHTML(this.valueInnerText);
|
|||
|
} else {
|
|||
|
var str = this._unescapeHTML(this.valueInnerText);
|
|||
|
value = this._stringCast(str);
|
|||
|
}
|
|||
|
if (value !== this.value) {
|
|||
|
var oldValue = this.value;
|
|||
|
this.value = value;
|
|||
|
this.editor.onAction("editValue", {
|
|||
|
node: this,
|
|||
|
oldValue: oldValue,
|
|||
|
newValue: value
|
|||
|
});
|
|||
|
}
|
|||
|
} catch (err) {
|
|||
|
this.value = undefined;
|
|||
|
// TODO: sent an action with the new, invalid value?
|
|||
|
if (silent != true) {
|
|||
|
throw err;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Update dom value:
|
|||
|
* - the text color of the value, depending on the type of the value
|
|||
|
* - the height of the field, depending on the width
|
|||
|
* - background color in case it is empty
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._updateDomValue = function() {
|
|||
|
var domValue = this.dom.value;
|
|||
|
if (domValue) {
|
|||
|
// set text color depending on value type
|
|||
|
var v = this.value;
|
|||
|
var t = this.type == "auto" ? typeof v : this.type;
|
|||
|
var color = "";
|
|||
|
if (t == "string") {
|
|||
|
color = "green";
|
|||
|
} else if (t == "number") {
|
|||
|
color = "red";
|
|||
|
} else if (t == "boolean") {
|
|||
|
color = "blue";
|
|||
|
} else if (this.type == "object" || this.type == "array") {
|
|||
|
// note: typeof(null)=="object", therefore check this.type instead of t
|
|||
|
color = "";
|
|||
|
} else if (v === null) {
|
|||
|
color = "purple";
|
|||
|
} else if (v === undefined) {
|
|||
|
// invalid value
|
|||
|
color = "green";
|
|||
|
}
|
|||
|
domValue.style.color = color;
|
|||
|
|
|||
|
// make backgound color lightgray when empty
|
|||
|
var isEmpty =
|
|||
|
String(this.value) == "" && this.type != "array" && this.type != "object";
|
|||
|
if (isEmpty) {
|
|||
|
JSONEditor.addClassName(domValue, "jsoneditor-empty");
|
|||
|
} else {
|
|||
|
JSONEditor.removeClassName(domValue, "jsoneditor-empty");
|
|||
|
}
|
|||
|
|
|||
|
// highlight when there is a search result
|
|||
|
if (this.searchValueActive) {
|
|||
|
JSONEditor.addClassName(domValue, "jsoneditor-search-highlight-active");
|
|||
|
} else {
|
|||
|
JSONEditor.removeClassName(
|
|||
|
domValue,
|
|||
|
"jsoneditor-search-highlight-active"
|
|||
|
);
|
|||
|
}
|
|||
|
if (this.searchValue) {
|
|||
|
JSONEditor.addClassName(domValue, "jsoneditor-search-highlight");
|
|||
|
} else {
|
|||
|
JSONEditor.removeClassName(domValue, "jsoneditor-search-highlight");
|
|||
|
}
|
|||
|
|
|||
|
// strip formatting from the contents of the editable div
|
|||
|
JSONEditor.stripFormatting(domValue);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Update dom field:
|
|||
|
* - the text color of the field, depending on the text
|
|||
|
* - the height of the field, depending on the width
|
|||
|
* - background color in case it is empty
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._updateDomField = function() {
|
|||
|
var domField = this.dom.field;
|
|||
|
if (domField) {
|
|||
|
// make backgound color lightgray when empty
|
|||
|
var isEmpty = String(this.field) == "";
|
|||
|
if (isEmpty) {
|
|||
|
JSONEditor.addClassName(domField, "jsoneditor-empty");
|
|||
|
} else {
|
|||
|
JSONEditor.removeClassName(domField, "jsoneditor-empty");
|
|||
|
}
|
|||
|
|
|||
|
// highlight when there is a search result
|
|||
|
if (this.searchFieldActive) {
|
|||
|
JSONEditor.addClassName(domField, "jsoneditor-search-highlight-active");
|
|||
|
} else {
|
|||
|
JSONEditor.removeClassName(
|
|||
|
domField,
|
|||
|
"jsoneditor-search-highlight-active"
|
|||
|
);
|
|||
|
}
|
|||
|
if (this.searchField) {
|
|||
|
JSONEditor.addClassName(domField, "jsoneditor-search-highlight");
|
|||
|
} else {
|
|||
|
JSONEditor.removeClassName(domField, "jsoneditor-search-highlight");
|
|||
|
}
|
|||
|
|
|||
|
// strip formatting from the contents of the editable div
|
|||
|
JSONEditor.stripFormatting(domField);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Retrieve field from DOM
|
|||
|
* @param {boolean} silent. If true (default), no errors will be thrown in
|
|||
|
* case of invalid data
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._getDomField = function(silent) {
|
|||
|
if (this.dom.field && this.fieldEditable) {
|
|||
|
this.fieldInnerText = JSONEditor.getInnerText(this.dom.field);
|
|||
|
}
|
|||
|
|
|||
|
if (this.fieldInnerText != undefined) {
|
|||
|
try {
|
|||
|
var field = this._unescapeHTML(this.fieldInnerText);
|
|||
|
|
|||
|
if (field !== this.field) {
|
|||
|
var oldField = this.field;
|
|||
|
this.field = field;
|
|||
|
this.editor.onAction("editField", {
|
|||
|
node: this,
|
|||
|
oldValue: oldField,
|
|||
|
newValue: field
|
|||
|
});
|
|||
|
}
|
|||
|
} catch (err) {
|
|||
|
this.field = undefined;
|
|||
|
// TODO: sent an action here, with the new, invalid value?
|
|||
|
if (silent != true) {
|
|||
|
throw err;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Clear the dom of the node
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.clearDom = function() {
|
|||
|
// TODO: hide the node first?
|
|||
|
//this.hide();
|
|||
|
// TOOD: recursively clear dom?
|
|||
|
|
|||
|
this.dom = {};
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Get the HTML DOM TR element of the node.
|
|||
|
* The dom will be generated when not yet created
|
|||
|
* @return {Element} tr HTML DOM TR Element
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.getDom = function() {
|
|||
|
var dom = this.dom;
|
|||
|
if (dom.tr) {
|
|||
|
return dom.tr;
|
|||
|
}
|
|||
|
|
|||
|
// create row
|
|||
|
dom.tr = document.createElement("tr");
|
|||
|
dom.tr.className = "jsoneditor-tr";
|
|||
|
dom.tr.node = this;
|
|||
|
|
|||
|
if (this.editor.editable) {
|
|||
|
// create draggable area
|
|||
|
var tdDrag = document.createElement("td");
|
|||
|
tdDrag.className = "jsoneditor-td";
|
|||
|
dom.drag = this._createDomDragArea();
|
|||
|
if (dom.drag) {
|
|||
|
tdDrag.appendChild(dom.drag);
|
|||
|
}
|
|||
|
dom.tr.appendChild(tdDrag);
|
|||
|
}
|
|||
|
|
|||
|
// create tree and field
|
|||
|
var tdField = document.createElement("td");
|
|||
|
tdField.className = "jsoneditor-td";
|
|||
|
dom.tr.appendChild(tdField);
|
|||
|
dom.expand = this._createDomExpandButton();
|
|||
|
dom.field = this._createDomField();
|
|||
|
dom.value = this._createDomValue();
|
|||
|
dom.tree = this._createDomTree(dom.expand, dom.field, dom.value);
|
|||
|
tdField.appendChild(dom.tree);
|
|||
|
|
|||
|
if (this.editor.editable) {
|
|||
|
// create type select box
|
|||
|
var tdType = document.createElement("td");
|
|||
|
tdType.className = "jsoneditor-td jsoneditor-td-edit";
|
|||
|
dom.tr.appendChild(tdType);
|
|||
|
dom.type = this._createDomTypeButton();
|
|||
|
tdType.appendChild(dom.type);
|
|||
|
|
|||
|
// create duplicate button
|
|||
|
var tdDuplicate = document.createElement("td");
|
|||
|
tdDuplicate.className = "jsoneditor-td jsoneditor-td-edit";
|
|||
|
dom.tr.appendChild(tdDuplicate);
|
|||
|
dom.duplicate = this._createDomDuplicateButton();
|
|||
|
if (dom.duplicate) {
|
|||
|
tdDuplicate.appendChild(dom.duplicate);
|
|||
|
}
|
|||
|
|
|||
|
// create remove button
|
|||
|
var tdRemove = document.createElement("td");
|
|||
|
tdRemove.className = "jsoneditor-td jsoneditor-td-edit";
|
|||
|
dom.tr.appendChild(tdRemove);
|
|||
|
dom.remove = this._createDomRemoveButton();
|
|||
|
if (dom.remove) {
|
|||
|
tdRemove.appendChild(dom.remove);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
this.updateDom(); // TODO: recurse here?
|
|||
|
|
|||
|
return dom.tr;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* DragStart event, fired on mousedown on the dragarea at the left side of a Node
|
|||
|
* @param {Event} event
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._onDragStart = function(event) {
|
|||
|
event = event || window.event;
|
|||
|
|
|||
|
var node = this;
|
|||
|
if (!this.mousemove) {
|
|||
|
this.mousemove = JSONEditor.Events.addEventListener(
|
|||
|
document,
|
|||
|
"mousemove",
|
|||
|
|
|||
|
function(event) {
|
|||
|
node._onDrag(event);
|
|||
|
}
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
if (!this.mouseup) {
|
|||
|
this.mouseup = JSONEditor.Events.addEventListener(
|
|||
|
document,
|
|||
|
"mouseup",
|
|||
|
|
|||
|
function(event) {
|
|||
|
node._onDragEnd(event);
|
|||
|
}
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
/* TODO: correct highlighting when the TypeDropDown is visible (And has highlighting locked)
|
|||
|
if (JSONEditor.freezeHighlight) {
|
|||
|
console.log('heee');
|
|||
|
JSONEditor.freezeHighlight = false;
|
|||
|
this.setHighlight(true);
|
|||
|
}
|
|||
|
*/
|
|||
|
JSONEditor.freezeHighlight = true;
|
|||
|
this.drag = {
|
|||
|
oldCursor: document.body.style.cursor,
|
|||
|
startParent: this.parent,
|
|||
|
startIndex: this.parent.childs.indexOf(this)
|
|||
|
};
|
|||
|
document.body.style.cursor = "move";
|
|||
|
|
|||
|
JSONEditor.Events.preventDefault(event);
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Drag event, fired when moving the mouse while dragging a Node
|
|||
|
* @param {Event} event
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._onDrag = function(event) {
|
|||
|
event = event || window.event;
|
|||
|
var trThis = this.dom.tr;
|
|||
|
|
|||
|
// TODO: add an ESC option, which resets to the original position
|
|||
|
|
|||
|
var topThis = JSONEditor.getAbsoluteTop(trThis);
|
|||
|
var heightThis = trThis.offsetHeight;
|
|||
|
var mouseY = event.pageY || event.clientY + document.body.scrollTop;
|
|||
|
if (mouseY < topThis) {
|
|||
|
// move up
|
|||
|
var trPrev = trThis.previousSibling;
|
|||
|
var topPrev = JSONEditor.getAbsoluteTop(trPrev);
|
|||
|
var nodePrev = JSONEditor.getNodeFromTarget(trPrev);
|
|||
|
while (trPrev && mouseY < topPrev) {
|
|||
|
nodePrev = JSONEditor.getNodeFromTarget(trPrev);
|
|||
|
trPrev = trPrev.previousSibling;
|
|||
|
topPrev = JSONEditor.getAbsoluteTop(trPrev);
|
|||
|
}
|
|||
|
|
|||
|
if (nodePrev) {
|
|||
|
trPrev = nodePrev.dom.tr;
|
|||
|
topPrev = JSONEditor.getAbsoluteTop(trPrev);
|
|||
|
if (mouseY > topPrev + heightThis) {
|
|||
|
nodePrev = undefined;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (nodePrev && nodePrev.parent) {
|
|||
|
nodePrev.parent.moveBefore(this, nodePrev);
|
|||
|
}
|
|||
|
} else {
|
|||
|
// move down
|
|||
|
var trLast =
|
|||
|
this.expanded && this.append ? this.append.getDom() : this.dom.tr;
|
|||
|
var trFirst = trLast ? trLast.nextSibling : undefined;
|
|||
|
if (trFirst) {
|
|||
|
var topFirst = JSONEditor.getAbsoluteTop(trFirst);
|
|||
|
|
|||
|
var nodeNext = undefined;
|
|||
|
var trNext = trFirst.nextSibling;
|
|||
|
var topNext = JSONEditor.getAbsoluteTop(trNext);
|
|||
|
var heightNext = trNext ? topNext - topFirst : 0;
|
|||
|
while (trNext && mouseY > topThis + heightNext) {
|
|||
|
nodeNext = JSONEditor.getNodeFromTarget(trNext);
|
|||
|
trNext = trNext.nextSibling;
|
|||
|
topNext = JSONEditor.getAbsoluteTop(trNext);
|
|||
|
heightNext = trNext ? topNext - topFirst : 0;
|
|||
|
}
|
|||
|
|
|||
|
if (nodeNext && nodeNext.parent) {
|
|||
|
nodeNext.parent.moveBefore(this, nodeNext);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
JSONEditor.Events.preventDefault(event);
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Drag event, fired on mouseup after having dragged a node
|
|||
|
* @param {Event} event
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._onDragEnd = function(event) {
|
|||
|
event = event || window.event;
|
|||
|
|
|||
|
var params = {
|
|||
|
node: this,
|
|||
|
startParent: this.drag.startParent,
|
|||
|
startIndex: this.drag.startIndex,
|
|||
|
endParent: this.parent,
|
|||
|
endIndex: this.parent.childs.indexOf(this)
|
|||
|
};
|
|||
|
if (
|
|||
|
params.startParent != params.endParent ||
|
|||
|
params.startIndex != params.endIndex
|
|||
|
) {
|
|||
|
// only register this action if the node is actually moved to another place
|
|||
|
this.editor.onAction("moveNode", params);
|
|||
|
}
|
|||
|
|
|||
|
document.body.style.cursor = this.drag.oldCursor;
|
|||
|
delete JSONEditor.freezeHighlight;
|
|||
|
delete this.drag;
|
|||
|
this.setHighlight(false);
|
|||
|
|
|||
|
if (this.mousemove) {
|
|||
|
JSONEditor.Events.removeEventListener(
|
|||
|
document,
|
|||
|
"mousemove",
|
|||
|
this.mousemove
|
|||
|
);
|
|||
|
delete this.mousemove;
|
|||
|
}
|
|||
|
if (this.mouseup) {
|
|||
|
JSONEditor.Events.removeEventListener(document, "mouseup", this.mouseup);
|
|||
|
delete this.mouseup;
|
|||
|
}
|
|||
|
|
|||
|
JSONEditor.Events.preventDefault(event);
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Create a drag area, displayed at the left side of the node
|
|||
|
* @return {Element | undefined} domDrag
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._createDomDragArea = function() {
|
|||
|
if (!this.parent) {
|
|||
|
return undefined;
|
|||
|
}
|
|||
|
|
|||
|
var domDrag = document.createElement("button");
|
|||
|
domDrag.className = "jsoneditor-dragarea";
|
|||
|
domDrag.title = "Move field (drag and drop)";
|
|||
|
|
|||
|
return domDrag;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Create an editable field
|
|||
|
* @return {Element} domField
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._createDomField = function() {
|
|||
|
return document.createElement("div");
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Set highlighting for this node and all its childs.
|
|||
|
* Only applied to the currently visible (expanded childs)
|
|||
|
* @param {boolean} highlight
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.setHighlight = function(highlight) {
|
|||
|
if (JSONEditor.freezeHighlight) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (this.dom.tr) {
|
|||
|
this.dom.tr.className =
|
|||
|
"jsoneditor-tr" + (highlight ? " jsoneditor-tr-highlight" : "");
|
|||
|
|
|||
|
if (this.append) {
|
|||
|
this.append.setHighlight(highlight);
|
|||
|
}
|
|||
|
|
|||
|
if (this.childs) {
|
|||
|
this.childs.forEach(function(child) {
|
|||
|
child.setHighlight(highlight);
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Update the value of the node. Only primitive types are allowed, no Object
|
|||
|
* or Array is allowed.
|
|||
|
* @param {String | Number | Boolean | null} value
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.updateValue = function(value) {
|
|||
|
this.value = value;
|
|||
|
this.updateDom();
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Update the field of the node.
|
|||
|
* @param {String} field
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.updateField = function(field) {
|
|||
|
this.field = field;
|
|||
|
this.updateDom();
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Update the HTML DOM, optionally recursing through the childs
|
|||
|
* @param {Object} [options] Available parameters:
|
|||
|
* {boolean} [recurse] If true, the
|
|||
|
* DOM of the childs will be updated recursively.
|
|||
|
* False by default.
|
|||
|
* {boolean} [updateIndexes] If true, the childs
|
|||
|
* indexes of the node will be updated too. False by
|
|||
|
* default.
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.updateDom = function(options) {
|
|||
|
// update level indentation
|
|||
|
var domTree = this.dom.tree;
|
|||
|
if (domTree) {
|
|||
|
domTree.style.marginLeft = this.getLevel() * 24 + "px";
|
|||
|
}
|
|||
|
|
|||
|
// update field
|
|||
|
var domField = this.dom.field;
|
|||
|
if (domField) {
|
|||
|
if (this.fieldEditable == true) {
|
|||
|
// parent is an object
|
|||
|
domField.contentEditable = this.editor.editable;
|
|||
|
domField.spellcheck = false;
|
|||
|
domField.className = "jsoneditor-field";
|
|||
|
} else {
|
|||
|
// parent is an array this is the root node
|
|||
|
domField.className = "jsoneditor-readonly";
|
|||
|
}
|
|||
|
|
|||
|
var field;
|
|||
|
if (this.index != undefined) {
|
|||
|
field = this.index;
|
|||
|
} else if (this.field != undefined) {
|
|||
|
field = this.field;
|
|||
|
} else if (this.type == "array" || this.type == "object") {
|
|||
|
field = this.type;
|
|||
|
} else {
|
|||
|
field = "field";
|
|||
|
}
|
|||
|
domField.innerHTML = this._escapeHTML(field);
|
|||
|
}
|
|||
|
|
|||
|
// update value
|
|||
|
var domValue = this.dom.value;
|
|||
|
if (domValue) {
|
|||
|
var count = this.childs ? this.childs.length : 0;
|
|||
|
if (this.type == "array") {
|
|||
|
domValue.innerHTML = "[" + count + "]";
|
|||
|
domValue.title = this.type + " containing " + count + " items";
|
|||
|
} else if (this.type == "object") {
|
|||
|
domValue.innerHTML = "{" + count + "}";
|
|||
|
domValue.title = this.type + " containing " + count + " items";
|
|||
|
} else {
|
|||
|
domValue.innerHTML = this._escapeHTML(this.value);
|
|||
|
delete domValue.title;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// update field and value
|
|||
|
this._updateDomField();
|
|||
|
this._updateDomValue();
|
|||
|
|
|||
|
// update childs indexes
|
|||
|
if (options && options.updateIndexes == true) {
|
|||
|
// updateIndexes is true or undefined
|
|||
|
this._updateDomIndexes();
|
|||
|
}
|
|||
|
|
|||
|
if (options && options.recurse == true) {
|
|||
|
// recurse is true or undefined. update childs recursively
|
|||
|
if (this.childs) {
|
|||
|
this.childs.forEach(function(child) {
|
|||
|
child.updateDom(options);
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
// update row with append button
|
|||
|
if (this.append) {
|
|||
|
this.append.updateDom();
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Update the DOM of the childs of a node: update indexes and undefined field
|
|||
|
* names.
|
|||
|
* Only applicable when structure is an array or object
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._updateDomIndexes = function() {
|
|||
|
var domValue = this.dom.value;
|
|||
|
var childs = this.childs;
|
|||
|
if (domValue && childs) {
|
|||
|
if (this.type == "array") {
|
|||
|
childs.forEach(function(child, index) {
|
|||
|
child.index = index;
|
|||
|
var childField = child.dom.field;
|
|||
|
if (childField) {
|
|||
|
childField.innerHTML = index;
|
|||
|
}
|
|||
|
});
|
|||
|
} else if (this.type == "object") {
|
|||
|
childs.forEach(function(child) {
|
|||
|
if (child.index != undefined) {
|
|||
|
delete child.index;
|
|||
|
|
|||
|
if (child.field == undefined) {
|
|||
|
child.field = "field";
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Create an editable value
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._createDomValue = function() {
|
|||
|
var domValue;
|
|||
|
|
|||
|
if (this.type == "array") {
|
|||
|
domValue = document.createElement("div");
|
|||
|
domValue.className = "jsoneditor-readonly";
|
|||
|
domValue.innerHTML = "[...]";
|
|||
|
} else if (this.type == "object") {
|
|||
|
domValue = document.createElement("div");
|
|||
|
domValue.className = "jsoneditor-readonly";
|
|||
|
domValue.innerHTML = "{...}";
|
|||
|
} else if (this.type == "string") {
|
|||
|
domValue = document.createElement("div");
|
|||
|
domValue.contentEditable = this.editor.editable;
|
|||
|
domValue.spellcheck = false;
|
|||
|
domValue.className = "jsoneditor-value";
|
|||
|
domValue.innerHTML = this._escapeHTML(this.value);
|
|||
|
} else {
|
|||
|
domValue = document.createElement("div");
|
|||
|
domValue.contentEditable = this.editor.editable;
|
|||
|
domValue.spellcheck = false;
|
|||
|
domValue.className = "jsoneditor-value";
|
|||
|
domValue.innerHTML = this._escapeHTML(this.value);
|
|||
|
}
|
|||
|
|
|||
|
// TODO: in FF spel/check of editable divs is done via the body. quite ugly
|
|||
|
// document.body.spellcheck = false;
|
|||
|
|
|||
|
return domValue;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Create an expand/collapse button
|
|||
|
* @return {Element} expand
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._createDomExpandButton = function() {
|
|||
|
// create expand button
|
|||
|
var expand = document.createElement("button");
|
|||
|
var expandable = this.type == "array" || this.type == "object";
|
|||
|
if (expandable) {
|
|||
|
expand.className = this.expanded
|
|||
|
? "jsoneditor-expanded"
|
|||
|
: "jsoneditor-collapsed";
|
|||
|
expand.title =
|
|||
|
"Click to expand/collapse this field. \n" +
|
|||
|
"Ctrl+Click to expand/collapse including all childs.";
|
|||
|
} else {
|
|||
|
expand.className = "jsoneditor-invisible";
|
|||
|
expand.title = "";
|
|||
|
}
|
|||
|
|
|||
|
return expand;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Create a DOM tree element, containing the expand/collapse button
|
|||
|
* @param {Element} domExpand
|
|||
|
* @param {Element} domField
|
|||
|
* @param {Element} domValue
|
|||
|
* @return {Element} domTree
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._createDomTree = function(
|
|||
|
domExpand,
|
|||
|
domField,
|
|||
|
domValue
|
|||
|
) {
|
|||
|
var dom = this.dom;
|
|||
|
var domTree = document.createElement("table");
|
|||
|
var tbody = document.createElement("tbody");
|
|||
|
domTree.style.borderCollapse = "collapse"; // TODO: put in css
|
|||
|
domTree.appendChild(tbody);
|
|||
|
var tr = document.createElement("tr");
|
|||
|
tbody.appendChild(tr);
|
|||
|
|
|||
|
// create expand button
|
|||
|
var tdExpand = document.createElement("td");
|
|||
|
tdExpand.className = "jsoneditor-td-tree";
|
|||
|
tr.appendChild(tdExpand);
|
|||
|
tdExpand.appendChild(domExpand);
|
|||
|
dom.tdExpand = tdExpand;
|
|||
|
|
|||
|
// add the field
|
|||
|
var tdField = document.createElement("td");
|
|||
|
tdField.className = "jsoneditor-td-tree";
|
|||
|
tr.appendChild(tdField);
|
|||
|
tdField.appendChild(domField);
|
|||
|
dom.tdField = tdField;
|
|||
|
|
|||
|
// add a separator
|
|||
|
var tdSeparator = document.createElement("td");
|
|||
|
tdSeparator.className = "jsoneditor-td-tree";
|
|||
|
tr.appendChild(tdSeparator);
|
|||
|
if (this.type != "object" && this.type != "array") {
|
|||
|
tdSeparator.appendChild(document.createTextNode(":"));
|
|||
|
tdSeparator.className = "jsoneditor-separator";
|
|||
|
}
|
|||
|
dom.tdSeparator = tdSeparator;
|
|||
|
|
|||
|
// add the value
|
|||
|
var tdValue = document.createElement("td");
|
|||
|
tdValue.className = "jsoneditor-td-tree";
|
|||
|
tr.appendChild(tdValue);
|
|||
|
tdValue.appendChild(domValue);
|
|||
|
dom.tdValue = tdValue;
|
|||
|
|
|||
|
return domTree;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Handle an event. The event is catched centrally by the editor
|
|||
|
* @param {Event} event
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.onEvent = function(event) {
|
|||
|
var type = event.type;
|
|||
|
var target = event.target || event.srcElement;
|
|||
|
var dom = this.dom;
|
|||
|
var node = this;
|
|||
|
var expandable = this.type == "array" || this.type == "object";
|
|||
|
|
|||
|
// value events
|
|||
|
var domValue = dom.value;
|
|||
|
if (target == domValue) {
|
|||
|
switch (type) {
|
|||
|
case "focus":
|
|||
|
JSONEditor.focusNode = this;
|
|||
|
break;
|
|||
|
|
|||
|
case "blur":
|
|||
|
case "change":
|
|||
|
this._getDomValue(true);
|
|||
|
this._updateDomValue();
|
|||
|
if (this.value) {
|
|||
|
domValue.innerHTML = this._escapeHTML(this.value);
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case "keyup":
|
|||
|
this._getDomValue(true);
|
|||
|
this._updateDomValue();
|
|||
|
break;
|
|||
|
|
|||
|
case "cut":
|
|||
|
case "paste":
|
|||
|
setTimeout(function() {
|
|||
|
node._getDomValue(true);
|
|||
|
node._updateDomValue();
|
|||
|
}, 1);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// field events
|
|||
|
var domField = dom.field;
|
|||
|
if (target == domField) {
|
|||
|
switch (type) {
|
|||
|
case "focus":
|
|||
|
JSONEditor.focusNode = this;
|
|||
|
break;
|
|||
|
|
|||
|
case "change":
|
|||
|
case "blur":
|
|||
|
this._getDomField(true);
|
|||
|
this._updateDomField();
|
|||
|
if (this.field) {
|
|||
|
domField.innerHTML = this._escapeHTML(this.field);
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case "keyup":
|
|||
|
this._getDomField(true);
|
|||
|
this._updateDomField();
|
|||
|
break;
|
|||
|
|
|||
|
case "cut":
|
|||
|
case "paste":
|
|||
|
setTimeout(function() {
|
|||
|
node._getDomField(true);
|
|||
|
node._updateDomField();
|
|||
|
}, 1);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// drag events
|
|||
|
var domDrag = dom.drag;
|
|||
|
if (target == domDrag) {
|
|||
|
switch (type) {
|
|||
|
case "mousedown":
|
|||
|
this._onDragStart(event);
|
|||
|
break;
|
|||
|
case "mouseover":
|
|||
|
this.setHighlight(true);
|
|||
|
break;
|
|||
|
case "mouseout":
|
|||
|
this.setHighlight(false);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// expand events
|
|||
|
var domExpand = dom.expand;
|
|||
|
if (target == domExpand) {
|
|||
|
if (type == "click") {
|
|||
|
if (expandable) {
|
|||
|
this._onExpand(event);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// duplicate button
|
|||
|
var domDuplicate = dom.duplicate;
|
|||
|
if (target == domDuplicate) {
|
|||
|
switch (type) {
|
|||
|
case "click":
|
|||
|
var clone = this.parent._duplicate(this);
|
|||
|
|
|||
|
this.editor.onAction("duplicateNode", {
|
|||
|
node: this,
|
|||
|
clone: clone,
|
|||
|
parent: this.parent
|
|||
|
});
|
|||
|
break;
|
|||
|
case "mouseover":
|
|||
|
this.setHighlight(true);
|
|||
|
break;
|
|||
|
case "mouseout":
|
|||
|
this.setHighlight(false);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// remove button
|
|||
|
var domRemove = dom.remove;
|
|||
|
if (target == domRemove) {
|
|||
|
switch (type) {
|
|||
|
case "click":
|
|||
|
this._onRemove();
|
|||
|
break;
|
|||
|
case "mouseover":
|
|||
|
this.setHighlight(true);
|
|||
|
break;
|
|||
|
case "mouseout":
|
|||
|
this.setHighlight(false);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// type button
|
|||
|
var domType = dom.type;
|
|||
|
if (target == domType) {
|
|||
|
switch (type) {
|
|||
|
case "click":
|
|||
|
this._onChangeType(event);
|
|||
|
break;
|
|||
|
case "mouseover":
|
|||
|
this.setHighlight(true);
|
|||
|
break;
|
|||
|
case "mouseout":
|
|||
|
this.setHighlight(false);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// focus
|
|||
|
// when clicked in whitespace left or right from the field or value, set focus
|
|||
|
var domTree = dom.tree;
|
|||
|
if (target == domTree.parentNode) {
|
|||
|
switch (type) {
|
|||
|
case "click":
|
|||
|
var left =
|
|||
|
event.offsetX != undefined
|
|||
|
? event.offsetX < (this.getLevel() + 1) * 24
|
|||
|
: event.clientX < JSONEditor.getAbsoluteLeft(dom.tdSeparator); // for FF
|
|||
|
if (left || expandable) {
|
|||
|
// node is expandable when it is an object or array
|
|||
|
if (domField) {
|
|||
|
JSONEditor.setEndOfContentEditable(domField);
|
|||
|
domField.focus();
|
|||
|
}
|
|||
|
} else {
|
|||
|
if (domValue) {
|
|||
|
JSONEditor.setEndOfContentEditable(domValue);
|
|||
|
domValue.focus();
|
|||
|
}
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (
|
|||
|
(target == dom.tdExpand && !expandable) ||
|
|||
|
target == dom.tdField ||
|
|||
|
target == dom.tdSeparator
|
|||
|
) {
|
|||
|
switch (type) {
|
|||
|
case "click":
|
|||
|
if (domField) {
|
|||
|
JSONEditor.setEndOfContentEditable(domField);
|
|||
|
domField.focus();
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Handle the expand event, when clicked on the expand button
|
|||
|
* @param {Event} event
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._onExpand = function(event) {
|
|||
|
event = event || window.event;
|
|||
|
var recurse = event.ctrlKey; // with ctrl-key, expand/collapse all
|
|||
|
|
|||
|
if (recurse) {
|
|||
|
// Take the table offline
|
|||
|
var table = this.dom.tr.parentNode; // TODO: not nice to access the main table like this
|
|||
|
var frame = table.parentNode;
|
|||
|
var scrollTop = frame.scrollTop;
|
|||
|
frame.removeChild(table);
|
|||
|
}
|
|||
|
|
|||
|
if (this.expanded) {
|
|||
|
this.collapse(recurse);
|
|||
|
} else {
|
|||
|
this.expand(recurse);
|
|||
|
}
|
|||
|
|
|||
|
if (recurse) {
|
|||
|
// Put the table online again
|
|||
|
frame.appendChild(table);
|
|||
|
frame.scrollTop = scrollTop;
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
JSONEditor.Node.types = [
|
|||
|
{
|
|||
|
value: "array",
|
|||
|
className: "jsoneditor-option-array",
|
|||
|
title: '"array" 类型: 包含了有序值集合的数组.'
|
|||
|
},
|
|||
|
{
|
|||
|
value: "auto",
|
|||
|
className: "jsoneditor-option-auto",
|
|||
|
title:
|
|||
|
'"auto" 类型: 节点类型将自动从值中获取, 可以是: string, number, boolean, 或 null.'
|
|||
|
},
|
|||
|
{
|
|||
|
value: "object",
|
|||
|
className: "jsoneditor-option-object",
|
|||
|
title: '"object" 类型: 对象包含了一些无序的键/值对.'
|
|||
|
},
|
|||
|
{
|
|||
|
value: "string",
|
|||
|
className: "jsoneditor-option-string",
|
|||
|
title: '"string" 类型: 节点类型不从值中自动获取, 但永远返回string.'
|
|||
|
}
|
|||
|
];
|
|||
|
|
|||
|
/**
|
|||
|
* Create a DOM select box containing the node type
|
|||
|
* @return {Element} domType
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._createDomTypeButton = function() {
|
|||
|
var node = this;
|
|||
|
var domType = document.createElement("button");
|
|||
|
domType.className = "jsoneditor-type-" + node.type;
|
|||
|
domType.title = "改变节点类型";
|
|||
|
|
|||
|
return domType;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Remove this node
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._onRemove = function() {
|
|||
|
this.setHighlight(false);
|
|||
|
var index = this.parent.childs.indexOf(this);
|
|||
|
|
|||
|
this.parent._remove(this);
|
|||
|
|
|||
|
this.editor.onAction("removeNode", {
|
|||
|
node: this,
|
|||
|
parent: this.parent,
|
|||
|
index: index
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Handle a click on the Type-button
|
|||
|
* @param {Event} event
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._onChangeType = function(event) {
|
|||
|
JSONEditor.Events.stopPropagation(event);
|
|||
|
|
|||
|
var domType = this.dom.type;
|
|||
|
|
|||
|
var node = this;
|
|||
|
var x = JSONEditor.getAbsoluteLeft(domType);
|
|||
|
var y = JSONEditor.getAbsoluteTop(domType) + domType.clientHeight;
|
|||
|
var callback = function(newType) {
|
|||
|
var oldType = node.type;
|
|||
|
node.changeType(newType);
|
|||
|
node.editor.onAction("changeType", {
|
|||
|
node: node,
|
|||
|
oldType: oldType,
|
|||
|
newType: newType
|
|||
|
});
|
|||
|
domType.className = "jsoneditor-type-" + node.type;
|
|||
|
};
|
|||
|
JSONEditor.showDropDownList({
|
|||
|
x: x,
|
|||
|
y: y,
|
|||
|
node: node,
|
|||
|
value: node.type,
|
|||
|
values: JSONEditor.Node.types,
|
|||
|
className: "jsoneditor-select",
|
|||
|
optionSelectedClassName: "jsoneditor-option-selected",
|
|||
|
optionClassName: "jsoneditor-option",
|
|||
|
callback: callback
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Show a dropdown list
|
|||
|
* @param {Object} params Available parameters:
|
|||
|
* {Number} x The absolute horizontal position
|
|||
|
* {Number} y The absolute vertical position
|
|||
|
* {JSONEditor.Node} node node used for highlighting
|
|||
|
* {String} value current selected value
|
|||
|
* {Object[]} values the available values. Each object
|
|||
|
* contains a value, title, and
|
|||
|
* className
|
|||
|
* {String} optionSelectedClassName
|
|||
|
* {String} optionClassName
|
|||
|
* {function} callback Callback method, called when
|
|||
|
* the selected value changed.
|
|||
|
*/
|
|||
|
JSONEditor.showDropDownList = function(params) {
|
|||
|
var select = document.createElement("div");
|
|||
|
select.className = params.className || "";
|
|||
|
select.style.position = "absolute";
|
|||
|
select.style.left = (params.x || 0) + "px";
|
|||
|
select.style.top = (params.y || 0) + "px";
|
|||
|
|
|||
|
params.values.forEach(function(v) {
|
|||
|
var text = v.value || String(v);
|
|||
|
var className = "jsoneditor-option";
|
|||
|
var selected = text == params.value;
|
|||
|
if (selected) {
|
|||
|
className += " " + params.optionSelectedClassName;
|
|||
|
}
|
|||
|
var option = document.createElement("div");
|
|||
|
option.className = className;
|
|||
|
if (v.title) {
|
|||
|
option.title = v.title;
|
|||
|
}
|
|||
|
|
|||
|
var divIcon = document.createElement("div");
|
|||
|
divIcon.className = v.className || "";
|
|||
|
option.appendChild(divIcon);
|
|||
|
|
|||
|
var divText = document.createElement("div");
|
|||
|
divText.className = "jsoneditor-option-text";
|
|||
|
divText.innerHTML = "<div>" + text + "</div>";
|
|||
|
option.appendChild(divText);
|
|||
|
|
|||
|
option.onmousedown = (function(value) {
|
|||
|
return function() {
|
|||
|
params.callback(value);
|
|||
|
};
|
|||
|
})(v.value);
|
|||
|
select.appendChild(option);
|
|||
|
});
|
|||
|
|
|||
|
document.body.appendChild(select);
|
|||
|
params.node.setHighlight(true);
|
|||
|
JSONEditor.freezeHighlight = true;
|
|||
|
|
|||
|
// TODO: change to onclick? -> but be sure to remove existing dropdown first
|
|||
|
var onmousedown = JSONEditor.Events.addEventListener(
|
|||
|
document,
|
|||
|
"mousedown",
|
|||
|
function() {
|
|||
|
JSONEditor.freezeHighlight = false;
|
|||
|
params.node.setHighlight(false);
|
|||
|
if (select && select.parentNode) {
|
|||
|
select.parentNode.removeChild(select);
|
|||
|
}
|
|||
|
JSONEditor.Events.removeEventListener(document, "mousedown", onmousedown);
|
|||
|
}
|
|||
|
);
|
|||
|
var onmousewheel = JSONEditor.Events.addEventListener(
|
|||
|
document,
|
|||
|
"mousewheel",
|
|||
|
function() {
|
|||
|
JSONEditor.freezeHighlight = false;
|
|||
|
params.node.setHighlight(false);
|
|||
|
if (select && select.parentNode) {
|
|||
|
select.parentNode.removeChild(select);
|
|||
|
}
|
|||
|
JSONEditor.Events.removeEventListener(
|
|||
|
document,
|
|||
|
"mousewheel",
|
|||
|
onmousewheel
|
|||
|
);
|
|||
|
}
|
|||
|
);
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Create a table row with an append button.
|
|||
|
* @return {Element | undefined} buttonAppend or undefined when inapplicable
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype.getAppend = function() {
|
|||
|
if (!this.append) {
|
|||
|
this.append = new JSONEditor.AppendNode(this.editor);
|
|||
|
this.append.setParent(this);
|
|||
|
}
|
|||
|
return this.append.getDom();
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Create a remove button. Returns undefined when the structure cannot
|
|||
|
* be removed
|
|||
|
* @return {Element | undefined} removeButton, or undefined when inapplicable
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._createDomRemoveButton = function() {
|
|||
|
if (
|
|||
|
this.parent &&
|
|||
|
(this.parent.type == "array" || this.parent.type == "object")
|
|||
|
) {
|
|||
|
var buttonRemove = document.createElement("button");
|
|||
|
buttonRemove.className = "jsoneditor-remove";
|
|||
|
buttonRemove.title = "删除节点 (包括所有子节点)";
|
|||
|
|
|||
|
return buttonRemove;
|
|||
|
} else {
|
|||
|
return undefined;
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Create a duplicate button.
|
|||
|
* If the Node is the root node, no duplicate button is available and undefined
|
|||
|
* will be returned
|
|||
|
* @return {Element | undefined} buttonDuplicate
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._createDomDuplicateButton = function() {
|
|||
|
if (
|
|||
|
this.parent &&
|
|||
|
(this.parent.type == "array" || this.parent.type == "object")
|
|||
|
) {
|
|||
|
var buttonDupliate = document.createElement("button");
|
|||
|
buttonDupliate.className = "jsoneditor-duplicate";
|
|||
|
buttonDupliate.title = "复制节点 (包括所有子节点)";
|
|||
|
|
|||
|
return buttonDupliate;
|
|||
|
} else {
|
|||
|
return undefined;
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* get the type of a value
|
|||
|
* @param {*} value
|
|||
|
* @return {String} type Can be 'object', 'array', 'string', 'auto'
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._getType = function(value) {
|
|||
|
if (value instanceof Array) {
|
|||
|
return "array";
|
|||
|
}
|
|||
|
if (value instanceof Object) {
|
|||
|
return "object";
|
|||
|
}
|
|||
|
if (typeof value == "string" && typeof this._stringCast(value) != "string") {
|
|||
|
return "string";
|
|||
|
}
|
|||
|
|
|||
|
return "auto";
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* cast contents of a string to the correct type. This can be a string,
|
|||
|
* a number, a boolean, etc
|
|||
|
* @param {String} str
|
|||
|
* @return {*} castedStr
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._stringCast = function(str) {
|
|||
|
var lower = str.toLowerCase(),
|
|||
|
num = Number(str), // will nicely fail with '123ab'
|
|||
|
numFloat = parseFloat(str); // will nicely fail with ' '
|
|||
|
|
|||
|
if (str == "") {
|
|||
|
return "";
|
|||
|
} else if (lower == "null") {
|
|||
|
return null;
|
|||
|
} else if (lower == "true") {
|
|||
|
return true;
|
|||
|
} else if (lower == "false") {
|
|||
|
return false;
|
|||
|
} else if (!isNaN(num) && !isNaN(numFloat)) {
|
|||
|
return num;
|
|||
|
} else {
|
|||
|
return str;
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* escape a text, such that it can be displayed safely in an HTML element
|
|||
|
* @param {String} text
|
|||
|
* @return {String} escapedText
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._escapeHTML = function(text) {
|
|||
|
var htmlEscaped = String(text)
|
|||
|
.replace(/</g, "<")
|
|||
|
.replace(/>/g, ">")
|
|||
|
.replace(/ /g, " ") // replace double space with an nbsp and space
|
|||
|
.replace(/^ /, " ") // space at start
|
|||
|
.replace(/ $/, " "); // space at end
|
|||
|
|
|||
|
var json = JSON.stringify(htmlEscaped);
|
|||
|
return json.substring(1, json.length - 1);
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* unescape a string.
|
|||
|
* @param {String} escapedText
|
|||
|
* @return {String} text
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._unescapeHTML = function(escapedText) {
|
|||
|
var json = '"' + this._escapeJSON(escapedText) + '"';
|
|||
|
var htmlEscaped = JSONEditor.parse(json);
|
|||
|
return htmlEscaped
|
|||
|
.replace(/</g, "<")
|
|||
|
.replace(/>/g, ">")
|
|||
|
.replace(/ /g, " ");
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* escape a text to make it a valid JSON string. The method will:
|
|||
|
* - replace unescaped double quotes with '\"'
|
|||
|
* - replace unescaped backslash with '\\'
|
|||
|
* - replace returns with '\n'
|
|||
|
* @param {String} text
|
|||
|
* @return {String} escapedText
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.Node.prototype._escapeJSON = function(text) {
|
|||
|
// TODO: replace with some smart regex (only when a new solution is faster!)
|
|||
|
var escaped = "";
|
|||
|
var i = 0,
|
|||
|
iMax = text.length;
|
|||
|
while (i < iMax) {
|
|||
|
var c = text.charAt(i);
|
|||
|
if (c == "\n") {
|
|||
|
escaped += "\\n";
|
|||
|
} else if (c == "\\") {
|
|||
|
escaped += c;
|
|||
|
i++;
|
|||
|
|
|||
|
c = text.charAt(i);
|
|||
|
if ('"\\/bfnrtu'.indexOf(c) == -1) {
|
|||
|
escaped += "\\"; // no valid escape character
|
|||
|
}
|
|||
|
escaped += c;
|
|||
|
} else if (c == '"') {
|
|||
|
escaped += '\\"';
|
|||
|
} else {
|
|||
|
escaped += c;
|
|||
|
}
|
|||
|
i++;
|
|||
|
}
|
|||
|
|
|||
|
return escaped;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* @constructor JSONEditor.AppendNode
|
|||
|
* @extends JSONEditor.Node
|
|||
|
* @param {JSONEditor} editor
|
|||
|
* Create a new AppendNode. This is a special node which is created at the
|
|||
|
* end of the list with childs for an object or array
|
|||
|
*/
|
|||
|
JSONEditor.AppendNode = function(editor) {
|
|||
|
this.editor = editor;
|
|||
|
this.dom = {};
|
|||
|
};
|
|||
|
|
|||
|
JSONEditor.AppendNode.prototype = new JSONEditor.Node();
|
|||
|
|
|||
|
/**
|
|||
|
* Return a table row with an append button.
|
|||
|
* @return {Element} dom TR element
|
|||
|
*/
|
|||
|
JSONEditor.AppendNode.prototype.getDom = function() {
|
|||
|
if (this.dom.tr) {
|
|||
|
return this.dom.tr;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Create a TD element, and give it the provided class name (if any)
|
|||
|
* @param {String} [className]
|
|||
|
* @return {Element} td
|
|||
|
*/
|
|||
|
function newTd(className) {
|
|||
|
var td = document.createElement("td");
|
|||
|
td.className = className || "";
|
|||
|
return td;
|
|||
|
}
|
|||
|
|
|||
|
// a row for the append button
|
|||
|
var trAppend = document.createElement("tr");
|
|||
|
trAppend.node = this;
|
|||
|
|
|||
|
// TODO: do not create an appendNode at all when in viewer mode
|
|||
|
if (!this.editor.editable) {
|
|||
|
return trAppend;
|
|||
|
}
|
|||
|
|
|||
|
// a cell for the drag area column
|
|||
|
trAppend.appendChild(newTd("jsoneditor-td"));
|
|||
|
|
|||
|
// a cell for the append button
|
|||
|
var tdAppend = document.createElement("td");
|
|||
|
trAppend.appendChild(tdAppend);
|
|||
|
tdAppend.className = "jsoneditor-td";
|
|||
|
|
|||
|
// create the append button
|
|||
|
var buttonAppend = document.createElement("button");
|
|||
|
buttonAppend.className = "jsoneditor-append";
|
|||
|
buttonAppend.title = "添加";
|
|||
|
this.dom.append = buttonAppend;
|
|||
|
tdAppend.appendChild(buttonAppend);
|
|||
|
|
|||
|
trAppend.appendChild(newTd("jsoneditor-td jsoneditor-td-edit"));
|
|||
|
trAppend.appendChild(newTd("jsoneditor-td jsoneditor-td-edit"));
|
|||
|
trAppend.appendChild(newTd("jsoneditor-td jsoneditor-td-edit"));
|
|||
|
|
|||
|
this.dom.tr = trAppend;
|
|||
|
this.dom.td = tdAppend;
|
|||
|
|
|||
|
this.updateDom();
|
|||
|
|
|||
|
return trAppend;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Update the HTML dom of the Node
|
|||
|
*/
|
|||
|
JSONEditor.AppendNode.prototype.updateDom = function() {
|
|||
|
var tdAppend = this.dom.td;
|
|||
|
if (tdAppend) {
|
|||
|
tdAppend.style.paddingLeft = this.getLevel() * 24 + 26 + "px";
|
|||
|
// TODO: not so nice hard coded offset
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Handle an event. The event is catched centrally by the editor
|
|||
|
* @param {Event} event
|
|||
|
*/
|
|||
|
JSONEditor.AppendNode.prototype.onEvent = function(event) {
|
|||
|
var type = event.type;
|
|||
|
var target = event.target || event.srcElement;
|
|||
|
var dom = this.dom;
|
|||
|
|
|||
|
var domAppend = dom.append;
|
|||
|
if (target == domAppend) {
|
|||
|
switch (type) {
|
|||
|
case "click":
|
|||
|
this._onAppend();
|
|||
|
break;
|
|||
|
|
|||
|
case "mouseover":
|
|||
|
this.parent.setHighlight(true);
|
|||
|
break;
|
|||
|
|
|||
|
case "mouseout":
|
|||
|
this.parent.setHighlight(false);
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Handle append event
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.AppendNode.prototype._onAppend = function() {
|
|||
|
var newNode = new JSONEditor.Node(this.editor, {
|
|||
|
field: "field",
|
|||
|
value: "value"
|
|||
|
});
|
|||
|
this.parent.appendChild(newNode);
|
|||
|
this.parent.setHighlight(false);
|
|||
|
newNode.focus();
|
|||
|
|
|||
|
this.editor.onAction("appendNode", {
|
|||
|
node: newNode,
|
|||
|
parent: this.parent
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Create main frame
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.prototype._createFrame = function() {
|
|||
|
// create the frame
|
|||
|
this.container.innerHTML = "";
|
|||
|
this.frame = document.createElement("div");
|
|||
|
this.frame.className = "jsoneditor-frame";
|
|||
|
this.container.appendChild(this.frame);
|
|||
|
|
|||
|
// create one global event listener to handle all events from all nodes
|
|||
|
var editor = this;
|
|||
|
// TODO: move this onEvent to JSONEditor.prototype.onEvent
|
|||
|
var onEvent = function(event) {
|
|||
|
event = event || window.event;
|
|||
|
var target = event.target || event.srcElement;
|
|||
|
|
|||
|
/* TODO: Enable quickkeys Ctrl+F and F3.
|
|||
|
// Requires knowing whether the JSONEditor has focus or not
|
|||
|
// (use a global event listener for that?)
|
|||
|
// Check for search quickkeys, Ctrl+F and F3
|
|||
|
if (editor.options.search) {
|
|||
|
if (event.type == 'keydown') {
|
|||
|
var keynum = event.which || event.keyCode;
|
|||
|
if (keynum == 70 && event.ctrlKey) { // Ctrl+F
|
|||
|
if (editor.searchBox) {
|
|||
|
editor.searchBox.dom.search.focus();
|
|||
|
editor.searchBox.dom.search.select();
|
|||
|
JSONEditor.Events.preventDefault(event);
|
|||
|
JSONEditor.Events.stopPropagation(event);
|
|||
|
}
|
|||
|
}
|
|||
|
else if (keynum == 114) { // F3
|
|||
|
if (!event.shiftKey) {
|
|||
|
// select next search result
|
|||
|
editor.searchBox.next();
|
|||
|
}
|
|||
|
else {
|
|||
|
// select previous search result
|
|||
|
editor.searchBox.previous();
|
|||
|
}
|
|||
|
editor.searchBox.focusActiveResult();
|
|||
|
|
|||
|
// set selection to the current
|
|||
|
JSONEditor.Events.preventDefault(event);
|
|||
|
JSONEditor.Events.stopPropagation(event);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
*/
|
|||
|
|
|||
|
var node = JSONEditor.getNodeFromTarget(target);
|
|||
|
if (node) {
|
|||
|
node.onEvent(event);
|
|||
|
}
|
|||
|
};
|
|||
|
this.frame.onclick = function(event) {
|
|||
|
onEvent(event);
|
|||
|
|
|||
|
// prevent default submit action when JSONEditor is located inside a form
|
|||
|
JSONEditor.Events.preventDefault(event);
|
|||
|
};
|
|||
|
this.frame.onchange = onEvent;
|
|||
|
this.frame.onkeydown = onEvent;
|
|||
|
this.frame.onkeyup = onEvent;
|
|||
|
this.frame.oncut = onEvent;
|
|||
|
this.frame.onpaste = onEvent;
|
|||
|
this.frame.onmousedown = onEvent;
|
|||
|
this.frame.onmouseup = onEvent;
|
|||
|
this.frame.onmouseover = onEvent;
|
|||
|
this.frame.onmouseout = onEvent;
|
|||
|
// Note: focus and blur events do not propagate, therefore they defined
|
|||
|
// using an eventListener with useCapture=true
|
|||
|
// see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
|
|||
|
JSONEditor.Events.addEventListener(this.frame, "focus", onEvent, true);
|
|||
|
JSONEditor.Events.addEventListener(this.frame, "blur", onEvent, true);
|
|||
|
this.frame.onfocusin = onEvent; // for IE
|
|||
|
this.frame.onfocusout = onEvent; // for IE
|
|||
|
|
|||
|
// create menu
|
|||
|
this.menu = document.createElement("div");
|
|||
|
this.menu.className = "jsoneditor-menu";
|
|||
|
this.frame.appendChild(this.menu);
|
|||
|
|
|||
|
// create expand all button
|
|||
|
var expandAll = document.createElement("button");
|
|||
|
expandAll.className = "jsoneditor-menu jsoneditor-expand-all";
|
|||
|
expandAll.title = "展开";
|
|||
|
expandAll.onclick = function() {
|
|||
|
editor.expandAll();
|
|||
|
};
|
|||
|
this.menu.appendChild(expandAll);
|
|||
|
|
|||
|
// create expand all button
|
|||
|
var collapseAll = document.createElement("button");
|
|||
|
collapseAll.title = "折叠";
|
|||
|
collapseAll.className = "jsoneditor-menu jsoneditor-collapse-all";
|
|||
|
collapseAll.onclick = function() {
|
|||
|
editor.collapseAll();
|
|||
|
};
|
|||
|
this.menu.appendChild(collapseAll);
|
|||
|
|
|||
|
// create expand/collapse buttons
|
|||
|
if (this.history) {
|
|||
|
// create separator
|
|||
|
var separator = document.createElement("span");
|
|||
|
separator.innerHTML = " ";
|
|||
|
this.menu.appendChild(separator);
|
|||
|
|
|||
|
// create undo button
|
|||
|
var undo = document.createElement("button");
|
|||
|
undo.className = "jsoneditor-menu jsoneditor-undo";
|
|||
|
undo.title = "撤销";
|
|||
|
undo.onclick = function() {
|
|||
|
// undo last action
|
|||
|
editor.history.undo();
|
|||
|
|
|||
|
// trigger change callback
|
|||
|
if (editor.options.change) {
|
|||
|
editor.options.change();
|
|||
|
}
|
|||
|
};
|
|||
|
this.menu.appendChild(undo);
|
|||
|
this.dom.undo = undo;
|
|||
|
|
|||
|
// create redo button
|
|||
|
var redo = document.createElement("button");
|
|||
|
redo.className = "jsoneditor-menu jsoneditor-redo";
|
|||
|
redo.title = "重做";
|
|||
|
redo.onclick = function() {
|
|||
|
// redo last action
|
|||
|
editor.history.redo();
|
|||
|
|
|||
|
// trigger change callback
|
|||
|
if (editor.options.change) {
|
|||
|
editor.options.change();
|
|||
|
}
|
|||
|
};
|
|||
|
this.menu.appendChild(redo);
|
|||
|
this.dom.redo = redo;
|
|||
|
|
|||
|
// register handler for onchange of history
|
|||
|
this.history.onChange = function() {
|
|||
|
undo.disabled = !editor.history.canUndo();
|
|||
|
redo.disabled = !editor.history.canRedo();
|
|||
|
};
|
|||
|
this.history.onChange();
|
|||
|
}
|
|||
|
|
|||
|
// create search box
|
|||
|
if (this.options.search) {
|
|||
|
this.searchBox = new JSONEditor.SearchBox(this, this.menu);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Create main table
|
|||
|
* @private
|
|||
|
*/
|
|||
|
JSONEditor.prototype._createTable = function() {
|
|||
|
var contentOuter = document.createElement("div");
|
|||
|
contentOuter.className = "jsoneditor-content-outer";
|
|||
|
this.contentOuter = contentOuter;
|
|||
|
|
|||
|
this.content = document.createElement("div");
|
|||
|
this.content.className = "jsoneditor-content";
|
|||
|
contentOuter.appendChild(this.content);
|
|||
|
|
|||
|
this.table = document.createElement("table");
|
|||
|
this.table.className = "jsoneditor-table";
|
|||
|
this.content.appendChild(this.table);
|
|||
|
|
|||
|
// IE8 does not handle overflow='auto' correctly.
|
|||
|
// Therefore, set overflow to 'scroll'
|
|||
|
var ieVersion = JSONEditor.getInternetExplorerVersion();
|
|||
|
if (ieVersion == 8) {
|
|||
|
this.content.style.overflow = "scroll";
|
|||
|
}
|
|||
|
|
|||
|
// create colgroup where the first two columns don't have a fixed
|
|||
|
// width, and the edit columns do have a fixed width
|
|||
|
var col;
|
|||
|
this.colgroupContent = document.createElement("colgroup");
|
|||
|
col = document.createElement("col");
|
|||
|
col.width = "24px";
|
|||
|
this.colgroupContent.appendChild(col);
|
|||
|
col = document.createElement("col");
|
|||
|
this.colgroupContent.appendChild(col);
|
|||
|
col = document.createElement("col");
|
|||
|
col.width = "24px";
|
|||
|
this.colgroupContent.appendChild(col);
|
|||
|
col = document.createElement("col");
|
|||
|
col.width = "24px";
|
|||
|
this.colgroupContent.appendChild(col);
|
|||
|
col = document.createElement("col");
|
|||
|
col.width = "24px";
|
|||
|
this.colgroupContent.appendChild(col);
|
|||
|
this.table.appendChild(this.colgroupContent);
|
|||
|
|
|||
|
this.tbody = document.createElement("tbody");
|
|||
|
this.table.appendChild(this.tbody);
|
|||
|
|
|||
|
this.frame.appendChild(contentOuter);
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Find the node from an event target
|
|||
|
* @param {Element} target
|
|||
|
* @return {JSONEditor.Node | undefined} node or undefined when not found
|
|||
|
*/
|
|||
|
JSONEditor.getNodeFromTarget = function(target) {
|
|||
|
while (target) {
|
|||
|
if (target.node) {
|
|||
|
return target.node;
|
|||
|
}
|
|||
|
target = target.parentNode;
|
|||
|
}
|
|||
|
|
|||
|
return undefined;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Create a JSONFormatter and attach it to given container
|
|||
|
* @constructor JSONFormatter
|
|||
|
* @param {Element} container
|
|||
|
* @param {Object} [options] Object with options. available options:
|
|||
|
* {Number} indentation Number of indentation
|
|||
|
* spaces. 4 by default.
|
|||
|
* {function} change Callback method
|
|||
|
* triggered on change
|
|||
|
* @param {JSON | String} [json] initial contents of the formatter
|
|||
|
*/
|
|||
|
JSONFormatter = function(container, options, json) {
|
|||
|
// check availability of JSON parser (not available in IE7 and older)
|
|||
|
if (!JSON) {
|
|||
|
throw new Error(
|
|||
|
"您当前使用的浏览器不支持 JSON. \n\n" +
|
|||
|
"请下载安装最新版本的浏览, 本站推荐Google Chrome.\n" +
|
|||
|
"(PS: 当前主流浏览器都支持JSON)."
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
this.container = container;
|
|||
|
this.indentation = 4; // number of spaces
|
|||
|
|
|||
|
this.width = container.clientWidth;
|
|||
|
this.height = container.clientHeight;
|
|||
|
|
|||
|
this.frame = document.createElement("div");
|
|||
|
this.frame.className = "jsoneditor-frame";
|
|||
|
this.frame.onclick = function(event) {
|
|||
|
// prevent default submit action when JSONFormatter is located inside a form
|
|||
|
JSONEditor.Events.preventDefault(event);
|
|||
|
};
|
|||
|
|
|||
|
// create menu
|
|||
|
this.menu = document.createElement("div");
|
|||
|
this.menu.className = "jsoneditor-menu";
|
|||
|
this.frame.appendChild(this.menu);
|
|||
|
|
|||
|
// create format button
|
|||
|
var buttonFormat = document.createElement("button");
|
|||
|
//buttonFormat.innerHTML = 'Format';
|
|||
|
buttonFormat.className = "jsoneditor-menu jsoneditor-format";
|
|||
|
buttonFormat.title = "格式化JSON数据";
|
|||
|
//buttonFormat.className = 'jsoneditor-button';
|
|||
|
this.menu.appendChild(buttonFormat);
|
|||
|
|
|||
|
// create compact button
|
|||
|
var buttonCompact = document.createElement("button");
|
|||
|
//buttonCompact.innerHTML = 'Compact';
|
|||
|
buttonCompact.className = "jsoneditor-menu jsoneditor-compact";
|
|||
|
buttonCompact.title = "压缩JSON数据, 清除所有空白字符";
|
|||
|
//buttonCompact.className = 'jsoneditor-button';
|
|||
|
this.menu.appendChild(buttonCompact);
|
|||
|
|
|||
|
this.content = document.createElement("div");
|
|||
|
this.content.className = "jsonformatter-content";
|
|||
|
this.frame.appendChild(this.content);
|
|||
|
|
|||
|
this.textarea = document.createElement("textarea");
|
|||
|
this.textarea.className = "jsonformatter-textarea";
|
|||
|
this.textarea.spellcheck = false;
|
|||
|
this.content.appendChild(this.textarea);
|
|||
|
|
|||
|
var textarea = this.textarea;
|
|||
|
|
|||
|
// read the options
|
|||
|
if (options) {
|
|||
|
if (options.change) {
|
|||
|
// register on change event
|
|||
|
if (this.textarea.oninput === null) {
|
|||
|
this.textarea.oninput = function() {
|
|||
|
options.change();
|
|||
|
};
|
|||
|
} else {
|
|||
|
// oninput is undefined. For IE8-
|
|||
|
this.textarea.onchange = function() {
|
|||
|
options.change();
|
|||
|
};
|
|||
|
}
|
|||
|
}
|
|||
|
if (options.indentation) {
|
|||
|
this.indentation = Number(options.indentation);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
var me = this;
|
|||
|
buttonFormat.onclick = function() {
|
|||
|
try {
|
|||
|
var json = JSONEditor.parse(textarea.value);
|
|||
|
textarea.value = JSON.stringify(json, null, me.indentation);
|
|||
|
} catch (err) {
|
|||
|
me.onError(err);
|
|||
|
}
|
|||
|
};
|
|||
|
buttonCompact.onclick = function() {
|
|||
|
try {
|
|||
|
var json = JSONEditor.parse(textarea.value);
|
|||
|
textarea.value = JSON.stringify(json);
|
|||
|
} catch (err) {
|
|||
|
me.onError(err);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
this.container.appendChild(this.frame);
|
|||
|
|
|||
|
// load initial json object or string
|
|||
|
if (typeof json == "string") {
|
|||
|
this.setText(json);
|
|||
|
} else {
|
|||
|
this.set(json);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* This method is executed on error.
|
|||
|
* It can be overwritten for each instance of the JSONFormatter
|
|||
|
* @param {String} err
|
|||
|
*/
|
|||
|
JSONFormatter.prototype.onError = function(err) {
|
|||
|
// action should be implemented for the instance
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Set json data in the formatter
|
|||
|
* @param {Object} json
|
|||
|
*/
|
|||
|
JSONFormatter.prototype.set = function(json) {
|
|||
|
this.textarea.value = JSON.stringify(json, null, this.indentation);
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Get json data from the formatter
|
|||
|
* @return {Object} json
|
|||
|
*/
|
|||
|
JSONFormatter.prototype.get = function() {
|
|||
|
return JSONEditor.parse(this.textarea.value);
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Get the text contents of the JSONFormatter
|
|||
|
* @return {String} text
|
|||
|
*/
|
|||
|
JSONFormatter.prototype.getText = function() {
|
|||
|
return this.textarea.value;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Set the text contents of the JSONFormatter
|
|||
|
* @param {String} text
|
|||
|
*/
|
|||
|
JSONFormatter.prototype.setText = function(text) {
|
|||
|
this.textarea.value = text;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* @constructor JSONEditor.SearchBox
|
|||
|
* Create a search box in given HTML container
|
|||
|
* @param {JSONEditor} editor The JSON Editor to attach to
|
|||
|
* @param {Element} container HTML container element of where to create the
|
|||
|
* search box
|
|||
|
*/
|
|||
|
JSONEditor.SearchBox = function(editor, container) {
|
|||
|
var searchBox = this;
|
|||
|
|
|||
|
this.editor = editor;
|
|||
|
this.timeout = undefined;
|
|||
|
this.delay = 200; // ms
|
|||
|
this.lastText = undefined;
|
|||
|
|
|||
|
this.dom = {};
|
|||
|
this.dom.container = container;
|
|||
|
|
|||
|
var table = document.createElement("table");
|
|||
|
this.dom.table = table;
|
|||
|
table.className = "jsoneditor-search";
|
|||
|
container.appendChild(table);
|
|||
|
var tbody = document.createElement("tbody");
|
|||
|
this.dom.tbody = tbody;
|
|||
|
table.appendChild(tbody);
|
|||
|
var tr = document.createElement("tr");
|
|||
|
tbody.appendChild(tr);
|
|||
|
|
|||
|
var td = document.createElement("td");
|
|||
|
td.className = "jsoneditor-search";
|
|||
|
tr.appendChild(td);
|
|||
|
var results = document.createElement("div");
|
|||
|
this.dom.results = results;
|
|||
|
results.className = "jsoneditor-search-results";
|
|||
|
td.appendChild(results);
|
|||
|
|
|||
|
td = document.createElement("td");
|
|||
|
td.className = "jsoneditor-search";
|
|||
|
tr.appendChild(td);
|
|||
|
var divInput = document.createElement("div");
|
|||
|
this.dom.input = divInput;
|
|||
|
divInput.className = "jsoneditor-search";
|
|||
|
divInput.title = "查找区块";
|
|||
|
td.appendChild(divInput);
|
|||
|
|
|||
|
// table to contain the text input and search button
|
|||
|
var tableInput = document.createElement("table");
|
|||
|
tableInput.className = "jsoneditor-search-input";
|
|||
|
divInput.appendChild(tableInput);
|
|||
|
var tbodySearch = document.createElement("tbody");
|
|||
|
tableInput.appendChild(tbodySearch);
|
|||
|
tr = document.createElement("tr");
|
|||
|
tbodySearch.appendChild(tr);
|
|||
|
|
|||
|
var refreshSearch = document.createElement("button");
|
|||
|
refreshSearch.className = "jsoneditor-search-refresh";
|
|||
|
td = document.createElement("td");
|
|||
|
td.appendChild(refreshSearch);
|
|||
|
tr.appendChild(td);
|
|||
|
|
|||
|
var search = document.createElement("input");
|
|||
|
this.dom.search = search;
|
|||
|
search.className = "jsoneditor-search";
|
|||
|
search.oninput = function(event) {
|
|||
|
searchBox.onDelayedSearch(event);
|
|||
|
};
|
|||
|
search.onchange = function(event) {
|
|||
|
// For IE 8
|
|||
|
searchBox.onSearch(event);
|
|||
|
};
|
|||
|
search.onkeydown = function(event) {
|
|||
|
searchBox.onKeyDown(event);
|
|||
|
};
|
|||
|
search.onkeyup = function(event) {
|
|||
|
searchBox.onKeyUp(event);
|
|||
|
};
|
|||
|
refreshSearch.onclick = function(event) {
|
|||
|
search.select();
|
|||
|
};
|
|||
|
|
|||
|
// TODO: ESC in FF restores the last input, is a FF bug, https://bugzilla.mozilla.org/show_bug.cgi?id=598819
|
|||
|
td = document.createElement("td");
|
|||
|
td.appendChild(search);
|
|||
|
tr.appendChild(td);
|
|||
|
|
|||
|
var searchNext = document.createElement("button");
|
|||
|
searchNext.title = "下一个 (Enter)";
|
|||
|
searchNext.className = "jsoneditor-search-next";
|
|||
|
searchNext.onclick = function() {
|
|||
|
searchBox.next();
|
|||
|
};
|
|||
|
td = document.createElement("td");
|
|||
|
td.appendChild(searchNext);
|
|||
|
tr.appendChild(td);
|
|||
|
|
|||
|
var searchPrevious = document.createElement("button");
|
|||
|
searchPrevious.title = "上一个 (Shift+Enter)";
|
|||
|
searchPrevious.className = "jsoneditor-search-previous";
|
|||
|
searchPrevious.onclick = function() {
|
|||
|
searchBox.previous();
|
|||
|
};
|
|||
|
td = document.createElement("td");
|
|||
|
td.appendChild(searchPrevious);
|
|||
|
tr.appendChild(td);
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Go to the next search result
|
|||
|
*/
|
|||
|
JSONEditor.SearchBox.prototype.next = function() {
|
|||
|
if (this.results != undefined) {
|
|||
|
var index = this.resultIndex != undefined ? this.resultIndex + 1 : 0;
|
|||
|
if (index > this.results.length - 1) {
|
|||
|
index = 0;
|
|||
|
}
|
|||
|
this.setActiveResult(index);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Go to the prevous search result
|
|||
|
*/
|
|||
|
JSONEditor.SearchBox.prototype.previous = function() {
|
|||
|
if (this.results != undefined) {
|
|||
|
var max = this.results.length - 1;
|
|||
|
var index = this.resultIndex != undefined ? this.resultIndex - 1 : max;
|
|||
|
if (index < 0) {
|
|||
|
index = max;
|
|||
|
}
|
|||
|
this.setActiveResult(index);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Set new value for the current active result
|
|||
|
* @param {Number} index
|
|||
|
*/
|
|||
|
JSONEditor.SearchBox.prototype.setActiveResult = function(index) {
|
|||
|
// de-activate current active result
|
|||
|
if (this.activeResult) {
|
|||
|
var prevNode = this.activeResult.node;
|
|||
|
var prevElem = this.activeResult.elem;
|
|||
|
if (prevElem == "field") {
|
|||
|
delete prevNode.searchFieldActive;
|
|||
|
} else {
|
|||
|
delete prevNode.searchValueActive;
|
|||
|
}
|
|||
|
prevNode.updateDom();
|
|||
|
}
|
|||
|
|
|||
|
if (!this.results || !this.results[index]) {
|
|||
|
// out of range, set to undefined
|
|||
|
this.resultIndex = undefined;
|
|||
|
this.activeResult = undefined;
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
this.resultIndex = index;
|
|||
|
|
|||
|
// set new node active
|
|||
|
var node = this.results[this.resultIndex].node;
|
|||
|
var elem = this.results[this.resultIndex].elem;
|
|||
|
if (elem == "field") {
|
|||
|
node.searchFieldActive = true;
|
|||
|
} else {
|
|||
|
node.searchValueActive = true;
|
|||
|
}
|
|||
|
this.activeResult = this.results[this.resultIndex];
|
|||
|
node.updateDom();
|
|||
|
|
|||
|
node.scrollTo();
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Set the focus to the currently active result. If there is no currently
|
|||
|
* active result, the next search result will get focus
|
|||
|
*/
|
|||
|
JSONEditor.SearchBox.prototype.focusActiveResult = function() {
|
|||
|
if (!this.activeResult) {
|
|||
|
this.next();
|
|||
|
}
|
|||
|
|
|||
|
if (this.activeResult) {
|
|||
|
this.activeResult.node.focus(this.activeResult.elem);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Cancel any running onDelayedSearch.
|
|||
|
*/
|
|||
|
JSONEditor.SearchBox.prototype.clearDelay = function() {
|
|||
|
if (this.timeout != undefined) {
|
|||
|
clearTimeout(this.timeout);
|
|||
|
delete this.timeout;
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Start a timer to execute a search after a short delay.
|
|||
|
* Used for reducing the number of searches while typing.
|
|||
|
* @param {Event} event
|
|||
|
*/
|
|||
|
JSONEditor.SearchBox.prototype.onDelayedSearch = function(event) {
|
|||
|
// execute the search after a short delay (reduces the number of
|
|||
|
// search actions while typing in the search text box)
|
|||
|
this.clearDelay();
|
|||
|
var searchBox = this;
|
|||
|
this.timeout = setTimeout(function(event) {
|
|||
|
searchBox.onSearch(event);
|
|||
|
}, this.delay);
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Handle onSearch event
|
|||
|
* @param {Event} event
|
|||
|
* @param {boolean} [forceSearch] If true, search will be executed again even
|
|||
|
* when the search text is not changed.
|
|||
|
* Default is false.
|
|||
|
*/
|
|||
|
JSONEditor.SearchBox.prototype.onSearch = function(event, forceSearch) {
|
|||
|
this.clearDelay();
|
|||
|
|
|||
|
var value = this.dom.search.value;
|
|||
|
var text = value.length > 0 ? value : undefined;
|
|||
|
if (text != this.lastText || forceSearch) {
|
|||
|
// only search again when changed
|
|||
|
this.lastText = text;
|
|||
|
this.results = this.editor.search(text);
|
|||
|
this.setActiveResult(undefined);
|
|||
|
|
|||
|
// display search results
|
|||
|
if (text != undefined) {
|
|||
|
var resultCount = this.results.length;
|
|||
|
switch (resultCount) {
|
|||
|
case 0:
|
|||
|
this.dom.results.innerHTML = "区块/值未找到";
|
|||
|
break;
|
|||
|
default:
|
|||
|
this.dom.results.innerHTML =
|
|||
|
"找到 " + resultCount + " 个节点";
|
|||
|
break;
|
|||
|
}
|
|||
|
} else {
|
|||
|
this.dom.results.innerHTML = "";
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Handle onKeyDown event in the input box
|
|||
|
* @param {Event} event
|
|||
|
*/
|
|||
|
JSONEditor.SearchBox.prototype.onKeyDown = function(event) {
|
|||
|
event = event || window.event;
|
|||
|
var keynum = event.which || event.keyCode;
|
|||
|
if (keynum == 27) {
|
|||
|
// ESC
|
|||
|
this.dom.search.value = ""; // clear search
|
|||
|
this.onSearch(event);
|
|||
|
JSONEditor.Events.preventDefault(event);
|
|||
|
JSONEditor.Events.stopPropagation(event);
|
|||
|
} else if (keynum == 13) {
|
|||
|
// Enter
|
|||
|
if (event.ctrlKey) {
|
|||
|
// force to search again
|
|||
|
this.onSearch(event, true);
|
|||
|
} else if (event.shiftKey) {
|
|||
|
// move to the previous search result
|
|||
|
this.previous();
|
|||
|
} else {
|
|||
|
// move to the next search result
|
|||
|
this.next();
|
|||
|
}
|
|||
|
JSONEditor.Events.preventDefault(event);
|
|||
|
JSONEditor.Events.stopPropagation(event);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Handle onKeyUp event in the input box
|
|||
|
* @param {Event} event
|
|||
|
*/
|
|||
|
JSONEditor.SearchBox.prototype.onKeyUp = function(event) {
|
|||
|
event = event || window.event;
|
|||
|
var keynum = event.which || event.keyCode;
|
|||
|
if (keynum != 27 && keynum != 13) {
|
|||
|
// !ESC and !Enter
|
|||
|
this.onDelayedSearch(event); // For IE 8
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
// create namespace for event methods
|
|||
|
JSONEditor.Events = {};
|
|||
|
|
|||
|
/**
|
|||
|
* Add and event listener. Works for all browsers
|
|||
|
* @param {Element} element An html element
|
|||
|
* @param {string} action The action, for example "click",
|
|||
|
* without the prefix "on"
|
|||
|
* @param {function} listener The callback function to be executed
|
|||
|
* @param {boolean} useCapture
|
|||
|
* @return {function} the created event listener
|
|||
|
*/
|
|||
|
JSONEditor.Events.addEventListener = function(
|
|||
|
element,
|
|||
|
action,
|
|||
|
listener,
|
|||
|
useCapture
|
|||
|
) {
|
|||
|
if (element.addEventListener) {
|
|||
|
if (useCapture === undefined) useCapture = false;
|
|||
|
|
|||
|
if (
|
|||
|
action === "mousewheel" &&
|
|||
|
navigator.userAgent.indexOf("Firefox") >= 0
|
|||
|
) {
|
|||
|
action = "DOMMouseScroll"; // For Firefox
|
|||
|
}
|
|||
|
|
|||
|
element.addEventListener(action, listener, useCapture);
|
|||
|
return listener;
|
|||
|
} else {
|
|||
|
// IE browsers
|
|||
|
var f = function() {
|
|||
|
return listener.call(element, window.event);
|
|||
|
};
|
|||
|
element.attachEvent("on" + action, f);
|
|||
|
return f;
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Remove an event listener from an element
|
|||
|
* @param {Element} element An html dom element
|
|||
|
* @param {string} action The name of the event, for example "mousedown"
|
|||
|
* @param {function} listener The listener function
|
|||
|
* @param {boolean} useCapture
|
|||
|
*/
|
|||
|
JSONEditor.Events.removeEventListener = function(
|
|||
|
element,
|
|||
|
action,
|
|||
|
listener,
|
|||
|
useCapture
|
|||
|
) {
|
|||
|
if (element.removeEventListener) {
|
|||
|
// non-IE browsers
|
|||
|
if (useCapture === undefined) useCapture = false;
|
|||
|
|
|||
|
if (
|
|||
|
action === "mousewheel" &&
|
|||
|
navigator.userAgent.indexOf("Firefox") >= 0
|
|||
|
) {
|
|||
|
action = "DOMMouseScroll"; // For Firefox
|
|||
|
}
|
|||
|
|
|||
|
element.removeEventListener(action, listener, useCapture);
|
|||
|
} else {
|
|||
|
// IE browsers
|
|||
|
element.detachEvent("on" + action, listener);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Stop event propagation
|
|||
|
* @param {Event} event
|
|||
|
*/
|
|||
|
JSONEditor.Events.stopPropagation = function(event) {
|
|||
|
if (!event) event = window.event;
|
|||
|
|
|||
|
if (event.stopPropagation) {
|
|||
|
event.stopPropagation(); // non-IE browsers
|
|||
|
} else {
|
|||
|
event.cancelBubble = true; // IE browsers
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Cancels the event if it is cancelable, without stopping further propagation of the event.
|
|||
|
* @param {Event} event
|
|||
|
*/
|
|||
|
JSONEditor.Events.preventDefault = function(event) {
|
|||
|
if (!event) event = window.event;
|
|||
|
|
|||
|
if (event.preventDefault) {
|
|||
|
event.preventDefault(); // non-IE browsers
|
|||
|
} else {
|
|||
|
event.returnValue = false; // IE browsers
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Retrieve the absolute left value of a DOM element
|
|||
|
* @param {Element} elem A dom element, for example a div
|
|||
|
* @return {Number} left The absolute left position of this element
|
|||
|
* in the browser page.
|
|||
|
*/
|
|||
|
JSONEditor.getAbsoluteLeft = function(elem) {
|
|||
|
var left = 0;
|
|||
|
var body = document.body;
|
|||
|
while (elem != null && elem != body) {
|
|||
|
left += elem.offsetLeft;
|
|||
|
left -= elem.scrollLeft;
|
|||
|
elem = elem.offsetParent;
|
|||
|
}
|
|||
|
return left;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Retrieve the absolute top value of a DOM element
|
|||
|
* @param {Element} elem A dom element, for example a div
|
|||
|
* @return {Number} top The absolute top position of this element
|
|||
|
* in the browser page.
|
|||
|
*/
|
|||
|
JSONEditor.getAbsoluteTop = function(elem) {
|
|||
|
var top = 0;
|
|||
|
var body = document.body;
|
|||
|
while (elem != null && elem != body) {
|
|||
|
top += elem.offsetTop;
|
|||
|
top -= elem.scrollTop;
|
|||
|
elem = elem.offsetParent;
|
|||
|
}
|
|||
|
return top;
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* add a className to the given elements style
|
|||
|
* @param {Element} elem
|
|||
|
* @param {String} className
|
|||
|
*/
|
|||
|
JSONEditor.addClassName = function(elem, className) {
|
|||
|
var classes = elem.className.split(" ");
|
|||
|
if (classes.indexOf(className) == -1) {
|
|||
|
classes.push(className); // add the class to the array
|
|||
|
elem.className = classes.join(" ");
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* add a className to the given elements style
|
|||
|
* @param {Element} elem
|
|||
|
* @param {String} className
|
|||
|
*/
|
|||
|
JSONEditor.removeClassName = function(elem, className) {
|
|||
|
var classes = elem.className.split(" ");
|
|||
|
var index = classes.indexOf(className);
|
|||
|
if (index != -1) {
|
|||
|
classes.splice(index, 1); // remove the class from the array
|
|||
|
elem.className = classes.join(" ");
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Strip the formatting from the contents of a div
|
|||
|
* the formatting from the div itself is not stripped, only from its childs.
|
|||
|
* @param {Element} divElement
|
|||
|
*/
|
|||
|
JSONEditor.stripFormatting = function(divElement) {
|
|||
|
var childs = divElement.childNodes;
|
|||
|
for (var i = 0, iMax = childs.length; i < iMax; i++) {
|
|||
|
var child = childs[i];
|
|||
|
|
|||
|
// remove the style
|
|||
|
if (child.style) {
|
|||
|
// TODO: test if child.attributes does contain style
|
|||
|
child.removeAttribute("style");
|
|||
|
}
|
|||
|
|
|||
|
// remove all attributes
|
|||
|
var attributes = child.attributes;
|
|||
|
if (attributes) {
|
|||
|
for (var j = attributes.length - 1; j >= 0; j--) {
|
|||
|
var attribute = attributes[j];
|
|||
|
if (attribute.specified == true) {
|
|||
|
child.removeAttribute(attribute.name);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// recursively strip childs
|
|||
|
JSONEditor.stripFormatting(child);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Set focus to the end of an editable div
|
|||
|
* code from Nico Burns
|
|||
|
* http://stackoverflow.com/users/140293/nico-burns
|
|||
|
* http://stackoverflow.com/questions/1125292/how-to-move-cursor-to-end-of-contenteditable-entity
|
|||
|
* @param {Element} contentEditableElement
|
|||
|
*/
|
|||
|
JSONEditor.setEndOfContentEditable = function(contentEditableElement) {
|
|||
|
var range, selection;
|
|||
|
if (document.createRange) {
|
|||
|
//Firefox, Chrome, Opera, Safari, IE 9+
|
|||
|
range = document.createRange(); //Create a range (a range is a like the selection but invisible)
|
|||
|
range.selectNodeContents(contentEditableElement); //Select the entire contents of the element with the range
|
|||
|
range.collapse(false); //collapse the range to the end point. false means collapse to end rather than the start
|
|||
|
selection = window.getSelection(); //get the selection object (allows you to change selection)
|
|||
|
selection.removeAllRanges(); //remove any selections already made
|
|||
|
selection.addRange(range); //make the range you have just created the visible selection
|
|||
|
} else if (document.selection) {
|
|||
|
//IE 8 and lower
|
|||
|
range = document.body.createTextRange(); //Create a range (a range is a like the selection but invisible)
|
|||
|
range.moveToElementText(contentEditableElement); //Select the entire contents of the element with the range
|
|||
|
range.collapse(false); //collapse the range to the end point. false means collapse to end rather than the start
|
|||
|
range.select(); //Select the range (make it the visible selection
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Get the inner text of an HTML element (for example a div element)
|
|||
|
* @param {Element} element
|
|||
|
* @param {Object} [buffer]
|
|||
|
* @return {String} innerText
|
|||
|
*/
|
|||
|
JSONEditor.getInnerText = function(element, buffer) {
|
|||
|
var first = buffer == undefined;
|
|||
|
if (first) {
|
|||
|
buffer = {
|
|||
|
text: "",
|
|||
|
flush: function() {
|
|||
|
var text = this.text;
|
|||
|
this.text = "";
|
|||
|
return text;
|
|||
|
},
|
|||
|
set: function(text) {
|
|||
|
this.text = text;
|
|||
|
}
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
// text node
|
|||
|
if (element.nodeValue) {
|
|||
|
return buffer.flush() + element.nodeValue;
|
|||
|
}
|
|||
|
|
|||
|
// divs or other HTML elements
|
|||
|
if (element.hasChildNodes()) {
|
|||
|
var childNodes = element.childNodes;
|
|||
|
var innerText = "";
|
|||
|
|
|||
|
for (var i = 0, iMax = childNodes.length; i < iMax; i++) {
|
|||
|
var child = childNodes[i];
|
|||
|
|
|||
|
if (child.nodeName == "DIV" || child.nodeName == "P") {
|
|||
|
var prevChild = childNodes[i - 1];
|
|||
|
var prevName = prevChild ? prevChild.nodeName : undefined;
|
|||
|
if (
|
|||
|
prevName &&
|
|||
|
prevName != "DIV" &&
|
|||
|
prevName != "P" &&
|
|||
|
prevName != "BR"
|
|||
|
) {
|
|||
|
innerText += "\n";
|
|||
|
buffer.flush();
|
|||
|
}
|
|||
|
innerText += JSONEditor.getInnerText(child, buffer);
|
|||
|
buffer.set("\n");
|
|||
|
} else if (child.nodeName == "BR") {
|
|||
|
innerText += buffer.flush();
|
|||
|
buffer.set("\n");
|
|||
|
} else {
|
|||
|
innerText += JSONEditor.getInnerText(child, buffer);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return innerText;
|
|||
|
} else {
|
|||
|
if (
|
|||
|
element.nodeName == "P" &&
|
|||
|
JSONEditor.getInternetExplorerVersion() != -1
|
|||
|
) {
|
|||
|
// On Internet Explorer, a <p> with hasChildNodes()==false is
|
|||
|
// rendered with a new line. Note that a <p> with
|
|||
|
// hasChildNodes()==true is rendered without a new line
|
|||
|
// Other browsers always ensure there is a <br> inside the <p>,
|
|||
|
// and if not, the <p> does not render a new line
|
|||
|
return buffer.flush();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// br or unknown
|
|||
|
return "";
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Returns the version of Internet Explorer or a -1
|
|||
|
* (indicating the use of another browser).
|
|||
|
* Source: http://msdn.microsoft.com/en-us/library/ms537509(v=vs.85).aspx
|
|||
|
* @return {Number} Internet Explorer version, or -1 in case of an other browser
|
|||
|
*/
|
|||
|
JSONEditor._ieVersion = undefined;
|
|||
|
JSONEditor.getInternetExplorerVersion = function() {
|
|||
|
if (JSONEditor._ieVersion == undefined) {
|
|||
|
var rv = -1; // Return value assumes failure.
|
|||
|
if (navigator.appName == "Microsoft Internet Explorer") {
|
|||
|
var ua = navigator.userAgent;
|
|||
|
var re = new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})");
|
|||
|
if (re.exec(ua) != null) {
|
|||
|
rv = parseFloat(RegExp.$1);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
JSONEditor._ieVersion = rv;
|
|||
|
}
|
|||
|
|
|||
|
return JSONEditor._ieVersion;
|
|||
|
};
|
|||
|
|
|||
|
JSONEditor.ieVersion = JSONEditor.getInternetExplorerVersion();
|
|||
|
|
|||
|
/**
|
|||
|
* Parse JSON using the parser built-in in the browser.
|
|||
|
* On exception, the jsonString is validated and a detailed error is thrown.
|
|||
|
* @param {String} jsonString
|
|||
|
*/
|
|||
|
JSONEditor.parse = function(jsonString) {
|
|||
|
try {
|
|||
|
return JSON.parse(jsonString);
|
|||
|
} catch (err) {
|
|||
|
// get a detailed error message using validate
|
|||
|
var message = JSONEditor.validate(jsonString) || err;
|
|||
|
throw new Error(message);
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Validate a string containing a JSON object
|
|||
|
* This method uses JSONLint to validate the String. If JSONLint is not
|
|||
|
* available, the built-in JSON parser of the browser is used.
|
|||
|
* @param {String} jsonString String with an (invalid) JSON object
|
|||
|
* @return {String | undefined} Returns undefined when the string is valid JSON,
|
|||
|
* returns a string with an error message when
|
|||
|
* the data is invalid
|
|||
|
*/
|
|||
|
JSONEditor.validate = function(jsonString) {
|
|||
|
var message = undefined;
|
|||
|
|
|||
|
try {
|
|||
|
if (window.jsonlint) {
|
|||
|
window.jsonlint.parse(jsonString);
|
|||
|
} else {
|
|||
|
JSON.parse(jsonString);
|
|||
|
}
|
|||
|
} catch (err) {
|
|||
|
message = '<pre class="error">' + err.toString() + "</pre>";
|
|||
|
if (window.jsonlint) {
|
|||
|
message +=
|
|||
|
'<div id="by-jsonlint"> <a class="error" href="http://zaach.github.com/jsonlint/" target="_blank">' +
|
|||
|
"JSONLint" +
|
|||
|
"</a> 提供验证.</div>";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return message;
|
|||
|
};
|
|||
|
|
|||
|
function jsonArea(ob) {
|
|||
|
let inputEle = ob.el;
|
|||
|
let insert = ob.insert;
|
|||
|
let nth = ob.nth;
|
|||
|
let change = ob.change;
|
|||
|
let thisare = new Object();
|
|||
|
if (!inputEle) {
|
|||
|
throw new Error("没有提供数据.");
|
|||
|
}
|
|||
|
// if target to multi dom
|
|||
|
if (nth) {
|
|||
|
thisare.data = document.querySelectorAll(inputEle)[nth - 1];
|
|||
|
} else {
|
|||
|
let all = document.querySelectorAll(inputEle);
|
|||
|
for (var i = 0; i < all.length; i++) {
|
|||
|
jsonArea({ el: inputEle, insert: insert, nth: i + 1, change: change });
|
|||
|
}
|
|||
|
// thisare.data = document.querySelector(inputEle);
|
|||
|
return;
|
|||
|
}
|
|||
|
// create container
|
|||
|
thisare.container = document.createElement("div");
|
|||
|
thisare.container.style.width = "100%";
|
|||
|
thisare.container.style.height = "auto";
|
|||
|
thisare.data.parentElement.insertBefore(thisare.container, thisare.data);
|
|||
|
// check wether is a json
|
|||
|
thisare.isJson = function(str) {
|
|||
|
if (!isNaN(str)) return false;
|
|||
|
if (str == "") return false;
|
|||
|
if (str == '""') return false;
|
|||
|
if (typeof str == "string") {
|
|||
|
try {
|
|||
|
JSON.parse(str);
|
|||
|
return true;
|
|||
|
} catch (e) {
|
|||
|
// console.log(e);
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
console.log("不是丢<E698AF>个stringify");
|
|||
|
};
|
|||
|
// json data
|
|||
|
thisare.jsonval = "{}";
|
|||
|
// callback when change
|
|||
|
thisare.syn = function() {};
|
|||
|
// whether is textarea or input
|
|||
|
if (thisare.data.tagName == "TEXTAREA") {
|
|||
|
thisare.jsonval = thisare.data.innerHTML;
|
|||
|
thisare.syn = function() {
|
|||
|
thisare.data.innerHTML = JSON.stringify(thisare.jsonEditor.get());
|
|||
|
change(thisare.jsonEditor.get());
|
|||
|
};
|
|||
|
} else if (thisare.data.tagName == "INPUT") {
|
|||
|
thisare.jsonval = thisare.data.value;
|
|||
|
thisare.syn = function() {
|
|||
|
thisare.data.value = JSON.stringify(thisare.jsonEditor.get());
|
|||
|
change(thisare.jsonEditor.get());
|
|||
|
};
|
|||
|
}
|
|||
|
// if not json, and not insert ,then json editer
|
|||
|
thisare.hashJson = thisare.isJson(thisare.jsonval);
|
|||
|
if (!thisare.hashJson && !insert) return;
|
|||
|
thisare.data.style.display = "none";
|
|||
|
// create jsoneditor for father
|
|||
|
thisare.jsonEditor = new JSONEditor(thisare.container, {
|
|||
|
change: function() {
|
|||
|
thisare.lastChanged = thisare.jsonEditor;
|
|||
|
thisare.syn();
|
|||
|
}
|
|||
|
});
|
|||
|
if (thisare.hashJson) {
|
|||
|
thisare.jsonEditor.set(JSON.parse(thisare.jsonval));
|
|||
|
}
|
|||
|
console.log("in jsonArea");
|
|||
|
return thisare;
|
|||
|
}
|