diff --git a/jsoneditor.js b/jsoneditor.js index 9f07ecc..ea99034 100644 --- a/jsoneditor.js +++ b/jsoneditor.js @@ -24,7 +24,7 @@ * * @author Jos de Jong, * @version 3.2.0 - * @date 2015-01-25 + * @date 2015-02-27 */ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') @@ -82,4287 +82,6188 @@ return /******/ (function(modules) { // webpackBootstrap /* 0 */ /***/ function(module, exports, __webpack_require__) { - var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(1), __webpack_require__(2), __webpack_require__(3)], __WEBPACK_AMD_DEFINE_RESULT__ = function (treemode, textmode, util) { + var treemode = __webpack_require__(1); + var textmode = __webpack_require__(2); + var util = __webpack_require__(3); - /** - * @constructor JSONEditor - * @param {Element} container Container element - * @param {Object} [options] Object with options. available options: - * {String} mode Editor mode. Available values: - * 'tree' (default), 'view', - * 'form', 'text', and 'code'. - * {function} change Callback method, triggered - * on change of contents - * {Boolean} search Enable search box. - * True by default - * Only applicable for modes - * 'tree', 'view', and 'form' - * {Boolean} history Enable history (undo/redo). - * True by default - * Only applicable for modes - * 'tree', 'view', and 'form' - * {String} name Field name for the root node. - * Only applicable for modes - * 'tree', 'view', and 'form' - * {Number} indentation Number of indentation - * spaces. 4 by default. - * Only applicable for - * modes 'text' and 'code' - * @param {Object | undefined} json JSON object - */ - function JSONEditor (container, options, json) { - if (!(this instanceof JSONEditor)) { - throw new Error('JSONEditor constructor called without "new".'); - } - - // check for unsupported browser (IE8 and older) - var ieVersion = util.getInternetExplorerVersion(); - if (ieVersion != -1 && ieVersion < 9) { - throw new Error('Unsupported browser, IE9 or newer required. ' + - 'Please install the newest version of your browser.'); - } - - if (arguments.length) { - this._create(container, options, json); - } + /** + * @constructor JSONEditor + * @param {Element} container Container element + * @param {Object} [options] Object with options. available options: + * {String} mode Editor mode. Available values: + * 'tree' (default), 'view', + * 'form', 'text', and 'code'. + * {function} change Callback method, triggered + * on change of contents + * {Boolean} search Enable search box. + * True by default + * Only applicable for modes + * 'tree', 'view', and 'form' + * {Boolean} history Enable history (undo/redo). + * True by default + * Only applicable for modes + * 'tree', 'view', and 'form' + * {String} name Field name for the root node. + * Only applicable for modes + * 'tree', 'view', and 'form' + * {Number} indentation Number of indentation + * spaces. 4 by default. + * Only applicable for + * modes 'text' and 'code' + * @param {Object | undefined} json JSON object + */ + function JSONEditor (container, options, json) { + if (!(this instanceof JSONEditor)) { + throw new Error('JSONEditor constructor called without "new".'); } - /** - * Configuration for all registered modes. Example: - * { - * tree: { - * mixin: TreeEditor, - * data: 'json' - * }, - * text: { - * mixin: TextEditor, - * data: 'text' - * } - * } - * - * @type { Object. } - */ - JSONEditor.modes = {}; + // check for unsupported browser (IE8 and older) + var ieVersion = util.getInternetExplorerVersion(); + if (ieVersion != -1 && ieVersion < 9) { + throw new Error('Unsupported browser, IE9 or newer required. ' + + 'Please install the newest version of your browser.'); + } - /** - * Create the JSONEditor - * @param {Element} container Container element - * @param {Object} [options] See description in constructor - * @param {Object | undefined} json JSON object - * @private - */ - JSONEditor.prototype._create = function (container, options, json) { - this.container = container; - this.options = options || {}; - this.json = json || {}; + if (arguments.length) { + this._create(container, options, json); + } + } - var mode = this.options.mode || 'tree'; - this.setMode(mode); - }; + /** + * Configuration for all registered modes. Example: + * { + * tree: { + * mixin: TreeEditor, + * data: 'json' + * }, + * text: { + * mixin: TextEditor, + * data: 'text' + * } + * } + * + * @type { Object. } + */ + JSONEditor.modes = {}; - /** - * Detach the editor from the DOM - * @private - */ - JSONEditor.prototype._delete = function () {}; + /** + * Create the JSONEditor + * @param {Element} container Container element + * @param {Object} [options] See description in constructor + * @param {Object | undefined} json JSON object + * @private + */ + JSONEditor.prototype._create = function (container, options, json) { + this.container = container; + this.options = options || {}; + this.json = json || {}; - /** - * Set JSON object in editor - * @param {Object | undefined} json JSON data - */ - JSONEditor.prototype.set = function (json) { - this.json = json; - }; + var mode = this.options.mode || 'tree'; + this.setMode(mode); + }; - /** - * Get JSON from the editor - * @returns {Object} json - */ - JSONEditor.prototype.get = function () { - return this.json; - }; + /** + * Detach the editor from the DOM + * @private + */ + JSONEditor.prototype._delete = function () {}; - /** - * Set string containing JSON for the editor - * @param {String | undefined} jsonText - */ - JSONEditor.prototype.setText = function (jsonText) { - this.json = util.parse(jsonText); - }; + /** + * Set JSON object in editor + * @param {Object | undefined} json JSON data + */ + JSONEditor.prototype.set = function (json) { + this.json = json; + }; - /** - * Get stringified JSON contents from the editor - * @returns {String} jsonText - */ - JSONEditor.prototype.getText = function () { - return JSON.stringify(this.json); - }; + /** + * Get JSON from the editor + * @returns {Object} json + */ + JSONEditor.prototype.get = function () { + return this.json; + }; - /** - * Set a field name for the root node. - * @param {String | undefined} name - */ - JSONEditor.prototype.setName = function (name) { - if (!this.options) { - this.options = {}; - } - this.options.name = name; - }; + /** + * Set string containing JSON for the editor + * @param {String | undefined} jsonText + */ + JSONEditor.prototype.setText = function (jsonText) { + this.json = util.parse(jsonText); + }; - /** - * Get the field name for the root node. - * @return {String | undefined} name - */ - JSONEditor.prototype.getName = function () { - return this.options && this.options.name; - }; + /** + * Get stringified JSON contents from the editor + * @returns {String} jsonText + */ + JSONEditor.prototype.getText = function () { + return JSON.stringify(this.json); + }; - /** - * Change the mode of the editor. - * JSONEditor will be extended with all methods needed for the chosen mode. - * @param {String} mode Available modes: 'tree' (default), 'view', 'form', - * 'text', and 'code'. - */ - JSONEditor.prototype.setMode = function (mode) { - var container = this.container, - options = util.extend({}, this.options), - data, - name; + /** + * Set a field name for the root node. + * @param {String | undefined} name + */ + JSONEditor.prototype.setName = function (name) { + if (!this.options) { + this.options = {}; + } + this.options.name = name; + }; - options.mode = mode; - var config = JSONEditor.modes[mode]; - if (config) { - try { - var asText = (config.data == 'text'); - name = this.getName(); - data = this[asText ? 'getText' : 'get'](); // get text or json + /** + * Get the field name for the root node. + * @return {String | undefined} name + */ + JSONEditor.prototype.getName = function () { + return this.options && this.options.name; + }; - this._delete(); - util.clear(this); - util.extend(this, config.mixin); - this.create(container, options); + /** + * Change the mode of the editor. + * JSONEditor will be extended with all methods needed for the chosen mode. + * @param {String} mode Available modes: 'tree' (default), 'view', 'form', + * 'text', and 'code'. + */ + JSONEditor.prototype.setMode = function (mode) { + var container = this.container, + options = util.extend({}, this.options), + data, + name; - this.setName(name); - this[asText ? 'setText' : 'set'](data); // set text or json + options.mode = mode; + var config = JSONEditor.modes[mode]; + if (config) { + try { + var asText = (config.data == 'text'); + name = this.getName(); + data = this[asText ? 'getText' : 'get'](); // get text or json - if (typeof config.load === 'function') { - try { - config.load.call(this); - } - catch (err) {} + this._delete(); + util.clear(this); + util.extend(this, config.mixin); + this.create(container, options); + + this.setName(name); + this[asText ? 'setText' : 'set'](data); // set text or json + + if (typeof config.load === 'function') { + try { + config.load.call(this); } - } - catch (err) { - this._onError(err); + catch (err) {} } } - else { - throw new Error('Unknown mode "' + options.mode + '"'); + catch (err) { + this._onError(err); } - }; + } + else { + throw new Error('Unknown mode "' + options.mode + '"'); + } + }; - /** - * Throw an error. If an error callback is configured in options.error, this - * callback will be invoked. Else, a regular error is thrown. - * @param {Error} err - * @private - */ - JSONEditor.prototype._onError = function(err) { - // TODO: onError is deprecated since version 2.2.0. cleanup some day - if (typeof this.onError === 'function') { - util.log('WARNING: JSONEditor.onError is deprecated. ' + - 'Use options.error instead.'); - this.onError(err); + /** + * Throw an error. If an error callback is configured in options.error, this + * callback will be invoked. Else, a regular error is thrown. + * @param {Error} err + * @private + */ + JSONEditor.prototype._onError = function(err) { + // TODO: onError is deprecated since version 2.2.0. cleanup some day + if (typeof this.onError === 'function') { + util.log('WARNING: JSONEditor.onError is deprecated. ' + + 'Use options.error instead.'); + this.onError(err); + } + + if (this.options && typeof this.options.error === 'function') { + this.options.error(err); + } + else { + throw err; + } + }; + + /** + * Register a plugin with one ore multiple modes for the JSON Editor. + * + * A mode is described as an object with properties: + * + * - `mode: String` The name of the mode. + * - `mixin: Object` An object containing the mixin functions which + * will be added to the JSONEditor. Must contain functions + * create, get, getText, set, and setText. May have + * additional functions. + * When the JSONEditor switches to a mixin, all mixin + * functions are added to the JSONEditor, and then + * the function `create(container, options)` is executed. + * - `data: 'text' | 'json'` The type of data that will be used to load the mixin. + * - `[load: function]` An optional function called after the mixin + * has been loaded. + * + * @param {Object | Array} mode A mode object or an array with multiple mode objects. + */ + JSONEditor.registerMode = function (mode) { + var i, prop; + + if (util.isArray(mode)) { + // multiple modes + for (i = 0; i < mode.length; i++) { + JSONEditor.registerMode(mode[i]); + } + } + else { + // validate the new mode + if (!('mode' in mode)) throw new Error('Property "mode" missing'); + if (!('mixin' in mode)) throw new Error('Property "mixin" missing'); + if (!('data' in mode)) throw new Error('Property "data" missing'); + var name = mode.mode; + if (name in JSONEditor.modes) { + throw new Error('Mode "' + name + '" already registered'); } - if (this.options && typeof this.options.error === 'function') { - this.options.error(err); + // validate the mixin + if (typeof mode.mixin.create !== 'function') { + throw new Error('Required function "create" missing on mixin'); } - else { - throw err; - } - }; - - /** - * Register a plugin with one ore multiple modes for the JSON Editor. - * - * A mode is described as an object with properties: - * - * - `mode: String` The name of the mode. - * - `mixin: Object` An object containing the mixin functions which - * will be added to the JSONEditor. Must contain functions - * create, get, getText, set, and setText. May have - * additional functions. - * When the JSONEditor switches to a mixin, all mixin - * functions are added to the JSONEditor, and then - * the function `create(container, options)` is executed. - * - `data: 'text' | 'json'` The type of data that will be used to load the mixin. - * - `[load: function]` An optional function called after the mixin - * has been loaded. - * - * @param {Object | Array} mode A mode object or an array with multiple mode objects. - */ - JSONEditor.registerMode = function (mode) { - var i, prop; - - if (util.isArray(mode)) { - // multiple modes - for (i = 0; i < mode.length; i++) { - JSONEditor.registerMode(mode[i]); + var reserved = ['setMode', 'registerMode', 'modes']; + for (i = 0; i < reserved.length; i++) { + prop = reserved[i]; + if (prop in mode.mixin) { + throw new Error('Reserved property "' + prop + '" not allowed in mixin'); } } - else { - // validate the new mode - if (!('mode' in mode)) throw new Error('Property "mode" missing'); - if (!('mixin' in mode)) throw new Error('Property "mixin" missing'); - if (!('data' in mode)) throw new Error('Property "data" missing'); - var name = mode.mode; - if (name in JSONEditor.modes) { - throw new Error('Mode "' + name + '" already registered'); - } - // validate the mixin - if (typeof mode.mixin.create !== 'function') { - throw new Error('Required function "create" missing on mixin'); - } - var reserved = ['setMode', 'registerMode', 'modes']; - for (i = 0; i < reserved.length; i++) { - prop = reserved[i]; - if (prop in mode.mixin) { - throw new Error('Reserved property "' + prop + '" not allowed in mixin'); - } - } + JSONEditor.modes[name] = mode; + } + }; - JSONEditor.modes[name] = mode; - } - }; + // register tree and text modes + JSONEditor.registerMode(treemode); + JSONEditor.registerMode(textmode); - // register tree and text modes - JSONEditor.registerMode(treemode); - JSONEditor.registerMode(textmode); + module.exports = JSONEditor; - return JSONEditor; - }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); /***/ }, /* 1 */ /***/ function(module, exports, __webpack_require__) { - var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(4), __webpack_require__(5), __webpack_require__(6), __webpack_require__(7), __webpack_require__(8), __webpack_require__(3)], __WEBPACK_AMD_DEFINE_RESULT__ = function (Highlighter, History, SearchBox, Node, modeswitcher, util) { + var Highlighter = __webpack_require__(4); + var History = __webpack_require__(5); + var SearchBox = __webpack_require__(6); + var Node = __webpack_require__(7); + var modeswitcher = __webpack_require__(8); + var util = __webpack_require__(3); - // create a mixin with the functions for tree mode - var treemode = {}; - - /** - * Create a tree editor - * @param {Element} container Container element - * @param {Object} [options] Object with options. available options: - * {String} mode Editor mode. Available values: - * 'tree' (default), 'view', - * and 'form'. - * {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 - */ - treemode.create = function (container, options) { - if (!container) { - throw new Error('No container element provided.'); - } - this.container = container; - this.dom = {}; - this.highlighter = new Highlighter(); - this.selection = undefined; // will hold the last input selection + // create a mixin with the functions for tree mode + var treemode = {}; - this._setOptions(options); + /** + * Create a tree editor + * @param {Element} container Container element + * @param {Object} [options] Object with options. available options: + * {String} mode Editor mode. Available values: + * 'tree' (default), 'view', + * and 'form'. + * {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 + */ + treemode.create = function (container, options) { + if (!container) { + throw new Error('No container element provided.'); + } + this.container = container; + this.dom = {}; + this.highlighter = new Highlighter(); + this.selection = undefined; // will hold the last input selection - if (this.options.history && this.options.mode !== 'view') { - this.history = new History(this); - } + this._setOptions(options); - this._createFrame(); - this._createTable(); + if (this.options.history && this.options.mode !== 'view') { + this.history = new History(this); + } + + this._createFrame(); + this._createTable(); + }; + + /** + * Detach the editor from the DOM + * @private + */ + treemode._delete = function () { + if (this.frame && this.container && this.frame.parentNode == this.container) { + this.container.removeChild(this.frame); + } + }; + + /** + * Initialize and set default options + * @param {Object} [options] See description in constructor + * @private + */ + treemode._setOptions = function (options) { + this.options = { + search: true, + history: true, + mode: 'tree', + name: undefined // field name of root node }; - /** - * Detach the editor from the DOM - * @private - */ - treemode._delete = function () { - if (this.frame && this.container && this.frame.parentNode == this.container) { - this.container.removeChild(this.frame); - } - }; - - /** - * Initialize and set default options - * @param {Object} [options] See description in constructor - * @private - */ - treemode._setOptions = function (options) { - this.options = { - search: true, - history: true, - mode: 'tree', - 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]; - } + // copy all options + if (options) { + for (var prop in options) { + if (options.hasOwnProperty(prop)) { + this.options[prop] = options[prop]; } } - }; + } + }; - // node currently being edited - var focusNode = undefined; + // node currently being edited + var focusNode = undefined; - // dom having focus - var domFocus = null; + // dom having focus + var domFocus = null; - /** - * 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). - */ - treemode.set = function (json, name) { - // adjust field name for root node - if (name) { - // TODO: deprecated since version 2.2.0. Cleanup some day. - util.log('Warning: second parameter "name" is deprecated. ' + - 'Use setName(name) instead.'); - 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 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 - */ - treemode.get = function () { - // remove focus from currently edited node - if (focusNode) { - focusNode.blur(); - } - - if (this.node) { - return this.node.getValue(); - } - else { - return undefined; - } - }; - - /** - * Get the text contents of the editor - * @return {String} jsonText - */ - treemode.getText = function() { - return JSON.stringify(this.get()); - }; - - /** - * Set the text contents of the editor - * @param {String} jsonText - */ - treemode.setText = function(jsonText) { - this.set(util.parse(jsonText)); - }; - - /** - * Set a field name for the root node. - * @param {String | undefined} name - */ - treemode.setName = function (name) { + /** + * 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). + */ + treemode.set = function (json, name) { + // adjust field name for root node + if (name) { + // TODO: deprecated since version 2.2.0. Cleanup some day. + util.log('Warning: second parameter "name" is deprecated. ' + + 'Use setName(name) instead.'); 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 - */ - treemode.getName = function () { - return this.options.name; - }; - - /** - * Remove the root node from the editor - */ - treemode.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 {Node} node - * @private - */ - treemode._setRoot = function (node) { + // 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 - 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: - * - {Node} node, - * - {String} elem the dom element name where - * the result is found ('field' or - * 'value') - */ - treemode.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 - */ - treemode.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 - */ - treemode.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. - * @private - */ - treemode._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) { - util.log('Error in change callback: ', err); - } - } - }; - - /** - * Start autoscrolling when given mouse position is above the top of the - * editor contents, or below the bottom. - * @param {Number} mouseY Absolute mouse position in pixels - */ - treemode.startAutoScroll = function (mouseY) { - var me = this; - var content = this.content; - var top = util.getAbsoluteTop(content); - var height = content.clientHeight; - var bottom = top + height; - var margin = 24; - var interval = 50; // ms - - if ((mouseY < top + margin) && content.scrollTop > 0) { - this.autoScrollStep = ((top + margin) - mouseY) / 3; - } - else if (mouseY > bottom - margin && - height + content.scrollTop < content.scrollHeight) { - this.autoScrollStep = ((bottom - margin) - mouseY) / 3; - } - else { - this.autoScrollStep = undefined; - } - - if (this.autoScrollStep) { - if (!this.autoScrollTimer) { - this.autoScrollTimer = setInterval(function () { - if (me.autoScrollStep) { - content.scrollTop -= me.autoScrollStep; - } - else { - me.stopAutoScroll(); - } - }, interval); - } - } - else { - this.stopAutoScroll(); - } - }; - - /** - * Stop auto scrolling. Only applicable when scrolling - */ - treemode.stopAutoScroll = function () { - if (this.autoScrollTimer) { - clearTimeout(this.autoScrollTimer); - delete this.autoScrollTimer; - } - if (this.autoScrollStep) { - delete this.autoScrollStep; - } - }; - - - /** - * Set the focus to an element in the editor, set text selection, and - * set scroll position. - * @param {Object} selection An object containing fields: - * {Element | undefined} dom The dom element - * which has focus - * {Range | TextRange} range A text selection - * {Number} scrollTop Scroll position - */ - treemode.setSelection = function (selection) { - if (!selection) { - return; - } - - if ('scrollTop' in selection && this.content) { - // TODO: animated scroll - this.content.scrollTop = selection.scrollTop; - } - if (selection.range) { - util.setSelectionOffset(selection.range); - } - if (selection.dom) { - selection.dom.focus(); - } - }; - - /** - * Get the current focus - * @return {Object} selection An object containing fields: - * {Element | undefined} dom The dom element - * which has focus - * {Range | TextRange} range A text selection - * {Number} scrollTop Scroll position - */ - treemode.getSelection = function () { - return { - dom: domFocus, - scrollTop: this.content ? this.content.scrollTop : 0, - range: util.getSelectionOffset() + // replace the root node + var params = { + 'field': this.options.name, + 'value': json }; - }; + var node = new Node(this, params); + this._setRoot(node); - /** - * Adjust the scroll position such that given top position is shown at 1/4 - * of the window height. - * @param {Number} top - * @param {function(boolean)} [callback] Callback, executed when animation is - * finished. The callback returns true - * when animation is finished, or false - * when not. - */ - treemode.scrollTo = function (top, callback) { - var content = this.content; - if (content) { - var editor = this; - // cancel any running animation - if (editor.animateTimeout) { - clearTimeout(editor.animateTimeout); - delete editor.animateTimeout; + // 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 + */ + treemode.get = function () { + // remove focus from currently edited node + if (focusNode) { + focusNode.blur(); + } + + if (this.node) { + return this.node.getValue(); + } + else { + return undefined; + } + }; + + /** + * Get the text contents of the editor + * @return {String} jsonText + */ + treemode.getText = function() { + return JSON.stringify(this.get()); + }; + + /** + * Set the text contents of the editor + * @param {String} jsonText + */ + treemode.setText = function(jsonText) { + this.set(util.parse(jsonText)); + }; + + /** + * Set a field name for the root node. + * @param {String | undefined} name + */ + treemode.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 + */ + treemode.getName = function () { + return this.options.name; + }; + + /** + * Remove the root node from the editor + */ + treemode.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 {Node} node + * @private + */ + treemode._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: + * - {Node} node, + * - {String} elem the dom element name where + * the result is found ('field' or + * 'value') + */ + treemode.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 + */ + treemode.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 + */ + treemode.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. + * @private + */ + treemode._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) { + util.log('Error in change callback: ', err); + } + } + }; + + /** + * Start autoscrolling when given mouse position is above the top of the + * editor contents, or below the bottom. + * @param {Number} mouseY Absolute mouse position in pixels + */ + treemode.startAutoScroll = function (mouseY) { + var me = this; + var content = this.content; + var top = util.getAbsoluteTop(content); + var height = content.clientHeight; + var bottom = top + height; + var margin = 24; + var interval = 50; // ms + + if ((mouseY < top + margin) && content.scrollTop > 0) { + this.autoScrollStep = ((top + margin) - mouseY) / 3; + } + else if (mouseY > bottom - margin && + height + content.scrollTop < content.scrollHeight) { + this.autoScrollStep = ((bottom - margin) - mouseY) / 3; + } + else { + this.autoScrollStep = undefined; + } + + if (this.autoScrollStep) { + if (!this.autoScrollTimer) { + this.autoScrollTimer = setInterval(function () { + if (me.autoScrollStep) { + content.scrollTop -= me.autoScrollStep; + } + else { + me.stopAutoScroll(); + } + }, interval); + } + } + else { + this.stopAutoScroll(); + } + }; + + /** + * Stop auto scrolling. Only applicable when scrolling + */ + treemode.stopAutoScroll = function () { + if (this.autoScrollTimer) { + clearTimeout(this.autoScrollTimer); + delete this.autoScrollTimer; + } + if (this.autoScrollStep) { + delete this.autoScrollStep; + } + }; + + + /** + * Set the focus to an element in the editor, set text selection, and + * set scroll position. + * @param {Object} selection An object containing fields: + * {Element | undefined} dom The dom element + * which has focus + * {Range | TextRange} range A text selection + * {Number} scrollTop Scroll position + */ + treemode.setSelection = function (selection) { + if (!selection) { + return; + } + + if ('scrollTop' in selection && this.content) { + // TODO: animated scroll + this.content.scrollTop = selection.scrollTop; + } + if (selection.range) { + util.setSelectionOffset(selection.range); + } + if (selection.dom) { + selection.dom.focus(); + } + }; + + /** + * Get the current focus + * @return {Object} selection An object containing fields: + * {Element | undefined} dom The dom element + * which has focus + * {Range | TextRange} range A text selection + * {Number} scrollTop Scroll position + */ + treemode.getSelection = function () { + return { + dom: domFocus, + scrollTop: this.content ? this.content.scrollTop : 0, + range: util.getSelectionOffset() + }; + }; + + /** + * Adjust the scroll position such that given top position is shown at 1/4 + * of the window height. + * @param {Number} top + * @param {function(boolean)} [callback] Callback, executed when animation is + * finished. The callback returns true + * when animation is finished, or false + * when not. + */ + treemode.scrollTo = function (top, callback) { + var content = this.content; + if (content) { + var editor = this; + // cancel any running animation + if (editor.animateTimeout) { + clearTimeout(editor.animateTimeout); + delete editor.animateTimeout; + } + if (editor.animateCallback) { + editor.animateCallback(false); + delete editor.animateCallback; + } + + // 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.animateCallback = callback; + editor.animateTimeout = setTimeout(animate, 50); } - if (editor.animateCallback) { - editor.animateCallback(false); + else { + // finished + if (callback) { + callback(true); + } + content.scrollTop = finalScrollTop; + delete editor.animateTimeout; delete editor.animateCallback; } - - // 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.animateCallback = callback; - editor.animateTimeout = setTimeout(animate, 50); - } - else { - // finished - if (callback) { - callback(true); - } - content.scrollTop = finalScrollTop; - delete editor.animateTimeout; - delete editor.animateCallback; - } - }; - animate(); - } - else { - if (callback) { - callback(false); - } - } - }; - - /** - * Create main frame - * @private - */ - treemode._createFrame = function () { - // create the frame - this.frame = document.createElement('div'); - this.frame.className = 'jsoneditor'; - this.container.appendChild(this.frame); - - // create one global event listener to handle all events from all nodes - var editor = this; - function onEvent(event) { - editor._onEvent(event); - } - this.frame.onclick = function (event) { - var target = event.target;// || event.srcElement; - - onEvent(event); - - // prevent default submit action of buttons when editor is located - // inside a form - if (target.nodeName == 'BUTTON') { - event.preventDefault(); - } }; - this.frame.oninput = onEvent; - 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 - util.addEventListener(this.frame, 'focus', onEvent, true); - util.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 = 'menu'; - this.frame.appendChild(this.menu); - - // create expand all button - var expandAll = document.createElement('button'); - expandAll.className = 'expand-all'; - expandAll.title = 'Expand all fields'; - expandAll.onclick = function () { - editor.expandAll(); - }; - this.menu.appendChild(expandAll); - - // create expand all button - var collapseAll = document.createElement('button'); - collapseAll.title = 'Collapse all fields'; - collapseAll.className = 'collapse-all'; - collapseAll.onclick = function () { - editor.collapseAll(); - }; - this.menu.appendChild(collapseAll); - - // create undo/redo buttons - if (this.history) { - // create undo button - var undo = document.createElement('button'); - undo.className = 'undo separator'; - undo.title = 'Undo last action (Ctrl+Z)'; - undo.onclick = function () { - editor._onUndo(); - }; - this.menu.appendChild(undo); - this.dom.undo = undo; - - // create redo button - var redo = document.createElement('button'); - redo.className = 'redo'; - redo.title = 'Redo (Ctrl+Shift+Z)'; - redo.onclick = function () { - editor._onRedo(); - }; - 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(); + animate(); + } + else { + if (callback) { + callback(false); } + } + }; - // create mode box - if (this.options && this.options.modes && this.options.modes.length) { - var modeBox = modeswitcher.create(this, this.options.modes, this.options.mode); - this.menu.appendChild(modeBox); - this.dom.modeBox = modeBox; - } + /** + * Create main frame + * @private + */ + treemode._createFrame = function () { + // create the frame + this.frame = document.createElement('div'); + this.frame.className = 'jsoneditor'; + this.container.appendChild(this.frame); - // create search box - if (this.options.search) { - this.searchBox = new SearchBox(this, this.menu); - } - }; + // create one global event listener to handle all events from all nodes + var editor = this; + function onEvent(event) { + editor._onEvent(event); + } + this.frame.onclick = function (event) { + var target = event.target;// || event.srcElement; - /** - * Perform an undo action - * @private - */ - treemode._onUndo = function () { - if (this.history) { - // undo last action - this.history.undo(); + onEvent(event); - // trigger change callback - if (this.options.change) { - this.options.change(); - } - } - }; - - /** - * Perform a redo action - * @private - */ - treemode._onRedo = function () { - if (this.history) { - // redo last action - this.history.redo(); - - // trigger change callback - if (this.options.change) { - this.options.change(); - } - } - }; - - /** - * Event handler - * @param event - * @private - */ - treemode._onEvent = function (event) { - var target = event.target; - - if (event.type == 'keydown') { - this._onKeyDown(event); - } - - if (event.type == 'focus') { - domFocus = target; - } - - var node = Node.getNodeFromTarget(target); - if (node) { - node.onEvent(event); - } - }; - - /** - * Event handler for keydown. Handles shortcut keys - * @param {Event} event - * @private - */ - treemode._onKeyDown = function (event) { - var keynum = event.which || event.keyCode; - var ctrlKey = event.ctrlKey; - var shiftKey = event.shiftKey; - var handled = false; - - if (keynum == 9) { // Tab or Shift+Tab - setTimeout(function () { - // select all text when moving focus to an editable div - util.selectContentEditable(domFocus); - }, 0); - } - - if (this.searchBox) { - if (ctrlKey && keynum == 70) { // Ctrl+F - this.searchBox.dom.search.focus(); - this.searchBox.dom.search.select(); - handled = true; - } - else if (keynum == 114 || (ctrlKey && keynum == 71)) { // F3 or Ctrl+G - var focus = true; - if (!shiftKey) { - // select next search result (F3 or Ctrl+G) - this.searchBox.next(focus); - } - else { - // select previous search result (Shift+F3 or Ctrl+Shift+G) - this.searchBox.previous(focus); - } - - handled = true; - } - } - - if (this.history) { - if (ctrlKey && !shiftKey && keynum == 90) { // Ctrl+Z - // undo - this._onUndo(); - handled = true; - } - else if (ctrlKey && shiftKey && keynum == 90) { // Ctrl+Shift+Z - // redo - this._onRedo(); - handled = true; - } - } - - if (handled) { + // prevent default submit action of buttons when editor is located + // inside a form + if (target.nodeName == 'BUTTON') { event.preventDefault(); - event.stopPropagation(); } }; + this.frame.oninput = onEvent; + 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 + util.addEventListener(this.frame, 'focus', onEvent, true); + util.addEventListener(this.frame, 'blur', onEvent, true); + this.frame.onfocusin = onEvent; // for IE + this.frame.onfocusout = onEvent; // for IE - /** - * Create main table - * @private - */ - treemode._createTable = function () { - var contentOuter = document.createElement('div'); - contentOuter.className = 'outer'; - this.contentOuter = contentOuter; + // create menu + this.menu = document.createElement('div'); + this.menu.className = 'menu'; + this.frame.appendChild(this.menu); - this.content = document.createElement('div'); - this.content.className = 'tree'; - contentOuter.appendChild(this.content); + // create expand all button + var expandAll = document.createElement('button'); + expandAll.className = 'expand-all'; + expandAll.title = 'Expand all fields'; + expandAll.onclick = function () { + editor.expandAll(); + }; + this.menu.appendChild(expandAll); - this.table = document.createElement('table'); - this.table.className = 'tree'; - this.content.appendChild(this.table); + // create expand all button + var collapseAll = document.createElement('button'); + collapseAll.title = 'Collapse all fields'; + collapseAll.className = 'collapse-all'; + collapseAll.onclick = function () { + editor.collapseAll(); + }; + this.menu.appendChild(collapseAll); - // 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'); - if (this.options.mode === 'tree') { - col = document.createElement('col'); - col.width = "24px"; - this.colgroupContent.appendChild(col); + // create undo/redo buttons + if (this.history) { + // create undo button + var undo = document.createElement('button'); + undo.className = 'undo separator'; + undo.title = 'Undo last action (Ctrl+Z)'; + undo.onclick = function () { + editor._onUndo(); + }; + this.menu.appendChild(undo); + this.dom.undo = undo; + + // create redo button + var redo = document.createElement('button'); + redo.className = 'redo'; + redo.title = 'Redo (Ctrl+Shift+Z)'; + redo.onclick = function () { + editor._onRedo(); + }; + 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 mode box + if (this.options && this.options.modes && this.options.modes.length) { + var modeBox = modeswitcher.create(this, this.options.modes, this.options.mode); + this.menu.appendChild(modeBox); + this.dom.modeBox = modeBox; + } + + // create search box + if (this.options.search) { + this.searchBox = new SearchBox(this, this.menu); + } + }; + + /** + * Perform an undo action + * @private + */ + treemode._onUndo = function () { + if (this.history) { + // undo last action + this.history.undo(); + + // trigger change callback + if (this.options.change) { + this.options.change(); } + } + }; + + /** + * Perform a redo action + * @private + */ + treemode._onRedo = function () { + if (this.history) { + // redo last action + this.history.redo(); + + // trigger change callback + if (this.options.change) { + this.options.change(); + } + } + }; + + /** + * Event handler + * @param event + * @private + */ + treemode._onEvent = function (event) { + var target = event.target; + + if (event.type == 'keydown') { + this._onKeyDown(event); + } + + if (event.type == 'focus') { + domFocus = target; + } + + var node = Node.getNodeFromTarget(target); + if (node) { + node.onEvent(event); + } + }; + + /** + * Event handler for keydown. Handles shortcut keys + * @param {Event} event + * @private + */ + treemode._onKeyDown = function (event) { + var keynum = event.which || event.keyCode; + var ctrlKey = event.ctrlKey; + var shiftKey = event.shiftKey; + var handled = false; + + if (keynum == 9) { // Tab or Shift+Tab + setTimeout(function () { + // select all text when moving focus to an editable div + util.selectContentEditable(domFocus); + }, 0); + } + + if (this.searchBox) { + if (ctrlKey && keynum == 70) { // Ctrl+F + this.searchBox.dom.search.focus(); + this.searchBox.dom.search.select(); + handled = true; + } + else if (keynum == 114 || (ctrlKey && keynum == 71)) { // F3 or Ctrl+G + var focus = true; + if (!shiftKey) { + // select next search result (F3 or Ctrl+G) + this.searchBox.next(focus); + } + else { + // select previous search result (Shift+F3 or Ctrl+Shift+G) + this.searchBox.previous(focus); + } + + handled = true; + } + } + + if (this.history) { + if (ctrlKey && !shiftKey && keynum == 90) { // Ctrl+Z + // undo + this._onUndo(); + handled = true; + } + else if (ctrlKey && shiftKey && keynum == 90) { // Ctrl+Shift+Z + // redo + this._onRedo(); + handled = true; + } + } + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } + }; + + /** + * Create main table + * @private + */ + treemode._createTable = function () { + var contentOuter = document.createElement('div'); + contentOuter.className = 'outer'; + this.contentOuter = contentOuter; + + this.content = document.createElement('div'); + this.content.className = 'tree'; + contentOuter.appendChild(this.content); + + this.table = document.createElement('table'); + this.table.className = 'tree'; + this.content.appendChild(this.table); + + // 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'); + if (this.options.mode === 'tree') { col = document.createElement('col'); col.width = "24px"; this.colgroupContent.appendChild(col); - col = document.createElement('col'); - this.colgroupContent.appendChild(col); - this.table.appendChild(this.colgroupContent); + } + col = document.createElement('col'); + col.width = "24px"; + this.colgroupContent.appendChild(col); + col = document.createElement('col'); + this.colgroupContent.appendChild(col); + this.table.appendChild(this.colgroupContent); - this.tbody = document.createElement('tbody'); - this.table.appendChild(this.tbody); + this.tbody = document.createElement('tbody'); + this.table.appendChild(this.tbody); - this.frame.appendChild(contentOuter); - }; - - // define modes - return [ - { - mode: 'tree', - mixin: treemode, - data: 'json' - }, - { - mode: 'view', - mixin: treemode, - data: 'json' - }, - { - mode: 'form', - mixin: treemode, - data: 'json' - } - ]; - }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + this.frame.appendChild(contentOuter); + }; + // define modes + module.exports = [ + { + mode: 'tree', + mixin: treemode, + data: 'json' + }, + { + mode: 'view', + mixin: treemode, + data: 'json' + }, + { + mode: 'form', + mixin: treemode, + data: 'json' + } + ]; /***/ }, /* 2 */ /***/ function(module, exports, __webpack_require__) { - var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(8), __webpack_require__(3)], __WEBPACK_AMD_DEFINE_RESULT__ = function (modeswitcher, util) { + var modeswitcher = __webpack_require__(8); + var util = __webpack_require__(3); - // create a mixin with the functions for text mode - var textmode = {}; + // create a mixin with the functions for text mode + var textmode = {}; - /** - * Create a text editor - * @param {Element} container - * @param {Object} [options] Object with options. available options: - * {String} mode Available values: - * "text" (default) - * or "code". - * {Number} indentation Number of indentation - * spaces. 2 by default. - * {function} change Callback method - * triggered on change - * @private - */ - textmode.create = function (container, options) { - // read options - options = options || {}; - this.options = options; - if (options.indentation) { - this.indentation = Number(options.indentation); - } - else { - this.indentation = 2; // number of spaces - } - this.mode = (options.mode == 'code') ? 'code' : 'text'; - if (this.mode == 'code') { - // verify whether Ace editor is available and supported - if (typeof ace === 'undefined') { - this.mode = 'text'; - util.log('WARNING: Cannot load code editor, Ace library not loaded. ' + - 'Falling back to plain text editor'); - } + /** + * Create a text editor + * @param {Element} container + * @param {Object} [options] Object with options. available options: + * {String} mode Available values: + * "text" (default) + * or "code". + * {Number} indentation Number of indentation + * spaces. 2 by default. + * {function} change Callback method + * triggered on change + * @private + */ + textmode.create = function (container, options) { + // read options + options = options || {}; + this.options = options; + if (options.indentation) { + this.indentation = Number(options.indentation); + } + else { + this.indentation = 2; // number of spaces + } + this.mode = (options.mode == 'code') ? 'code' : 'text'; + if (this.mode == 'code') { + // verify whether Ace editor is available and supported + if (typeof ace === 'undefined') { + this.mode = 'text'; + util.log('WARNING: Cannot load code editor, Ace library not loaded. ' + + 'Falling back to plain text editor'); } + } - var me = this; - this.container = container; - this.dom = {}; - this.editor = undefined; // ace code editor - this.textarea = undefined; // plain text editor (fallback when Ace is not available) + var me = this; + this.container = container; + this.dom = {}; + this.editor = undefined; // ace code editor + this.textarea = undefined; // plain text editor (fallback when Ace is not available) - this.width = container.clientWidth; - this.height = container.clientHeight; + this.width = container.clientWidth; + this.height = container.clientHeight; - this.frame = document.createElement('div'); - this.frame.className = 'jsoneditor'; - this.frame.onclick = function (event) { - // prevent default submit action when the editor is located inside a form - event.preventDefault(); - }; - this.frame.onkeydown = function (event) { - me._onKeyDown(event); - }; - - // create menu - this.menu = document.createElement('div'); - this.menu.className = 'menu'; - this.frame.appendChild(this.menu); - - // create format button - var buttonFormat = document.createElement('button'); - buttonFormat.className = 'format'; - buttonFormat.title = 'Format JSON data, with proper indentation and line feeds (Ctrl+\\)'; - this.menu.appendChild(buttonFormat); - buttonFormat.onclick = function () { - try { - me.format(); - } - catch (err) { - me._onError(err); - } - }; - - // create compact button - var buttonCompact = document.createElement('button'); - buttonCompact.className = 'compact'; - buttonCompact.title = 'Compact JSON data, remove all whitespaces (Ctrl+Shift+\\)'; - this.menu.appendChild(buttonCompact); - buttonCompact.onclick = function () { - try { - me.compact(); - } - catch (err) { - me._onError(err); - } - }; - - // create mode box - if (this.options && this.options.modes && this.options.modes.length) { - var modeBox = modeswitcher.create(this, this.options.modes, this.options.mode); - this.menu.appendChild(modeBox); - this.dom.modeBox = modeBox; - } - - this.content = document.createElement('div'); - this.content.className = 'outer'; - this.frame.appendChild(this.content); - - this.container.appendChild(this.frame); - - if (this.mode == 'code') { - this.editorDom = document.createElement('div'); - this.editorDom.style.height = '100%'; // TODO: move to css - this.editorDom.style.width = '100%'; // TODO: move to css - this.content.appendChild(this.editorDom); - - var editor = ace.edit(this.editorDom); - editor.setTheme('ace/theme/jsoneditor'); - editor.setShowPrintMargin(false); - editor.setFontSize(13); - editor.getSession().setMode('ace/mode/json'); - editor.getSession().setTabSize(this.indentation); - editor.getSession().setUseSoftTabs(true); - editor.getSession().setUseWrapMode(true); - this.editor = editor; - - var poweredBy = document.createElement('a'); - poweredBy.appendChild(document.createTextNode('powered by ace')); - poweredBy.href = 'http://ace.ajax.org'; - poweredBy.target = '_blank'; - poweredBy.className = 'poweredBy'; - poweredBy.onclick = function () { - // TODO: this anchor falls below the margin of the content, - // therefore the normal a.href does not work. We use a click event - // for now, but this should be fixed. - window.open(poweredBy.href, poweredBy.target); - }; - this.menu.appendChild(poweredBy); - - if (options.change) { - // register onchange event - editor.on('change', function () { - options.change(); - }); - } - } - else { - // load a plain text textarea - var textarea = document.createElement('textarea'); - textarea.className = 'text'; - textarea.spellcheck = false; - this.content.appendChild(textarea); - this.textarea = textarea; - - if (options.change) { - // register onchange event - if (this.textarea.oninput === null) { - this.textarea.oninput = function () { - options.change(); - } - } - else { - // oninput is undefined. For IE8- - this.textarea.onchange = function () { - options.change(); - } - } - } - } + this.frame = document.createElement('div'); + this.frame.className = 'jsoneditor'; + this.frame.onclick = function (event) { + // prevent default submit action when the editor is located inside a form + event.preventDefault(); + }; + this.frame.onkeydown = function (event) { + me._onKeyDown(event); }; - /** - * Event handler for keydown. Handles shortcut keys - * @param {Event} event - * @private - */ - textmode._onKeyDown = function (event) { - var keynum = event.which || event.keyCode; - var handled = false; - - if (keynum == 220 && event.ctrlKey) { - if (event.shiftKey) { // Ctrl+Shift+\ - this.compact(); - } - else { // Ctrl+\ - this.format(); - } - handled = true; - } - - if (handled) { - event.preventDefault(); - event.stopPropagation(); - } - }; - - /** - * Detach the editor from the DOM - * @private - */ - textmode._delete = function () { - if (this.frame && this.container && this.frame.parentNode == this.container) { - this.container.removeChild(this.frame); - } - }; - - /** - * Throw an error. If an error callback is configured in options.error, this - * callback will be invoked. Else, a regular error is thrown. - * @param {Error} err - * @private - */ - textmode._onError = function(err) { - // TODO: onError is deprecated since version 2.2.0. cleanup some day - if (typeof this.onError === 'function') { - util.log('WARNING: JSONEditor.onError is deprecated. ' + - 'Use options.error instead.'); - this.onError(err); - } - - if (this.options && typeof this.options.error === 'function') { - this.options.error(err); - } - else { - throw err; - } - }; - - /** - * Compact the code in the formatter - */ - textmode.compact = function () { - var json = this.get(); - var text = JSON.stringify(json); - this.setText(text); - }; - - /** - * Format the code in the formatter - */ - textmode.format = function () { - var json = this.get(); - var text = JSON.stringify(json, null, this.indentation); - this.setText(text); - }; - - /** - * Set focus to the formatter - */ - textmode.focus = function () { - if (this.textarea) { - this.textarea.focus(); - } - if (this.editor) { - this.editor.focus(); - } - }; - - /** - * Resize the formatter - */ - textmode.resize = function () { - if (this.editor) { - var force = false; - this.editor.resize(force); - } - }; - - /** - * Set json data in the formatter - * @param {Object} json - */ - textmode.set = function(json) { - this.setText(JSON.stringify(json, null, this.indentation)); - }; - - /** - * Get json data from the formatter - * @return {Object} json - */ - textmode.get = function() { - var text = this.getText(); - var json; + // create menu + this.menu = document.createElement('div'); + this.menu.className = 'menu'; + this.frame.appendChild(this.menu); + // create format button + var buttonFormat = document.createElement('button'); + buttonFormat.className = 'format'; + buttonFormat.title = 'Format JSON data, with proper indentation and line feeds (Ctrl+\\)'; + this.menu.appendChild(buttonFormat); + buttonFormat.onclick = function () { try { - json = util.parse(text); // this can throw an error + me.format(); } catch (err) { - // try to sanitize json, replace JavaScript notation with JSON notation - text = util.sanitize(text); - this.setText(text); - - // try to parse again - json = util.parse(text); // this can throw an error - } - - return json; - }; - - /** - * Get the text contents of the editor - * @return {String} jsonText - */ - textmode.getText = function() { - if (this.textarea) { - return this.textarea.value; - } - if (this.editor) { - return this.editor.getValue(); - } - return ''; - }; - - /** - * Set the text contents of the editor - * @param {String} jsonText - */ - textmode.setText = function(jsonText) { - if (this.textarea) { - this.textarea.value = jsonText; - } - if (this.editor) { - this.editor.setValue(jsonText, -1); + me._onError(err); } }; - // define modes - return [ - { - mode: 'text', - mixin: textmode, - data: 'text', - load: textmode.format - }, - { - mode: 'code', - mixin: textmode, - data: 'text', - load: textmode.format + // create compact button + var buttonCompact = document.createElement('button'); + buttonCompact.className = 'compact'; + buttonCompact.title = 'Compact JSON data, remove all whitespaces (Ctrl+Shift+\\)'; + this.menu.appendChild(buttonCompact); + buttonCompact.onclick = function () { + try { + me.compact(); } - ]; - }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + catch (err) { + me._onError(err); + } + }; + + // create mode box + if (this.options && this.options.modes && this.options.modes.length) { + var modeBox = modeswitcher.create(this, this.options.modes, this.options.mode); + this.menu.appendChild(modeBox); + this.dom.modeBox = modeBox; + } + + this.content = document.createElement('div'); + this.content.className = 'outer'; + this.frame.appendChild(this.content); + + this.container.appendChild(this.frame); + + if (this.mode == 'code') { + this.editorDom = document.createElement('div'); + this.editorDom.style.height = '100%'; // TODO: move to css + this.editorDom.style.width = '100%'; // TODO: move to css + this.content.appendChild(this.editorDom); + + var editor = ace.edit(this.editorDom); + editor.setTheme('ace/theme/jsoneditor'); + editor.setShowPrintMargin(false); + editor.setFontSize(13); + editor.getSession().setMode('ace/mode/json'); + editor.getSession().setTabSize(this.indentation); + editor.getSession().setUseSoftTabs(true); + editor.getSession().setUseWrapMode(true); + this.editor = editor; + + var poweredBy = document.createElement('a'); + poweredBy.appendChild(document.createTextNode('powered by ace')); + poweredBy.href = 'http://ace.ajax.org'; + poweredBy.target = '_blank'; + poweredBy.className = 'poweredBy'; + poweredBy.onclick = function () { + // TODO: this anchor falls below the margin of the content, + // therefore the normal a.href does not work. We use a click event + // for now, but this should be fixed. + window.open(poweredBy.href, poweredBy.target); + }; + this.menu.appendChild(poweredBy); + + if (options.change) { + // register onchange event + editor.on('change', function () { + options.change(); + }); + } + } + else { + // load a plain text textarea + var textarea = document.createElement('textarea'); + textarea.className = 'text'; + textarea.spellcheck = false; + this.content.appendChild(textarea); + this.textarea = textarea; + + if (options.change) { + // register onchange event + if (this.textarea.oninput === null) { + this.textarea.oninput = function () { + options.change(); + } + } + else { + // oninput is undefined. For IE8- + this.textarea.onchange = function () { + options.change(); + } + } + } + } + }; + + /** + * Event handler for keydown. Handles shortcut keys + * @param {Event} event + * @private + */ + textmode._onKeyDown = function (event) { + var keynum = event.which || event.keyCode; + var handled = false; + + if (keynum == 220 && event.ctrlKey) { + if (event.shiftKey) { // Ctrl+Shift+\ + this.compact(); + } + else { // Ctrl+\ + this.format(); + } + handled = true; + } + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } + }; + + /** + * Detach the editor from the DOM + * @private + */ + textmode._delete = function () { + if (this.frame && this.container && this.frame.parentNode == this.container) { + this.container.removeChild(this.frame); + } + }; + + /** + * Throw an error. If an error callback is configured in options.error, this + * callback will be invoked. Else, a regular error is thrown. + * @param {Error} err + * @private + */ + textmode._onError = function(err) { + // TODO: onError is deprecated since version 2.2.0. cleanup some day + if (typeof this.onError === 'function') { + util.log('WARNING: JSONEditor.onError is deprecated. ' + + 'Use options.error instead.'); + this.onError(err); + } + + if (this.options && typeof this.options.error === 'function') { + this.options.error(err); + } + else { + throw err; + } + }; + + /** + * Compact the code in the formatter + */ + textmode.compact = function () { + var json = this.get(); + var text = JSON.stringify(json); + this.setText(text); + }; + + /** + * Format the code in the formatter + */ + textmode.format = function () { + var json = this.get(); + var text = JSON.stringify(json, null, this.indentation); + this.setText(text); + }; + + /** + * Set focus to the formatter + */ + textmode.focus = function () { + if (this.textarea) { + this.textarea.focus(); + } + if (this.editor) { + this.editor.focus(); + } + }; + + /** + * Resize the formatter + */ + textmode.resize = function () { + if (this.editor) { + var force = false; + this.editor.resize(force); + } + }; + + /** + * Set json data in the formatter + * @param {Object} json + */ + textmode.set = function(json) { + this.setText(JSON.stringify(json, null, this.indentation)); + }; + + /** + * Get json data from the formatter + * @return {Object} json + */ + textmode.get = function() { + var text = this.getText(); + var json; + + try { + json = util.parse(text); // this can throw an error + } + catch (err) { + // try to sanitize json, replace JavaScript notation with JSON notation + text = util.sanitize(text); + this.setText(text); + + // try to parse again + json = util.parse(text); // this can throw an error + } + + return json; + }; + + /** + * Get the text contents of the editor + * @return {String} jsonText + */ + textmode.getText = function() { + if (this.textarea) { + return this.textarea.value; + } + if (this.editor) { + return this.editor.getValue(); + } + return ''; + }; + + /** + * Set the text contents of the editor + * @param {String} jsonText + */ + textmode.setText = function(jsonText) { + if (this.textarea) { + this.textarea.value = jsonText; + } + if (this.editor) { + this.editor.setValue(jsonText, -1); + } + }; + + // define modes + module.exports = [ + { + mode: 'text', + mixin: textmode, + data: 'text', + load: textmode.format + }, + { + mode: 'code', + mixin: textmode, + data: 'text', + load: textmode.format + } + ]; /***/ }, /* 3 */ /***/ function(module, exports, __webpack_require__) { - var __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_RESULT__ = function () { + /** + * 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 + * @return {JSON} json + */ + exports.parse = function parse(jsonString) { + try { + return JSON.parse(jsonString); + } + catch (err) { + // try to throw a more detailed error message using validate + exports.validate(jsonString); - // create namespace - var util = {}; + // rethrow the original error + throw err; + } + }; - /** - * 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 - * @return {JSON} json - */ - util.parse = function parse(jsonString) { - try { - return JSON.parse(jsonString); + /** + * Sanitize a JSON-like string containing. For example changes JavaScript + * notation into JSON notation. + * This function for example changes a string like "{a: 2, 'b': {c: 'd'}" + * into '{"a": 2, "b": {"c": "d"}' + * @param {string} jsString + * @returns {string} json + */ + exports.sanitize = function (jsString) { + // escape all single and double quotes inside strings + var chars = []; + var inString = false; + var i = 0; + while(i < jsString.length) { + var c = jsString.charAt(i); + var isEscaped = jsString.charAt(i - 1) === '\\'; + + if ((c === '"' || c === '\'') && !isEscaped) { + if (c === inString) { + // end of string + inString = false; + } + else if (!inString) { + // start of string + inString = c; + } + else { + // add escape character + chars.push('\\'); + } } - catch (err) { - // try to throw a more detailed error message using validate - util.validate(jsonString); - // rethrow the original error - throw err; + chars.push(c); + i++; + } + var jsonString = chars.join(''); + + // replace unescaped single quotes with double quotes, + // and replace escaped single quotes with unescaped single quotes + // TODO: we could do this step immediately in the previous step + jsonString = jsonString.replace(/(.?)'/g, function ($0, $1) { + return ($1 == '\\') ? '\'' : $1 + '"'; + }); + + // enclose unquoted object keys with double quotes + jsonString = jsonString.replace(/([{,]\s*)([a-zA-Z_][a-zA-Z0-9_]*)(\s*:)/g, function ($0, $1, $2, $3) { + return $1 + '"' + $2 + '"' + $3; + }); + + return jsonString; + }; + + /** + * 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 + * @throws Error + */ + exports.validate = function validate(jsonString) { + if (typeof(jsonlint) != 'undefined') { + jsonlint.parse(jsonString); + } + else { + JSON.parse(jsonString); + } + }; + + /** + * Extend object a with the properties of object b + * @param {Object} a + * @param {Object} b + * @return {Object} a + */ + exports.extend = function extend(a, b) { + for (var prop in b) { + if (b.hasOwnProperty(prop)) { + a[prop] = b[prop]; } - }; + } + return a; + }; - /** - * Sanitize a JSON-like string containing. For example changes JavaScript - * notation into JSON notation. - * This function for example changes a string like "{a: 2, 'b': {c: 'd'}" - * into '{"a": 2, "b": {"c": "d"}' - * @param {string} jsString - * @returns {string} json - */ - util.sanitize = function (jsString) { - // escape all single and double quotes inside strings - var chars = []; - var inString = false; - var i = 0; - while(i < jsString.length) { - var c = jsString.charAt(i); - var isEscaped = jsString.charAt(i - 1) === '\\'; + /** + * Remove all properties from object a + * @param {Object} a + * @return {Object} a + */ + exports.clear = function clear (a) { + for (var prop in a) { + if (a.hasOwnProperty(prop)) { + delete a[prop]; + } + } + return a; + }; - if ((c === '"' || c === '\'') && !isEscaped) { - if (c === inString) { - // end of string - inString = false; - } - else if (!inString) { - // start of string - inString = c; - } - else { - // add escape character - chars.push('\\'); + /** + * Output text to the console, if console is available + * @param {...*} args + */ + exports.log = function log (args) { + if (typeof console !== 'undefined' && typeof console.log === 'function') { + console.log.apply(console, arguments); + } + }; + + /** + * Get the type of an object + * @param {*} object + * @return {String} type + */ + exports.type = function type (object) { + if (object === null) { + return 'null'; + } + if (object === undefined) { + return 'undefined'; + } + if ((object instanceof Number) || (typeof object === 'number')) { + return 'number'; + } + if ((object instanceof String) || (typeof object === 'string')) { + return 'string'; + } + if ((object instanceof Boolean) || (typeof object === 'boolean')) { + return 'boolean'; + } + if ((object instanceof RegExp) || (typeof object === 'regexp')) { + return 'regexp'; + } + if (exports.isArray(object)) { + return 'array'; + } + + return 'object'; + }; + + /** + * Test whether a text contains a url (matches when a string starts + * with 'http://*' or 'https://*' and has no whitespace characters) + * @param {String} text + */ + var isUrlRegex = /^https?:\/\/\S+$/; + exports.isUrl = function isUrl (text) { + return (typeof text == 'string' || text instanceof String) && + isUrlRegex.test(text); + }; + + /** + * Tes whether given object is an Array + * @param {*} obj + * @returns {boolean} returns true when obj is an array + */ + exports.isArray = function (obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; + }; + + /** + * 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. + */ + exports.getAbsoluteLeft = function getAbsoluteLeft(elem) { + var rect = elem.getBoundingClientRect(); + return rect.left + window.pageXOffset || document.scrollLeft || 0; + }; + + /** + * 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. + */ + exports.getAbsoluteTop = function getAbsoluteTop(elem) { + var rect = elem.getBoundingClientRect(); + return rect.top + window.pageYOffset || document.scrollTop || 0; + }; + + /** + * add a className to the given elements style + * @param {Element} elem + * @param {String} className + */ + exports.addClassName = function addClassName(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 + */ + exports.removeClassName = function removeClassName(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 + */ + exports.stripFormatting = function stripFormatting(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); } } - - chars.push(c); - i++; - } - var jsonString = chars.join(''); - - // replace unescaped single quotes with double quotes, - // and replace escaped single quotes with unescaped single quotes - // TODO: we could do this step immediately in the previous step - jsonString = jsonString.replace(/(.?)'/g, function ($0, $1) { - return ($1 == '\\') ? '\'' : $1 + '"'; - }); - - // enclose unquoted object keys with double quotes - jsonString = jsonString.replace(/([{,]\s*)([a-zA-Z_][a-zA-Z0-9_]*)(\s*:)/g, function ($0, $1, $2, $3) { - return $1 + '"' + $2 + '"' + $3; - }); - - return jsonString; - }; - - /** - * 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 - * @throws Error - */ - util.validate = function validate(jsonString) { - if (typeof(jsonlint) != 'undefined') { - jsonlint.parse(jsonString); - } - else { - JSON.parse(jsonString); - } - }; - - /** - * Extend object a with the properties of object b - * @param {Object} a - * @param {Object} b - * @return {Object} a - */ - util.extend = function extend(a, b) { - for (var prop in b) { - if (b.hasOwnProperty(prop)) { - a[prop] = b[prop]; - } - } - return a; - }; - - /** - * Remove all properties from object a - * @param {Object} a - * @return {Object} a - */ - util.clear = function clear (a) { - for (var prop in a) { - if (a.hasOwnProperty(prop)) { - delete a[prop]; - } - } - return a; - }; - - /** - * Output text to the console, if console is available - * @param {...*} args - */ - util.log = function log (args) { - if (typeof console !== 'undefined' && typeof console.log === 'function') { - console.log.apply(console, arguments); - } - }; - - /** - * Get the type of an object - * @param {*} object - * @return {String} type - */ - util.type = function type (object) { - if (object === null) { - return 'null'; - } - if (object === undefined) { - return 'undefined'; - } - if ((object instanceof Number) || (typeof object === 'number')) { - return 'number'; - } - if ((object instanceof String) || (typeof object === 'string')) { - return 'string'; - } - if ((object instanceof Boolean) || (typeof object === 'boolean')) { - return 'boolean'; - } - if ((object instanceof RegExp) || (typeof object === 'regexp')) { - return 'regexp'; - } - if (util.isArray(object)) { - return 'array'; } - return 'object'; - }; + // recursively strip childs + exports.stripFormatting(child); + } + }; - /** - * Test whether a text contains a url (matches when a string starts - * with 'http://*' or 'https://*' and has no whitespace characters) - * @param {String} text - */ - var isUrlRegex = /^https?:\/\/\S+$/; - util.isUrl = function isUrl (text) { - return (typeof text == 'string' || text instanceof String) && - isUrlRegex.test(text); - }; + /** + * 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 A content editable div + */ + exports.setEndOfContentEditable = function setEndOfContentEditable(contentEditableElement) { + var range, selection; + if(document.createRange) { + 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 + } + }; - /** - * Tes whether given object is an Array - * @param {*} obj - * @returns {boolean} returns true when obj is an array - */ - util.isArray = function (obj) { - return Object.prototype.toString.call(obj) === '[object Array]'; - }; + /** + * Select all text of a content editable div. + * http://stackoverflow.com/a/3806004/1262753 + * @param {Element} contentEditableElement A content editable div + */ + exports.selectContentEditable = function selectContentEditable(contentEditableElement) { + if (!contentEditableElement || contentEditableElement.nodeName != 'DIV') { + return; + } - /** - * 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. - */ - util.getAbsoluteLeft = function getAbsoluteLeft(elem) { - var rect = elem.getBoundingClientRect(); - return rect.left + window.pageXOffset || document.scrollLeft || 0; - }; + var sel, range; + if (window.getSelection && document.createRange) { + range = document.createRange(); + range.selectNodeContents(contentEditableElement); + sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + } + }; - /** - * 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. - */ - util.getAbsoluteTop = function getAbsoluteTop(elem) { - var rect = elem.getBoundingClientRect(); - return rect.top + window.pageYOffset || document.scrollTop || 0; - }; - - /** - * add a className to the given elements style - * @param {Element} elem - * @param {String} className - */ - util.addClassName = function addClassName(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(' '); + /** + * Get text selection + * http://stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore + * @return {Range | TextRange | null} range + */ + exports.getSelection = function getSelection() { + if (window.getSelection) { + var sel = window.getSelection(); + if (sel.getRangeAt && sel.rangeCount) { + return sel.getRangeAt(0); } - }; + } + return null; + }; - /** - * add a className to the given elements style - * @param {Element} elem - * @param {String} className - */ - util.removeClassName = function removeClassName(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 - */ - util.stripFormatting = function stripFormatting(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 - util.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 A content editable div - */ - util.setEndOfContentEditable = function setEndOfContentEditable(contentEditableElement) { - var range, selection; - if(document.createRange) { - 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 - } - }; - - /** - * Select all text of a content editable div. - * http://stackoverflow.com/a/3806004/1262753 - * @param {Element} contentEditableElement A content editable div - */ - util.selectContentEditable = function selectContentEditable(contentEditableElement) { - if (!contentEditableElement || contentEditableElement.nodeName != 'DIV') { - return; - } - - var sel, range; - if (window.getSelection && document.createRange) { - range = document.createRange(); - range.selectNodeContents(contentEditableElement); - sel = window.getSelection(); + /** + * Set text selection + * http://stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore + * @param {Range | TextRange | null} range + */ + exports.setSelection = function setSelection(range) { + if (range) { + if (window.getSelection) { + var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); } - }; + } + }; - /** - * Get text selection - * http://stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore - * @return {Range | TextRange | null} range - */ - util.getSelection = function getSelection() { - if (window.getSelection) { - var sel = window.getSelection(); - if (sel.getRangeAt && sel.rangeCount) { - return sel.getRangeAt(0); + /** + * Get selected text range + * @return {Object} params object containing parameters: + * {Number} startOffset + * {Number} endOffset + * {Element} container HTML element holding the + * selected text element + * Returns null if no text selection is found + */ + exports.getSelectionOffset = function getSelectionOffset() { + var range = exports.getSelection(); + + if (range && 'startOffset' in range && 'endOffset' in range && + range.startContainer && (range.startContainer == range.endContainer)) { + return { + startOffset: range.startOffset, + endOffset: range.endOffset, + container: range.startContainer.parentNode + }; + } + + return null; + }; + + /** + * Set selected text range in given element + * @param {Object} params An object containing: + * {Element} container + * {Number} startOffset + * {Number} endOffset + */ + exports.setSelectionOffset = function setSelectionOffset(params) { + if (document.createRange && window.getSelection) { + var selection = window.getSelection(); + if(selection) { + var range = document.createRange(); + // TODO: do not suppose that the first child of the container is a textnode, + // but recursively find the textnodes + range.setStart(params.container.firstChild, params.startOffset); + range.setEnd(params.container.firstChild, params.endOffset); + + exports.setSelection(range); + } + } + }; + + /** + * Get the inner text of an HTML element (for example a div element) + * @param {Element} element + * @param {Object} [buffer] + * @return {String} innerText + */ + exports.getInnerText = function getInnerText(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; } - } - return null; - }; + }; + } - /** - * Set text selection - * http://stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore - * @param {Range | TextRange | null} range - */ - util.setSelection = function setSelection(range) { - if (range) { - if (window.getSelection) { - var sel = window.getSelection(); - sel.removeAllRanges(); - sel.addRange(range); - } - } - }; + // text node + if (element.nodeValue) { + return buffer.flush() + element.nodeValue; + } - /** - * Get selected text range - * @return {Object} params object containing parameters: - * {Number} startOffset - * {Number} endOffset - * {Element} container HTML element holding the - * selected text element - * Returns null if no text selection is found - */ - util.getSelectionOffset = function getSelectionOffset() { - var range = util.getSelection(); + // divs or other HTML elements + if (element.hasChildNodes()) { + var childNodes = element.childNodes; + var innerText = ''; - if (range && 'startOffset' in range && 'endOffset' in range && - range.startContainer && (range.startContainer == range.endContainer)) { - return { - startOffset: range.startOffset, - endOffset: range.endOffset, - container: range.startContainer.parentNode - }; - } + for (var i = 0, iMax = childNodes.length; i < iMax; i++) { + var child = childNodes[i]; - return null; - }; - - /** - * Set selected text range in given element - * @param {Object} params An object containing: - * {Element} container - * {Number} startOffset - * {Number} endOffset - */ - util.setSelectionOffset = function setSelectionOffset(params) { - if (document.createRange && window.getSelection) { - var selection = window.getSelection(); - if(selection) { - var range = document.createRange(); - // TODO: do not suppose that the first child of the container is a textnode, - // but recursively find the textnodes - range.setStart(params.container.firstChild, params.startOffset); - range.setEnd(params.container.firstChild, params.endOffset); - - util.setSelection(range); - } - } - }; - - /** - * Get the inner text of an HTML element (for example a div element) - * @param {Element} element - * @param {Object} [buffer] - * @return {String} innerText - */ - util.getInnerText = function getInnerText(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 += util.getInnerText(child, buffer); - buffer.set('\n'); - } - else if (child.nodeName == 'BR') { - innerText += buffer.flush(); - buffer.set('\n'); - } - else { - innerText += util.getInnerText(child, buffer); + 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 += exports.getInnerText(child, buffer); + buffer.set('\n'); } - - return innerText; - } - else { - if (element.nodeName == 'P' && util.getInternetExplorerVersion() != -1) { - // On Internet Explorer, a

with hasChildNodes()==false is - // rendered with a new line. Note that a

with - // hasChildNodes()==true is rendered without a new line - // Other browsers always ensure there is a
inside the

, - // and if not, the

does not render a new line - return buffer.flush(); + else if (child.nodeName == 'BR') { + innerText += buffer.flush(); + buffer.set('\n'); + } + else { + innerText += exports.getInnerText(child, buffer); } } - // br or unknown - return ''; - }; + return innerText; + } + else { + if (element.nodeName == 'P' && exports.getInternetExplorerVersion() != -1) { + // On Internet Explorer, a

with hasChildNodes()==false is + // rendered with a new line. Note that a

with + // hasChildNodes()==true is rendered without a new line + // Other browsers always ensure there is a
inside the

, + // and if not, the

does not render a new line + return buffer.flush(); + } + } - /** - * 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 - */ - util.getInternetExplorerVersion = function getInternetExplorerVersion() { - if (_ieVersion == -1) { - 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 ); - } + // 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 + */ + exports.getInternetExplorerVersion = function getInternetExplorerVersion() { + if (_ieVersion == -1) { + 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 ); } - - _ieVersion = rv; } - return _ieVersion; - }; + _ieVersion = rv; + } - /** - * Test whether the current browser is Firefox - * @returns {boolean} isFirefox - */ - util.isFirefox = function isFirefox () { - return (navigator.userAgent.indexOf("Firefox") != -1); - }; + return _ieVersion; + }; - /** - * cached internet explorer version - * @type {Number} - * @private - */ - var _ieVersion = -1; + /** + * Test whether the current browser is Firefox + * @returns {boolean} isFirefox + */ + exports.isFirefox = function isFirefox () { + return (navigator.userAgent.indexOf("Firefox") != -1); + }; - /** - * 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] false by default - * @return {function} the created event listener - */ - util.addEventListener = function addEventListener(element, action, listener, useCapture) { - if (element.addEventListener) { - if (useCapture === undefined) - useCapture = false; + /** + * cached internet explorer version + * @type {Number} + * @private + */ + var _ieVersion = -1; - if (action === "mousewheel" && util.isFirefox()) { - action = "DOMMouseScroll"; // For Firefox - } + /** + * 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] false by default + * @return {function} the created event listener + */ + exports.addEventListener = function addEventListener(element, action, listener, useCapture) { + if (element.addEventListener) { + if (useCapture === undefined) + useCapture = false; - element.addEventListener(action, listener, useCapture); - return listener; - } else if (element.attachEvent) { - // Old IE browsers - var f = function () { - return listener.call(element, window.event); - }; - element.attachEvent("on" + action, f); - return f; + if (action === "mousewheel" && exports.isFirefox()) { + action = "DOMMouseScroll"; // For Firefox } - }; - /** - * 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] false by default - */ - util.removeEventListener = function removeEventListener(element, action, listener, useCapture) { - if (element.removeEventListener) { - if (useCapture === undefined) - useCapture = false; + element.addEventListener(action, listener, useCapture); + return listener; + } else if (element.attachEvent) { + // Old IE browsers + var f = function () { + return listener.call(element, window.event); + }; + element.attachEvent("on" + action, f); + return f; + } + }; - if (action === "mousewheel" && util.isFirefox()) { - action = "DOMMouseScroll"; // For Firefox - } + /** + * 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] false by default + */ + exports.removeEventListener = function removeEventListener(element, action, listener, useCapture) { + if (element.removeEventListener) { + if (useCapture === undefined) + useCapture = false; - element.removeEventListener(action, listener, useCapture); - } else if (element.detachEvent) { - // Old IE browsers - element.detachEvent("on" + action, listener); + if (action === "mousewheel" && exports.isFirefox()) { + action = "DOMMouseScroll"; // For Firefox } - }; - return util; - }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + element.removeEventListener(action, listener, useCapture); + } else if (element.detachEvent) { + // Old IE browsers + element.detachEvent("on" + action, listener); + } + }; + /***/ }, /* 4 */ /***/ function(module, exports, __webpack_require__) { - var __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_RESULT__ = function () { + /** + * The highlighter can highlight/unhighlight a node, and + * animate the visibility of a context menu. + * @constructor Highlighter + */ + function Highlighter () { + this.locked = false; + } - /** - * The highlighter can highlight/unhighlight a node, and - * animate the visibility of a context menu. - * @constructor Highlighter - */ - function Highlighter () { - this.locked = false; + /** + * Hightlight given node and its childs + * @param {Node} node + */ + Highlighter.prototype.highlight = function (node) { + if (this.locked) { + return; } - /** - * Hightlight given node and its childs - * @param {Node} node - */ - Highlighter.prototype.highlight = function (node) { - if (this.locked) { - return; - } - - if (this.node != node) { - // unhighlight current node - if (this.node) { - this.node.setHighlight(false); - } - - // highlight new node - this.node = node; - this.node.setHighlight(true); - } - - // cancel any current timeout - this._cancelUnhighlight(); - }; - - /** - * Unhighlight currently highlighted node. - * Will be done after a delay - */ - Highlighter.prototype.unhighlight = function () { - if (this.locked) { - return; - } - - var me = this; + if (this.node != node) { + // unhighlight current node if (this.node) { - this._cancelUnhighlight(); - - // do the unhighlighting after a small delay, to prevent re-highlighting - // the same node when moving from the drag-icon to the contextmenu-icon - // or vice versa. - this.unhighlightTimer = setTimeout(function () { - me.node.setHighlight(false); - me.node = undefined; - me.unhighlightTimer = undefined; - }, 0); + this.node.setHighlight(false); } - }; - /** - * Cancel an unhighlight action (if before the timeout of the unhighlight action) - * @private - */ - Highlighter.prototype._cancelUnhighlight = function () { - if (this.unhighlightTimer) { - clearTimeout(this.unhighlightTimer); - this.unhighlightTimer = undefined; - } - }; + // highlight new node + this.node = node; + this.node.setHighlight(true); + } - /** - * Lock highlighting or unhighlighting nodes. - * methods highlight and unhighlight do not work while locked. - */ - Highlighter.prototype.lock = function () { - this.locked = true; - }; + // cancel any current timeout + this._cancelUnhighlight(); + }; - /** - * Unlock highlighting or unhighlighting nodes - */ - Highlighter.prototype.unlock = function () { - this.locked = false; - }; + /** + * Unhighlight currently highlighted node. + * Will be done after a delay + */ + Highlighter.prototype.unhighlight = function () { + if (this.locked) { + return; + } + + var me = this; + if (this.node) { + this._cancelUnhighlight(); + + // do the unhighlighting after a small delay, to prevent re-highlighting + // the same node when moving from the drag-icon to the contextmenu-icon + // or vice versa. + this.unhighlightTimer = setTimeout(function () { + me.node.setHighlight(false); + me.node = undefined; + me.unhighlightTimer = undefined; + }, 0); + } + }; + + /** + * Cancel an unhighlight action (if before the timeout of the unhighlight action) + * @private + */ + Highlighter.prototype._cancelUnhighlight = function () { + if (this.unhighlightTimer) { + clearTimeout(this.unhighlightTimer); + this.unhighlightTimer = undefined; + } + }; + + /** + * Lock highlighting or unhighlighting nodes. + * methods highlight and unhighlight do not work while locked. + */ + Highlighter.prototype.lock = function () { + this.locked = true; + }; + + /** + * Unlock highlighting or unhighlighting nodes + */ + Highlighter.prototype.unlock = function () { + this.locked = false; + }; + + module.exports = Highlighter; - return Highlighter; - }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); /***/ }, /* 5 */ /***/ function(module, exports, __webpack_require__) { - var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(3)], __WEBPACK_AMD_DEFINE_RESULT__ = function (util) { + var util = __webpack_require__(3); - /** - * @constructor History - * Store action history, enables undo and redo - * @param {JSONEditor} editor - */ - function History (editor) { - this.editor = editor; - this.clear(); + /** + * @constructor History + * Store action history, enables undo and redo + * @param {JSONEditor} editor + */ + function History (editor) { + this.editor = editor; + this.clear(); - // map with all supported actions - this.actions = { - 'editField': { - 'undo': function (params) { - params.node.updateField(params.oldValue); - }, - 'redo': function (params) { - params.node.updateField(params.newValue); - } + // map with all supported actions + this.actions = { + 'editField': { + 'undo': function (params) { + params.node.updateField(params.oldValue); }, - 'editValue': { - 'undo': function (params) { - params.node.updateValue(params.oldValue); - }, - 'redo': function (params) { - params.node.updateValue(params.newValue); - } - }, - 'appendNode': { - 'undo': function (params) { - params.parent.removeChild(params.node); - }, - 'redo': function (params) { - params.parent.appendChild(params.node); - } - }, - 'insertBeforeNode': { - 'undo': function (params) { - params.parent.removeChild(params.node); - }, - 'redo': function (params) { - params.parent.insertBefore(params.node, params.beforeNode); - } - }, - 'insertAfterNode': { - 'undo': function (params) { - params.parent.removeChild(params.node); - }, - 'redo': function (params) { - params.parent.insertAfter(params.node, params.afterNode); - } - }, - 'removeNode': { - 'undo': function (params) { - var parent = params.parent; - var beforeNode = parent.childs[params.index] || parent.append; - parent.insertBefore(params.node, beforeNode); - }, - 'redo': function (params) { - params.parent.removeChild(params.node); - } - }, - 'duplicateNode': { - 'undo': function (params) { - params.parent.removeChild(params.clone); - }, - 'redo': function (params) { - params.parent.insertAfter(params.clone, params.node); - } - }, - 'changeType': { - 'undo': function (params) { - params.node.changeType(params.oldType); - }, - 'redo': function (params) { - params.node.changeType(params.newType); - } - }, - 'moveNode': { - 'undo': function (params) { - params.startParent.moveTo(params.node, params.startIndex); - }, - 'redo': function (params) { - params.endParent.moveTo(params.node, params.endIndex); - } - }, - 'sort': { - 'undo': function (params) { - var node = params.node; - node.hideChilds(); - node.sort = params.oldSort; - node.childs = params.oldChilds; - node.showChilds(); - }, - 'redo': function (params) { - var node = params.node; - node.hideChilds(); - node.sort = params.newSort; - node.childs = params.newChilds; - node.showChilds(); - } + 'redo': function (params) { + params.node.updateField(params.newValue); } + }, + 'editValue': { + 'undo': function (params) { + params.node.updateValue(params.oldValue); + }, + 'redo': function (params) { + params.node.updateValue(params.newValue); + } + }, + 'appendNode': { + 'undo': function (params) { + params.parent.removeChild(params.node); + }, + 'redo': function (params) { + params.parent.appendChild(params.node); + } + }, + 'insertBeforeNode': { + 'undo': function (params) { + params.parent.removeChild(params.node); + }, + 'redo': function (params) { + params.parent.insertBefore(params.node, params.beforeNode); + } + }, + 'insertAfterNode': { + 'undo': function (params) { + params.parent.removeChild(params.node); + }, + 'redo': function (params) { + params.parent.insertAfter(params.node, params.afterNode); + } + }, + 'removeNode': { + 'undo': function (params) { + var parent = params.parent; + var beforeNode = parent.childs[params.index] || parent.append; + parent.insertBefore(params.node, beforeNode); + }, + 'redo': function (params) { + params.parent.removeChild(params.node); + } + }, + 'duplicateNode': { + 'undo': function (params) { + params.parent.removeChild(params.clone); + }, + 'redo': function (params) { + params.parent.insertAfter(params.clone, params.node); + } + }, + 'changeType': { + 'undo': function (params) { + params.node.changeType(params.oldType); + }, + 'redo': function (params) { + params.node.changeType(params.newType); + } + }, + 'moveNode': { + 'undo': function (params) { + params.startParent.moveTo(params.node, params.startIndex); + }, + 'redo': function (params) { + params.endParent.moveTo(params.node, params.endIndex); + } + }, + 'sort': { + 'undo': function (params) { + var node = params.node; + node.hideChilds(); + node.sort = params.oldSort; + node.childs = params.oldChilds; + node.showChilds(); + }, + 'redo': function (params) { + var node = params.node; + node.hideChilds(); + node.sort = params.newSort; + node.childs = params.newChilds; + node.showChilds(); + } + } - // TODO: restore the original caret position and selection with each undo - // TODO: implement history for actions "expand", "collapse", "scroll", "setDocument" - }; + // 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. + */ + 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. + */ + 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); } - /** - * The method onChange is executed when the History is changed, and can - * be overloaded. - */ - History.prototype.onChange = function () {}; + // fire onchange event + this.onChange(); + }; - /** - * 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. - */ - History.prototype.add = function (action, params) { + /** + * Clear history + */ + 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 + */ + History.prototype.canUndo = function () { + return (this.index >= 0); + }; + + /** + * Check if there is an action available for redo + * @return {Boolean} canRedo + */ + History.prototype.canRedo = function () { + return (this.index < this.history.length - 1); + }; + + /** + * Undo the last action + */ + 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.params); + if (obj.params.oldSelection) { + this.editor.setSelection(obj.params.oldSelection); + } + } + else { + util.log('Error: unknown action "' + obj.action + '"'); + } + } + this.index--; + + // fire onchange event + this.onChange(); + } + }; + + /** + * Redo the last action + */ + History.prototype.redo = function () { + if (this.canRedo()) { 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); + var obj = this.history[this.index]; + if (obj) { + var action = this.actions[obj.action]; + if (action && action.redo) { + action.redo(obj.params); + if (obj.params.newSelection) { + this.editor.setSelection(obj.params.newSelection); + } + } + else { + util.log('Error: unknown action "' + obj.action + '"'); + } } // fire onchange event this.onChange(); - }; + } + }; - /** - * Clear history - */ - 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 - */ - History.prototype.canUndo = function () { - return (this.index >= 0); - }; - - /** - * Check if there is an action available for redo - * @return {Boolean} canRedo - */ - History.prototype.canRedo = function () { - return (this.index < this.history.length - 1); - }; - - /** - * Undo the last action - */ - 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.params); - if (obj.params.oldSelection) { - this.editor.setSelection(obj.params.oldSelection); - } - } - else { - util.log('Error: unknown action "' + obj.action + '"'); - } - } - this.index--; - - // fire onchange event - this.onChange(); - } - }; - - /** - * Redo the last action - */ - History.prototype.redo = function () { - if (this.canRedo()) { - this.index++; - - var obj = this.history[this.index]; - if (obj) { - var action = this.actions[obj.action]; - if (action && action.redo) { - action.redo(obj.params); - if (obj.params.newSelection) { - this.editor.setSelection(obj.params.newSelection); - } - } - else { - util.log('Error: unknown action "' + obj.action + '"'); - } - } - - // fire onchange event - this.onChange(); - } - }; - - return History; - }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + module.exports = History; /***/ }, /* 6 */ /***/ function(module, exports, __webpack_require__) { - var __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_RESULT__ = function () { + /** + * @constructor 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 + */ + function SearchBox (editor, container) { + var searchBox = this; - /** - * @constructor 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 - */ - function SearchBox (editor, container) { - var searchBox = this; + this.editor = editor; + this.timeout = undefined; + this.delay = 200; // ms + this.lastText = undefined; - this.editor = editor; - this.timeout = undefined; - this.delay = 200; // ms - this.lastText = undefined; + this.dom = {}; + this.dom.container = container; - this.dom = {}; - this.dom.container = container; + var table = document.createElement('table'); + this.dom.table = table; + table.className = '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 table = document.createElement('table'); - this.dom.table = table; - table.className = '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'); + tr.appendChild(td); + var results = document.createElement('div'); + this.dom.results = results; + results.className = 'results'; + td.appendChild(results); - var td = document.createElement('td'); - tr.appendChild(td); - var results = document.createElement('div'); - this.dom.results = results; - results.className = 'results'; - td.appendChild(results); + td = document.createElement('td'); + tr.appendChild(td); + var divInput = document.createElement('div'); + this.dom.input = divInput; + divInput.className = 'frame'; + divInput.title = 'Search fields and values'; + td.appendChild(divInput); - td = document.createElement('td'); - tr.appendChild(td); - var divInput = document.createElement('div'); - this.dom.input = divInput; - divInput.className = 'frame'; - divInput.title = 'Search fields and values'; - td.appendChild(divInput); + // table to contain the text input and search button + var tableInput = document.createElement('table'); + divInput.appendChild(tableInput); + var tbodySearch = document.createElement('tbody'); + tableInput.appendChild(tbodySearch); + tr = document.createElement('tr'); + tbodySearch.appendChild(tr); - // table to contain the text input and search button - var tableInput = document.createElement('table'); - 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 = 'refresh'; + td = document.createElement('td'); + td.appendChild(refreshSearch); + tr.appendChild(td); - var refreshSearch = document.createElement('button'); - refreshSearch.className = 'refresh'; - td = document.createElement('td'); - td.appendChild(refreshSearch); - tr.appendChild(td); + var search = document.createElement('input'); + this.dom.search = search; + search.oninput = function (event) { + searchBox._onDelayedSearch(event); + }; + search.onchange = function (event) { // For IE 9 + searchBox._onSearch(event); + }; + search.onkeydown = function (event) { + searchBox._onKeyDown(event); + }; + search.onkeyup = function (event) { + searchBox._onKeyUp(event); + }; + refreshSearch.onclick = function (event) { + search.select(); + }; - var search = document.createElement('input'); - this.dom.search = search; - search.oninput = function (event) { - searchBox._onDelayedSearch(event); - }; - search.onchange = function (event) { // For IE 9 - 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); - // 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 = 'Next result (Enter)'; + searchNext.className = 'next'; + searchNext.onclick = function () { + searchBox.next(); + }; + td = document.createElement('td'); + td.appendChild(searchNext); + tr.appendChild(td); - var searchNext = document.createElement('button'); - searchNext.title = 'Next result (Enter)'; - searchNext.className = 'next'; - searchNext.onclick = function () { - searchBox.next(); - }; - td = document.createElement('td'); - td.appendChild(searchNext); - tr.appendChild(td); + var searchPrevious = document.createElement('button'); + searchPrevious.title = 'Previous result (Shift+Enter)'; + searchPrevious.className = 'previous'; + searchPrevious.onclick = function () { + searchBox.previous(); + }; + td = document.createElement('td'); + td.appendChild(searchPrevious); + tr.appendChild(td); + } - var searchPrevious = document.createElement('button'); - searchPrevious.title = 'Previous result (Shift+Enter)'; - searchPrevious.className = 'previous'; - searchPrevious.onclick = function () { - searchBox.previous(); - }; - td = document.createElement('td'); - td.appendChild(searchPrevious); - tr.appendChild(td); + /** + * Go to the next search result + * @param {boolean} [focus] If true, focus will be set to the next result + * focus is false by default. + */ + SearchBox.prototype.next = function(focus) { + if (this.results != undefined) { + var index = (this.resultIndex != undefined) ? this.resultIndex + 1 : 0; + if (index > this.results.length - 1) { + index = 0; + } + this._setActiveResult(index, focus); } + }; - /** - * Go to the next search result - * @param {boolean} [focus] If true, focus will be set to the next result - * focus is false by default. - */ - SearchBox.prototype.next = function(focus) { - if (this.results != undefined) { - var index = (this.resultIndex != undefined) ? this.resultIndex + 1 : 0; - if (index > this.results.length - 1) { - index = 0; - } - this._setActiveResult(index, focus); + /** + * Go to the prevous search result + * @param {boolean} [focus] If true, focus will be set to the next result + * focus is false by default. + */ + SearchBox.prototype.previous = function(focus) { + 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, focus); + } + }; - /** - * Go to the prevous search result - * @param {boolean} [focus] If true, focus will be set to the next result - * focus is false by default. - */ - SearchBox.prototype.previous = function(focus) { - 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, focus); - } - }; - - /** - * Set new value for the current active result - * @param {Number} index - * @param {boolean} [focus] If true, focus will be set to the next result. - * focus is false by default. - * @private - */ - SearchBox.prototype._setActiveResult = function(index, focus) { - // 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; + /** + * Set new value for the current active result + * @param {Number} index + * @param {boolean} [focus] If true, focus will be set to the next result. + * focus is false by default. + * @private + */ + SearchBox.prototype._setActiveResult = function(index, focus) { + // 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 { - node.searchValueActive = true; + delete prevNode.searchValueActive; } - this.activeResult = this.results[this.resultIndex]; - node.updateDom(); + prevNode.updateDom(); + } - // TODO: not so nice that the focus is only set after the animation is finished - node.scrollTo(function () { - if (focus) { - node.focus(elem); - } - }); - }; + if (!this.results || !this.results[index]) { + // out of range, set to undefined + this.resultIndex = undefined; + this.activeResult = undefined; + return; + } - /** - * Cancel any running onDelayedSearch. - * @private - */ - SearchBox.prototype._clearDelay = function() { - if (this.timeout != undefined) { - clearTimeout(this.timeout); - delete this.timeout; + 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(); + + // TODO: not so nice that the focus is only set after the animation is finished + node.scrollTo(function () { + if (focus) { + node.focus(elem); } - }; + }); + }; - /** - * Start a timer to execute a search after a short delay. - * Used for reducing the number of searches while typing. - * @param {Event} event - * @private - */ - 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); - }; + /** + * Cancel any running onDelayedSearch. + * @private + */ + SearchBox.prototype._clearDelay = function() { + if (this.timeout != undefined) { + clearTimeout(this.timeout); + delete this.timeout; + } + }; - /** - * 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. - * @private - */ - SearchBox.prototype._onSearch = function (event, forceSearch) { - this._clearDelay(); + /** + * Start a timer to execute a search after a short delay. + * Used for reducing the number of searches while typing. + * @param {Event} event + * @private + */ + 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); + }; - 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); + /** + * 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. + * @private + */ + SearchBox.prototype._onSearch = function (event, forceSearch) { + this._clearDelay(); - // display search results - if (text != undefined) { - var resultCount = this.results.length; - switch (resultCount) { - case 0: this.dom.results.innerHTML = 'no results'; break; - case 1: this.dom.results.innerHTML = '1 result'; break; - default: this.dom.results.innerHTML = resultCount + ' results'; break; - } - } - else { - this.dom.results.innerHTML = ''; + 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 = 'no results'; break; + case 1: this.dom.results.innerHTML = '1 result'; break; + default: this.dom.results.innerHTML = resultCount + ' results'; break; } } - }; - - /** - * Handle onKeyDown event in the input box - * @param {Event} event - * @private - */ - SearchBox.prototype._onKeyDown = function (event) { - var keynum = event.which; - if (keynum == 27) { // ESC - this.dom.search.value = ''; // clear search - this._onSearch(event); - event.preventDefault(); - event.stopPropagation(); + else { + this.dom.results.innerHTML = ''; } - 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(); - } - event.preventDefault(); - event.stopPropagation(); + } + }; + + /** + * Handle onKeyDown event in the input box + * @param {Event} event + * @private + */ + SearchBox.prototype._onKeyDown = function (event) { + var keynum = event.which; + if (keynum == 27) { // ESC + this.dom.search.value = ''; // clear search + this._onSearch(event); + event.preventDefault(); + event.stopPropagation(); + } + else if (keynum == 13) { // Enter + if (event.ctrlKey) { + // force to search again + this._onSearch(event, true); } - }; - - /** - * Handle onKeyUp event in the input box - * @param {Event} event - * @private - */ - SearchBox.prototype._onKeyUp = function (event) { - var keynum = event.keyCode; - if (keynum != 27 && keynum != 13) { // !show and !Enter - this._onDelayedSearch(event); // For IE 9 + else if (event.shiftKey) { + // move to the previous search result + this.previous(); } - }; - - return SearchBox; - }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + else { + // move to the next search result + this.next(); + } + event.preventDefault(); + event.stopPropagation(); + } + }; + /** + * Handle onKeyUp event in the input box + * @param {Event} event + * @private + */ + SearchBox.prototype._onKeyUp = function (event) { + var keynum = event.keyCode; + if (keynum != 27 && keynum != 13) { // !show and !Enter + this._onDelayedSearch(event); // For IE 9 + } + }; + module.exports = SearchBox; /***/ }, /* 7 */ /***/ function(module, exports, __webpack_require__) { - var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(9), __webpack_require__(10), __webpack_require__(3)], __WEBPACK_AMD_DEFINE_RESULT__ = function (ContextMenu, appendNodeFactory, util) { + var ContextMenu = __webpack_require__(9); + var appendNodeFactory = __webpack_require__(10); + var util = __webpack_require__(3); - /** - * @constructor Node - * Create a new Node - * @param {TreeEditor} editor - * @param {Object} [params] Can contain parameters: - * {string} field - * {boolean} fieldEditable - * {*} value - * {String} type Can have values 'auto', 'array', - * 'object', or 'string'. - */ - function Node (editor, params) { - /** @type {TreeEditor} */ - this.editor = editor; - this.dom = {}; - this.expanded = false; + /** + * @constructor Node + * Create a new Node + * @param {TreeEditor} editor + * @param {Object} [params] Can contain parameters: + * {string} field + * {boolean} fieldEditable + * {*} value + * {String} type Can have values 'auto', 'array', + * 'object', or 'string'. + */ + function Node (editor, params) { + /** @type {TreeEditor} */ + this.editor = editor; + this.dom = {}; + this.expanded = false; - if(params && (params instanceof Object)) { - this.setField(params.field, params.fieldEditable); - this.setValue(params.value, params.type); + if(params && (params instanceof Object)) { + this.setField(params.field, params.fieldEditable); + this.setValue(params.value, params.type); + } + else { + this.setField(''); + this.setValue(null); + } + } + + /** + * Determine whether the field and/or value of this node are editable + * @private + */ + Node.prototype._updateEditability = function () { + this.editable = { + field: true, + value: true + }; + + if (this.editor) { + this.editable.field = this.editor.options.mode === 'tree'; + this.editable.value = this.editor.options.mode !== 'view'; + + if (this.editor.options.mode === 'tree' && (typeof this.editor.options.editable === 'function')) { + var editable = this.editor.options.editable({ + field: this.field, + value: this.value, + path: this.path() + }); + + if (typeof editable === 'boolean') { + this.editable.field = editable; + this.editable.value = editable; + } + else { + if (typeof editable.field === 'boolean') this.editable.field = editable.field; + if (typeof editable.value === 'boolean') this.editable.value = editable.value; + } } - else { - this.setField(''); - this.setValue(null); + } + }; + + /** + * Get the path of this node + * @return {String[]} Array containing the path to this node + */ + Node.prototype.path = function () { + var node = this; + var path = []; + while (node) { + var field = node.field != undefined ? node.field : node.index; + if (field !== undefined) { + path.unshift(field); + } + node = node.parent; + } + return path; + }; + + /** + * Set parent node + * @param {Node} parent + */ + Node.prototype.setParent = function(parent) { + this.parent = parent; + }; + + /** + * Set field + * @param {String} field + * @param {boolean} [fieldEditable] + */ + Node.prototype.setField = function(field, fieldEditable) { + this.field = field; + this.fieldEditable = (fieldEditable == true); + }; + + /** + * Get field + * @return {String} + */ + 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 + * @param {String} [type] Specify the type of the value. Can be 'auto', + * 'array', 'object', or 'string' + */ + Node.prototype.setValue = function(value, type) { + var childValue, child; + + // first clear all current childs (if any) + var childs = this.childs; + if (childs) { + while (childs.length) { + this.removeChild(childs[0]); } } - /** - * Determine whether the field and/or value of this node are editable - * @private - */ - Node.prototype._updateEditability = function () { - this.editable = { - field: true, - value: true - }; + // TODO: remove the DOM of this Node - if (this.editor) { - this.editable.field = this.editor.options.mode === 'tree'; - this.editable.value = this.editor.options.mode !== 'view'; + this.type = this._getType(value); - if (this.editor.options.mode === 'tree' && (typeof this.editor.options.editable === 'function')) { - var editable = this.editor.options.editable({ - field: this.field, - value: this.value, - path: this.path() + // check if type corresponds with the provided type + if (type && type != this.type) { + if (type == 'string' && this.type == 'auto') { + this.type = type; + } + else { + throw new Error('Type mismatch: ' + + 'cannot cast value of type "' + this.type + + ' to the specified type "' + type + '"'); + } + } + + 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 Node(this.editor, { + value: childValue }); - - if (typeof editable === 'boolean') { - this.editable.field = editable; - this.editable.value = editable; - } - else { - if (typeof editable.field === 'boolean') this.editable.field = editable.field; - if (typeof editable.value === 'boolean') this.editable.value = editable.value; - } + this.appendChild(child); } } - }; - - /** - * Get the path of this node - * @return {String[]} Array containing the path to this node - */ - Node.prototype.path = function () { - var node = this; - var path = []; - while (node) { - var field = node.field != undefined ? node.field : node.index; - if (field !== undefined) { - path.unshift(field); - } - node = node.parent; - } - return path; - }; - - /** - * Set parent node - * @param {Node} parent - */ - Node.prototype.setParent = function(parent) { - this.parent = parent; - }; - - /** - * Set field - * @param {String} field - * @param {boolean} [fieldEditable] - */ - Node.prototype.setField = function(field, fieldEditable) { - this.field = field; - this.fieldEditable = (fieldEditable == true); - }; - - /** - * Get field - * @return {String} - */ - 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 - * @param {String} [type] Specify the type of the value. Can be 'auto', - * 'array', 'object', or 'string' - */ - Node.prototype.setValue = function(value, type) { - 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); - - // check if type corresponds with the provided type - if (type && type != this.type) { - if (type == 'string' && this.type == 'auto') { - this.type = type; - } - else { - throw new Error('Type mismatch: ' + - 'cannot cast value of type "' + this.type + - ' to the specified type "' + type + '"'); - } - } - - if (this.type == 'array') { - // array - this.childs = []; - for (var i = 0, iMax = value.length; i < iMax; i++) { - childValue = value[i]; + 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 Node(this.editor, { + field: childField, 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 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); - util.log('check', value, this.value); - } - else { - this.value = value; - } - */ - } - }; - - /** - * Get value. Value is a JSON structure - * @return {*} value - */ - 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 - */ - 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 {Node} clone - */ - Node.prototype.clone = function() { - var clone = new 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 - */ - Node.prototype.expand = function(recurse) { - if (!this.childs) { - return; - } - - // set this node expanded - this.expanded = true; - if (this.dom.expand) { - this.dom.expand.className = 'expanded'; - } - - this.showChilds(); - - if (recurse != false) { - this.childs.forEach(function (child) { - child.expand(recurse); - }); - } - }; - - /** - * Collapse this node and optionally its childs. - * @param {boolean} [recurse] Optional recursion, true by default. When - * true, all childs will be collapsed recursively - */ - 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 = 'collapsed'; - } - this.expanded = false; - }; - - /** - * Recursively show all childs when they are expanded - */ - 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 - */ - 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 - */ - 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 {Node} node - */ - Node.prototype.appendChild = function(node) { - if (this._hasChilds()) { - // 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 {Node} node - * @param {Node} beforeNode - */ - Node.prototype.moveBefore = function(node, beforeNode) { - if (this._hasChilds()) { - // 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); - } - - if (node.parent) { - node.parent.removeChild(node); - } - - if (beforeNode instanceof 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 {Node} node - * @param {Number} index - */ - 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 {Node} node - * @param {Node} beforeNode - */ - Node.prototype.insertBefore = function(node, beforeNode) { - if (this._hasChilds()) { - 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('Node not found'); - } - - // 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}); - } - }; - - /** - * Insert a new child before a given node - * Only applicable when Node value is of type array or object - * @param {Node} node - * @param {Node} afterNode - */ - Node.prototype.insertAfter = function(node, afterNode) { - if (this._hasChilds()) { - var index = this.childs.indexOf(afterNode); - var beforeNode = this.childs[index + 1]; - if (beforeNode) { - this.insertBefore(node, beforeNode); - } - else { - this.appendChild(node); - } - } - }; - - /** - * 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 {Node[]} results Array with nodes containing the search text - */ - 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._hasChilds()) { - // 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 - * @param {function(boolean)} [callback] - */ - Node.prototype.scrollTo = function(callback) { - 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, callback); - } - }; - - - // stores the element name currently having the focus - Node.focusElement = undefined; - - /** - * Set focus to this node - * @param {String} [elementName] The field name of the element to get the - * focus available values: 'drag', 'menu', - * 'expand', 'field', 'value' (default) - */ - Node.prototype.focus = function(elementName) { - Node.focusElement = elementName; - - if (this.dom.tr && this.dom.tr.parentNode) { - var dom = this.dom; - - switch (elementName) { - case 'drag': - if (dom.drag) { - dom.drag.focus(); - } - else { - dom.menu.focus(); - } - break; - - case 'menu': - dom.menu.focus(); - break; - - case 'expand': - if (this._hasChilds()) { - dom.expand.focus(); - } - else if (dom.field && this.fieldEditable) { - dom.field.focus(); - util.selectContentEditable(dom.field); - } - else if (dom.value && !this._hasChilds()) { - dom.value.focus(); - util.selectContentEditable(dom.value); - } - else { - dom.menu.focus(); - } - break; - - case 'field': - if (dom.field && this.fieldEditable) { - dom.field.focus(); - util.selectContentEditable(dom.field); - } - else if (dom.value && !this._hasChilds()) { - dom.value.focus(); - util.selectContentEditable(dom.value); - } - else if (this._hasChilds()) { - dom.expand.focus(); - } - else { - dom.menu.focus(); - } - break; - - case 'value': - default: - if (dom.value && !this._hasChilds()) { - dom.value.focus(); - util.selectContentEditable(dom.value); - } - else if (dom.field && this.fieldEditable) { - dom.field.focus(); - util.selectContentEditable(dom.field); - } - else if (this._hasChilds()) { - dom.expand.focus(); - } - else { - dom.menu.focus(); - } - break; - } - } - }; - - /** - * Select all text in an editable div after a delay of 0 ms - * @param {Element} editableDiv - */ - Node.select = function(editableDiv) { - setTimeout(function () { - util.selectContentEditable(editableDiv); - }, 0); - }; - - /** - * Update the values from the DOM field and value of this node - */ - 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 {Node} node the childNode to be duplicated - * @return {Node} clone the clone of the node - * @private - */ - Node.prototype._duplicate = function(node) { - var clone = node.clone(); - - /* TODO: adjust the field name (to prevent equal field names) - if (this.type == 'object') { + 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); + util.log('check', value, this.value); + } + else { + this.value = value; } */ + } + }; - this.insertAfter(clone, node); + /** + * Get value. Value is a JSON structure + * @return {*} value + */ + Node.prototype.getValue = function() { + //var childs, i, iMax; - return clone; - }; - - /** - * Check if given node is a child. The method will check recursively to find - * this node. - * @param {Node} node - * @return {boolean} containsNode - */ - Node.prototype.containsNode = function(node) { - if (this == node) { - return true; + 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(); } - var childs = this.childs; - if (childs) { - // TODO: use the js5 Array.some() here? - for (var i = 0, iMax = childs.length; i < iMax; i++) { - if (childs[i].containsNode(node)) { - return true; - } + return this.value; + } + }; + + /** + * Get the nesting level of this node + * @return {Number} level + */ + 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 {Node} clone + */ + Node.prototype.clone = function() { + var clone = new 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 + */ + Node.prototype.expand = function(recurse) { + if (!this.childs) { + return; + } + + // set this node expanded + this.expanded = true; + if (this.dom.expand) { + this.dom.expand.className = 'expanded'; + } + + this.showChilds(); + + if (recurse != false) { + this.childs.forEach(function (child) { + child.expand(recurse); + }); + } + }; + + /** + * Collapse this node and optionally its childs. + * @param {boolean} [recurse] Optional recursion, true by default. When + * true, all childs will be collapsed recursively + */ + 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 = 'collapsed'; + } + this.expanded = false; + }; + + /** + * Recursively show all childs when they are expanded + */ + 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 + */ + 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 + */ + 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 {Node} node + */ + Node.prototype.appendChild = function(node) { + if (this._hasChilds()) { + // 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(); } - return false; - }; + this.updateDom({'updateIndexes': true}); + node.updateDom({'recurse': true}); + } + }; - /** - * Move given node into this node - * @param {Node} node the childNode to be moved - * @param {Node} beforeNode node will be inserted before given - * node. If no beforeNode is given, - * the node is appended at the end - * @private - */ - Node.prototype._move = function(node, beforeNode) { - if (node == beforeNode) { - // nothing to do... - return; + + /** + * Move a node from its current parent to this node + * Only applicable when Node value is of type array or object + * @param {Node} node + * @param {Node} beforeNode + */ + Node.prototype.moveBefore = function(node, beforeNode) { + if (this._hasChilds()) { + // 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); } - // check if this node is not a child of the node to be moved here - if (node.containsNode(this)) { - throw new Error('Cannot move a field into a child of itself'); - } - - // 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); + if (beforeNode instanceof AppendNode) { + this.appendChild(node); } else { - this.appendChild(clone); + this.insertBefore(node, beforeNode); } - /* 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 {Node} node The child node to be removed; - * @return {Node | undefined} node The removed node on success, - * else undefined - */ - 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; - } + if (tbody) { + tbody.removeChild(trTemp); } + } + }; - 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 {Node} node - * @private - */ - Node.prototype._remove = function (node) { - this.removeChild(node); - }; - - /** - * Change the type of the value of this Node - * @param {String} newType - */ - Node.prototype.changeType = function (newType) { - var oldType = this.type; - - if (oldType == newType) { - // type is not changed - return; + /** + * 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 {Node} node + * @param {Number} index + */ + 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++; } + } - if ((newType == 'string' || newType == 'auto') && - (oldType == 'string' || oldType == 'auto')) { - // this is an easy change - this.type = newType; + 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 {Node} node + * @param {Node} beforeNode + */ + Node.prototype.insertBefore = function(node, beforeNode) { + if (this._hasChilds()) { + 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 { - // 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 = ''; - } - }); - - 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; + // insert before a child node + var index = this.childs.indexOf(beforeNode); + if (index == -1) { + throw new Error('Node not found'); } - // create new DOM - if (table) { - if (nextTr) { - table.insertBefore(this.getDom(), nextTr); - } - else { - table.appendChild(this.getDom()); - } - } - this.showChilds(); + // adjust the link to the parent + node.setParent(this); + node.fieldEditable = (this.type == 'object'); + this.childs.splice(index, 0, node); } - 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)); + 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); } - this.focus(); + node.showChilds(); } this.updateDom({'updateIndexes': true}); - }; + node.updateDom({'recurse': true}); + } + }; - /** - * Retrieve value from DOM - * @param {boolean} [silent] If true (default), no errors will be thrown in - * case of invalid data - * @private - */ - Node.prototype._getDomValue = function(silent) { - if (this.dom.value && this.type != 'array' && this.type != 'object') { - this.valueInnerText = util.getInnerText(this.dom.value); + /** + * Insert a new child before a given node + * Only applicable when Node value is of type array or object + * @param {Node} node + * @param {Node} afterNode + */ + Node.prototype.insertAfter = function(node, afterNode) { + if (this._hasChilds()) { + var index = this.childs.indexOf(afterNode); + var beforeNode = this.childs[index + 1]; + if (beforeNode) { + this.insertBefore(node, beforeNode); + } + else { + this.appendChild(node); + } + } + }; + + /** + * 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 {Node[]} results Array with nodes containing the search text + */ + 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' + }); } - if (this.valueInnerText != undefined) { - try { - // retrieve the value - var value; - if (this.type == 'string') { - value = this._unescapeHTML(this.valueInnerText); + // update dom + this._updateDomField(); + } + + // search in value + if (this._hasChilds()) { + // 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 + * @param {function(boolean)} [callback] + */ + Node.prototype.scrollTo = function(callback) { + 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, callback); + } + }; + + + // stores the element name currently having the focus + Node.focusElement = undefined; + + /** + * Set focus to this node + * @param {String} [elementName] The field name of the element to get the + * focus available values: 'drag', 'menu', + * 'expand', 'field', 'value' (default) + */ + Node.prototype.focus = function(elementName) { + Node.focusElement = elementName; + + if (this.dom.tr && this.dom.tr.parentNode) { + var dom = this.dom; + + switch (elementName) { + case 'drag': + if (dom.drag) { + dom.drag.focus(); } else { - var str = this._unescapeHTML(this.valueInnerText); - value = this._stringCast(str); + dom.menu.focus(); } - if (value !== this.value) { - var oldValue = this.value; - this.value = value; - this.editor._onAction('editValue', { - 'node': this, - 'oldValue': oldValue, - 'newValue': value, - 'oldSelection': this.editor.selection, - 'newSelection': this.editor.getSelection() - }); + break; + + case 'menu': + dom.menu.focus(); + break; + + case 'expand': + if (this._hasChilds()) { + dom.expand.focus(); + } + else if (dom.field && this.fieldEditable) { + dom.field.focus(); + util.selectContentEditable(dom.field); + } + else if (dom.value && !this._hasChilds()) { + dom.value.focus(); + util.selectContentEditable(dom.value); + } + else { + dom.menu.focus(); + } + break; + + case 'field': + if (dom.field && this.fieldEditable) { + dom.field.focus(); + util.selectContentEditable(dom.field); + } + else if (dom.value && !this._hasChilds()) { + dom.value.focus(); + util.selectContentEditable(dom.value); + } + else if (this._hasChilds()) { + dom.expand.focus(); + } + else { + dom.menu.focus(); + } + break; + + case 'value': + default: + if (dom.value && !this._hasChilds()) { + dom.value.focus(); + util.selectContentEditable(dom.value); + } + else if (dom.field && this.fieldEditable) { + dom.field.focus(); + util.selectContentEditable(dom.field); + } + else if (this._hasChilds()) { + dom.expand.focus(); + } + else { + dom.menu.focus(); + } + break; + } + } + }; + + /** + * Select all text in an editable div after a delay of 0 ms + * @param {Element} editableDiv + */ + Node.select = function(editableDiv) { + setTimeout(function () { + util.selectContentEditable(editableDiv); + }, 0); + }; + + /** + * Update the values from the DOM field and value of this node + */ + 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 {Node} node the childNode to be duplicated + * @return {Node} clone the clone of the node + * @private + */ + Node.prototype._duplicate = function(node) { + var clone = node.clone(); + + /* TODO: adjust the field name (to prevent equal field names) + if (this.type == 'object') { + } + */ + + this.insertAfter(clone, node); + + return clone; + }; + + /** + * Check if given node is a child. The method will check recursively to find + * this node. + * @param {Node} node + * @return {boolean} containsNode + */ + Node.prototype.containsNode = function(node) { + if (this == node) { + return true; + } + + var childs = this.childs; + if (childs) { + // TODO: 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 {Node} node the childNode to be moved + * @param {Node} beforeNode node will be inserted before given + * node. If no beforeNode is given, + * the node is appended at the end + * @private + */ + 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('Cannot move a field into a child of itself'); + } + + // 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 {Node} node The child node to be removed; + * @return {Node | undefined} node The removed node on success, + * else undefined + */ + 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 {Node} node + * @private + */ + Node.prototype._remove = function (node) { + this.removeChild(node); + }; + + /** + * Change the type of the value of this Node + * @param {String} newType + */ + Node.prototype.changeType = function (newType) { + var oldType = this.type; + + if (oldType == newType) { + // type is not changed + return; + } + + 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 = ''; + } + }); + + 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 + */ + Node.prototype._getDomValue = function(silent) { + if (this.dom.value && this.type != 'array' && this.type != 'object') { + this.valueInnerText = util.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, + 'oldSelection': this.editor.selection, + 'newSelection': this.editor.getSelection() + }); + } + } + 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 + */ + Node.prototype._updateDomValue = function () { + var domValue = this.dom.value; + if (domValue) { + // set text color depending on value type + // TODO: put colors in css + var v = this.value; + var t = (this.type == 'auto') ? util.type(v) : this.type; + var isUrl = (t == 'string' && util.isUrl(v)); + var color = ''; + if (isUrl && !this.editable.value) { // TODO: when to apply this? + color = ''; + } + else if (t == 'string') { + color = 'green'; + } + else if (t == 'number') { + color = 'red'; + } + else if (t == 'boolean') { + color = 'darkorange'; + } + else if (this._hasChilds()) { + color = ''; + } + else if (v === null) { + color = '#004ED0'; // blue + } + else { + // invalid value + color = 'black'; + } + domValue.style.color = color; + + // make background color light-gray when empty + var isEmpty = (String(this.value) == '' && this.type != 'array' && this.type != 'object'); + if (isEmpty) { + util.addClassName(domValue, 'empty'); + } + else { + util.removeClassName(domValue, 'empty'); + } + + // underline url + if (isUrl) { + util.addClassName(domValue, 'url'); + } + else { + util.removeClassName(domValue, 'url'); + } + + // update title + if (t == 'array' || t == 'object') { + var count = this.childs ? this.childs.length : 0; + domValue.title = this.type + ' containing ' + count + ' items'; + } + else if (t == 'string' && util.isUrl(v)) { + if (this.editable.value) { + domValue.title = 'Ctrl+Click or Ctrl+Enter to open url in new window'; + } + } + else { + domValue.title = ''; + } + + // highlight when there is a search result + if (this.searchValueActive) { + util.addClassName(domValue, 'highlight-active'); + } + else { + util.removeClassName(domValue, 'highlight-active'); + } + if (this.searchValue) { + util.addClassName(domValue, 'highlight'); + } + else { + util.removeClassName(domValue, 'highlight'); + } + + // strip formatting from the contents of the editable div + util.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 + */ + Node.prototype._updateDomField = function () { + var domField = this.dom.field; + if (domField) { + // make backgound color lightgray when empty + var isEmpty = (String(this.field) == '' && this.parent.type != 'array'); + if (isEmpty) { + util.addClassName(domField, 'empty'); + } + else { + util.removeClassName(domField, 'empty'); + } + + // highlight when there is a search result + if (this.searchFieldActive) { + util.addClassName(domField, 'highlight-active'); + } + else { + util.removeClassName(domField, 'highlight-active'); + } + if (this.searchField) { + util.addClassName(domField, 'highlight'); + } + else { + util.removeClassName(domField, 'highlight'); + } + + // strip formatting from the contents of the editable div + util.stripFormatting(domField); + } + }; + + /** + * Retrieve field from DOM + * @param {boolean} [silent] If true (default), no errors will be thrown in + * case of invalid data + * @private + */ + Node.prototype._getDomField = function(silent) { + if (this.dom.field && this.fieldEditable) { + this.fieldInnerText = util.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, + 'oldSelection': this.editor.selection, + 'newSelection': this.editor.getSelection() + }); + } + } + 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 + */ + Node.prototype.clearDom = function() { + // TODO: hide the node first? + //this.hide(); + // TODO: 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 + */ + Node.prototype.getDom = function() { + var dom = this.dom; + if (dom.tr) { + return dom.tr; + } + + this._updateEditability(); + + // create row + dom.tr = document.createElement('tr'); + dom.tr.node = this; + + if (this.editor.options.mode === 'tree') { // note: we take here the global setting + var tdDrag = document.createElement('td'); + if (this.editable.field) { + // create draggable area + if (this.parent) { + var domDrag = document.createElement('button'); + dom.drag = domDrag; + domDrag.className = 'dragarea'; + domDrag.title = 'Drag to move this field (Alt+Shift+Arrows)'; + tdDrag.appendChild(domDrag); + } + } + dom.tr.appendChild(tdDrag); + + // create context menu + var tdMenu = document.createElement('td'); + var menu = document.createElement('button'); + dom.menu = menu; + menu.className = 'contextmenu'; + menu.title = 'Click to open the actions menu (Ctrl+M)'; + tdMenu.appendChild(dom.menu); + dom.tr.appendChild(tdMenu); + } + + // create tree and field + var tdField = document.createElement('td'); + dom.tr.appendChild(tdField); + dom.tree = this._createDomTree(); + tdField.appendChild(dom.tree); + + this.updateDom({'updateIndexes': true}); + + return dom.tr; + }; + + /** + * DragStart event, fired on mousedown on the dragarea at the left side of a Node + * @param {Event} event + * @private + */ + Node.prototype._onDragStart = function (event) { + var node = this; + if (!this.mousemove) { + this.mousemove = util.addEventListener(document, 'mousemove', + function (event) { + node._onDrag(event); + }); + } + + if (!this.mouseup) { + this.mouseup = util.addEventListener(document, 'mouseup', + function (event ) { + node._onDragEnd(event); + }); + } + + this.editor.highlighter.lock(); + this.drag = { + 'oldCursor': document.body.style.cursor, + 'startParent': this.parent, + 'startIndex': this.parent.childs.indexOf(this), + 'mouseX': event.pageX, + 'level': this.getLevel() + }; + document.body.style.cursor = 'move'; + + event.preventDefault(); + }; + + /** + * Drag event, fired when moving the mouse while dragging a Node + * @param {Event} event + * @private + */ + Node.prototype._onDrag = function (event) { + // TODO: this method has grown too large. Split it in a number of methods + var mouseY = event.pageY; + var mouseX = event.pageX; + + var trThis, trPrev, trNext, trFirst, trLast, trRoot; + var nodePrev, nodeNext; + var topThis, topPrev, topFirst, heightThis, bottomNext, heightNext; + var moved = false; + + // TODO: add an ESC option, which resets to the original position + + // move up/down + trThis = this.dom.tr; + topThis = util.getAbsoluteTop(trThis); + heightThis = trThis.offsetHeight; + if (mouseY < topThis) { + // move up + trPrev = trThis; + do { + trPrev = trPrev.previousSibling; + nodePrev = Node.getNodeFromTarget(trPrev); + topPrev = trPrev ? util.getAbsoluteTop(trPrev) : 0; + } + while (trPrev && mouseY < topPrev); + + if (nodePrev && !nodePrev.parent) { + nodePrev = undefined; + } + + if (!nodePrev) { + // move to the first node + trRoot = trThis.parentNode.firstChild; + trPrev = trRoot ? trRoot.nextSibling : undefined; + nodePrev = Node.getNodeFromTarget(trPrev); + if (nodePrev == this) { + nodePrev = undefined; + } + } + + if (nodePrev) { + // check if mouseY is really inside the found node + trPrev = nodePrev.dom.tr; + topPrev = trPrev ? util.getAbsoluteTop(trPrev) : 0; + if (mouseY > topPrev + heightThis) { + nodePrev = undefined; + } + } + + if (nodePrev) { + nodePrev.parent.moveBefore(this, nodePrev); + moved = true; + } + } + else { + // move down + trLast = (this.expanded && this.append) ? this.append.getDom() : this.dom.tr; + trFirst = trLast ? trLast.nextSibling : undefined; + if (trFirst) { + topFirst = util.getAbsoluteTop(trFirst); + trNext = trFirst; + do { + nodeNext = Node.getNodeFromTarget(trNext); + if (trNext) { + bottomNext = trNext.nextSibling ? + util.getAbsoluteTop(trNext.nextSibling) : 0; + heightNext = trNext ? (bottomNext - topFirst) : 0; + + if (nodeNext.parent.childs.length == 1 && nodeNext.parent.childs[0] == this) { + // We are about to remove the last child of this parent, + // which will make the parents appendNode visible. + topThis += 24 - 1; + // TODO: dangerous to suppose the height of the appendNode a constant of 24-1 px. + } + } + + trNext = trNext.nextSibling; + } + while (trNext && mouseY > topThis + heightNext); + + if (nodeNext && nodeNext.parent) { + // calculate the desired level + var diffX = (mouseX - this.drag.mouseX); + var diffLevel = Math.round(diffX / 24 / 2); + var level = this.drag.level + diffLevel; // desired level + var levelNext = nodeNext.getLevel(); // level to be + + // find the best fitting level (move upwards over the append nodes) + trPrev = nodeNext.dom.tr.previousSibling; + while (levelNext < level && trPrev) { + nodePrev = Node.getNodeFromTarget(trPrev); + if (nodePrev == this || nodePrev._isChildOf(this)) { + // neglect itself and its childs + } + else if (nodePrev instanceof AppendNode) { + var childs = nodePrev.parent.childs; + if (childs.length > 1 || + (childs.length == 1 && childs[0] != this)) { + // non-visible append node of a list of childs + // consisting of not only this node (else the + // append node will change into a visible "empty" + // text when removing this node). + nodeNext = Node.getNodeFromTarget(trPrev); + levelNext = nodeNext.getLevel(); + } + else { + break; + } + } + else { + break; + } + + trPrev = trPrev.previousSibling; + } + + // move the node when its position is changed + if (trLast.nextSibling != nodeNext.dom.tr) { + nodeNext.parent.moveBefore(this, nodeNext); + moved = true; } } - catch (err) { - this.value = undefined; - // TODO: sent an action with the new, invalid value? - if (silent != true) { - throw err; + } + } + + if (moved) { + // update the dragging parameters when moved + this.drag.mouseX = mouseX; + this.drag.level = this.getLevel(); + } + + // auto scroll when hovering around the top of the editor + this.editor.startAutoScroll(mouseY); + + event.preventDefault(); + }; + + /** + * Drag event, fired on mouseup after having dragged a node + * @param {Event} event + * @private + */ + Node.prototype._onDragEnd = function (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; + this.editor.highlighter.unlock(); + delete this.drag; + + if (this.mousemove) { + util.removeEventListener(document, 'mousemove', this.mousemove); + delete this.mousemove;} + if (this.mouseup) { + util.removeEventListener(document, 'mouseup', this.mouseup); + delete this.mouseup; + } + + // Stop any running auto scroll + this.editor.stopAutoScroll(); + + event.preventDefault(); + }; + + /** + * Test if this node is a child of an other node + * @param {Node} node + * @return {boolean} isChild + * @private + */ + Node.prototype._isChildOf = function (node) { + var n = this.parent; + while (n) { + if (n == node) { + return true; + } + n = n.parent; + } + + return false; + }; + + /** + * Create an editable field + * @return {Element} domField + * @private + */ + 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 + */ + Node.prototype.setHighlight = function (highlight) { + if (this.dom.tr) { + this.dom.tr.className = (highlight ? '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 + */ + Node.prototype.updateValue = function (value) { + this.value = value; + this.updateDom(); + }; + + /** + * Update the field of the node. + * @param {String} field + */ + 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. + */ + 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) { + // parent is an object + domField.contentEditable = this.editable.field; + domField.spellcheck = false; + domField.className = 'field'; + } + else { + // parent is an array this is the root node + domField.className = 'readonly'; + } + + var field; + if (this.index != undefined) { + field = this.index; + } + else if (this.field != undefined) { + field = this.field; + } + else if (this._hasChilds()) { + field = this.type; + } + else { + 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 + ']'; + } + else if (this.type == 'object') { + domValue.innerHTML = '{' + count + '}'; + } + else { + domValue.innerHTML = this._escapeHTML(this.value); + } + } + + // 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 + */ + 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 = ''; + } + } + }); + } + } + }; + + /** + * Create an editable value + * @private + */ + Node.prototype._createDomValue = function () { + var domValue; + + if (this.type == 'array') { + domValue = document.createElement('div'); + domValue.className = 'readonly'; + domValue.innerHTML = '[...]'; + } + else if (this.type == 'object') { + domValue = document.createElement('div'); + domValue.className = 'readonly'; + domValue.innerHTML = '{...}'; + } + else { + if (!this.editable.value && util.isUrl(this.value)) { + // create a link in case of read-only editor and value containing an url + domValue = document.createElement('a'); + domValue.className = 'value'; + domValue.href = this.value; + domValue.target = '_blank'; + domValue.innerHTML = this._escapeHTML(this.value); + } + else { + // create an editable or read-only div + domValue = document.createElement('div'); + domValue.contentEditable = this.editable.value; + domValue.spellcheck = false; + domValue.className = 'value'; + domValue.innerHTML = this._escapeHTML(this.value); + } + } + + return domValue; + }; + + /** + * Create an expand/collapse button + * @return {Element} expand + * @private + */ + Node.prototype._createDomExpandButton = function () { + // create expand button + var expand = document.createElement('button'); + if (this._hasChilds()) { + expand.className = this.expanded ? 'expanded' : 'collapsed'; + expand.title = + 'Click to expand/collapse this field (Ctrl+E). \n' + + 'Ctrl+Click to expand/collapse including all childs.'; + } + else { + expand.className = 'invisible'; + expand.title = ''; + } + + return expand; + }; + + + /** + * Create a DOM tree element, containing the expand/collapse button + * @return {Element} domTree + * @private + */ + Node.prototype._createDomTree = function () { + var dom = this.dom; + var domTree = document.createElement('table'); + var tbody = document.createElement('tbody'); + domTree.style.borderCollapse = 'collapse'; // TODO: put in css + domTree.className = 'values'; + domTree.appendChild(tbody); + var tr = document.createElement('tr'); + tbody.appendChild(tr); + + // create expand button + var tdExpand = document.createElement('td'); + tdExpand.className = 'tree'; + tr.appendChild(tdExpand); + dom.expand = this._createDomExpandButton(); + tdExpand.appendChild(dom.expand); + dom.tdExpand = tdExpand; + + // create the field + var tdField = document.createElement('td'); + tdField.className = 'tree'; + tr.appendChild(tdField); + dom.field = this._createDomField(); + tdField.appendChild(dom.field); + dom.tdField = tdField; + + // create a separator + var tdSeparator = document.createElement('td'); + tdSeparator.className = 'tree'; + tr.appendChild(tdSeparator); + if (this.type != 'object' && this.type != 'array') { + tdSeparator.appendChild(document.createTextNode(':')); + tdSeparator.className = 'separator'; + } + dom.tdSeparator = tdSeparator; + + // create the value + var tdValue = document.createElement('td'); + tdValue.className = 'tree'; + tr.appendChild(tdValue); + dom.value = this._createDomValue(); + tdValue.appendChild(dom.value); + dom.tdValue = tdValue; + + return domTree; + }; + + /** + * Handle an event. The event is catched centrally by the editor + * @param {Event} event + */ + Node.prototype.onEvent = function (event) { + var type = event.type, + target = event.target || event.srcElement, + dom = this.dom, + node = this, + focusNode, + expandable = this._hasChilds(); + + // check if mouse is on menu or on dragarea. + // If so, highlight current row and its childs + if (target == dom.drag || target == dom.menu) { + if (type == 'mouseover') { + this.editor.highlighter.highlight(this); + } + else if (type == 'mouseout') { + this.editor.highlighter.unhighlight(); + } + } + + // drag events + if (type == 'mousedown' && target == dom.drag) { + this._onDragStart(event); + } + + // context menu events + if (type == 'click' && target == dom.menu) { + var highlighter = node.editor.highlighter; + highlighter.highlight(node); + highlighter.lock(); + util.addClassName(dom.menu, 'selected'); + this.showContextMenu(dom.menu, function () { + util.removeClassName(dom.menu, 'selected'); + highlighter.unlock(); + highlighter.unhighlight(); + }); + } + + // expand events + if (type == 'click' && target == dom.expand) { + if (expandable) { + var recurse = event.ctrlKey; // with ctrl-key, expand/collapse all + this._onExpand(recurse); + } + } + + // value events + var domValue = dom.value; + if (target == domValue) { + //noinspection FallthroughInSwitchStatementJS + switch (type) { + case 'focus': + focusNode = this; + break; + + case 'blur': + case 'change': + this._getDomValue(true); + this._updateDomValue(); + if (this.value) { + domValue.innerHTML = this._escapeHTML(this.value); + } + break; + + case 'input': + this._getDomValue(true); + this._updateDomValue(); + break; + + case 'keydown': + case 'mousedown': + this.editor.selection = this.editor.getSelection(); + break; + + case 'click': + if (event.ctrlKey || !this.editable.value) { + if (util.isUrl(this.value)) { + window.open(this.value, '_blank'); + } + } + 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': + focusNode = this; + break; + + case 'blur': + case 'change': + this._getDomField(true); + this._updateDomField(); + if (this.field) { + domField.innerHTML = this._escapeHTML(this.field); + } + break; + + case 'input': + this._getDomField(true); + this._updateDomField(); + break; + + case 'keydown': + case 'mousedown': + this.editor.selection = this.editor.getSelection(); + break; + + case 'keyup': + this._getDomField(true); + this._updateDomField(); + break; + + case 'cut': + case 'paste': + setTimeout(function () { + node._getDomField(true); + node._updateDomField(); + }, 1); + 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.pageX < util.getAbsoluteLeft(dom.tdSeparator));// for FF + if (left || expandable) { + // node is expandable when it is an object or array + if (domField) { + util.setEndOfContentEditable(domField); + domField.focus(); + } + } + else { + if (domValue) { + util.setEndOfContentEditable(domValue); + domValue.focus(); + } + } + break; + } + } + if ((target == dom.tdExpand && !expandable) || target == dom.tdField || + target == dom.tdSeparator) { + switch (type) { + case 'click': + if (domField) { + util.setEndOfContentEditable(domField); + domField.focus(); + } + break; + } + } + + if (type == 'keydown') { + this.onKeyDown(event); + } + }; + + /** + * Key down event handler + * @param {Event} event + */ + Node.prototype.onKeyDown = function (event) { + var keynum = event.which || event.keyCode; + var target = event.target || event.srcElement; + var ctrlKey = event.ctrlKey; + var shiftKey = event.shiftKey; + var altKey = event.altKey; + var handled = false; + var prevNode, nextNode, nextDom, nextDom2; + var editable = this.editor.options.mode === 'tree'; + + // util.log(ctrlKey, keynum, event.charCode); // TODO: cleanup + if (keynum == 13) { // Enter + if (target == this.dom.value) { + if (!this.editable.value || event.ctrlKey) { + if (util.isUrl(this.value)) { + window.open(this.value, '_blank'); + handled = true; + } + } + } + else if (target == this.dom.expand) { + var expandable = this._hasChilds(); + if (expandable) { + var recurse = event.ctrlKey; // with ctrl-key, expand/collapse all + this._onExpand(recurse); + target.focus(); + handled = true; + } + } + } + else if (keynum == 68) { // D + if (ctrlKey && editable) { // Ctrl+D + this._onDuplicate(); + handled = true; + } + } + else if (keynum == 69) { // E + if (ctrlKey) { // Ctrl+E and Ctrl+Shift+E + this._onExpand(shiftKey); // recurse = shiftKey + target.focus(); // TODO: should restore focus in case of recursing expand (which takes DOM offline) + handled = true; + } + } + else if (keynum == 77 && editable) { // M + if (ctrlKey) { // Ctrl+M + this.showContextMenu(target); + handled = true; + } + } + else if (keynum == 46 && editable) { // Del + if (ctrlKey) { // Ctrl+Del + this._onRemove(); + handled = true; + } + } + else if (keynum == 45 && editable) { // Ins + if (ctrlKey && !shiftKey) { // Ctrl+Ins + this._onInsertBefore(); + handled = true; + } + else if (ctrlKey && shiftKey) { // Ctrl+Shift+Ins + this._onInsertAfter(); + handled = true; + } + } + else if (keynum == 35) { // End + if (altKey) { // Alt+End + // find the last node + var lastNode = this._lastNode(); + if (lastNode) { + lastNode.focus(Node.focusElement || this._getElementName(target)); + } + handled = true; + } + } + else if (keynum == 36) { // Home + if (altKey) { // Alt+Home + // find the first node + var firstNode = this._firstNode(); + if (firstNode) { + firstNode.focus(Node.focusElement || this._getElementName(target)); + } + handled = true; + } + } + else if (keynum == 37) { // Arrow Left + if (altKey && !shiftKey) { // Alt + Arrow Left + // move to left element + var prevElement = this._previousElement(target); + if (prevElement) { + this.focus(this._getElementName(prevElement)); + } + handled = true; + } + else if (altKey && shiftKey && editable) { // Alt + Shift Arrow left + if (this.expanded) { + var appendDom = this.getAppend(); + nextDom = appendDom ? appendDom.nextSibling : undefined; + } + else { + var dom = this.getDom(); + nextDom = dom.nextSibling; + } + if (nextDom) { + nextNode = Node.getNodeFromTarget(nextDom); + nextDom2 = nextDom.nextSibling; + nextNode2 = Node.getNodeFromTarget(nextDom2); + if (nextNode && nextNode instanceof AppendNode && + !(this.parent.childs.length == 1) && + nextNode2 && nextNode2.parent) { + nextNode2.parent.moveBefore(this, nextNode2); + this.focus(Node.focusElement || this._getElementName(target)); + } + } + } + } + else if (keynum == 38) { // Arrow Up + if (altKey && !shiftKey) { // Alt + Arrow Up + // find the previous node + prevNode = this._previousNode(); + if (prevNode) { + prevNode.focus(Node.focusElement || this._getElementName(target)); + } + handled = true; + } + else if (altKey && shiftKey) { // Alt + Shift + Arrow Up + // find the previous node + prevNode = this._previousNode(); + if (prevNode && prevNode.parent) { + prevNode.parent.moveBefore(this, prevNode); + this.focus(Node.focusElement || this._getElementName(target)); + } + handled = true; + } + } + else if (keynum == 39) { // Arrow Right + if (altKey && !shiftKey) { // Alt + Arrow Right + // move to right element + var nextElement = this._nextElement(target); + if (nextElement) { + this.focus(this._getElementName(nextElement)); + } + handled = true; + } + else if (altKey && shiftKey) { // Alt + Shift Arrow Right + dom = this.getDom(); + var prevDom = dom.previousSibling; + if (prevDom) { + prevNode = Node.getNodeFromTarget(prevDom); + if (prevNode && prevNode.parent && + (prevNode instanceof AppendNode) + && !prevNode.isVisible()) { + prevNode.parent.moveBefore(this, prevNode); + this.focus(Node.focusElement || this._getElementName(target)); + } + } + } + } + else if (keynum == 40) { // Arrow Down + if (altKey && !shiftKey) { // Alt + Arrow Down + // find the next node + nextNode = this._nextNode(); + if (nextNode) { + nextNode.focus(Node.focusElement || this._getElementName(target)); + } + handled = true; + } + else if (altKey && shiftKey && editable) { // Alt + Shift + Arrow Down + // find the 2nd next node and move before that one + if (this.expanded) { + nextNode = this.append ? this.append._nextNode() : undefined; + } + else { + nextNode = this._nextNode(); + } + nextDom = nextNode ? nextNode.getDom() : undefined; + if (this.parent.childs.length == 1) { + nextDom2 = nextDom; + } + else { + nextDom2 = nextDom ? nextDom.nextSibling : undefined; + } + var nextNode2 = Node.getNodeFromTarget(nextDom2); + if (nextNode2 && nextNode2.parent) { + nextNode2.parent.moveBefore(this, nextNode2); + this.focus(Node.focusElement || this._getElementName(target)); + } + handled = true; + } + } + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } + }; + + /** + * Handle the expand event, when clicked on the expand button + * @param {boolean} recurse If true, child nodes will be expanded too + * @private + */ + Node.prototype._onExpand = function (recurse) { + 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; + } + }; + + /** + * Remove this node + * @private + */ + Node.prototype._onRemove = function() { + this.editor.highlighter.unhighlight(); + var childs = this.parent.childs; + var index = childs.indexOf(this); + + // adjust the focus + var oldSelection = this.editor.getSelection(); + if (childs[index + 1]) { + childs[index + 1].focus(); + } + else if (childs[index - 1]) { + childs[index - 1].focus(); + } + else { + this.parent.focus(); + } + var newSelection = this.editor.getSelection(); + + // remove the node + this.parent._remove(this); + + // store history action + this.editor._onAction('removeNode', { + node: this, + parent: this.parent, + index: index, + oldSelection: oldSelection, + newSelection: newSelection + }); + }; + + /** + * Duplicate this node + * @private + */ + Node.prototype._onDuplicate = function() { + var oldSelection = this.editor.getSelection(); + var clone = this.parent._duplicate(this); + clone.focus(); + var newSelection = this.editor.getSelection(); + + this.editor._onAction('duplicateNode', { + node: this, + clone: clone, + parent: this.parent, + oldSelection: oldSelection, + newSelection: newSelection + }); + }; + + /** + * Handle insert before event + * @param {String} [field] + * @param {*} [value] + * @param {String} [type] Can be 'auto', 'array', 'object', or 'string' + * @private + */ + Node.prototype._onInsertBefore = function (field, value, type) { + var oldSelection = this.editor.getSelection(); + + var newNode = new Node(this.editor, { + field: (field != undefined) ? field : '', + value: (value != undefined) ? value : '', + type: type + }); + newNode.expand(true); + this.parent.insertBefore(newNode, this); + this.editor.highlighter.unhighlight(); + newNode.focus('field'); + var newSelection = this.editor.getSelection(); + + this.editor._onAction('insertBeforeNode', { + node: newNode, + beforeNode: this, + parent: this.parent, + oldSelection: oldSelection, + newSelection: newSelection + }); + }; + + /** + * Handle insert after event + * @param {String} [field] + * @param {*} [value] + * @param {String} [type] Can be 'auto', 'array', 'object', or 'string' + * @private + */ + Node.prototype._onInsertAfter = function (field, value, type) { + var oldSelection = this.editor.getSelection(); + + var newNode = new Node(this.editor, { + field: (field != undefined) ? field : '', + value: (value != undefined) ? value : '', + type: type + }); + newNode.expand(true); + this.parent.insertAfter(newNode, this); + this.editor.highlighter.unhighlight(); + newNode.focus('field'); + var newSelection = this.editor.getSelection(); + + this.editor._onAction('insertAfterNode', { + node: newNode, + afterNode: this, + parent: this.parent, + oldSelection: oldSelection, + newSelection: newSelection + }); + }; + + /** + * Handle append event + * @param {String} [field] + * @param {*} [value] + * @param {String} [type] Can be 'auto', 'array', 'object', or 'string' + * @private + */ + Node.prototype._onAppend = function (field, value, type) { + var oldSelection = this.editor.getSelection(); + + var newNode = new Node(this.editor, { + field: (field != undefined) ? field : '', + value: (value != undefined) ? value : '', + type: type + }); + newNode.expand(true); + this.parent.appendChild(newNode); + this.editor.highlighter.unhighlight(); + newNode.focus('field'); + var newSelection = this.editor.getSelection(); + + this.editor._onAction('appendNode', { + node: newNode, + parent: this.parent, + oldSelection: oldSelection, + newSelection: newSelection + }); + }; + + /** + * Change the type of the node's value + * @param {String} newType + * @private + */ + Node.prototype._onChangeType = function (newType) { + var oldType = this.type; + if (newType != oldType) { + var oldSelection = this.editor.getSelection(); + this.changeType(newType); + var newSelection = this.editor.getSelection(); + + this.editor._onAction('changeType', { + node: this, + oldType: oldType, + newType: newType, + oldSelection: oldSelection, + newSelection: newSelection + }); + } + }; + + /** + * Sort the childs of the node. Only applicable when the node has type 'object' + * or 'array'. + * @param {String} direction Sorting direction. Available values: "asc", "desc" + * @private + */ + Node.prototype._onSort = function (direction) { + if (this._hasChilds()) { + var order = (direction == 'desc') ? -1 : 1; + var prop = (this.type == 'array') ? 'value': 'field'; + this.hideChilds(); + + var oldChilds = this.childs; + var oldSort = this.sort; + + // copy the array (the old one will be kept for an undo action + this.childs = this.childs.concat(); + + // sort the arrays + this.childs.sort(function (a, b) { + if (a[prop] > b[prop]) return order; + if (a[prop] < b[prop]) return -order; + return 0; + }); + this.sort = (order == 1) ? 'asc' : 'desc'; + + this.editor._onAction('sort', { + node: this, + oldChilds: oldChilds, + oldSort: oldSort, + newChilds: this.childs, + newSort: this.sort + }); + + this.showChilds(); + } + }; + + /** + * Create a table row with an append button. + * @return {HTMLElement | undefined} buttonAppend or undefined when inapplicable + */ + Node.prototype.getAppend = function () { + if (!this.append) { + this.append = new AppendNode(this.editor); + this.append.setParent(this); + } + return this.append.getDom(); + }; + + /** + * Find the node from an event target + * @param {Node} target + * @return {Node | undefined} node or undefined when not found + * @static + */ + Node.getNodeFromTarget = function (target) { + while (target) { + if (target.node) { + return target.node; + } + target = target.parentNode; + } + + return undefined; + }; + + /** + * Get the previously rendered node + * @return {Node | null} previousNode + * @private + */ + Node.prototype._previousNode = function () { + var prevNode = null; + var dom = this.getDom(); + if (dom && dom.parentNode) { + // find the previous field + var prevDom = dom; + do { + prevDom = prevDom.previousSibling; + prevNode = Node.getNodeFromTarget(prevDom); + } + while (prevDom && (prevNode instanceof AppendNode && !prevNode.isVisible())); + } + return prevNode; + }; + + /** + * Get the next rendered node + * @return {Node | null} nextNode + * @private + */ + Node.prototype._nextNode = function () { + var nextNode = null; + var dom = this.getDom(); + if (dom && dom.parentNode) { + // find the previous field + var nextDom = dom; + do { + nextDom = nextDom.nextSibling; + nextNode = Node.getNodeFromTarget(nextDom); + } + while (nextDom && (nextNode instanceof AppendNode && !nextNode.isVisible())); + } + + return nextNode; + }; + + /** + * Get the first rendered node + * @return {Node | null} firstNode + * @private + */ + Node.prototype._firstNode = function () { + var firstNode = null; + var dom = this.getDom(); + if (dom && dom.parentNode) { + var firstDom = dom.parentNode.firstChild; + firstNode = Node.getNodeFromTarget(firstDom); + } + + return firstNode; + }; + + /** + * Get the last rendered node + * @return {Node | null} lastNode + * @private + */ + Node.prototype._lastNode = function () { + var lastNode = null; + var dom = this.getDom(); + if (dom && dom.parentNode) { + var lastDom = dom.parentNode.lastChild; + lastNode = Node.getNodeFromTarget(lastDom); + while (lastDom && (lastNode instanceof AppendNode && !lastNode.isVisible())) { + lastDom = lastDom.previousSibling; + lastNode = Node.getNodeFromTarget(lastDom); + } + } + return lastNode; + }; + + /** + * Get the next element which can have focus. + * @param {Element} elem + * @return {Element | null} nextElem + * @private + */ + Node.prototype._previousElement = function (elem) { + var dom = this.dom; + // noinspection FallthroughInSwitchStatementJS + switch (elem) { + case dom.value: + if (this.fieldEditable) { + return dom.field; + } + // intentional fall through + case dom.field: + if (this._hasChilds()) { + return dom.expand; + } + // intentional fall through + case dom.expand: + return dom.menu; + case dom.menu: + if (dom.drag) { + return dom.drag; + } + // intentional fall through + default: + return null; + } + }; + + /** + * Get the next element which can have focus. + * @param {Element} elem + * @return {Element | null} nextElem + * @private + */ + Node.prototype._nextElement = function (elem) { + var dom = this.dom; + // noinspection FallthroughInSwitchStatementJS + switch (elem) { + case dom.drag: + return dom.menu; + case dom.menu: + if (this._hasChilds()) { + return dom.expand; + } + // intentional fall through + case dom.expand: + if (this.fieldEditable) { + return dom.field; + } + // intentional fall through + case dom.field: + if (!this._hasChilds()) { + return dom.value; + } + default: + return null; + } + }; + + /** + * Get the dom name of given element. returns null if not found. + * For example when element == dom.field, "field" is returned. + * @param {Element} element + * @return {String | null} elementName Available elements with name: 'drag', + * 'menu', 'expand', 'field', 'value' + * @private + */ + Node.prototype._getElementName = function (element) { + var dom = this.dom; + for (var name in dom) { + if (dom.hasOwnProperty(name)) { + if (dom[name] == element) { + return name; + } + } + } + return null; + }; + + /** + * Test if this node has childs. This is the case when the node is an object + * or array. + * @return {boolean} hasChilds + * @private + */ + Node.prototype._hasChilds = function () { + return this.type == 'array' || this.type == 'object'; + }; + + // titles with explanation for the different types + Node.TYPE_TITLES = { + 'auto': 'Field type "auto". ' + + 'The field type is automatically determined from the value ' + + 'and can be a string, number, boolean, or null.', + 'object': 'Field type "object". ' + + 'An object contains an unordered set of key/value pairs.', + 'array': 'Field type "array". ' + + 'An array contains an ordered collection of values.', + 'string': 'Field type "string". ' + + 'Field type is not determined from the value, ' + + 'but always returned as string.' + }; + + /** + * Show a contextmenu for this node + * @param {HTMLElement} anchor Anchor element to attache the context menu to. + * @param {function} [onClose] Callback method called when the context menu + * is being closed. + */ + Node.prototype.showContextMenu = function (anchor, onClose) { + var node = this; + var titles = Node.TYPE_TITLES; + var items = []; + + if (this.editable.value) { + items.push({ + text: 'Type', + title: 'Change the type of this field', + className: 'type-' + this.type, + submenu: [ + { + text: 'Auto', + className: 'type-auto' + + (this.type == 'auto' ? ' selected' : ''), + title: titles.auto, + click: function () { + node._onChangeType('auto'); + } + }, + { + text: 'Array', + className: 'type-array' + + (this.type == 'array' ? ' selected' : ''), + title: titles.array, + click: function () { + node._onChangeType('array'); + } + }, + { + text: 'Object', + className: 'type-object' + + (this.type == 'object' ? ' selected' : ''), + title: titles.object, + click: function () { + node._onChangeType('object'); + } + }, + { + text: 'String', + className: 'type-string' + + (this.type == 'string' ? ' selected' : ''), + title: titles.string, + click: function () { + node._onChangeType('string'); + } + } + ] + }); + } + + if (this._hasChilds()) { + var direction = ((this.sort == 'asc') ? 'desc': 'asc'); + items.push({ + text: 'Sort', + title: 'Sort the childs of this ' + this.type, + className: 'sort-' + direction, + click: function () { + node._onSort(direction); + }, + submenu: [ + { + text: 'Ascending', + className: 'sort-asc', + title: 'Sort the childs of this ' + this.type + ' in ascending order', + click: function () { + node._onSort('asc'); + } + }, + { + text: 'Descending', + className: 'sort-desc', + title: 'Sort the childs of this ' + this.type +' in descending order', + click: function () { + node._onSort('desc'); + } + } + ] + }); + } + + if (this.parent && this.parent._hasChilds()) { + if (items.length) { + // create a separator + items.push({ + 'type': 'separator' + }); + } + + // create append button (for last child node only) + var childs = node.parent.childs; + if (node == childs[childs.length - 1]) { + items.push({ + text: 'Append', + title: 'Append a new field with type \'auto\' after this field (Ctrl+Shift+Ins)', + submenuTitle: 'Select the type of the field to be appended', + className: 'append', + click: function () { + node._onAppend('', '', 'auto'); + }, + submenu: [ + { + text: 'Auto', + className: 'type-auto', + title: titles.auto, + click: function () { + node._onAppend('', '', 'auto'); + } + }, + { + text: 'Array', + className: 'type-array', + title: titles.array, + click: function () { + node._onAppend('', []); + } + }, + { + text: 'Object', + className: 'type-object', + title: titles.object, + click: function () { + node._onAppend('', {}); + } + }, + { + text: 'String', + className: 'type-string', + title: titles.string, + click: function () { + node._onAppend('', '', 'string'); + } + } + ] + }); + } + + // create insert button + items.push({ + text: 'Insert', + title: 'Insert a new field with type \'auto\' before this field (Ctrl+Ins)', + submenuTitle: 'Select the type of the field to be inserted', + className: 'insert', + click: function () { + node._onInsertBefore('', '', 'auto'); + }, + submenu: [ + { + text: 'Auto', + className: 'type-auto', + title: titles.auto, + click: function () { + node._onInsertBefore('', '', 'auto'); + } + }, + { + text: 'Array', + className: 'type-array', + title: titles.array, + click: function () { + node._onInsertBefore('', []); + } + }, + { + text: 'Object', + className: 'type-object', + title: titles.object, + click: function () { + node._onInsertBefore('', {}); + } + }, + { + text: 'String', + className: 'type-string', + title: titles.string, + click: function () { + node._onInsertBefore('', '', 'string'); + } + } + ] + }); + + if (this.editable.field) { + // create duplicate button + items.push({ + text: 'Duplicate', + title: 'Duplicate this field (Ctrl+D)', + className: 'duplicate', + click: function () { + node._onDuplicate(); + } + }); + + // create remove button + items.push({ + text: 'Remove', + title: 'Remove this field (Ctrl+Del)', + className: 'remove', + click: function () { + node._onRemove(); + } + }); + } + } + + var menu = new ContextMenu(items, {close: onClose}); + menu.show(anchor); + }; + + /** + * get the type of a value + * @param {*} value + * @return {String} type Can be 'object', 'array', 'string', 'auto' + * @private + */ + 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 + */ + 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 + */ + Node.prototype._escapeHTML = function (text) { + var htmlEscaped = String(text) + .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 + */ + Node.prototype._unescapeHTML = function (escapedText) { + var json = '"' + this._escapeJSON(escapedText) + '"'; + var htmlEscaped = util.parse(json); + return htmlEscaped + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/ |\u00A0/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 + */ + 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; + }; + + // TODO: find a nicer solution to resolve this circular dependency between Node and AppendNode + var AppendNode = appendNodeFactory(Node); + + module.exports = Node; + + +/***/ }, +/* 8 */ +/***/ function(module, exports, __webpack_require__) { + + var ContextMenu = __webpack_require__(9); + + /** + * Create a select box to be used in the editor menu's, which allows to switch mode + * @param {Object} editor + * @param {String[]} modes Available modes: 'code', 'form', 'text', 'tree', 'view' + * @param {String} current Available modes: 'code', 'form', 'text', 'tree', 'view' + * @returns {HTMLElement} box + */ + function createModeSwitcher(editor, modes, current) { + // TODO: decouple mode switcher from editor + + /** + * Switch the mode of the editor + * @param {String} mode + */ + function switchMode(mode) { + // switch mode + editor.setMode(mode); + + // restore focus on mode box + var modeBox = editor.dom && editor.dom.modeBox; + if (modeBox) { + modeBox.focus(); + } + } + + // available modes + var availableModes = { + code: { + 'text': 'Code', + 'title': 'Switch to code highlighter', + 'click': function () { + switchMode('code') + } + }, + form: { + 'text': 'Form', + 'title': 'Switch to form editor', + 'click': function () { + switchMode('form'); + } + }, + text: { + 'text': 'Text', + 'title': 'Switch to plain text editor', + 'click': function () { + switchMode('text'); + } + }, + tree: { + 'text': 'Tree', + 'title': 'Switch to tree editor', + 'click': function () { + switchMode('tree'); + } + }, + view: { + 'text': 'View', + 'title': 'Switch to tree view', + 'click': function () { + switchMode('view'); } } }; - /** - * 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 - */ - Node.prototype._updateDomValue = function () { - var domValue = this.dom.value; - if (domValue) { - // set text color depending on value type - // TODO: put colors in css - var v = this.value; - var t = (this.type == 'auto') ? util.type(v) : this.type; - var isUrl = (t == 'string' && util.isUrl(v)); - var color = ''; - if (isUrl && !this.editable.value) { // TODO: when to apply this? - color = ''; - } - else if (t == 'string') { - color = 'green'; - } - else if (t == 'number') { - color = 'red'; - } - else if (t == 'boolean') { - color = 'darkorange'; - } - else if (this._hasChilds()) { - color = ''; - } - else if (v === null) { - color = '#004ED0'; // blue - } - else { - // invalid value - color = 'black'; - } - domValue.style.color = color; - - // make background color light-gray when empty - var isEmpty = (String(this.value) == '' && this.type != 'array' && this.type != 'object'); - if (isEmpty) { - util.addClassName(domValue, 'empty'); - } - else { - util.removeClassName(domValue, 'empty'); - } - - // underline url - if (isUrl) { - util.addClassName(domValue, 'url'); - } - else { - util.removeClassName(domValue, 'url'); - } - - // update title - if (t == 'array' || t == 'object') { - var count = this.childs ? this.childs.length : 0; - domValue.title = this.type + ' containing ' + count + ' items'; - } - else if (t == 'string' && util.isUrl(v)) { - if (this.editable.value) { - domValue.title = 'Ctrl+Click or Ctrl+Enter to open url in new window'; - } - } - else { - domValue.title = ''; - } - - // highlight when there is a search result - if (this.searchValueActive) { - util.addClassName(domValue, 'highlight-active'); - } - else { - util.removeClassName(domValue, 'highlight-active'); - } - if (this.searchValue) { - util.addClassName(domValue, 'highlight'); - } - else { - util.removeClassName(domValue, 'highlight'); - } - - // strip formatting from the contents of the editable div - util.stripFormatting(domValue); + // list the selected modes + var items = []; + for (var i = 0; i < modes.length; i++) { + var mode = modes[i]; + var item = availableModes[mode]; + if (!item) { + throw new Error('Unknown mode "' + mode + '"'); } + + item.className = 'type-modes' + ((current == mode) ? ' selected' : ''); + items.push(item); + } + + // retrieve the title of current mode + var currentMode = availableModes[current]; + if (!currentMode) { + throw new Error('Unknown mode "' + current + '"'); + } + var currentTitle = currentMode.text; + + // create the html element + var box = document.createElement('button'); + box.className = 'modes separator'; + box.innerHTML = currentTitle + ' ▾'; + box.title = 'Switch editor mode'; + box.onclick = function () { + var menu = new ContextMenu(items); + menu.show(box); }; - /** - * 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 - */ - Node.prototype._updateDomField = function () { - var domField = this.dom.field; - if (domField) { - // make backgound color lightgray when empty - var isEmpty = (String(this.field) == '' && this.parent.type != 'array'); - if (isEmpty) { - util.addClassName(domField, 'empty'); + return box; + } + + exports.create = createModeSwitcher; + + +/***/ }, +/* 9 */ +/***/ function(module, exports, __webpack_require__) { + + var util = __webpack_require__(3); + + /** + * A context menu + * @param {Object[]} items Array containing the menu structure + * TODO: describe structure + * @param {Object} [options] Object with options. Available options: + * {function} close Callback called when the + * context menu is being closed. + * @constructor + */ + function ContextMenu (items, options) { + this.dom = {}; + + var me = this; + var dom = this.dom; + this.anchor = undefined; + this.items = items; + this.eventListeners = {}; + this.selection = undefined; // holds the selection before the menu was opened + this.visibleSubmenu = undefined; + this.onClose = options ? options.close : undefined; + + // create a container element + var menu = document.createElement('div'); + menu.className = 'jsoneditor-contextmenu'; + dom.menu = menu; + + // create a list to hold the menu items + var list = document.createElement('ul'); + list.className = 'menu'; + menu.appendChild(list); + dom.list = list; + dom.items = []; // list with all buttons + + // create a (non-visible) button to set the focus to the menu + var focusButton = document.createElement('button'); + dom.focusButton = focusButton; + var li = document.createElement('li'); + li.style.overflow = 'hidden'; + li.style.height = '0'; + li.appendChild(focusButton); + list.appendChild(li); + + function createMenuItems (list, domItems, items) { + items.forEach(function (item) { + if (item.type == 'separator') { + // create a separator + var separator = document.createElement('div'); + separator.className = 'separator'; + li = document.createElement('li'); + li.appendChild(separator); + list.appendChild(li); } else { - util.removeClassName(domField, 'empty'); - } + var domItem = {}; - // highlight when there is a search result - if (this.searchFieldActive) { - util.addClassName(domField, 'highlight-active'); - } - else { - util.removeClassName(domField, 'highlight-active'); - } - if (this.searchField) { - util.addClassName(domField, 'highlight'); - } - else { - util.removeClassName(domField, 'highlight'); - } + // create a menu item + var li = document.createElement('li'); + list.appendChild(li); - // strip formatting from the contents of the editable div - util.stripFormatting(domField); - } - }; - - /** - * Retrieve field from DOM - * @param {boolean} [silent] If true (default), no errors will be thrown in - * case of invalid data - * @private - */ - Node.prototype._getDomField = function(silent) { - if (this.dom.field && this.fieldEditable) { - this.fieldInnerText = util.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, - 'oldSelection': this.editor.selection, - 'newSelection': this.editor.getSelection() - }); + // create a button in the menu item + var button = document.createElement('button'); + button.className = item.className; + domItem.button = button; + if (item.title) { + button.title = item.title; } - } - catch (err) { - this.field = undefined; - // TODO: sent an action here, with the new, invalid value? - if (silent != true) { - throw err; + if (item.click) { + button.onclick = function () { + me.hide(); + item.click(); + }; } + li.appendChild(button); + + // create the contents of the button + if (item.submenu) { + // add the icon to the button + var divIcon = document.createElement('div'); + divIcon.className = 'icon'; + button.appendChild(divIcon); + button.appendChild(document.createTextNode(item.text)); + + var buttonSubmenu; + if (item.click) { + // submenu and a button with a click handler + button.className += ' default'; + + var buttonExpand = document.createElement('button'); + domItem.buttonExpand = buttonExpand; + buttonExpand.className = 'expand'; + buttonExpand.innerHTML = '

'; + li.appendChild(buttonExpand); + if (item.submenuTitle) { + buttonExpand.title = item.submenuTitle; + } + + buttonSubmenu = buttonExpand; + } + else { + // submenu and a button without a click handler + var divExpand = document.createElement('div'); + divExpand.className = 'expand'; + button.appendChild(divExpand); + + buttonSubmenu = button; + } + + // attach a handler to expand/collapse the submenu + buttonSubmenu.onclick = function () { + me._onExpandItem(domItem); + buttonSubmenu.focus(); + }; + + // create the submenu + var domSubItems = []; + domItem.subItems = domSubItems; + var ul = document.createElement('ul'); + domItem.ul = ul; + ul.className = 'menu'; + ul.style.height = '0'; + li.appendChild(ul); + createMenuItems(ul, domSubItems, item.submenu); + } + else { + // no submenu, just a button with clickhandler + button.innerHTML = '
' + item.text; + } + + domItems.push(domItem); + } + }); + } + createMenuItems(list, this.dom.items, items); + + // TODO: when the editor is small, show the submenu on the right instead of inline? + + // calculate the max height of the menu with one submenu expanded + this.maxHeight = 0; // height in pixels + items.forEach(function (item) { + var height = (items.length + (item.submenu ? item.submenu.length : 0)) * 24; + me.maxHeight = Math.max(me.maxHeight, height); + }); + } + + /** + * Get the currently visible buttons + * @return {Array.} buttons + * @private + */ + ContextMenu.prototype._getVisibleButtons = function () { + var buttons = []; + var me = this; + this.dom.items.forEach(function (item) { + buttons.push(item.button); + if (item.buttonExpand) { + buttons.push(item.buttonExpand); + } + if (item.subItems && item == me.expandedItem) { + item.subItems.forEach(function (subItem) { + buttons.push(subItem.button); + if (subItem.buttonExpand) { + buttons.push(subItem.buttonExpand); + } + // TODO: change to fully recursive method + }); + } + }); + + return buttons; + }; + + // currently displayed context menu, a singleton. We may only have one visible context menu + ContextMenu.visibleMenu = undefined; + + /** + * Attach the menu to an anchor + * @param {HTMLElement} anchor + */ + ContextMenu.prototype.show = function (anchor) { + this.hide(); + + // calculate whether the menu fits below the anchor + var windowHeight = window.innerHeight, + windowScroll = (window.pageYOffset || document.scrollTop || 0), + windowBottom = windowHeight + windowScroll, + anchorHeight = anchor.offsetHeight, + menuHeight = this.maxHeight; + + // position the menu + var left = util.getAbsoluteLeft(anchor); + var top = util.getAbsoluteTop(anchor); + if (top + anchorHeight + menuHeight < windowBottom) { + // display the menu below the anchor + this.dom.menu.style.left = left + 'px'; + this.dom.menu.style.top = (top + anchorHeight) + 'px'; + this.dom.menu.style.bottom = ''; + } + else { + // display the menu above the anchor + this.dom.menu.style.left = left + 'px'; + this.dom.menu.style.top = ''; + this.dom.menu.style.bottom = (windowHeight - top) + 'px'; + } + + // attach the menu to the document + document.body.appendChild(this.dom.menu); + + // create and attach event listeners + var me = this; + var list = this.dom.list; + this.eventListeners.mousedown = util.addEventListener( + document, 'mousedown', function (event) { + // hide menu on click outside of the menu + var target = event.target; + if ((target != list) && !me._isChildOf(target, list)) { + me.hide(); + event.stopPropagation(); + event.preventDefault(); + } + }); + this.eventListeners.mousewheel = util.addEventListener( + document, 'mousewheel', function (event) { + // block scrolling when context menu is visible + event.stopPropagation(); + event.preventDefault(); + }); + this.eventListeners.keydown = util.addEventListener( + document, 'keydown', function (event) { + me._onKeyDown(event); + }); + + // move focus to the first button in the context menu + this.selection = util.getSelection(); + this.anchor = anchor; + setTimeout(function () { + me.dom.focusButton.focus(); + }, 0); + + if (ContextMenu.visibleMenu) { + ContextMenu.visibleMenu.hide(); + } + ContextMenu.visibleMenu = this; + }; + + /** + * Hide the context menu if visible + */ + ContextMenu.prototype.hide = function () { + // remove the menu from the DOM + if (this.dom.menu.parentNode) { + this.dom.menu.parentNode.removeChild(this.dom.menu); + if (this.onClose) { + this.onClose(); + } + } + + // remove all event listeners + // all event listeners are supposed to be attached to document. + for (var name in this.eventListeners) { + if (this.eventListeners.hasOwnProperty(name)) { + var fn = this.eventListeners[name]; + if (fn) { + util.removeEventListener(document, name, fn); + } + delete this.eventListeners[name]; + } + } + + if (ContextMenu.visibleMenu == this) { + ContextMenu.visibleMenu = undefined; + } + }; + + /** + * Expand a submenu + * Any currently expanded submenu will be hided. + * @param {Object} domItem + * @private + */ + ContextMenu.prototype._onExpandItem = function (domItem) { + var me = this; + var alreadyVisible = (domItem == this.expandedItem); + + // hide the currently visible submenu + var expandedItem = this.expandedItem; + if (expandedItem) { + //var ul = expandedItem.ul; + expandedItem.ul.style.height = '0'; + expandedItem.ul.style.padding = ''; + setTimeout(function () { + if (me.expandedItem != expandedItem) { + expandedItem.ul.style.display = ''; + util.removeClassName(expandedItem.ul.parentNode, 'selected'); + } + }, 300); // timeout duration must match the css transition duration + this.expandedItem = undefined; + } + + if (!alreadyVisible) { + var ul = domItem.ul; + ul.style.display = 'block'; + var height = ul.clientHeight; // force a reflow in Firefox + setTimeout(function () { + if (me.expandedItem == domItem) { + ul.style.height = (ul.childNodes.length * 24) + 'px'; + ul.style.padding = '5px 10px'; + } + }, 0); + util.addClassName(ul.parentNode, 'selected'); + this.expandedItem = domItem; + } + }; + + /** + * Handle onkeydown event + * @param {Event} event + * @private + */ + ContextMenu.prototype._onKeyDown = function (event) { + var target = event.target; + var keynum = event.which; + var handled = false; + var buttons, targetIndex, prevButton, nextButton; + + if (keynum == 27) { // ESC + // hide the menu on ESC key + + // restore previous selection and focus + if (this.selection) { + util.setSelection(this.selection); + } + if (this.anchor) { + this.anchor.focus(); + } + + this.hide(); + + handled = true; + } + else if (keynum == 9) { // Tab + if (!event.shiftKey) { // Tab + buttons = this._getVisibleButtons(); + targetIndex = buttons.indexOf(target); + if (targetIndex == buttons.length - 1) { + // move to first button + buttons[0].focus(); + handled = true; } } - }; + else { // Shift+Tab + buttons = this._getVisibleButtons(); + targetIndex = buttons.indexOf(target); + if (targetIndex == 0) { + // move to last button + buttons[buttons.length - 1].focus(); + handled = true; + } + } + } + else if (keynum == 37) { // Arrow Left + if (target.className == 'expand') { + buttons = this._getVisibleButtons(); + targetIndex = buttons.indexOf(target); + prevButton = buttons[targetIndex - 1]; + if (prevButton) { + prevButton.focus(); + } + } + handled = true; + } + else if (keynum == 38) { // Arrow Up + buttons = this._getVisibleButtons(); + targetIndex = buttons.indexOf(target); + prevButton = buttons[targetIndex - 1]; + if (prevButton && prevButton.className == 'expand') { + // skip expand button + prevButton = buttons[targetIndex - 2]; + } + if (!prevButton) { + // move to last button + prevButton = buttons[buttons.length - 1]; + } + if (prevButton) { + prevButton.focus(); + } + handled = true; + } + else if (keynum == 39) { // Arrow Right + buttons = this._getVisibleButtons(); + targetIndex = buttons.indexOf(target); + nextButton = buttons[targetIndex + 1]; + if (nextButton && nextButton.className == 'expand') { + nextButton.focus(); + } + handled = true; + } + else if (keynum == 40) { // Arrow Down + buttons = this._getVisibleButtons(); + targetIndex = buttons.indexOf(target); + nextButton = buttons[targetIndex + 1]; + if (nextButton && nextButton.className == 'expand') { + // skip expand button + nextButton = buttons[targetIndex + 2]; + } + if (!nextButton) { + // move to first button + nextButton = buttons[0]; + } + if (nextButton) { + nextButton.focus(); + handled = true; + } + handled = true; + } + // TODO: arrow left and right + if (handled) { + event.stopPropagation(); + event.preventDefault(); + } + }; + + /** + * Test if an element is a child of a parent element. + * @param {Element} child + * @param {Element} parent + * @return {boolean} isChild + */ + ContextMenu.prototype._isChildOf = function (child, parent) { + var e = child.parentNode; + while (e) { + if (e == parent) { + return true; + } + e = e.parentNode; + } + + return false; + }; + + module.exports = ContextMenu; + + +/***/ }, +/* 10 */ +/***/ function(module, exports, __webpack_require__) { + + var util = __webpack_require__(3); + var ContextMenu = __webpack_require__(9); + + /** + * A factory function to create an AppendNode, which depends on a Node + * @param {Node} Node + */ + function appendNodeFactory(Node) { /** - * Clear the dom of the node + * @constructor AppendNode + * @extends Node + * @param {TreeEditor} 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 */ - Node.prototype.clearDom = function() { - // TODO: hide the node first? - //this.hide(); - // TODO: recursively clear dom? - + function AppendNode (editor) { + /** @type {TreeEditor} */ + this.editor = editor; this.dom = {}; - }; + } + + AppendNode.prototype = new Node(); /** - * 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 + * Return a table row with an append button. + * @return {Element} dom TR element */ - Node.prototype.getDom = function() { + AppendNode.prototype.getDom = function () { + // TODO: implement a new solution for the append node var dom = this.dom; + if (dom.tr) { return dom.tr; } this._updateEditability(); - // create row - dom.tr = document.createElement('tr'); - dom.tr.node = this; + // a row for the append button + var trAppend = document.createElement('tr'); + trAppend.node = this; + dom.tr = trAppend; - if (this.editor.options.mode === 'tree') { // note: we take here the global setting - var tdDrag = document.createElement('td'); - if (this.editable.field) { - // create draggable area - if (this.parent) { - var domDrag = document.createElement('button'); - dom.drag = domDrag; - domDrag.className = 'dragarea'; - domDrag.title = 'Drag to move this field (Alt+Shift+Arrows)'; - tdDrag.appendChild(domDrag); - } - } - dom.tr.appendChild(tdDrag); + // TODO: consistent naming + + if (this.editable.field) { + // a cell for the dragarea column + dom.tdDrag = document.createElement('td'); // create context menu var tdMenu = document.createElement('td'); + dom.tdMenu = tdMenu; var menu = document.createElement('button'); - dom.menu = menu; menu.className = 'contextmenu'; menu.title = 'Click to open the actions menu (Ctrl+M)'; + dom.menu = menu; tdMenu.appendChild(dom.menu); - dom.tr.appendChild(tdMenu); } - // create tree and field - var tdField = document.createElement('td'); - dom.tr.appendChild(tdField); - dom.tree = this._createDomTree(); - tdField.appendChild(dom.tree); + // a cell for the contents (showing text 'empty') + var tdAppend = document.createElement('td'); + var domText = document.createElement('div'); + domText.innerHTML = '(empty)'; + domText.className = 'readonly'; + tdAppend.appendChild(domText); + dom.td = tdAppend; + dom.text = domText; - this.updateDom({'updateIndexes': true}); - - return dom.tr; - }; - - /** - * DragStart event, fired on mousedown on the dragarea at the left side of a Node - * @param {Event} event - * @private - */ - Node.prototype._onDragStart = function (event) { - var node = this; - if (!this.mousemove) { - this.mousemove = util.addEventListener(document, 'mousemove', - function (event) { - node._onDrag(event); - }); - } - - if (!this.mouseup) { - this.mouseup = util.addEventListener(document, 'mouseup', - function (event ) { - node._onDragEnd(event); - }); - } - - this.editor.highlighter.lock(); - this.drag = { - 'oldCursor': document.body.style.cursor, - 'startParent': this.parent, - 'startIndex': this.parent.childs.indexOf(this), - 'mouseX': event.pageX, - 'level': this.getLevel() - }; - document.body.style.cursor = 'move'; - - event.preventDefault(); - }; - - /** - * Drag event, fired when moving the mouse while dragging a Node - * @param {Event} event - * @private - */ - Node.prototype._onDrag = function (event) { - // TODO: this method has grown too large. Split it in a number of methods - var mouseY = event.pageY; - var mouseX = event.pageX; - - var trThis, trPrev, trNext, trFirst, trLast, trRoot; - var nodePrev, nodeNext; - var topThis, topPrev, topFirst, heightThis, bottomNext, heightNext; - var moved = false; - - // TODO: add an ESC option, which resets to the original position - - // move up/down - trThis = this.dom.tr; - topThis = util.getAbsoluteTop(trThis); - heightThis = trThis.offsetHeight; - if (mouseY < topThis) { - // move up - trPrev = trThis; - do { - trPrev = trPrev.previousSibling; - nodePrev = Node.getNodeFromTarget(trPrev); - topPrev = trPrev ? util.getAbsoluteTop(trPrev) : 0; - } - while (trPrev && mouseY < topPrev); - - if (nodePrev && !nodePrev.parent) { - nodePrev = undefined; - } - - if (!nodePrev) { - // move to the first node - trRoot = trThis.parentNode.firstChild; - trPrev = trRoot ? trRoot.nextSibling : undefined; - nodePrev = Node.getNodeFromTarget(trPrev); - if (nodePrev == this) { - nodePrev = undefined; - } - } - - if (nodePrev) { - // check if mouseY is really inside the found node - trPrev = nodePrev.dom.tr; - topPrev = trPrev ? util.getAbsoluteTop(trPrev) : 0; - if (mouseY > topPrev + heightThis) { - nodePrev = undefined; - } - } - - if (nodePrev) { - nodePrev.parent.moveBefore(this, nodePrev); - moved = true; - } - } - else { - // move down - trLast = (this.expanded && this.append) ? this.append.getDom() : this.dom.tr; - trFirst = trLast ? trLast.nextSibling : undefined; - if (trFirst) { - topFirst = util.getAbsoluteTop(trFirst); - trNext = trFirst; - do { - nodeNext = Node.getNodeFromTarget(trNext); - if (trNext) { - bottomNext = trNext.nextSibling ? - util.getAbsoluteTop(trNext.nextSibling) : 0; - heightNext = trNext ? (bottomNext - topFirst) : 0; - - if (nodeNext.parent.childs.length == 1 && nodeNext.parent.childs[0] == this) { - // We are about to remove the last child of this parent, - // which will make the parents appendNode visible. - topThis += 24 - 1; - // TODO: dangerous to suppose the height of the appendNode a constant of 24-1 px. - } - } - - trNext = trNext.nextSibling; - } - while (trNext && mouseY > topThis + heightNext); - - if (nodeNext && nodeNext.parent) { - // calculate the desired level - var diffX = (mouseX - this.drag.mouseX); - var diffLevel = Math.round(diffX / 24 / 2); - var level = this.drag.level + diffLevel; // desired level - var levelNext = nodeNext.getLevel(); // level to be - - // find the best fitting level (move upwards over the append nodes) - trPrev = nodeNext.dom.tr.previousSibling; - while (levelNext < level && trPrev) { - nodePrev = Node.getNodeFromTarget(trPrev); - if (nodePrev == this || nodePrev._isChildOf(this)) { - // neglect itself and its childs - } - else if (nodePrev instanceof AppendNode) { - var childs = nodePrev.parent.childs; - if (childs.length > 1 || - (childs.length == 1 && childs[0] != this)) { - // non-visible append node of a list of childs - // consisting of not only this node (else the - // append node will change into a visible "empty" - // text when removing this node). - nodeNext = Node.getNodeFromTarget(trPrev); - levelNext = nodeNext.getLevel(); - } - else { - break; - } - } - else { - break; - } - - trPrev = trPrev.previousSibling; - } - - // move the node when its position is changed - if (trLast.nextSibling != nodeNext.dom.tr) { - nodeNext.parent.moveBefore(this, nodeNext); - moved = true; - } - } - } - } - - if (moved) { - // update the dragging parameters when moved - this.drag.mouseX = mouseX; - this.drag.level = this.getLevel(); - } - - // auto scroll when hovering around the top of the editor - this.editor.startAutoScroll(mouseY); - - event.preventDefault(); - }; - - /** - * Drag event, fired on mouseup after having dragged a node - * @param {Event} event - * @private - */ - Node.prototype._onDragEnd = function (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; - this.editor.highlighter.unlock(); - delete this.drag; - - if (this.mousemove) { - util.removeEventListener(document, 'mousemove', this.mousemove); - delete this.mousemove;} - if (this.mouseup) { - util.removeEventListener(document, 'mouseup', this.mouseup); - delete this.mouseup; - } - - // Stop any running auto scroll - this.editor.stopAutoScroll(); - - event.preventDefault(); - }; - - /** - * Test if this node is a child of an other node - * @param {Node} node - * @return {boolean} isChild - * @private - */ - Node.prototype._isChildOf = function (node) { - var n = this.parent; - while (n) { - if (n == node) { - return true; - } - n = n.parent; - } - - return false; - }; - - /** - * Create an editable field - * @return {Element} domField - * @private - */ - 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 - */ - Node.prototype.setHighlight = function (highlight) { - if (this.dom.tr) { - this.dom.tr.className = (highlight ? '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 - */ - Node.prototype.updateValue = function (value) { - this.value = value; this.updateDom(); + + return trAppend; }; /** - * Update the field of the node. - * @param {String} field + * Update the HTML dom of the Node */ - 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. - */ - 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) { - // parent is an object - domField.contentEditable = this.editable.field; - domField.spellcheck = false; - domField.className = 'field'; - } - else { - // parent is an array this is the root node - domField.className = 'readonly'; - } - - var field; - if (this.index != undefined) { - field = this.index; - } - else if (this.field != undefined) { - field = this.field; - } - else if (this._hasChilds()) { - field = this.type; - } - else { - 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 + ']'; - } - else if (this.type == 'object') { - domValue.innerHTML = '{' + count + '}'; - } - else { - domValue.innerHTML = this._escapeHTML(this.value); - } - } - - // 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 - */ - 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 = ''; - } - } - }); - } - } - }; - - /** - * Create an editable value - * @private - */ - Node.prototype._createDomValue = function () { - var domValue; - - if (this.type == 'array') { - domValue = document.createElement('div'); - domValue.className = 'readonly'; - domValue.innerHTML = '[...]'; - } - else if (this.type == 'object') { - domValue = document.createElement('div'); - domValue.className = 'readonly'; - domValue.innerHTML = '{...}'; - } - else { - if (!this.editable.value && util.isUrl(this.value)) { - // create a link in case of read-only editor and value containing an url - domValue = document.createElement('a'); - domValue.className = 'value'; - domValue.href = this.value; - domValue.target = '_blank'; - domValue.innerHTML = this._escapeHTML(this.value); - } - else { - // create an editable or read-only div - domValue = document.createElement('div'); - domValue.contentEditable = this.editable.value; - domValue.spellcheck = false; - domValue.className = 'value'; - domValue.innerHTML = this._escapeHTML(this.value); - } - } - - return domValue; - }; - - /** - * Create an expand/collapse button - * @return {Element} expand - * @private - */ - Node.prototype._createDomExpandButton = function () { - // create expand button - var expand = document.createElement('button'); - if (this._hasChilds()) { - expand.className = this.expanded ? 'expanded' : 'collapsed'; - expand.title = - 'Click to expand/collapse this field (Ctrl+E). \n' + - 'Ctrl+Click to expand/collapse including all childs.'; - } - else { - expand.className = 'invisible'; - expand.title = ''; - } - - return expand; - }; - - - /** - * Create a DOM tree element, containing the expand/collapse button - * @return {Element} domTree - * @private - */ - Node.prototype._createDomTree = function () { + AppendNode.prototype.updateDom = function () { var dom = this.dom; - var domTree = document.createElement('table'); - var tbody = document.createElement('tbody'); - domTree.style.borderCollapse = 'collapse'; // TODO: put in css - domTree.className = 'values'; - domTree.appendChild(tbody); - var tr = document.createElement('tr'); - tbody.appendChild(tr); - - // create expand button - var tdExpand = document.createElement('td'); - tdExpand.className = 'tree'; - tr.appendChild(tdExpand); - dom.expand = this._createDomExpandButton(); - tdExpand.appendChild(dom.expand); - dom.tdExpand = tdExpand; - - // create the field - var tdField = document.createElement('td'); - tdField.className = 'tree'; - tr.appendChild(tdField); - dom.field = this._createDomField(); - tdField.appendChild(dom.field); - dom.tdField = tdField; - - // create a separator - var tdSeparator = document.createElement('td'); - tdSeparator.className = 'tree'; - tr.appendChild(tdSeparator); - if (this.type != 'object' && this.type != 'array') { - tdSeparator.appendChild(document.createTextNode(':')); - tdSeparator.className = 'separator'; + var tdAppend = dom.td; + if (tdAppend) { + tdAppend.style.paddingLeft = (this.getLevel() * 24 + 26) + 'px'; + // TODO: not so nice hard coded offset } - dom.tdSeparator = tdSeparator; - // create the value - var tdValue = document.createElement('td'); - tdValue.className = 'tree'; - tr.appendChild(tdValue); - dom.value = this._createDomValue(); - tdValue.appendChild(dom.value); - dom.tdValue = tdValue; + var domText = dom.text; + if (domText) { + domText.innerHTML = '(empty ' + this.parent.type + ')'; + } - return domTree; + // attach or detach the contents of the append node: + // hide when the parent has childs, show when the parent has no childs + var trAppend = dom.tr; + if (!this.isVisible()) { + if (dom.tr.firstChild) { + if (dom.tdDrag) { + trAppend.removeChild(dom.tdDrag); + } + if (dom.tdMenu) { + trAppend.removeChild(dom.tdMenu); + } + trAppend.removeChild(tdAppend); + } + } + else { + if (!dom.tr.firstChild) { + if (dom.tdDrag) { + trAppend.appendChild(dom.tdDrag); + } + if (dom.tdMenu) { + trAppend.appendChild(dom.tdMenu); + } + trAppend.appendChild(tdAppend); + } + } + }; + + /** + * Check whether the AppendNode is currently visible. + * the AppendNode is visible when its parent has no childs (i.e. is empty). + * @return {boolean} isVisible + */ + AppendNode.prototype.isVisible = function () { + return (this.parent.childs.length == 0); + }; + + /** + * Show a contextmenu for this node + * @param {HTMLElement} anchor The element to attach the menu to. + * @param {function} [onClose] Callback method called when the context menu + * is being closed. + */ + AppendNode.prototype.showContextMenu = function (anchor, onClose) { + var node = this; + var titles = Node.TYPE_TITLES; + var items = [ + // create append button + { + 'text': 'Append', + 'title': 'Append a new field with type \'auto\' (Ctrl+Shift+Ins)', + 'submenuTitle': 'Select the type of the field to be appended', + 'className': 'insert', + 'click': function () { + node._onAppend('', '', 'auto'); + }, + 'submenu': [ + { + 'text': 'Auto', + 'className': 'type-auto', + 'title': titles.auto, + 'click': function () { + node._onAppend('', '', 'auto'); + } + }, + { + 'text': 'Array', + 'className': 'type-array', + 'title': titles.array, + 'click': function () { + node._onAppend('', []); + } + }, + { + 'text': 'Object', + 'className': 'type-object', + 'title': titles.object, + 'click': function () { + node._onAppend('', {}); + } + }, + { + 'text': 'String', + 'className': 'type-string', + 'title': titles.string, + 'click': function () { + node._onAppend('', '', 'string'); + } + } + ] + } + ]; + + var menu = new ContextMenu(items, {close: onClose}); + menu.show(anchor); }; /** * Handle an event. The event is catched centrally by the editor * @param {Event} event */ - Node.prototype.onEvent = function (event) { - var type = event.type, - target = event.target || event.srcElement, - dom = this.dom, - node = this, - focusNode, - expandable = this._hasChilds(); + AppendNode.prototype.onEvent = function (event) { + var type = event.type; + var target = event.target || event.srcElement; + var dom = this.dom; - // check if mouse is on menu or on dragarea. - // If so, highlight current row and its childs - if (target == dom.drag || target == dom.menu) { + // highlight the append nodes parent + var menu = dom.menu; + if (target == menu) { if (type == 'mouseover') { - this.editor.highlighter.highlight(this); + this.editor.highlighter.highlight(this.parent); } else if (type == 'mouseout') { this.editor.highlighter.unhighlight(); } } - // drag events - if (type == 'mousedown' && target == dom.drag) { - this._onDragStart(event); - } - // context menu events if (type == 'click' && target == dom.menu) { - var highlighter = node.editor.highlighter; - highlighter.highlight(node); + var highlighter = this.editor.highlighter; + highlighter.highlight(this.parent); highlighter.lock(); util.addClassName(dom.menu, 'selected'); this.showContextMenu(dom.menu, function () { @@ -4372,1929 +6273,15 @@ return /******/ (function(modules) { // webpackBootstrap }); } - // expand events - if (type == 'click' && target == dom.expand) { - if (expandable) { - var recurse = event.ctrlKey; // with ctrl-key, expand/collapse all - this._onExpand(recurse); - } - } - - // value events - var domValue = dom.value; - if (target == domValue) { - //noinspection FallthroughInSwitchStatementJS - switch (type) { - case 'focus': - focusNode = this; - break; - - case 'blur': - case 'change': - this._getDomValue(true); - this._updateDomValue(); - if (this.value) { - domValue.innerHTML = this._escapeHTML(this.value); - } - break; - - case 'input': - this._getDomValue(true); - this._updateDomValue(); - break; - - case 'keydown': - case 'mousedown': - this.editor.selection = this.editor.getSelection(); - break; - - case 'click': - if (event.ctrlKey || !this.editable.value) { - if (util.isUrl(this.value)) { - window.open(this.value, '_blank'); - } - } - 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': - focusNode = this; - break; - - case 'blur': - case 'change': - this._getDomField(true); - this._updateDomField(); - if (this.field) { - domField.innerHTML = this._escapeHTML(this.field); - } - break; - - case 'input': - this._getDomField(true); - this._updateDomField(); - break; - - case 'keydown': - case 'mousedown': - this.editor.selection = this.editor.getSelection(); - break; - - case 'keyup': - this._getDomField(true); - this._updateDomField(); - break; - - case 'cut': - case 'paste': - setTimeout(function () { - node._getDomField(true); - node._updateDomField(); - }, 1); - 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.pageX < util.getAbsoluteLeft(dom.tdSeparator));// for FF - if (left || expandable) { - // node is expandable when it is an object or array - if (domField) { - util.setEndOfContentEditable(domField); - domField.focus(); - } - } - else { - if (domValue) { - util.setEndOfContentEditable(domValue); - domValue.focus(); - } - } - break; - } - } - if ((target == dom.tdExpand && !expandable) || target == dom.tdField || - target == dom.tdSeparator) { - switch (type) { - case 'click': - if (domField) { - util.setEndOfContentEditable(domField); - domField.focus(); - } - break; - } - } - if (type == 'keydown') { this.onKeyDown(event); } }; - /** - * Key down event handler - * @param {Event} event - */ - Node.prototype.onKeyDown = function (event) { - var keynum = event.which || event.keyCode; - var target = event.target || event.srcElement; - var ctrlKey = event.ctrlKey; - var shiftKey = event.shiftKey; - var altKey = event.altKey; - var handled = false; - var prevNode, nextNode, nextDom, nextDom2; - var editable = this.editor.options.mode === 'tree'; + return AppendNode; + } - // util.log(ctrlKey, keynum, event.charCode); // TODO: cleanup - if (keynum == 13) { // Enter - if (target == this.dom.value) { - if (!this.editable.value || event.ctrlKey) { - if (util.isUrl(this.value)) { - window.open(this.value, '_blank'); - handled = true; - } - } - } - else if (target == this.dom.expand) { - var expandable = this._hasChilds(); - if (expandable) { - var recurse = event.ctrlKey; // with ctrl-key, expand/collapse all - this._onExpand(recurse); - target.focus(); - handled = true; - } - } - } - else if (keynum == 68) { // D - if (ctrlKey && editable) { // Ctrl+D - this._onDuplicate(); - handled = true; - } - } - else if (keynum == 69) { // E - if (ctrlKey) { // Ctrl+E and Ctrl+Shift+E - this._onExpand(shiftKey); // recurse = shiftKey - target.focus(); // TODO: should restore focus in case of recursing expand (which takes DOM offline) - handled = true; - } - } - else if (keynum == 77 && editable) { // M - if (ctrlKey) { // Ctrl+M - this.showContextMenu(target); - handled = true; - } - } - else if (keynum == 46 && editable) { // Del - if (ctrlKey) { // Ctrl+Del - this._onRemove(); - handled = true; - } - } - else if (keynum == 45 && editable) { // Ins - if (ctrlKey && !shiftKey) { // Ctrl+Ins - this._onInsertBefore(); - handled = true; - } - else if (ctrlKey && shiftKey) { // Ctrl+Shift+Ins - this._onInsertAfter(); - handled = true; - } - } - else if (keynum == 35) { // End - if (altKey) { // Alt+End - // find the last node - var lastNode = this._lastNode(); - if (lastNode) { - lastNode.focus(Node.focusElement || this._getElementName(target)); - } - handled = true; - } - } - else if (keynum == 36) { // Home - if (altKey) { // Alt+Home - // find the first node - var firstNode = this._firstNode(); - if (firstNode) { - firstNode.focus(Node.focusElement || this._getElementName(target)); - } - handled = true; - } - } - else if (keynum == 37) { // Arrow Left - if (altKey && !shiftKey) { // Alt + Arrow Left - // move to left element - var prevElement = this._previousElement(target); - if (prevElement) { - this.focus(this._getElementName(prevElement)); - } - handled = true; - } - else if (altKey && shiftKey && editable) { // Alt + Shift Arrow left - if (this.expanded) { - var appendDom = this.getAppend(); - nextDom = appendDom ? appendDom.nextSibling : undefined; - } - else { - var dom = this.getDom(); - nextDom = dom.nextSibling; - } - if (nextDom) { - nextNode = Node.getNodeFromTarget(nextDom); - nextDom2 = nextDom.nextSibling; - nextNode2 = Node.getNodeFromTarget(nextDom2); - if (nextNode && nextNode instanceof AppendNode && - !(this.parent.childs.length == 1) && - nextNode2 && nextNode2.parent) { - nextNode2.parent.moveBefore(this, nextNode2); - this.focus(Node.focusElement || this._getElementName(target)); - } - } - } - } - else if (keynum == 38) { // Arrow Up - if (altKey && !shiftKey) { // Alt + Arrow Up - // find the previous node - prevNode = this._previousNode(); - if (prevNode) { - prevNode.focus(Node.focusElement || this._getElementName(target)); - } - handled = true; - } - else if (altKey && shiftKey) { // Alt + Shift + Arrow Up - // find the previous node - prevNode = this._previousNode(); - if (prevNode && prevNode.parent) { - prevNode.parent.moveBefore(this, prevNode); - this.focus(Node.focusElement || this._getElementName(target)); - } - handled = true; - } - } - else if (keynum == 39) { // Arrow Right - if (altKey && !shiftKey) { // Alt + Arrow Right - // move to right element - var nextElement = this._nextElement(target); - if (nextElement) { - this.focus(this._getElementName(nextElement)); - } - handled = true; - } - else if (altKey && shiftKey) { // Alt + Shift Arrow Right - dom = this.getDom(); - var prevDom = dom.previousSibling; - if (prevDom) { - prevNode = Node.getNodeFromTarget(prevDom); - if (prevNode && prevNode.parent && - (prevNode instanceof AppendNode) - && !prevNode.isVisible()) { - prevNode.parent.moveBefore(this, prevNode); - this.focus(Node.focusElement || this._getElementName(target)); - } - } - } - } - else if (keynum == 40) { // Arrow Down - if (altKey && !shiftKey) { // Alt + Arrow Down - // find the next node - nextNode = this._nextNode(); - if (nextNode) { - nextNode.focus(Node.focusElement || this._getElementName(target)); - } - handled = true; - } - else if (altKey && shiftKey && editable) { // Alt + Shift + Arrow Down - // find the 2nd next node and move before that one - if (this.expanded) { - nextNode = this.append ? this.append._nextNode() : undefined; - } - else { - nextNode = this._nextNode(); - } - nextDom = nextNode ? nextNode.getDom() : undefined; - if (this.parent.childs.length == 1) { - nextDom2 = nextDom; - } - else { - nextDom2 = nextDom ? nextDom.nextSibling : undefined; - } - var nextNode2 = Node.getNodeFromTarget(nextDom2); - if (nextNode2 && nextNode2.parent) { - nextNode2.parent.moveBefore(this, nextNode2); - this.focus(Node.focusElement || this._getElementName(target)); - } - handled = true; - } - } - - if (handled) { - event.preventDefault(); - event.stopPropagation(); - } - }; - - /** - * Handle the expand event, when clicked on the expand button - * @param {boolean} recurse If true, child nodes will be expanded too - * @private - */ - Node.prototype._onExpand = function (recurse) { - 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; - } - }; - - /** - * Remove this node - * @private - */ - Node.prototype._onRemove = function() { - this.editor.highlighter.unhighlight(); - var childs = this.parent.childs; - var index = childs.indexOf(this); - - // adjust the focus - var oldSelection = this.editor.getSelection(); - if (childs[index + 1]) { - childs[index + 1].focus(); - } - else if (childs[index - 1]) { - childs[index - 1].focus(); - } - else { - this.parent.focus(); - } - var newSelection = this.editor.getSelection(); - - // remove the node - this.parent._remove(this); - - // store history action - this.editor._onAction('removeNode', { - node: this, - parent: this.parent, - index: index, - oldSelection: oldSelection, - newSelection: newSelection - }); - }; - - /** - * Duplicate this node - * @private - */ - Node.prototype._onDuplicate = function() { - var oldSelection = this.editor.getSelection(); - var clone = this.parent._duplicate(this); - clone.focus(); - var newSelection = this.editor.getSelection(); - - this.editor._onAction('duplicateNode', { - node: this, - clone: clone, - parent: this.parent, - oldSelection: oldSelection, - newSelection: newSelection - }); - }; - - /** - * Handle insert before event - * @param {String} [field] - * @param {*} [value] - * @param {String} [type] Can be 'auto', 'array', 'object', or 'string' - * @private - */ - Node.prototype._onInsertBefore = function (field, value, type) { - var oldSelection = this.editor.getSelection(); - - var newNode = new Node(this.editor, { - field: (field != undefined) ? field : '', - value: (value != undefined) ? value : '', - type: type - }); - newNode.expand(true); - this.parent.insertBefore(newNode, this); - this.editor.highlighter.unhighlight(); - newNode.focus('field'); - var newSelection = this.editor.getSelection(); - - this.editor._onAction('insertBeforeNode', { - node: newNode, - beforeNode: this, - parent: this.parent, - oldSelection: oldSelection, - newSelection: newSelection - }); - }; - - /** - * Handle insert after event - * @param {String} [field] - * @param {*} [value] - * @param {String} [type] Can be 'auto', 'array', 'object', or 'string' - * @private - */ - Node.prototype._onInsertAfter = function (field, value, type) { - var oldSelection = this.editor.getSelection(); - - var newNode = new Node(this.editor, { - field: (field != undefined) ? field : '', - value: (value != undefined) ? value : '', - type: type - }); - newNode.expand(true); - this.parent.insertAfter(newNode, this); - this.editor.highlighter.unhighlight(); - newNode.focus('field'); - var newSelection = this.editor.getSelection(); - - this.editor._onAction('insertAfterNode', { - node: newNode, - afterNode: this, - parent: this.parent, - oldSelection: oldSelection, - newSelection: newSelection - }); - }; - - /** - * Handle append event - * @param {String} [field] - * @param {*} [value] - * @param {String} [type] Can be 'auto', 'array', 'object', or 'string' - * @private - */ - Node.prototype._onAppend = function (field, value, type) { - var oldSelection = this.editor.getSelection(); - - var newNode = new Node(this.editor, { - field: (field != undefined) ? field : '', - value: (value != undefined) ? value : '', - type: type - }); - newNode.expand(true); - this.parent.appendChild(newNode); - this.editor.highlighter.unhighlight(); - newNode.focus('field'); - var newSelection = this.editor.getSelection(); - - this.editor._onAction('appendNode', { - node: newNode, - parent: this.parent, - oldSelection: oldSelection, - newSelection: newSelection - }); - }; - - /** - * Change the type of the node's value - * @param {String} newType - * @private - */ - Node.prototype._onChangeType = function (newType) { - var oldType = this.type; - if (newType != oldType) { - var oldSelection = this.editor.getSelection(); - this.changeType(newType); - var newSelection = this.editor.getSelection(); - - this.editor._onAction('changeType', { - node: this, - oldType: oldType, - newType: newType, - oldSelection: oldSelection, - newSelection: newSelection - }); - } - }; - - /** - * Sort the childs of the node. Only applicable when the node has type 'object' - * or 'array'. - * @param {String} direction Sorting direction. Available values: "asc", "desc" - * @private - */ - Node.prototype._onSort = function (direction) { - if (this._hasChilds()) { - var order = (direction == 'desc') ? -1 : 1; - var prop = (this.type == 'array') ? 'value': 'field'; - this.hideChilds(); - - var oldChilds = this.childs; - var oldSort = this.sort; - - // copy the array (the old one will be kept for an undo action - this.childs = this.childs.concat(); - - // sort the arrays - this.childs.sort(function (a, b) { - if (a[prop] > b[prop]) return order; - if (a[prop] < b[prop]) return -order; - return 0; - }); - this.sort = (order == 1) ? 'asc' : 'desc'; - - this.editor._onAction('sort', { - node: this, - oldChilds: oldChilds, - oldSort: oldSort, - newChilds: this.childs, - newSort: this.sort - }); - - this.showChilds(); - } - }; - - /** - * Create a table row with an append button. - * @return {HTMLElement | undefined} buttonAppend or undefined when inapplicable - */ - Node.prototype.getAppend = function () { - if (!this.append) { - this.append = new AppendNode(this.editor); - this.append.setParent(this); - } - return this.append.getDom(); - }; - - /** - * Find the node from an event target - * @param {Node} target - * @return {Node | undefined} node or undefined when not found - * @static - */ - Node.getNodeFromTarget = function (target) { - while (target) { - if (target.node) { - return target.node; - } - target = target.parentNode; - } - - return undefined; - }; - - /** - * Get the previously rendered node - * @return {Node | null} previousNode - * @private - */ - Node.prototype._previousNode = function () { - var prevNode = null; - var dom = this.getDom(); - if (dom && dom.parentNode) { - // find the previous field - var prevDom = dom; - do { - prevDom = prevDom.previousSibling; - prevNode = Node.getNodeFromTarget(prevDom); - } - while (prevDom && (prevNode instanceof AppendNode && !prevNode.isVisible())); - } - return prevNode; - }; - - /** - * Get the next rendered node - * @return {Node | null} nextNode - * @private - */ - Node.prototype._nextNode = function () { - var nextNode = null; - var dom = this.getDom(); - if (dom && dom.parentNode) { - // find the previous field - var nextDom = dom; - do { - nextDom = nextDom.nextSibling; - nextNode = Node.getNodeFromTarget(nextDom); - } - while (nextDom && (nextNode instanceof AppendNode && !nextNode.isVisible())); - } - - return nextNode; - }; - - /** - * Get the first rendered node - * @return {Node | null} firstNode - * @private - */ - Node.prototype._firstNode = function () { - var firstNode = null; - var dom = this.getDom(); - if (dom && dom.parentNode) { - var firstDom = dom.parentNode.firstChild; - firstNode = Node.getNodeFromTarget(firstDom); - } - - return firstNode; - }; - - /** - * Get the last rendered node - * @return {Node | null} lastNode - * @private - */ - Node.prototype._lastNode = function () { - var lastNode = null; - var dom = this.getDom(); - if (dom && dom.parentNode) { - var lastDom = dom.parentNode.lastChild; - lastNode = Node.getNodeFromTarget(lastDom); - while (lastDom && (lastNode instanceof AppendNode && !lastNode.isVisible())) { - lastDom = lastDom.previousSibling; - lastNode = Node.getNodeFromTarget(lastDom); - } - } - return lastNode; - }; - - /** - * Get the next element which can have focus. - * @param {Element} elem - * @return {Element | null} nextElem - * @private - */ - Node.prototype._previousElement = function (elem) { - var dom = this.dom; - // noinspection FallthroughInSwitchStatementJS - switch (elem) { - case dom.value: - if (this.fieldEditable) { - return dom.field; - } - // intentional fall through - case dom.field: - if (this._hasChilds()) { - return dom.expand; - } - // intentional fall through - case dom.expand: - return dom.menu; - case dom.menu: - if (dom.drag) { - return dom.drag; - } - // intentional fall through - default: - return null; - } - }; - - /** - * Get the next element which can have focus. - * @param {Element} elem - * @return {Element | null} nextElem - * @private - */ - Node.prototype._nextElement = function (elem) { - var dom = this.dom; - // noinspection FallthroughInSwitchStatementJS - switch (elem) { - case dom.drag: - return dom.menu; - case dom.menu: - if (this._hasChilds()) { - return dom.expand; - } - // intentional fall through - case dom.expand: - if (this.fieldEditable) { - return dom.field; - } - // intentional fall through - case dom.field: - if (!this._hasChilds()) { - return dom.value; - } - default: - return null; - } - }; - - /** - * Get the dom name of given element. returns null if not found. - * For example when element == dom.field, "field" is returned. - * @param {Element} element - * @return {String | null} elementName Available elements with name: 'drag', - * 'menu', 'expand', 'field', 'value' - * @private - */ - Node.prototype._getElementName = function (element) { - var dom = this.dom; - for (var name in dom) { - if (dom.hasOwnProperty(name)) { - if (dom[name] == element) { - return name; - } - } - } - return null; - }; - - /** - * Test if this node has childs. This is the case when the node is an object - * or array. - * @return {boolean} hasChilds - * @private - */ - Node.prototype._hasChilds = function () { - return this.type == 'array' || this.type == 'object'; - }; - - // titles with explanation for the different types - Node.TYPE_TITLES = { - 'auto': 'Field type "auto". ' + - 'The field type is automatically determined from the value ' + - 'and can be a string, number, boolean, or null.', - 'object': 'Field type "object". ' + - 'An object contains an unordered set of key/value pairs.', - 'array': 'Field type "array". ' + - 'An array contains an ordered collection of values.', - 'string': 'Field type "string". ' + - 'Field type is not determined from the value, ' + - 'but always returned as string.' - }; - - /** - * Show a contextmenu for this node - * @param {HTMLElement} anchor Anchor element to attache the context menu to. - * @param {function} [onClose] Callback method called when the context menu - * is being closed. - */ - Node.prototype.showContextMenu = function (anchor, onClose) { - var node = this; - var titles = Node.TYPE_TITLES; - var items = []; - - if (this.editable.value) { - items.push({ - text: 'Type', - title: 'Change the type of this field', - className: 'type-' + this.type, - submenu: [ - { - text: 'Auto', - className: 'type-auto' + - (this.type == 'auto' ? ' selected' : ''), - title: titles.auto, - click: function () { - node._onChangeType('auto'); - } - }, - { - text: 'Array', - className: 'type-array' + - (this.type == 'array' ? ' selected' : ''), - title: titles.array, - click: function () { - node._onChangeType('array'); - } - }, - { - text: 'Object', - className: 'type-object' + - (this.type == 'object' ? ' selected' : ''), - title: titles.object, - click: function () { - node._onChangeType('object'); - } - }, - { - text: 'String', - className: 'type-string' + - (this.type == 'string' ? ' selected' : ''), - title: titles.string, - click: function () { - node._onChangeType('string'); - } - } - ] - }); - } - - if (this._hasChilds()) { - var direction = ((this.sort == 'asc') ? 'desc': 'asc'); - items.push({ - text: 'Sort', - title: 'Sort the childs of this ' + this.type, - className: 'sort-' + direction, - click: function () { - node._onSort(direction); - }, - submenu: [ - { - text: 'Ascending', - className: 'sort-asc', - title: 'Sort the childs of this ' + this.type + ' in ascending order', - click: function () { - node._onSort('asc'); - } - }, - { - text: 'Descending', - className: 'sort-desc', - title: 'Sort the childs of this ' + this.type +' in descending order', - click: function () { - node._onSort('desc'); - } - } - ] - }); - } - - if (this.parent && this.parent._hasChilds()) { - if (items.length) { - // create a separator - items.push({ - 'type': 'separator' - }); - } - - // create append button (for last child node only) - var childs = node.parent.childs; - if (node == childs[childs.length - 1]) { - items.push({ - text: 'Append', - title: 'Append a new field with type \'auto\' after this field (Ctrl+Shift+Ins)', - submenuTitle: 'Select the type of the field to be appended', - className: 'append', - click: function () { - node._onAppend('', '', 'auto'); - }, - submenu: [ - { - text: 'Auto', - className: 'type-auto', - title: titles.auto, - click: function () { - node._onAppend('', '', 'auto'); - } - }, - { - text: 'Array', - className: 'type-array', - title: titles.array, - click: function () { - node._onAppend('', []); - } - }, - { - text: 'Object', - className: 'type-object', - title: titles.object, - click: function () { - node._onAppend('', {}); - } - }, - { - text: 'String', - className: 'type-string', - title: titles.string, - click: function () { - node._onAppend('', '', 'string'); - } - } - ] - }); - } - - // create insert button - items.push({ - text: 'Insert', - title: 'Insert a new field with type \'auto\' before this field (Ctrl+Ins)', - submenuTitle: 'Select the type of the field to be inserted', - className: 'insert', - click: function () { - node._onInsertBefore('', '', 'auto'); - }, - submenu: [ - { - text: 'Auto', - className: 'type-auto', - title: titles.auto, - click: function () { - node._onInsertBefore('', '', 'auto'); - } - }, - { - text: 'Array', - className: 'type-array', - title: titles.array, - click: function () { - node._onInsertBefore('', []); - } - }, - { - text: 'Object', - className: 'type-object', - title: titles.object, - click: function () { - node._onInsertBefore('', {}); - } - }, - { - text: 'String', - className: 'type-string', - title: titles.string, - click: function () { - node._onInsertBefore('', '', 'string'); - } - } - ] - }); - - if (this.editable.field) { - // create duplicate button - items.push({ - text: 'Duplicate', - title: 'Duplicate this field (Ctrl+D)', - className: 'duplicate', - click: function () { - node._onDuplicate(); - } - }); - - // create remove button - items.push({ - text: 'Remove', - title: 'Remove this field (Ctrl+Del)', - className: 'remove', - click: function () { - node._onRemove(); - } - }); - } - } - - var menu = new ContextMenu(items, {close: onClose}); - menu.show(anchor); - }; - - /** - * get the type of a value - * @param {*} value - * @return {String} type Can be 'object', 'array', 'string', 'auto' - * @private - */ - 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 - */ - 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 - */ - Node.prototype._escapeHTML = function (text) { - var htmlEscaped = String(text) - .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 - */ - Node.prototype._unescapeHTML = function (escapedText) { - var json = '"' + this._escapeJSON(escapedText) + '"'; - var htmlEscaped = util.parse(json); - return htmlEscaped - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/ |\u00A0/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 - */ - 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; - }; - - // TODO: find a nicer solution to resolve this circular dependency between Node and AppendNode - var AppendNode = appendNodeFactory(Node); - - return Node; - }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); - -/***/ }, -/* 8 */ -/***/ function(module, exports, __webpack_require__) { - - var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(9)], __WEBPACK_AMD_DEFINE_RESULT__ = function (ContextMenu) { - - /** - * Create a select box to be used in the editor menu's, which allows to switch mode - * @param {Object} editor - * @param {String[]} modes Available modes: 'code', 'form', 'text', 'tree', 'view' - * @param {String} current Available modes: 'code', 'form', 'text', 'tree', 'view' - * @returns {HTMLElement} box - */ - function createModeSwitcher(editor, modes, current) { - // TODO: decouple mode switcher from editor - - /** - * Switch the mode of the editor - * @param {String} mode - */ - function switchMode(mode) { - // switch mode - editor.setMode(mode); - - // restore focus on mode box - var modeBox = editor.dom && editor.dom.modeBox; - if (modeBox) { - modeBox.focus(); - } - } - - // available modes - var availableModes = { - code: { - 'text': 'Code', - 'title': 'Switch to code highlighter', - 'click': function () { - switchMode('code') - } - }, - form: { - 'text': 'Form', - 'title': 'Switch to form editor', - 'click': function () { - switchMode('form'); - } - }, - text: { - 'text': 'Text', - 'title': 'Switch to plain text editor', - 'click': function () { - switchMode('text'); - } - }, - tree: { - 'text': 'Tree', - 'title': 'Switch to tree editor', - 'click': function () { - switchMode('tree'); - } - }, - view: { - 'text': 'View', - 'title': 'Switch to tree view', - 'click': function () { - switchMode('view'); - } - } - }; - - // list the selected modes - var items = []; - for (var i = 0; i < modes.length; i++) { - var mode = modes[i]; - var item = availableModes[mode]; - if (!item) { - throw new Error('Unknown mode "' + mode + '"'); - } - - item.className = 'type-modes' + ((current == mode) ? ' selected' : ''); - items.push(item); - } - - // retrieve the title of current mode - var currentMode = availableModes[current]; - if (!currentMode) { - throw new Error('Unknown mode "' + current + '"'); - } - var currentTitle = currentMode.text; - - // create the html element - var box = document.createElement('button'); - box.className = 'modes separator'; - box.innerHTML = currentTitle + ' ▾'; - box.title = 'Switch editor mode'; - box.onclick = function () { - var menu = new ContextMenu(items); - menu.show(box); - }; - - return box; - } - - return { - create: createModeSwitcher - } - }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); - - -/***/ }, -/* 9 */ -/***/ function(module, exports, __webpack_require__) { - - var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(3)], __WEBPACK_AMD_DEFINE_RESULT__ = function (util) { - - /** - * A context menu - * @param {Object[]} items Array containing the menu structure - * TODO: describe structure - * @param {Object} [options] Object with options. Available options: - * {function} close Callback called when the - * context menu is being closed. - * @constructor - */ - function ContextMenu (items, options) { - this.dom = {}; - - var me = this; - var dom = this.dom; - this.anchor = undefined; - this.items = items; - this.eventListeners = {}; - this.selection = undefined; // holds the selection before the menu was opened - this.visibleSubmenu = undefined; - this.onClose = options ? options.close : undefined; - - // create a container element - var menu = document.createElement('div'); - menu.className = 'jsoneditor-contextmenu'; - dom.menu = menu; - - // create a list to hold the menu items - var list = document.createElement('ul'); - list.className = 'menu'; - menu.appendChild(list); - dom.list = list; - dom.items = []; // list with all buttons - - // create a (non-visible) button to set the focus to the menu - var focusButton = document.createElement('button'); - dom.focusButton = focusButton; - var li = document.createElement('li'); - li.style.overflow = 'hidden'; - li.style.height = '0'; - li.appendChild(focusButton); - list.appendChild(li); - - function createMenuItems (list, domItems, items) { - items.forEach(function (item) { - if (item.type == 'separator') { - // create a separator - var separator = document.createElement('div'); - separator.className = 'separator'; - li = document.createElement('li'); - li.appendChild(separator); - list.appendChild(li); - } - else { - var domItem = {}; - - // create a menu item - var li = document.createElement('li'); - list.appendChild(li); - - // create a button in the menu item - var button = document.createElement('button'); - button.className = item.className; - domItem.button = button; - if (item.title) { - button.title = item.title; - } - if (item.click) { - button.onclick = function () { - me.hide(); - item.click(); - }; - } - li.appendChild(button); - - // create the contents of the button - if (item.submenu) { - // add the icon to the button - var divIcon = document.createElement('div'); - divIcon.className = 'icon'; - button.appendChild(divIcon); - button.appendChild(document.createTextNode(item.text)); - - var buttonSubmenu; - if (item.click) { - // submenu and a button with a click handler - button.className += ' default'; - - var buttonExpand = document.createElement('button'); - domItem.buttonExpand = buttonExpand; - buttonExpand.className = 'expand'; - buttonExpand.innerHTML = '
'; - li.appendChild(buttonExpand); - if (item.submenuTitle) { - buttonExpand.title = item.submenuTitle; - } - - buttonSubmenu = buttonExpand; - } - else { - // submenu and a button without a click handler - var divExpand = document.createElement('div'); - divExpand.className = 'expand'; - button.appendChild(divExpand); - - buttonSubmenu = button; - } - - // attach a handler to expand/collapse the submenu - buttonSubmenu.onclick = function () { - me._onExpandItem(domItem); - buttonSubmenu.focus(); - }; - - // create the submenu - var domSubItems = []; - domItem.subItems = domSubItems; - var ul = document.createElement('ul'); - domItem.ul = ul; - ul.className = 'menu'; - ul.style.height = '0'; - li.appendChild(ul); - createMenuItems(ul, domSubItems, item.submenu); - } - else { - // no submenu, just a button with clickhandler - button.innerHTML = '
' + item.text; - } - - domItems.push(domItem); - } - }); - } - createMenuItems(list, this.dom.items, items); - - // TODO: when the editor is small, show the submenu on the right instead of inline? - - // calculate the max height of the menu with one submenu expanded - this.maxHeight = 0; // height in pixels - items.forEach(function (item) { - var height = (items.length + (item.submenu ? item.submenu.length : 0)) * 24; - me.maxHeight = Math.max(me.maxHeight, height); - }); - } - - /** - * Get the currently visible buttons - * @return {Array.} buttons - * @private - */ - ContextMenu.prototype._getVisibleButtons = function () { - var buttons = []; - var me = this; - this.dom.items.forEach(function (item) { - buttons.push(item.button); - if (item.buttonExpand) { - buttons.push(item.buttonExpand); - } - if (item.subItems && item == me.expandedItem) { - item.subItems.forEach(function (subItem) { - buttons.push(subItem.button); - if (subItem.buttonExpand) { - buttons.push(subItem.buttonExpand); - } - // TODO: change to fully recursive method - }); - } - }); - - return buttons; - }; - - // currently displayed context menu, a singleton. We may only have one visible context menu - ContextMenu.visibleMenu = undefined; - - /** - * Attach the menu to an anchor - * @param {HTMLElement} anchor - */ - ContextMenu.prototype.show = function (anchor) { - this.hide(); - - // calculate whether the menu fits below the anchor - var windowHeight = window.innerHeight, - windowScroll = (window.pageYOffset || document.scrollTop || 0), - windowBottom = windowHeight + windowScroll, - anchorHeight = anchor.offsetHeight, - menuHeight = this.maxHeight; - - // position the menu - var left = util.getAbsoluteLeft(anchor); - var top = util.getAbsoluteTop(anchor); - if (top + anchorHeight + menuHeight < windowBottom) { - // display the menu below the anchor - this.dom.menu.style.left = left + 'px'; - this.dom.menu.style.top = (top + anchorHeight) + 'px'; - this.dom.menu.style.bottom = ''; - } - else { - // display the menu above the anchor - this.dom.menu.style.left = left + 'px'; - this.dom.menu.style.top = ''; - this.dom.menu.style.bottom = (windowHeight - top) + 'px'; - } - - // attach the menu to the document - document.body.appendChild(this.dom.menu); - - // create and attach event listeners - var me = this; - var list = this.dom.list; - this.eventListeners.mousedown = util.addEventListener( - document, 'mousedown', function (event) { - // hide menu on click outside of the menu - var target = event.target; - if ((target != list) && !me._isChildOf(target, list)) { - me.hide(); - event.stopPropagation(); - event.preventDefault(); - } - }); - this.eventListeners.mousewheel = util.addEventListener( - document, 'mousewheel', function (event) { - // block scrolling when context menu is visible - event.stopPropagation(); - event.preventDefault(); - }); - this.eventListeners.keydown = util.addEventListener( - document, 'keydown', function (event) { - me._onKeyDown(event); - }); - - // move focus to the first button in the context menu - this.selection = util.getSelection(); - this.anchor = anchor; - setTimeout(function () { - me.dom.focusButton.focus(); - }, 0); - - if (ContextMenu.visibleMenu) { - ContextMenu.visibleMenu.hide(); - } - ContextMenu.visibleMenu = this; - }; - - /** - * Hide the context menu if visible - */ - ContextMenu.prototype.hide = function () { - // remove the menu from the DOM - if (this.dom.menu.parentNode) { - this.dom.menu.parentNode.removeChild(this.dom.menu); - if (this.onClose) { - this.onClose(); - } - } - - // remove all event listeners - // all event listeners are supposed to be attached to document. - for (var name in this.eventListeners) { - if (this.eventListeners.hasOwnProperty(name)) { - var fn = this.eventListeners[name]; - if (fn) { - util.removeEventListener(document, name, fn); - } - delete this.eventListeners[name]; - } - } - - if (ContextMenu.visibleMenu == this) { - ContextMenu.visibleMenu = undefined; - } - }; - - /** - * Expand a submenu - * Any currently expanded submenu will be hided. - * @param {Object} domItem - * @private - */ - ContextMenu.prototype._onExpandItem = function (domItem) { - var me = this; - var alreadyVisible = (domItem == this.expandedItem); - - // hide the currently visible submenu - var expandedItem = this.expandedItem; - if (expandedItem) { - //var ul = expandedItem.ul; - expandedItem.ul.style.height = '0'; - expandedItem.ul.style.padding = ''; - setTimeout(function () { - if (me.expandedItem != expandedItem) { - expandedItem.ul.style.display = ''; - util.removeClassName(expandedItem.ul.parentNode, 'selected'); - } - }, 300); // timeout duration must match the css transition duration - this.expandedItem = undefined; - } - - if (!alreadyVisible) { - var ul = domItem.ul; - ul.style.display = 'block'; - var height = ul.clientHeight; // force a reflow in Firefox - setTimeout(function () { - if (me.expandedItem == domItem) { - ul.style.height = (ul.childNodes.length * 24) + 'px'; - ul.style.padding = '5px 10px'; - } - }, 0); - util.addClassName(ul.parentNode, 'selected'); - this.expandedItem = domItem; - } - }; - - /** - * Handle onkeydown event - * @param {Event} event - * @private - */ - ContextMenu.prototype._onKeyDown = function (event) { - var target = event.target; - var keynum = event.which; - var handled = false; - var buttons, targetIndex, prevButton, nextButton; - - if (keynum == 27) { // ESC - // hide the menu on ESC key - - // restore previous selection and focus - if (this.selection) { - util.setSelection(this.selection); - } - if (this.anchor) { - this.anchor.focus(); - } - - this.hide(); - - handled = true; - } - else if (keynum == 9) { // Tab - if (!event.shiftKey) { // Tab - buttons = this._getVisibleButtons(); - targetIndex = buttons.indexOf(target); - if (targetIndex == buttons.length - 1) { - // move to first button - buttons[0].focus(); - handled = true; - } - } - else { // Shift+Tab - buttons = this._getVisibleButtons(); - targetIndex = buttons.indexOf(target); - if (targetIndex == 0) { - // move to last button - buttons[buttons.length - 1].focus(); - handled = true; - } - } - } - else if (keynum == 37) { // Arrow Left - if (target.className == 'expand') { - buttons = this._getVisibleButtons(); - targetIndex = buttons.indexOf(target); - prevButton = buttons[targetIndex - 1]; - if (prevButton) { - prevButton.focus(); - } - } - handled = true; - } - else if (keynum == 38) { // Arrow Up - buttons = this._getVisibleButtons(); - targetIndex = buttons.indexOf(target); - prevButton = buttons[targetIndex - 1]; - if (prevButton && prevButton.className == 'expand') { - // skip expand button - prevButton = buttons[targetIndex - 2]; - } - if (!prevButton) { - // move to last button - prevButton = buttons[buttons.length - 1]; - } - if (prevButton) { - prevButton.focus(); - } - handled = true; - } - else if (keynum == 39) { // Arrow Right - buttons = this._getVisibleButtons(); - targetIndex = buttons.indexOf(target); - nextButton = buttons[targetIndex + 1]; - if (nextButton && nextButton.className == 'expand') { - nextButton.focus(); - } - handled = true; - } - else if (keynum == 40) { // Arrow Down - buttons = this._getVisibleButtons(); - targetIndex = buttons.indexOf(target); - nextButton = buttons[targetIndex + 1]; - if (nextButton && nextButton.className == 'expand') { - // skip expand button - nextButton = buttons[targetIndex + 2]; - } - if (!nextButton) { - // move to first button - nextButton = buttons[0]; - } - if (nextButton) { - nextButton.focus(); - handled = true; - } - handled = true; - } - // TODO: arrow left and right - - if (handled) { - event.stopPropagation(); - event.preventDefault(); - } - }; - - /** - * Test if an element is a child of a parent element. - * @param {Element} child - * @param {Element} parent - * @return {boolean} isChild - */ - ContextMenu.prototype._isChildOf = function (child, parent) { - var e = child.parentNode; - while (e) { - if (e == parent) { - return true; - } - e = e.parentNode; - } - - return false; - }; - - return ContextMenu; - }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); - - -/***/ }, -/* 10 */ -/***/ function(module, exports, __webpack_require__) { - - var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(9), __webpack_require__(3)], __WEBPACK_AMD_DEFINE_RESULT__ = function (ContextMenu, util) { - - /** - * A factory function to create an AppendNode, which depends on a Node - * @param {Node} Node - */ - function appendNodeFactory(Node) { - /** - * @constructor AppendNode - * @extends Node - * @param {TreeEditor} 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 - */ - function AppendNode (editor) { - /** @type {TreeEditor} */ - this.editor = editor; - this.dom = {}; - } - - AppendNode.prototype = new Node(); - - /** - * Return a table row with an append button. - * @return {Element} dom TR element - */ - AppendNode.prototype.getDom = function () { - // TODO: implement a new solution for the append node - var dom = this.dom; - - if (dom.tr) { - return dom.tr; - } - - this._updateEditability(); - - // a row for the append button - var trAppend = document.createElement('tr'); - trAppend.node = this; - dom.tr = trAppend; - - // TODO: consistent naming - - if (this.editable.field) { - // a cell for the dragarea column - dom.tdDrag = document.createElement('td'); - - // create context menu - var tdMenu = document.createElement('td'); - dom.tdMenu = tdMenu; - var menu = document.createElement('button'); - menu.className = 'contextmenu'; - menu.title = 'Click to open the actions menu (Ctrl+M)'; - dom.menu = menu; - tdMenu.appendChild(dom.menu); - } - - // a cell for the contents (showing text 'empty') - var tdAppend = document.createElement('td'); - var domText = document.createElement('div'); - domText.innerHTML = '(empty)'; - domText.className = 'readonly'; - tdAppend.appendChild(domText); - dom.td = tdAppend; - dom.text = domText; - - this.updateDom(); - - return trAppend; - }; - - /** - * Update the HTML dom of the Node - */ - AppendNode.prototype.updateDom = function () { - var dom = this.dom; - var tdAppend = dom.td; - if (tdAppend) { - tdAppend.style.paddingLeft = (this.getLevel() * 24 + 26) + 'px'; - // TODO: not so nice hard coded offset - } - - var domText = dom.text; - if (domText) { - domText.innerHTML = '(empty ' + this.parent.type + ')'; - } - - // attach or detach the contents of the append node: - // hide when the parent has childs, show when the parent has no childs - var trAppend = dom.tr; - if (!this.isVisible()) { - if (dom.tr.firstChild) { - if (dom.tdDrag) { - trAppend.removeChild(dom.tdDrag); - } - if (dom.tdMenu) { - trAppend.removeChild(dom.tdMenu); - } - trAppend.removeChild(tdAppend); - } - } - else { - if (!dom.tr.firstChild) { - if (dom.tdDrag) { - trAppend.appendChild(dom.tdDrag); - } - if (dom.tdMenu) { - trAppend.appendChild(dom.tdMenu); - } - trAppend.appendChild(tdAppend); - } - } - }; - - /** - * Check whether the AppendNode is currently visible. - * the AppendNode is visible when its parent has no childs (i.e. is empty). - * @return {boolean} isVisible - */ - AppendNode.prototype.isVisible = function () { - return (this.parent.childs.length == 0); - }; - - /** - * Show a contextmenu for this node - * @param {HTMLElement} anchor The element to attach the menu to. - * @param {function} [onClose] Callback method called when the context menu - * is being closed. - */ - AppendNode.prototype.showContextMenu = function (anchor, onClose) { - var node = this; - var titles = Node.TYPE_TITLES; - var items = [ - // create append button - { - 'text': 'Append', - 'title': 'Append a new field with type \'auto\' (Ctrl+Shift+Ins)', - 'submenuTitle': 'Select the type of the field to be appended', - 'className': 'insert', - 'click': function () { - node._onAppend('', '', 'auto'); - }, - 'submenu': [ - { - 'text': 'Auto', - 'className': 'type-auto', - 'title': titles.auto, - 'click': function () { - node._onAppend('', '', 'auto'); - } - }, - { - 'text': 'Array', - 'className': 'type-array', - 'title': titles.array, - 'click': function () { - node._onAppend('', []); - } - }, - { - 'text': 'Object', - 'className': 'type-object', - 'title': titles.object, - 'click': function () { - node._onAppend('', {}); - } - }, - { - 'text': 'String', - 'className': 'type-string', - 'title': titles.string, - 'click': function () { - node._onAppend('', '', 'string'); - } - } - ] - } - ]; - - var menu = new ContextMenu(items, {close: onClose}); - menu.show(anchor); - }; - - /** - * Handle an event. The event is catched centrally by the editor - * @param {Event} event - */ - AppendNode.prototype.onEvent = function (event) { - var type = event.type; - var target = event.target || event.srcElement; - var dom = this.dom; - - // highlight the append nodes parent - var menu = dom.menu; - if (target == menu) { - if (type == 'mouseover') { - this.editor.highlighter.highlight(this.parent); - } - else if (type == 'mouseout') { - this.editor.highlighter.unhighlight(); - } - } - - // context menu events - if (type == 'click' && target == dom.menu) { - var highlighter = this.editor.highlighter; - highlighter.highlight(this.parent); - highlighter.lock(); - util.addClassName(dom.menu, 'selected'); - this.showContextMenu(dom.menu, function () { - util.removeClassName(dom.menu, 'selected'); - highlighter.unlock(); - highlighter.unhighlight(); - }); - } - - if (type == 'keydown') { - this.onKeyDown(event); - } - }; - - return AppendNode; - } - - // return the factory function - return appendNodeFactory; - }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + module.exports = appendNodeFactory; /***/ } diff --git a/jsoneditor.map b/jsoneditor.map index 2bad648..7e7b935 100644 --- a/jsoneditor.map +++ b/jsoneditor.map @@ -1 +1 @@ -{"version":3,"file":"jsoneditor.map","sources":["./jsoneditor.js"],"names":["root","factory","exports","module","define","amd","this","modules","__webpack_require__","moduleId","installedModules","id","loaded","call","m","c","p","__WEBPACK_AMD_DEFINE_ARRAY__","__WEBPACK_AMD_DEFINE_RESULT__","treemode","textmode","util","JSONEditor","container","options","json","Error","ieVersion","getInternetExplorerVersion","arguments","length","_create","modes","prototype","mode","setMode","_delete","set","get","setText","jsonText","parse","getText","JSON","stringify","setName","name","getName","data","extend","config","asText","clear","mixin","create","load","err","_onError","onError","log","error","registerMode","i","prop","isArray","reserved","apply","undefined","Highlighter","History","SearchBox","Node","modeswitcher","dom","highlighter","selection","_setOptions","history","_createFrame","_createTable","frame","parentNode","removeChild","search","hasOwnProperty","focusNode","domFocus","Function","content","table","params","field","value","node","_setRoot","recurse","expand","appendChild","blur","getValue","updateField","collapse","tbody","getDom","text","results","expandAll","collapseAll","_onAction","action","add","change","startAutoScroll","mouseY","me","top","getAbsoluteTop","height","clientHeight","bottom","margin","interval","autoScrollStep","scrollTop","scrollHeight","autoScrollTimer","setInterval","stopAutoScroll","clearTimeout","setSelection","range","setSelectionOffset","focus","getSelection","getSelectionOffset","scrollTo","callback","editor","animateTimeout","animateCallback","finalScrollTop","Math","min","max","animate","diff","abs","setTimeout","onEvent","event","_onEvent","document","createElement","className","onclick","target","nodeName","preventDefault","oninput","onchange","onkeydown","onkeyup","oncut","onpaste","onmousedown","onmouseup","onmouseover","onmouseout","addEventListener","onfocusin","onfocusout","menu","title","undo","_onUndo","redo","_onRedo","onChange","disabled","canUndo","canRedo","modeBox","searchBox","type","_onKeyDown","getNodeFromTarget","keynum","which","keyCode","ctrlKey","shiftKey","handled","selectContentEditable","select","previous","next","stopPropagation","contentOuter","col","colgroupContent","width","indentation","Number","ace","textarea","clientWidth","buttonFormat","format","buttonCompact","compact","editorDom","style","edit","setTheme","setShowPrintMargin","setFontSize","getSession","setTabSize","setUseSoftTabs","setUseWrapMode","poweredBy","createTextNode","href","window","open","on","spellcheck","resize","force","sanitize","setValue","jsonString","validate","jsString","chars","inString","charAt","isEscaped","push","join","replace","$0","$1","$2","$3","jsonlint","a","b","console","object","String","Boolean","RegExp","isUrlRegex","isUrl","test","obj","Object","toString","getAbsoluteLeft","elem","rect","getBoundingClientRect","left","pageXOffset","scrollLeft","pageYOffset","addClassName","classes","split","indexOf","removeClassName","index","splice","stripFormatting","divElement","childs","childNodes","iMax","child","removeAttribute","attributes","j","attribute","specified","setEndOfContentEditable","contentEditableElement","createRange","selectNodeContents","removeAllRanges","addRange","sel","getRangeAt","rangeCount","startContainer","endContainer","startOffset","endOffset","setStart","firstChild","setEnd","getInnerText","element","buffer","first","flush","nodeValue","hasChildNodes","innerText","prevChild","prevName","_ieVersion","rv","navigator","appName","ua","userAgent","re","exec","parseFloat","isFirefox","listener","useCapture","attachEvent","f","removeEventListener","detachEvent","locked","highlight","setHighlight","_cancelUnhighlight","unhighlight","unhighlightTimer","lock","unlock","actions","editField","oldValue","newValue","editValue","updateValue","appendNode","parent","insertBeforeNode","insertBefore","beforeNode","insertAfterNode","insertAfter","afterNode","removeNode","append","duplicateNode","clone","changeType","oldType","newType","moveNode","startParent","moveTo","startIndex","endParent","endIndex","sort","hideChilds","oldSort","oldChilds","showChilds","newSort","newChilds","timestamp","Date","oldSelection","newSelection","timeout","delay","lastText","tr","td","divInput","input","tableInput","tbodySearch","refreshSearch","_onDelayedSearch","_onSearch","_onKeyUp","searchNext","searchPrevious","resultIndex","_setActiveResult","activeResult","prevNode","prevElem","searchFieldActive","searchValueActive","updateDom","_clearDelay","forceSearch","resultCount","innerHTML","ContextMenu","appendNodeFactory","expanded","setField","fieldEditable","_updateEditability","editable","path","unshift","setParent","getField","_getDomField","childValue","_getType","childField","arr","forEach","_getDomValue","getLevel","fieldInnerText","valueInnerText","cloneChilds","childClone","getAppend","nextTr","nextSibling","hide","_hasChilds","newTr","appendTr","updateIndexes","moveBefore","trTemp","AppendNode","currentIndex","toLowerCase","searchField","searchValue","_updateDomField","childResults","concat","_updateDomValue","offsetTop","focusElement","elementName","drag","editableDiv","_duplicate","containsNode","_move","clearDom","removedNode","_remove","lastTr","_stringCast","silent","_unescapeHTML","str","domValue","v","t","color","isEmpty","count","domField","oldField","tdDrag","domDrag","tdMenu","tdField","tree","_createDomTree","_onDragStart","mousemove","_onDrag","mouseup","_onDragEnd","oldCursor","body","cursor","mouseX","pageX","level","trThis","trPrev","trNext","trFirst","trLast","trRoot","nodePrev","nodeNext","topThis","topPrev","topFirst","heightThis","bottomNext","heightNext","pageY","moved","offsetHeight","previousSibling","diffX","diffLevel","round","levelNext","_isChildOf","n","_createDomField","domTree","marginLeft","contentEditable","_escapeHTML","_updateDomIndexes","_createDomValue","_createDomExpandButton","borderCollapse","tdExpand","tdSeparator","tdValue","srcElement","expandable","showContextMenu","_onExpand","offsetX","onKeyDown","nextNode","nextDom","nextDom2","altKey","_onDuplicate","_onRemove","_onInsertBefore","_onInsertAfter","lastNode","_lastNode","_getElementName","firstNode","_firstNode","prevElement","_previousElement","appendDom","nextNode2","_previousNode","nextElement","_nextElement","prevDom","isVisible","_nextNode","newNode","_onAppend","_onChangeType","_onSort","direction","order","firstDom","lastDom","lastChild","TYPE_TITLES","auto","array","string","anchor","onClose","titles","items","submenu","click","submenuTitle","close","show","Array","lower","num","numFloat","isNaN","htmlEscaped","substring","escapedText","_escapeJSON","escaped","createModeSwitcher","current","switchMode","availableModes","code","form","view","item","currentMode","currentTitle","box","createMenuItems","list","domItems","separator","li","domItem","button","divIcon","buttonSubmenu","buttonExpand","divExpand","_onExpandItem","domSubItems","subItems","ul","eventListeners","visibleSubmenu","focusButton","overflow","maxHeight","_getVisibleButtons","buttons","expandedItem","subItem","visibleMenu","windowHeight","innerHeight","windowScroll","windowBottom","anchorHeight","menuHeight","mousedown","mousewheel","keydown","fn","alreadyVisible","padding","display","targetIndex","prevButton","nextButton","e","trAppend","tdAppend","domText","paddingLeft"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"jsoneditor.map","sources":["./jsoneditor.js"],"names":["root","factory","exports","module","define","amd","this","modules","__webpack_require__","moduleId","installedModules","id","loaded","call","m","c","p","JSONEditor","container","options","json","Error","ieVersion","util","getInternetExplorerVersion","arguments","length","_create","treemode","textmode","modes","prototype","mode","setMode","_delete","set","get","setText","jsonText","parse","getText","JSON","stringify","setName","name","getName","data","extend","config","asText","clear","mixin","create","load","err","_onError","onError","log","error","registerMode","i","prop","isArray","reserved","Highlighter","History","SearchBox","Node","modeswitcher","dom","highlighter","selection","undefined","_setOptions","history","_createFrame","_createTable","frame","parentNode","removeChild","search","hasOwnProperty","focusNode","domFocus","Function","content","table","params","field","value","node","_setRoot","recurse","expand","appendChild","blur","getValue","updateField","collapse","tbody","getDom","text","results","expandAll","collapseAll","_onAction","action","add","change","startAutoScroll","mouseY","me","top","getAbsoluteTop","height","clientHeight","bottom","margin","interval","autoScrollStep","scrollTop","scrollHeight","autoScrollTimer","setInterval","stopAutoScroll","clearTimeout","setSelection","range","setSelectionOffset","focus","getSelection","getSelectionOffset","scrollTo","callback","editor","animateTimeout","animateCallback","finalScrollTop","Math","min","max","animate","diff","abs","setTimeout","onEvent","event","_onEvent","document","createElement","className","onclick","target","nodeName","preventDefault","oninput","onchange","onkeydown","onkeyup","oncut","onpaste","onmousedown","onmouseup","onmouseover","onmouseout","addEventListener","onfocusin","onfocusout","menu","title","undo","_onUndo","redo","_onRedo","onChange","disabled","canUndo","canRedo","modeBox","searchBox","type","_onKeyDown","getNodeFromTarget","keynum","which","keyCode","ctrlKey","shiftKey","handled","selectContentEditable","select","previous","next","stopPropagation","contentOuter","col","colgroupContent","width","indentation","Number","ace","textarea","clientWidth","buttonFormat","format","buttonCompact","compact","editorDom","style","edit","setTheme","setShowPrintMargin","setFontSize","getSession","setTabSize","setUseSoftTabs","setUseWrapMode","poweredBy","createTextNode","href","window","open","on","spellcheck","resize","force","sanitize","setValue","jsonString","validate","jsString","chars","inString","charAt","isEscaped","push","join","replace","$0","$1","$2","$3","jsonlint","a","b","console","apply","object","String","Boolean","RegExp","isUrlRegex","isUrl","test","obj","Object","toString","getAbsoluteLeft","elem","rect","getBoundingClientRect","left","pageXOffset","scrollLeft","pageYOffset","addClassName","classes","split","indexOf","removeClassName","index","splice","stripFormatting","divElement","childs","childNodes","iMax","child","removeAttribute","attributes","j","attribute","specified","setEndOfContentEditable","contentEditableElement","createRange","selectNodeContents","removeAllRanges","addRange","sel","getRangeAt","rangeCount","startContainer","endContainer","startOffset","endOffset","setStart","firstChild","setEnd","getInnerText","element","buffer","first","flush","nodeValue","hasChildNodes","innerText","prevChild","prevName","_ieVersion","rv","navigator","appName","ua","userAgent","re","exec","parseFloat","isFirefox","listener","useCapture","attachEvent","f","removeEventListener","detachEvent","locked","highlight","setHighlight","_cancelUnhighlight","unhighlight","unhighlightTimer","lock","unlock","actions","editField","oldValue","newValue","editValue","updateValue","appendNode","parent","insertBeforeNode","insertBefore","beforeNode","insertAfterNode","insertAfter","afterNode","removeNode","append","duplicateNode","clone","changeType","oldType","newType","moveNode","startParent","moveTo","startIndex","endParent","endIndex","sort","hideChilds","oldSort","oldChilds","showChilds","newSort","newChilds","timestamp","Date","oldSelection","newSelection","timeout","delay","lastText","tr","td","divInput","input","tableInput","tbodySearch","refreshSearch","_onDelayedSearch","_onSearch","_onKeyUp","searchNext","searchPrevious","resultIndex","_setActiveResult","activeResult","prevNode","prevElem","searchFieldActive","searchValueActive","updateDom","_clearDelay","forceSearch","resultCount","innerHTML","expanded","setField","fieldEditable","ContextMenu","appendNodeFactory","_updateEditability","editable","path","unshift","setParent","getField","_getDomField","childValue","_getType","childField","arr","forEach","_getDomValue","getLevel","fieldInnerText","valueInnerText","cloneChilds","childClone","getAppend","nextTr","nextSibling","hide","_hasChilds","newTr","appendTr","updateIndexes","moveBefore","trTemp","AppendNode","currentIndex","toLowerCase","searchField","searchValue","_updateDomField","childResults","concat","_updateDomValue","offsetTop","focusElement","elementName","drag","editableDiv","_duplicate","containsNode","_move","clearDom","removedNode","_remove","lastTr","_stringCast","silent","_unescapeHTML","str","domValue","v","t","color","isEmpty","count","domField","oldField","tdDrag","domDrag","tdMenu","tdField","tree","_createDomTree","_onDragStart","mousemove","_onDrag","mouseup","_onDragEnd","oldCursor","body","cursor","mouseX","pageX","level","trThis","trPrev","trNext","trFirst","trLast","trRoot","nodePrev","nodeNext","topThis","topPrev","topFirst","heightThis","bottomNext","heightNext","pageY","moved","offsetHeight","previousSibling","diffX","diffLevel","round","levelNext","_isChildOf","n","_createDomField","domTree","marginLeft","contentEditable","_escapeHTML","_updateDomIndexes","_createDomValue","_createDomExpandButton","borderCollapse","tdExpand","tdSeparator","tdValue","srcElement","expandable","showContextMenu","_onExpand","offsetX","onKeyDown","nextNode","nextDom","nextDom2","altKey","_onDuplicate","_onRemove","_onInsertBefore","_onInsertAfter","lastNode","_lastNode","_getElementName","firstNode","_firstNode","prevElement","_previousElement","appendDom","nextNode2","_previousNode","nextElement","_nextElement","prevDom","isVisible","_nextNode","newNode","_onAppend","_onChangeType","_onSort","direction","order","firstDom","lastDom","lastChild","TYPE_TITLES","auto","array","string","anchor","onClose","titles","items","submenu","click","submenuTitle","close","show","Array","lower","num","numFloat","isNaN","htmlEscaped","substring","escapedText","_escapeJSON","escaped","createModeSwitcher","current","switchMode","availableModes","code","form","view","item","currentMode","currentTitle","box","createMenuItems","list","domItems","separator","li","domItem","button","divIcon","buttonSubmenu","buttonExpand","divExpand","_onExpandItem","domSubItems","subItems","ul","eventListeners","visibleSubmenu","focusButton","overflow","maxHeight","_getVisibleButtons","buttons","expandedItem","subItem","visibleMenu","windowHeight","innerHeight","windowScroll","windowBottom","anchorHeight","menuHeight","mousedown","mousewheel","keydown","fn","alreadyVisible","padding","display","targetIndex","prevButton","nextButton","e","trAppend","tdAppend","domText","paddingLeft"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BA,SAA2CA,EAAMC,GAC1B,gBAAZC,UAA0C,gBAAXC,QACxCA,OAAOD,QAAUD,IACQ,kBAAXG,SAAyBA,OAAOC,IAC9CD,OAAOH,GACmB,gBAAZC,SACdA,QAAoB,WAAID,IAExBD,EAAiB,WAAIC,KACpBK,KAAM,WACT,MAAgB,UAAUC,GAKhB,QAASC,GAAoBC,GAG5B,GAAGC,EAAiBD,GACnB,MAAOC,GAAiBD,GAAUP,OAGnC,IAAIC,GAASO,EAAiBD,IAC7BP,WACAS,GAAIF,EACJG,QAAQ,EAUT,OANAL,GAAQE,GAAUI,KAAKV,EAAOD,QAASC,EAAQA,EAAOD,QAASM,GAG/DL,EAAOS,QAAS,EAGTT,EAAOD,QAvBf,GAAIQ,KAqCJ,OATAF,GAAoBM,EAAIP,EAGxBC,EAAoBO,EAAIL,EAGxBF,EAAoBQ,EAAI,GAGjBR,EAAoB,KAK/B,SAASL,EAAQD,EAASM,GAgC/B,QAASS,GAAYC,EAAWC,EAASC,GACvC,KAAMd,eAAgBW,IACpB,KAAM,IAAII,OAAM,+CAIlB,IAAIC,GAAYC,EAAKC,4BACrB,IAAiB,IAAbF,GAA+B,EAAZA,EACrB,KAAM,IAAID,OAAM,iGAIdI,WAAUC,QACZpB,KAAKqB,QAAQT,EAAWC,EAASC,GA3CrC,GAAIQ,GAAWpB,EAAoB,GAC/BqB,EAAWrB,EAAoB,GAC/Be,EAAOf,EAAoB,EA4D/BS,GAAWa,SASXb,EAAWc,UAAUJ,QAAU,SAAUT,EAAWC,EAASC,GAC3Dd,KAAKY,UAAYA,EACjBZ,KAAKa,QAAUA,MACfb,KAAKc,KAAOA,KAEZ,IAAIY,GAAO1B,KAAKa,QAAQa,MAAQ,MAChC1B,MAAK2B,QAAQD,IAOff,EAAWc,UAAUG,QAAU,aAM/BjB,EAAWc,UAAUI,IAAM,SAAUf,GACnCd,KAAKc,KAAOA,GAOdH,EAAWc,UAAUK,IAAM,WACzB,MAAO9B,MAAKc,MAOdH,EAAWc,UAAUM,QAAU,SAAUC,GACvChC,KAAKc,KAAOG,EAAKgB,MAAMD,IAOzBrB,EAAWc,UAAUS,QAAU,WAC7B,MAAOC,MAAKC,UAAUpC,KAAKc,OAO7BH,EAAWc,UAAUY,QAAU,SAAUC,GAClCtC,KAAKa,UACRb,KAAKa,YAEPb,KAAKa,QAAQyB,KAAOA,GAOtB3B,EAAWc,UAAUc,QAAU,WAC7B,MAAOvC,MAAKa,SAAWb,KAAKa,QAAQyB,MAStC3B,EAAWc,UAAUE,QAAU,SAAUD,GACvC,GAEIc,GACAF,EAHA1B,EAAYZ,KAAKY,UACjBC,EAAUI,EAAKwB,UAAWzC,KAAKa,QAInCA,GAAQa,KAAOA,CACf,IAAIgB,GAAS/B,EAAWa,MAAME,EAC9B,KAAIgB,EA0BF,KAAM,IAAI3B,OAAM,iBAAmBF,EAAQa,KAAO,IAzBlD,KACE,GAAIiB,GAAyB,QAAfD,EAAOF,IAYrB,IAXAF,EAAOtC,KAAKuC,UACZC,EAAOxC,KAAK2C,EAAS,UAAY,SAEjC3C,KAAK4B,UACLX,EAAK2B,MAAM5C,MACXiB,EAAKwB,OAAOzC,KAAM0C,EAAOG,OACzB7C,KAAK8C,OAAOlC,EAAWC,GAEvBb,KAAKqC,QAAQC,GACbtC,KAAK2C,EAAS,UAAY,OAAOH,GAEN,kBAAhBE,GAAOK,KAChB,IACEL,EAAOK,KAAKxC,KAAKP,MAEnB,MAAOgD,KAGX,MAAOA,GACLhD,KAAKiD,SAASD,KAcpBrC,EAAWc,UAAUwB,SAAW,SAASD,GAQvC,GAN4B,kBAAjBhD,MAAKkD,UACdjC,EAAKkC,IAAI,yEAETnD,KAAKkD,QAAQF,KAGXhD,KAAKa,SAAyC,kBAAvBb,MAAKa,QAAQuC,MAItC,KAAMJ,EAHNhD,MAAKa,QAAQuC,MAAMJ,IA0BvBrC,EAAW0C,aAAe,SAAU3B,GAClC,GAAI4B,GAAGC,CAEP,IAAItC,EAAKuC,QAAQ9B,GAEf,IAAK4B,EAAI,EAAGA,EAAI5B,EAAKN,OAAQkC,IAC3B3C,EAAW0C,aAAa3B,EAAK4B,QAG5B,CAEH,KAAM,QAAU5B,IAAO,KAAM,IAAIX,OAAM,0BACvC,MAAM,SAAWW,IAAO,KAAM,IAAIX,OAAM,2BACxC,MAAM,QAAUW,IAAO,KAAM,IAAIX,OAAM,0BACvC,IAAIuB,GAAOZ,EAAKA,IAChB,IAAIY,IAAQ3B,GAAWa,MACrB,KAAM,IAAIT,OAAM,SAAWuB,EAAO,uBAIpC,IAAiC,kBAAtBZ,GAAKmB,MAAMC,OACpB,KAAM,IAAI/B,OAAM,8CAElB,IAAI0C,IAAY,UAAW,eAAgB,QAC3C,KAAKH,EAAI,EAAGA,EAAIG,EAASrC,OAAQkC,IAE/B,GADAC,EAAOE,EAASH,GACZC,IAAQ7B,GAAKmB,MACf,KAAM,IAAI9B,OAAM,sBAAwBwC,EAAO,yBAInD5C,GAAWa,MAAMc,GAAQZ,IAK7Bf,EAAW0C,aAAa/B,GACxBX,EAAW0C,aAAa9B,GAExB1B,EAAOD,QAAUe,GAKZ,SAASd,EAAQD,EAASM,GAE/B,GAAIwD,GAAcxD,EAAoB,GAClCyD,EAAUzD,EAAoB,GAC9B0D,EAAY1D,EAAoB,GAChC2D,EAAO3D,EAAoB,GAC3B4D,EAAe5D,EAAoB,GACnCe,EAAOf,EAAoB,GAG3BoB,IAkBJA,GAASwB,OAAS,SAAUlC,EAAWC,GACrC,IAAKD,EACH,KAAM,IAAIG,OAAM,iCAElBf,MAAKY,UAAYA,EACjBZ,KAAK+D,OACL/D,KAAKgE,YAAc,GAAIN,GACvB1D,KAAKiE,UAAYC,OAEjBlE,KAAKmE,YAAYtD,GAEbb,KAAKa,QAAQuD,SAAiC,SAAtBpE,KAAKa,QAAQa,OACvC1B,KAAKoE,QAAU,GAAIT,GAAQ3D,OAG7BA,KAAKqE,eACLrE,KAAKsE,gBAOPhD,EAASM,QAAU,WACb5B,KAAKuE,OAASvE,KAAKY,WAAaZ,KAAKuE,MAAMC,YAAcxE,KAAKY,WAChEZ,KAAKY,UAAU6D,YAAYzE,KAAKuE,QASpCjD,EAAS6C,YAAc,SAAUtD,GAS/B,GARAb,KAAKa,SACH6D,QAAQ,EACRN,SAAS,EACT1C,KAAM,OACNY,KAAM4B,QAIJrD,EACF,IAAK,GAAI0C,KAAQ1C,GACXA,EAAQ8D,eAAepB,KACzBvD,KAAKa,QAAQ0C,GAAQ1C,EAAQ0C,IAOrC,IAAIqB,GAAYV,OAGZW,EAAW,IAQfvD,GAASO,IAAM,SAAUf,EAAMwB,GAU7B,GARIA,IAEFrB,EAAKkC,IAAI,8EAETnD,KAAKa,QAAQyB,KAAOA,GAIlBxB,YAAgBgE,WAAsBZ,SAATpD,EAC/Bd,KAAK4C,YAEF,CACH5C,KAAK+E,QAAQN,YAAYzE,KAAKgF,MAG9B,IAAIC,IACFC,MAASlF,KAAKa,QAAQyB,KACtB6C,MAASrE,GAEPsE,EAAO,GAAIvB,GAAK7D,KAAMiF,EAC1BjF,MAAKqF,SAASD,EAGd,IAAIE,IAAU,CACdtF,MAAKoF,KAAKG,OAAOD,GAEjBtF,KAAK+E,QAAQS,YAAYxF,KAAKgF,OAI5BhF,KAAKoE,SACPpE,KAAKoE,QAAQxB,SAQjBtB,EAASQ,IAAM,WAMb,MAJI8C,IACFA,EAAUa,OAGRzF,KAAKoF,KACApF,KAAKoF,KAAKM,WAGVxB,QAQX5C,EAASY,QAAU,WACjB,MAAOC,MAAKC,UAAUpC,KAAK8B,QAO7BR,EAASS,QAAU,SAASC,GAC1BhC,KAAK6B,IAAIZ,EAAKgB,MAAMD,KAOtBV,EAASe,QAAU,SAAUC,GAC3BtC,KAAKa,QAAQyB,KAAOA,EAChBtC,KAAKoF,MACPpF,KAAKoF,KAAKO,YAAY3F,KAAKa,QAAQyB,OAQvChB,EAASiB,QAAU,WACjB,MAAOvC,MAAKa,QAAQyB,MAMtBhB,EAASsB,MAAQ,WACX5C,KAAKoF,OACPpF,KAAKoF,KAAKQ,WACV5F,KAAK6F,MAAMpB,YAAYzE,KAAKoF,KAAKU,gBAC1B9F,MAAKoF,OAShB9D,EAAS+D,SAAW,SAAUD,GAC5BpF,KAAK4C,QAEL5C,KAAKoF,KAAOA,EAGZpF,KAAK6F,MAAML,YAAYJ,EAAKU,WAe9BxE,EAASoD,OAAS,SAAUqB,GAC1B,GAAIC,EAUJ,OATIhG,MAAKoF,MACPpF,KAAK+E,QAAQN,YAAYzE,KAAKgF,OAC9BgB,EAAUhG,KAAKoF,KAAKV,OAAOqB,GAC3B/F,KAAK+E,QAAQS,YAAYxF,KAAKgF,QAG9BgB,KAGKA,GAMT1E,EAAS2E,UAAY,WACfjG,KAAKoF,OACPpF,KAAK+E,QAAQN,YAAYzE,KAAKgF,OAC9BhF,KAAKoF,KAAKG,SACVvF,KAAK+E,QAAQS,YAAYxF,KAAKgF,SAOlC1D,EAAS4E,YAAc,WACjBlG,KAAKoF,OACPpF,KAAK+E,QAAQN,YAAYzE,KAAKgF,OAC9BhF,KAAKoF,KAAKQ,WACV5F,KAAK+E,QAAQS,YAAYxF,KAAKgF,SAkBlC1D,EAAS6E,UAAY,SAAUC,EAAQnB,GAOrC,GALIjF,KAAKoE,SACPpE,KAAKoE,QAAQiC,IAAID,EAAQnB,GAIvBjF,KAAKa,QAAQyF,OACf,IACEtG,KAAKa,QAAQyF,SAEf,MAAOtD,GACL/B,EAAKkC,IAAI,6BAA8BH,KAU7C1B,EAASiF,gBAAkB,SAAUC,GACnC,GAAIC,GAAKzG,KACL+E,EAAU/E,KAAK+E,QACf2B,EAAMzF,EAAK0F,eAAe5B,GAC1B6B,EAAS7B,EAAQ8B,aACjBC,EAASJ,EAAME,EACfG,EAAS,GACTC,EAAW,EAGbhH,MAAKiH,eADOP,EAAMK,EAAfP,GAA0BzB,EAAQmC,UAAY,GACzBR,EAAMK,EAAUP,GAAU,EAE3CA,EAASM,EAASC,GACvBH,EAAS7B,EAAQmC,UAAYnC,EAAQoC,cACfL,EAASC,EAAUP,GAAU,EAG/BtC,OAGpBlE,KAAKiH,eACFjH,KAAKoH,kBACRpH,KAAKoH,gBAAkBC,YAAY,WAC7BZ,EAAGQ,eACLlC,EAAQmC,WAAaT,EAAGQ,eAGxBR,EAAGa,kBAEJN,IAILhH,KAAKsH,kBAOThG,EAASgG,eAAiB,WACpBtH,KAAKoH,kBACPG,aAAavH,KAAKoH,uBACXpH,MAAKoH,iBAEVpH,KAAKiH,sBACAjH,MAAKiH,gBAchB3F,EAASkG,aAAe,SAAUvD,GAC3BA,IAID,aAAeA,IAAajE,KAAK+E,UAEnC/E,KAAK+E,QAAQmC,UAAYjD,EAAUiD,WAEjCjD,EAAUwD,OACZxG,EAAKyG,mBAAmBzD,EAAUwD,OAEhCxD,EAAUF,KACZE,EAAUF,IAAI4D,UAYlBrG,EAASsG,aAAe,WACtB,OACE7D,IAAKc,EACLqC,UAAWlH,KAAK+E,QAAU/E,KAAK+E,QAAQmC,UAAY,EACnDO,MAAOxG,EAAK4G,uBAahBvG,EAASwG,SAAW,SAAUpB,EAAKqB,GACjC,GAAIhD,GAAU/E,KAAK+E,OACnB,IAAIA,EAAS,CACX,GAAIiD,GAAShI,IAETgI,GAAOC,iBACTV,aAAaS,EAAOC,sBACbD,GAAOC,gBAEZD,EAAOE,kBACTF,EAAOE,iBAAgB,SAChBF,GAAOE,gBAIhB,IAAItB,GAAS7B,EAAQ8B,aACjBC,EAAS/B,EAAQoC,aAAeP,EAChCuB,EAAiBC,KAAKC,IAAID,KAAKE,IAAI5B,EAAME,EAAS,EAAG,GAAIE,GAGzDyB,EAAU,WACZ,GAAIrB,GAAYnC,EAAQmC,UACpBsB,EAAQL,EAAiBjB,CACzBkB,MAAKK,IAAID,GAAQ,GACnBzD,EAAQmC,WAAasB,EAAO,EAC5BR,EAAOE,gBAAkBH,EACzBC,EAAOC,eAAiBS,WAAWH,EAAS,MAIxCR,GACFA,GAAS,GAEXhD,EAAQmC,UAAYiB,QACbH,GAAOC,qBACPD,GAAOE,iBAGlBK,SAGIR,IACFA,GAAS,IASfzG,EAAS+C,aAAe,WAQtB,QAASsE,GAAQC,GACfZ,EAAOa,SAASD,GAPlB5I,KAAKuE,MAAQuE,SAASC,cAAc,OACpC/I,KAAKuE,MAAMyE,UAAY,aACvBhJ,KAAKY,UAAU4E,YAAYxF,KAAKuE,MAGhC,IAAIyD,GAAShI,IAIbA,MAAKuE,MAAM0E,QAAU,SAAUL,GAC7B,GAAIM,GAASN,EAAMM,MAEnBP,GAAQC,GAIe,UAAnBM,EAAOC,UACTP,EAAMQ,kBAGVpJ,KAAKuE,MAAM8E,QAAUV,EACrB3I,KAAKuE,MAAM+E,SAAWX,EACtB3I,KAAKuE,MAAMgF,UAAYZ,EACvB3I,KAAKuE,MAAMiF,QAAUb,EACrB3I,KAAKuE,MAAMkF,MAAQd,EACnB3I,KAAKuE,MAAMmF,QAAUf,EACrB3I,KAAKuE,MAAMoF,YAAchB,EACzB3I,KAAKuE,MAAMqF,UAAYjB,EACvB3I,KAAKuE,MAAMsF,YAAclB,EACzB3I,KAAKuE,MAAMuF,WAAanB,EAIxB1H,EAAK8I,iBAAiB/J,KAAKuE,MAAO,QAASoE,GAAS,GACpD1H,EAAK8I,iBAAiB/J,KAAKuE,MAAO,OAAQoE,GAAS,GACnD3I,KAAKuE,MAAMyF,UAAYrB,EACvB3I,KAAKuE,MAAM0F,WAAatB,EAGxB3I,KAAKkK,KAAOpB,SAASC,cAAc,OACnC/I,KAAKkK,KAAKlB,UAAY,OACtBhJ,KAAKuE,MAAMiB,YAAYxF,KAAKkK,KAG5B,IAAIjE,GAAY6C,SAASC,cAAc,SACvC9C,GAAU+C,UAAY,aACtB/C,EAAUkE,MAAQ,oBAClBlE,EAAUgD,QAAU,WAClBjB,EAAO/B,aAETjG,KAAKkK,KAAK1E,YAAYS,EAGtB,IAAIC,GAAc4C,SAASC,cAAc,SASzC,IARA7C,EAAYiE,MAAQ,sBACpBjE,EAAY8C,UAAY,eACxB9C,EAAY+C,QAAU,WACpBjB,EAAO9B,eAETlG,KAAKkK,KAAK1E,YAAYU,GAGlBlG,KAAKoE,QAAS,CAEhB,GAAIgG,GAAOtB,SAASC,cAAc,SAClCqB,GAAKpB,UAAY,iBACjBoB,EAAKD,MAAQ,4BACbC,EAAKnB,QAAU,WACbjB,EAAOqC,WAETrK,KAAKkK,KAAK1E,YAAY4E,GACtBpK,KAAK+D,IAAIqG,KAAOA,CAGhB,IAAIE,GAAOxB,SAASC,cAAc,SAClCuB,GAAKtB,UAAY,OACjBsB,EAAKH,MAAQ,sBACbG,EAAKrB,QAAU,WACbjB,EAAOuC,WAETvK,KAAKkK,KAAK1E,YAAY8E,GACtBtK,KAAK+D,IAAIuG,KAAOA,EAGhBtK,KAAKoE,QAAQoG,SAAW,WACtBJ,EAAKK,UAAYzC,EAAO5D,QAAQsG,UAChCJ,EAAKG,UAAYzC,EAAO5D,QAAQuG,WAElC3K,KAAKoE,QAAQoG,WAIf,GAAIxK,KAAKa,SAAWb,KAAKa,QAAQW,OAASxB,KAAKa,QAAQW,MAAMJ,OAAQ,CACnE,GAAIwJ,GAAU9G,EAAahB,OAAO9C,KAAMA,KAAKa,QAAQW,MAAOxB,KAAKa,QAAQa,KACzE1B,MAAKkK,KAAK1E,YAAYoF,GACtB5K,KAAK+D,IAAI6G,QAAUA,EAIjB5K,KAAKa,QAAQ6D,SACf1E,KAAK6K,UAAY,GAAIjH,GAAU5D,KAAMA,KAAKkK,QAQ9C5I,EAAS+I,QAAU,WACbrK,KAAKoE,UAEPpE,KAAKoE,QAAQgG,OAGTpK,KAAKa,QAAQyF,QACftG,KAAKa,QAAQyF,WASnBhF,EAASiJ,QAAU,WACbvK,KAAKoE,UAEPpE,KAAKoE,QAAQkG,OAGTtK,KAAKa,QAAQyF,QACftG,KAAKa,QAAQyF,WAUnBhF,EAASuH,SAAW,SAAUD,GAC5B,GAAIM,GAASN,EAAMM,MAED,YAAdN,EAAMkC,MACR9K,KAAK+K,WAAWnC,GAGA,SAAdA,EAAMkC,OACRjG,EAAWqE,EAGb,IAAI9D,GAAOvB,EAAKmH,kBAAkB9B,EAC9B9D,IACFA,EAAKuD,QAAQC,IASjBtH,EAASyJ,WAAa,SAAUnC,GAC9B,GAAIqC,GAASrC,EAAMsC,OAAStC,EAAMuC,QAC9BC,EAAUxC,EAAMwC,QAChBC,EAAWzC,EAAMyC,SACjBC,GAAU,CASd,IAPc,GAAVL,GACFvC,WAAW,WAETzH,EAAKsK,sBAAsB1G,IAC1B,GAGD7E,KAAK6K,UACP,GAAIO,GAAqB,IAAVH,EACbjL,KAAK6K,UAAU9G,IAAIW,OAAOiD,QAC1B3H,KAAK6K,UAAU9G,IAAIW,OAAO8G,SAC1BF,GAAU,MAEP,IAAc,KAAVL,GAAkBG,GAAqB,IAAVH,EAAe,CACnD,GAAItD,IAAQ,CACP0D,GAMHrL,KAAK6K,UAAUY,SAAS9D,GAJxB3H,KAAK6K,UAAUa,KAAK/D,GAOtB2D,GAAU,EAIVtL,KAAKoE,UACHgH,IAAYC,GAAsB,IAAVJ,GAE1BjL,KAAKqK,UACLiB,GAAU,GAEHF,GAAWC,GAAsB,IAAVJ,IAE9BjL,KAAKuK,UACLe,GAAU,IAIVA,IACF1C,EAAMQ,iBACNR,EAAM+C,oBAQVrK,EAASgD,aAAe,WACtB,GAAIsH,GAAe9C,SAASC,cAAc,MAC1C6C,GAAa5C,UAAY,QACzBhJ,KAAK4L,aAAeA,EAEpB5L,KAAK+E,QAAU+D,SAASC,cAAc,OACtC/I,KAAK+E,QAAQiE,UAAY,OACzB4C,EAAapG,YAAYxF,KAAK+E,SAE9B/E,KAAKgF,MAAQ8D,SAASC,cAAc,SACpC/I,KAAKgF,MAAMgE,UAAY,OACvBhJ,KAAK+E,QAAQS,YAAYxF,KAAKgF,MAI9B,IAAI6G,EACJ7L,MAAK8L,gBAAkBhD,SAASC,cAAc,YACpB,SAAtB/I,KAAKa,QAAQa,OACfmK,EAAM/C,SAASC,cAAc,OAC7B8C,EAAIE,MAAQ,OACZ/L,KAAK8L,gBAAgBtG,YAAYqG,IAEnCA,EAAM/C,SAASC,cAAc,OAC7B8C,EAAIE,MAAQ,OACZ/L,KAAK8L,gBAAgBtG,YAAYqG,GACjCA,EAAM/C,SAASC,cAAc,OAC7B/I,KAAK8L,gBAAgBtG,YAAYqG,GACjC7L,KAAKgF,MAAMQ,YAAYxF,KAAK8L,iBAE5B9L,KAAK6F,MAAQiD,SAASC,cAAc,SACpC/I,KAAKgF,MAAMQ,YAAYxF,KAAK6F,OAE5B7F,KAAKuE,MAAMiB,YAAYoG,IAIzB/L,EAAOD,UAEH8B,KAAM,OACNmB,MAAOvB,EACPkB,KAAM,SAGNd,KAAM,OACNmB,MAAOvB,EACPkB,KAAM,SAGNd,KAAM,OACNmB,MAAOvB,EACPkB,KAAM,UAML,SAAS3C,EAAQD,EAASM,GAE/B,GAAI4D,GAAe5D,EAAoB,GACnCe,EAAOf,EAAoB,GAG3BqB,IAeJA,GAASuB,OAAS,SAAUlC,EAAWC,GAErCA,EAAUA,MACVb,KAAKa,QAAUA,EAEbb,KAAKgM,YADHnL,EAAQmL,YACSC,OAAOpL,EAAQmL,aAGf,EAErBhM,KAAK0B,KAAwB,QAAhBb,EAAQa,KAAkB,OAAS,OAC/B,QAAb1B,KAAK0B,MAEY,mBAARwK,OACTlM,KAAK0B,KAAO,OACZT,EAAKkC,IAAI,+FAKb,IAAIsD,GAAKzG,IACTA,MAAKY,UAAYA,EACjBZ,KAAK+D,OACL/D,KAAKgI,OAAS9D,OACdlE,KAAKmM,SAAWjI,OAEhBlE,KAAK+L,MAAQnL,EAAUwL,YACvBpM,KAAK4G,OAAShG,EAAUiG,aAExB7G,KAAKuE,MAAQuE,SAASC,cAAc,OACpC/I,KAAKuE,MAAMyE,UAAY,aACvBhJ,KAAKuE,MAAM0E,QAAU,SAAUL,GAE7BA,EAAMQ,kBAERpJ,KAAKuE,MAAMgF,UAAY,SAAUX,GAC/BnC,EAAGsE,WAAWnC,IAIhB5I,KAAKkK,KAAOpB,SAASC,cAAc,OACnC/I,KAAKkK,KAAKlB,UAAY,OACtBhJ,KAAKuE,MAAMiB,YAAYxF,KAAKkK,KAG5B,IAAImC,GAAevD,SAASC,cAAc,SAC1CsD,GAAarD,UAAY,SACzBqD,EAAalC,MAAQ,qEACrBnK,KAAKkK,KAAK1E,YAAY6G,GACtBA,EAAapD,QAAU,WACrB,IACExC,EAAG6F,SAEL,MAAOtJ,GACLyD,EAAGxD,SAASD,IAKhB,IAAIuJ,GAAgBzD,SAASC,cAAc,SAc3C,IAbAwD,EAAcvD,UAAY,UAC1BuD,EAAcpC,MAAQ,4DACtBnK,KAAKkK,KAAK1E,YAAY+G,GACtBA,EAActD,QAAU,WACtB,IACExC,EAAG+F,UAEL,MAAOxJ,GACLyD,EAAGxD,SAASD,KAKZhD,KAAKa,SAAWb,KAAKa,QAAQW,OAASxB,KAAKa,QAAQW,MAAMJ,OAAQ,CACnE,GAAIwJ,GAAU9G,EAAahB,OAAO9C,KAAMA,KAAKa,QAAQW,MAAOxB,KAAKa,QAAQa,KACzE1B,MAAKkK,KAAK1E,YAAYoF,GACtB5K,KAAK+D,IAAI6G,QAAUA,EASrB,GANA5K,KAAK+E,QAAU+D,SAASC,cAAc,OACtC/I,KAAK+E,QAAQiE,UAAY,QACzBhJ,KAAKuE,MAAMiB,YAAYxF,KAAK+E,SAE5B/E,KAAKY,UAAU4E,YAAYxF,KAAKuE,OAEf,QAAbvE,KAAK0B,KAAgB,CACvB1B,KAAKyM,UAAY3D,SAASC,cAAc,OACxC/I,KAAKyM,UAAUC,MAAM9F,OAAS,OAC9B5G,KAAKyM,UAAUC,MAAMX,MAAQ,OAC7B/L,KAAK+E,QAAQS,YAAYxF,KAAKyM,UAE9B,IAAIzE,GAASkE,IAAIS,KAAK3M,KAAKyM,UAC3BzE,GAAO4E,SAAS,wBAChB5E,EAAO6E,oBAAmB,GAC1B7E,EAAO8E,YAAY,IACnB9E,EAAO+E,aAAapL,QAAQ,iBAC5BqG,EAAO+E,aAAaC,WAAWhN,KAAKgM,aACpChE,EAAO+E,aAAaE,gBAAe,GACnCjF,EAAO+E,aAAaG,gBAAe,GACnClN,KAAKgI,OAASA,CAEd,IAAImF,GAAYrE,SAASC,cAAc,IACvCoE,GAAU3H,YAAYsD,SAASsE,eAAe,mBAC9CD,EAAUE,KAAO,sBACjBF,EAAUjE,OAAS,SACnBiE,EAAUnE,UAAY,YACtBmE,EAAUlE,QAAU,WAIlBqE,OAAOC,KAAKJ,EAAUE,KAAMF,EAAUjE,SAExClJ,KAAKkK,KAAK1E,YAAY2H,GAElBtM,EAAQyF,QAEV0B,EAAOwF,GAAG,SAAU,WAClB3M,EAAQyF,eAIT,CAEH,GAAI6F,GAAWrD,SAASC,cAAc,WACtCoD,GAASnD,UAAY,OACrBmD,EAASsB,YAAa,EACtBzN,KAAK+E,QAAQS,YAAY2G,GACzBnM,KAAKmM,SAAWA,EAEZtL,EAAQyF,SAEoB,OAA1BtG,KAAKmM,SAAS9C,QAChBrJ,KAAKmM,SAAS9C,QAAU,WACtBxI,EAAQyF,UAKVtG,KAAKmM,SAAS7C,SAAW,WACvBzI,EAAQyF,aAYlB/E,EAASwJ,WAAa,SAAUnC,GAC9B,GAAIqC,GAASrC,EAAMsC,OAAStC,EAAMuC,QAC9BG,GAAU,CAEA,MAAVL,GAAiBrC,EAAMwC,UACrBxC,EAAMyC,SACRrL,KAAKwM,UAGLxM,KAAKsM,SAEPhB,GAAU,GAGRA,IACF1C,EAAMQ,iBACNR,EAAM+C,oBAQVpK,EAASK,QAAU,WACb5B,KAAKuE,OAASvE,KAAKY,WAAaZ,KAAKuE,MAAMC,YAAcxE,KAAKY,WAChEZ,KAAKY,UAAU6D,YAAYzE,KAAKuE,QAUpChD,EAAS0B,SAAW,SAASD,GAQ3B,GAN4B,kBAAjBhD,MAAKkD,UACdjC,EAAKkC,IAAI,yEAETnD,KAAKkD,QAAQF,KAGXhD,KAAKa,SAAyC,kBAAvBb,MAAKa,QAAQuC,MAItC,KAAMJ,EAHNhD,MAAKa,QAAQuC,MAAMJ,IAUvBzB,EAASiL,QAAU,WACjB,GAAI1L,GAAOd,KAAK8B,MACZiE,EAAO5D,KAAKC,UAAUtB,EAC1Bd,MAAK+B,QAAQgE,IAMfxE,EAAS+K,OAAS,WAChB,GAAIxL,GAAOd,KAAK8B,MACZiE,EAAO5D,KAAKC,UAAUtB,EAAM,KAAMd,KAAKgM,YAC3ChM,MAAK+B,QAAQgE,IAMfxE,EAASoG,MAAQ,WACX3H,KAAKmM,UACPnM,KAAKmM,SAASxE,QAEZ3H,KAAKgI,QACPhI,KAAKgI,OAAOL,SAOhBpG,EAASmM,OAAS,WAChB,GAAI1N,KAAKgI,OAAQ,CACf,GAAI2F,IAAQ,CACZ3N,MAAKgI,OAAO0F,OAAOC,KAQvBpM,EAASM,IAAM,SAASf,GACtBd,KAAK+B,QAAQI,KAAKC,UAAUtB,EAAM,KAAMd,KAAKgM,eAO/CzK,EAASO,IAAM,WACb,GACIhB,GADAiF,EAAO/F,KAAKkC,SAGhB,KACEpB,EAAOG,EAAKgB,MAAM8D,GAEpB,MAAO/C,GAEL+C,EAAO9E,EAAK2M,SAAS7H,GACrB/F,KAAK+B,QAAQgE,GAGbjF,EAAOG,EAAKgB,MAAM8D,GAGpB,MAAOjF,IAOTS,EAASW,QAAU,WACjB,MAAIlC,MAAKmM,SACAnM,KAAKmM,SAAShH,MAEnBnF,KAAKgI,OACAhI,KAAKgI,OAAOtC,WAEd,IAOTnE,EAASQ,QAAU,SAASC,GACtBhC,KAAKmM,WACPnM,KAAKmM,SAAShH,MAAQnD,GAEpBhC,KAAKgI,QACPhI,KAAKgI,OAAO6F,SAAS7L,EAAU,KAKnCnC,EAAOD,UAEH8B,KAAM,OACNmB,MAAOtB,EACPiB,KAAM,OACNO,KAAMxB,EAAS+K,SAGf5K,KAAM,OACNmB,MAAOtB,EACPiB,KAAM,OACNO,KAAMxB,EAAS+K,UAOd,SAASzM,EAAQD,GAQtBA,EAAQqC,MAAQ,SAAe6L,GAC7B,IACE,MAAO3L,MAAKF,MAAM6L,GAEpB,MAAO9K,GAKL,KAHApD,GAAQmO,SAASD,GAGX9K,IAYVpD,EAAQgO,SAAW,SAAUI,GAK3B,IAHA,GAAIC,MACAC,GAAW,EACX5K,EAAI,EACFA,EAAI0K,EAAS5M,QAAQ,CACzB,GAAIX,GAAIuN,EAASG,OAAO7K,GACpB8K,EAAuC,OAA3BJ,EAASG,OAAO7K,EAAI,EAEzB,OAAN7C,GAAmB,MAANA,GAAgB2N,IAC5B3N,IAAMyN,EAERA,GAAW,EAEHA,EAMRD,EAAMI,KAAK,MAJXH,EAAWzN,GAQfwN,EAAMI,KAAK5N,GACX6C,IAEF,GAAIwK,GAAaG,EAAMK,KAAK,GAc5B,OATAR,GAAaA,EAAWS,QAAQ,SAAU,SAAUC,EAAIC,GACtD,MAAc,MAANA,EAAc,IAAOA,EAAK,MAIpCX,EAAaA,EAAWS,QAAQ,2CAA4C,SAAUC,EAAIC,EAAIC,EAAIC,GAChG,MAAOF,GAAK,IAAMC,EAAK,IAAMC,KAajC/O,EAAQmO,SAAW,SAAkBD,GACX,mBAAd,UACRc,SAAS3M,MAAM6L,GAGf3L,KAAKF,MAAM6L,IAUflO,EAAQ6C,OAAS,SAAgBoM,EAAGC,GAClC,IAAK,GAAIvL,KAAQuL,GACXA,EAAEnK,eAAepB,KACnBsL,EAAEtL,GAAQuL,EAAEvL,GAGhB,OAAOsL,IAQTjP,EAAQgD,MAAQ,SAAgBiM,GAC9B,IAAK,GAAItL,KAAQsL,GACXA,EAAElK,eAAepB,UACZsL,GAAEtL,EAGb,OAAOsL,IAOTjP,EAAQuD,IAAM,WACW,mBAAZ4L,UAAkD,kBAAhBA,SAAQ5L,KACnD4L,QAAQ5L,IAAI6L,MAAMD,QAAS5N,YAS/BvB,EAAQkL,KAAO,SAAemE,GAC5B,MAAe,QAAXA,EACK,OAEM/K,SAAX+K,EACK,YAEJA,YAAkBhD,SAA8B,gBAAXgD,GACjC,SAEJA,YAAkBC,SAA8B,gBAAXD,GACjC,SAEJA,YAAkBE,UAA+B,iBAAXF,GAClC,UAEJA,YAAkBG,SAA8B,gBAAXH,GACjC,SAELrP,EAAQ4D,QAAQyL,GACX,QAGF,SAQT,IAAII,GAAa,kBACjBzP,GAAQ0P,MAAQ,SAAgBvJ,GAC9B,OAAuB,gBAARA,IAAoBA,YAAgBmJ,UAC/CG,EAAWE,KAAKxJ,IAQtBnG,EAAQ4D,QAAU,SAAUgM,GAC1B,MAA+C,mBAAxCC,OAAOhO,UAAUiO,SAASnP,KAAKiP,IASxC5P,EAAQ+P,gBAAkB,SAAyBC,GACjD,GAAIC,GAAOD,EAAKE,uBAChB,OAAOD,GAAKE,KAAOzC,OAAO0C,aAAelH,SAASmH,YAAc,GASlErQ,EAAQ+G,eAAiB,SAAwBiJ,GAC/C,GAAIC,GAAOD,EAAKE,uBAChB,OAAOD,GAAKnJ,IAAM4G,OAAO4C,aAAepH,SAAS5B,WAAa,GAQhEtH,EAAQuQ,aAAe,SAAsBP,EAAM5G,GACjD,GAAIoH,GAAUR,EAAK5G,UAAUqH,MAAM,IACD,KAA9BD,EAAQE,QAAQtH,KAClBoH,EAAQ/B,KAAKrF,GACb4G,EAAK5G,UAAYoH,EAAQ9B,KAAK,OASlC1O,EAAQ2Q,gBAAkB,SAAyBX,EAAM5G,GACvD,GAAIoH,GAAUR,EAAK5G,UAAUqH,MAAM,KAC/BG,EAAQJ,EAAQE,QAAQtH,EACf,KAATwH,IACFJ,EAAQK,OAAOD,EAAO,GACtBZ,EAAK5G,UAAYoH,EAAQ9B,KAAK,OASlC1O,EAAQ8Q,gBAAkB,SAAyBC,GAEjD,IAAK,GADDC,GAASD,EAAWE,WACfvN,EAAI,EAAGwN,EAAOF,EAAOxP,OAAY0P,EAAJxN,EAAUA,IAAK,CACnD,GAAIyN,GAAQH,EAAOtN,EAGfyN,GAAMrE,OAERqE,EAAMC,gBAAgB,QAIxB,IAAIC,GAAaF,EAAME,UACvB,IAAIA,EACF,IAAK,GAAIC,GAAID,EAAW7P,OAAS,EAAG8P,GAAK,EAAGA,IAAK,CAC/C,GAAIC,GAAYF,EAAWC,EACA,IAAvBC,EAAUC,WACZL,EAAMC,gBAAgBG,EAAU7O,MAMtC1C,EAAQ8Q,gBAAgBK,KAW5BnR,EAAQyR,wBAA0B,SAAiCC,GACjE,GAAI7J,GAAOxD,CACR6E,UAASyI,cACV9J,EAAQqB,SAASyI,cACjB9J,EAAM+J,mBAAmBF,GACzB7J,EAAM7B,UAAS,GACf3B,EAAYqJ,OAAO1F,eACnB3D,EAAUwN,kBACVxN,EAAUyN,SAASjK,KASvB7H,EAAQ2L,sBAAwB,SAA+B+F,GAC7D,GAAKA,GAA6D,OAAnCA,EAAuBnI,SAAtD,CAIA,GAAIwI,GAAKlK,CACL6F,QAAO1F,cAAgBkB,SAASyI,cAClC9J,EAAQqB,SAASyI,cACjB9J,EAAM+J,mBAAmBF,GACzBK,EAAMrE,OAAO1F,eACb+J,EAAIF,kBACJE,EAAID,SAASjK,MASjB7H,EAAQgI,aAAe,WACrB,GAAI0F,OAAO1F,aAAc,CACvB,GAAI+J,GAAMrE,OAAO1F,cACjB,IAAI+J,EAAIC,YAAcD,EAAIE,WACxB,MAAOF,GAAIC,WAAW,GAG1B,MAAO,OAQThS,EAAQ4H,aAAe,SAAsBC,GAC3C,GAAIA,GACE6F,OAAO1F,aAAc,CACvB,GAAI+J,GAAMrE,OAAO1F,cACjB+J,GAAIF,kBACJE,EAAID,SAASjK,KAcnB7H,EAAQiI,mBAAqB,WAC3B,GAAIJ,GAAQ7H,EAAQgI,cAEpB,OAAIH,IAAS,eAAiBA,IAAS,aAAeA,IAClDA,EAAMqK,gBAAmBrK,EAAMqK,gBAAkBrK,EAAMsK,cAEvDC,YAAavK,EAAMuK,YACnBC,UAAWxK,EAAMwK,UACjBrR,UAAW6G,EAAMqK,eAAetN,YAI7B,MAUT5E,EAAQ8H,mBAAqB,SAA4BzC,GACvD,GAAI6D,SAASyI,aAAejE,OAAO1F,aAAc,CAC/C,GAAI3D,GAAYqJ,OAAO1F,cACvB,IAAG3D,EAAW,CACZ,GAAIwD,GAAQqB,SAASyI,aAGrB9J,GAAMyK,SAASjN,EAAOrE,UAAUuR,WAAYlN,EAAO+M,aACnDvK,EAAM2K,OAAOnN,EAAOrE,UAAUuR,WAAYlN,EAAOgN,WAEjDrS,EAAQ4H,aAAaC,MAW3B7H,EAAQyS,aAAe,SAAsBC,EAASC,GACpD,GAAIC,GAAmBtO,QAAVqO,CAgBb,IAfIC,IACFD,GACExM,KAAQ,GACR0M,MAAS,WACP,GAAI1M,GAAO/F,KAAK+F,IAEhB,OADA/F,MAAK+F,KAAO,GACLA,GAETlE,IAAO,SAAUkE,GACf/F,KAAK+F,KAAOA,KAMduM,EAAQI,UACV,MAAOH,GAAOE,QAAUH,EAAQI,SAIlC,IAAIJ,EAAQK,gBAAiB,CAI3B,IAAK,GAHD9B,GAAayB,EAAQzB,WACrB+B,EAAY,GAEPtP,EAAI,EAAGwN,EAAOD,EAAWzP,OAAY0P,EAAJxN,EAAUA,IAAK,CACvD,GAAIyN,GAAQF,EAAWvN,EAEvB,IAAsB,OAAlByN,EAAM5H,UAAuC,KAAlB4H,EAAM5H,SAAiB,CACpD,GAAI0J,GAAYhC,EAAWvN,EAAI,GAC3BwP,EAAWD,EAAYA,EAAU1J,SAAWjF,MAC5C4O,IAAwB,OAAZA,GAAiC,KAAZA,GAA+B,MAAZA,IACtDF,GAAa,KACbL,EAAOE,SAETG,GAAahT,EAAQyS,aAAatB,EAAOwB,GACzCA,EAAO1Q,IAAI,UAEc,MAAlBkP,EAAM5H,UACbyJ,GAAaL,EAAOE,QACpBF,EAAO1Q,IAAI,OAGX+Q,GAAahT,EAAQyS,aAAatB,EAAOwB,GAI7C,MAAOK,GAGP,MAAwB,KAApBN,EAAQnJ,UAA2D,IAAxCvJ,EAAQsB,6BAM9BqR,EAAOE,QAKX,IAST7S,EAAQsB,2BAA6B,WACnC,GAAkB,IAAd6R,EAAkB,CACpB,GAAIC,GAAK,EACT,IAAyB,+BAArBC,UAAUC,QACd,CACE,GAAIC,GAAKF,UAAUG,UACfC,EAAM,GAAIjE,QAAO,6BACF,OAAfiE,EAAGC,KAAKH,KACVH,EAAKO,WAAYnE,OAAOX,KAI5BsE,EAAaC,EAGf,MAAOD,IAOTnT,EAAQ4T,UAAY,WAClB,MAAkD,IAA1CP,UAAUG,UAAU9C,QAAQ,WAQtC,IAAIyC,GAAa,EAWjBnT,GAAQmK,iBAAmB,SAA0BuI,EAASlM,EAAQqN,EAAUC,GAC9E,GAAIpB,EAAQvI,iBASV,MARmB7F,UAAfwP,IACFA,GAAa,GAEA,eAAXtN,GAA2BxG,EAAQ4T,cACrCpN,EAAS,kBAGXkM,EAAQvI,iBAAiB3D,EAAQqN,EAAUC,GACpCD,CACF,IAAInB,EAAQqB,YAAa,CAE9B,GAAIC,GAAI,WACN,MAAOH,GAASlT,KAAK+R,EAAShF,OAAO1E,OAGvC,OADA0J,GAAQqB,YAAY,KAAOvN,EAAQwN,GAC5BA,IAWXhU,EAAQiU,oBAAsB,SAA6BvB,EAASlM,EAAQqN,EAAUC,GAChFpB,EAAQuB,qBACS3P,SAAfwP,IACFA,GAAa,GAEA,eAAXtN,GAA2BxG,EAAQ4T,cACrCpN,EAAS,kBAGXkM,EAAQuB,oBAAoBzN,EAAQqN,EAAUC,IACrCpB,EAAQwB,aAEjBxB,EAAQwB,YAAY,KAAO1N,EAAQqN,KAOlC,SAAS5T,GAOd,QAAS6D,KACP1D,KAAK+T,QAAS,EAOhBrQ,EAAYjC,UAAUuS,UAAY,SAAU5O,GACtCpF,KAAK+T,SAIL/T,KAAKoF,MAAQA,IAEXpF,KAAKoF,MACPpF,KAAKoF,KAAK6O,cAAa,GAIzBjU,KAAKoF,KAAOA,EACZpF,KAAKoF,KAAK6O,cAAa,IAIzBjU,KAAKkU,uBAOPxQ,EAAYjC,UAAU0S,YAAc,WAClC,IAAInU,KAAK+T,OAAT,CAIA,GAAItN,GAAKzG,IACLA,MAAKoF,OACPpF,KAAKkU,qBAKLlU,KAAKoU,iBAAmB1L,WAAW,WACjCjC,EAAGrB,KAAK6O,cAAa,GACrBxN,EAAGrB,KAAOlB,OACVuC,EAAG2N,iBAAmBlQ,QACrB,MAQPR,EAAYjC,UAAUyS,mBAAqB,WACrClU,KAAKoU,mBACP7M,aAAavH,KAAKoU,kBAClBpU,KAAKoU,iBAAmBlQ,SAQ5BR,EAAYjC,UAAU4S,KAAO,WAC3BrU,KAAK+T,QAAS,GAMhBrQ,EAAYjC,UAAU6S,OAAS,WAC7BtU,KAAK+T,QAAS,GAGhBlU,EAAOD,QAAU8D,GAKZ,SAAS7D,EAAQD,EAASM,GAS/B,QAASyD,GAASqE,GAChBhI,KAAKgI,OAASA,EACdhI,KAAK4C,QAGL5C,KAAKuU,SACHC,WACEpK,KAAQ,SAAUnF,GAChBA,EAAOG,KAAKO,YAAYV,EAAOwP,WAEjCnK,KAAQ,SAAUrF,GAChBA,EAAOG,KAAKO,YAAYV,EAAOyP,YAGnCC,WACEvK,KAAQ,SAAUnF,GAChBA,EAAOG,KAAKwP,YAAY3P,EAAOwP,WAEjCnK,KAAQ,SAAUrF,GAChBA,EAAOG,KAAKwP,YAAY3P,EAAOyP,YAGnCG,YACEzK,KAAQ,SAAUnF,GAChBA,EAAO6P,OAAOrQ,YAAYQ,EAAOG,OAEnCkF,KAAQ,SAAUrF,GAChBA,EAAO6P,OAAOtP,YAAYP,EAAOG,QAGrC2P,kBACE3K,KAAQ,SAAUnF,GAChBA,EAAO6P,OAAOrQ,YAAYQ,EAAOG,OAEnCkF,KAAQ,SAAUrF,GAChBA,EAAO6P,OAAOE,aAAa/P,EAAOG,KAAMH,EAAOgQ,cAGnDC,iBACE9K,KAAQ,SAAUnF,GAChBA,EAAO6P,OAAOrQ,YAAYQ,EAAOG,OAEnCkF,KAAQ,SAAUrF,GAChBA,EAAO6P,OAAOK,YAAYlQ,EAAOG,KAAMH,EAAOmQ,aAGlDC,YACEjL,KAAQ,SAAUnF,GAChB,GAAI6P,GAAS7P,EAAO6P,OAChBG,EAAaH,EAAOlE,OAAO3L,EAAOuL,QAAUsE,EAAOQ,MACvDR,GAAOE,aAAa/P,EAAOG,KAAM6P,IAEnC3K,KAAQ,SAAUrF,GAChBA,EAAO6P,OAAOrQ,YAAYQ,EAAOG,QAGrCmQ,eACEnL,KAAQ,SAAUnF,GAChBA,EAAO6P,OAAOrQ,YAAYQ,EAAOuQ,QAEnClL,KAAQ,SAAUrF,GAChBA,EAAO6P,OAAOK,YAAYlQ,EAAOuQ,MAAOvQ,EAAOG,QAGnDqQ,YACErL,KAAQ,SAAUnF,GAChBA,EAAOG,KAAKqQ,WAAWxQ,EAAOyQ,UAEhCpL,KAAQ,SAAUrF,GAChBA,EAAOG,KAAKqQ,WAAWxQ,EAAO0Q,WAGlCC,UACExL,KAAQ,SAAUnF,GAChBA,EAAO4Q,YAAYC,OAAO7Q,EAAOG,KAAMH,EAAO8Q,aAEhDzL,KAAQ,SAAUrF,GAChBA,EAAO+Q,UAAUF,OAAO7Q,EAAOG,KAAMH,EAAOgR,YAGhDC,MACE9L,KAAQ,SAAUnF,GAChB,GAAIG,GAAOH,EAAOG,IAClBA,GAAK+Q,aACL/Q,EAAK8Q,KAAOjR,EAAOmR,QACnBhR,EAAKwL,OAAS3L,EAAOoR,UACrBjR,EAAKkR,cAEPhM,KAAQ,SAAUrF,GAChB,GAAIG,GAAOH,EAAOG,IAClBA,GAAK+Q,aACL/Q,EAAK8Q,KAAOjR,EAAOsR,QACnBnR,EAAKwL,OAAS3L,EAAOuR,UACrBpR,EAAKkR,gBApGb,GAAIrV,GAAOf,EAAoB,EAiH/ByD,GAAQlC,UAAU+I,SAAW,aAa7B7G,EAAQlC,UAAU4E,IAAM,SAAUD,EAAQnB,GACxCjF,KAAKwQ,QACLxQ,KAAKoE,QAAQpE,KAAKwQ,QAChBpK,OAAUA,EACVnB,OAAUA,EACVwR,UAAa,GAAIC,OAIf1W,KAAKwQ,MAAQxQ,KAAKoE,QAAQhD,OAAS,GACrCpB,KAAKoE,QAAQqM,OAAOzQ,KAAKwQ,MAAQ,EAAGxQ,KAAKoE,QAAQhD,OAASpB,KAAKwQ,MAAQ,GAIzExQ,KAAKwK,YAMP7G,EAAQlC,UAAUmB,MAAQ,WACxB5C,KAAKoE,WACLpE,KAAKwQ,MAAQ,GAGbxQ,KAAKwK,YAOP7G,EAAQlC,UAAUiJ,QAAU,WAC1B,MAAQ1K,MAAKwQ,OAAS,GAOxB7M,EAAQlC,UAAUkJ,QAAU,WAC1B,MAAQ3K,MAAKwQ,MAAQxQ,KAAKoE,QAAQhD,OAAS,GAM7CuC,EAAQlC,UAAU2I,KAAO,WACvB,GAAIpK,KAAK0K,UAAW,CAClB,GAAI8E,GAAMxP,KAAKoE,QAAQpE,KAAKwQ,MAC5B,IAAIhB,EAAK,CACP,GAAIpJ,GAASpG,KAAKuU,QAAQ/E,EAAIpJ,OAC1BA,IAAUA,EAAOgE,MACnBhE,EAAOgE,KAAKoF,EAAIvK,QACZuK,EAAIvK,OAAO0R,cACb3W,KAAKgI,OAAOR,aAAagI,EAAIvK,OAAO0R,eAItC1V,EAAKkC,IAAI,0BAA4BqM,EAAIpJ,OAAS,KAGtDpG,KAAKwQ,QAGLxQ,KAAKwK,aAOT7G,EAAQlC,UAAU6I,KAAO,WACvB,GAAItK,KAAK2K,UAAW,CAClB3K,KAAKwQ,OAEL,IAAIhB,GAAMxP,KAAKoE,QAAQpE,KAAKwQ,MAC5B,IAAIhB,EAAK,CACP,GAAIpJ,GAASpG,KAAKuU,QAAQ/E,EAAIpJ,OAC1BA,IAAUA,EAAOkE,MACnBlE,EAAOkE,KAAKkF,EAAIvK,QACZuK,EAAIvK,OAAO2R,cACb5W,KAAKgI,OAAOR,aAAagI,EAAIvK,OAAO2R,eAItC3V,EAAKkC,IAAI,0BAA4BqM,EAAIpJ,OAAS,KAKtDpG,KAAKwK,aAIT3K,EAAOD,QAAU+D,GAKZ,SAAS9D,GASd,QAAS+D,GAAWoE,EAAQpH,GAC1B,GAAIiK,GAAY7K,IAEhBA,MAAKgI,OAASA,EACdhI,KAAK6W,QAAU3S,OACflE,KAAK8W,MAAQ,IACb9W,KAAK+W,SAAW7S,OAEhBlE,KAAK+D,OACL/D,KAAK+D,IAAInD,UAAYA,CAErB,IAAIoE,GAAQ8D,SAASC,cAAc,QACnC/I,MAAK+D,IAAIiB,MAAQA,EACjBA,EAAMgE,UAAY,SAClBpI,EAAU4E,YAAYR,EACtB,IAAIa,GAAQiD,SAASC,cAAc,QACnC/I,MAAK+D,IAAI8B,MAAQA,EACjBb,EAAMQ,YAAYK,EAClB,IAAImR,GAAKlO,SAASC,cAAc,KAChClD,GAAML,YAAYwR,EAElB,IAAIC,GAAKnO,SAASC,cAAc,KAChCiO,GAAGxR,YAAYyR,EACf,IAAIjR,GAAU8C,SAASC,cAAc,MACrC/I,MAAK+D,IAAIiC,QAAUA,EACnBA,EAAQgD,UAAY,UACpBiO,EAAGzR,YAAYQ,GAEfiR,EAAKnO,SAASC,cAAc,MAC5BiO,EAAGxR,YAAYyR,EACf,IAAIC,GAAWpO,SAASC,cAAc,MACtC/I,MAAK+D,IAAIoT,MAAQD,EACjBA,EAASlO,UAAY,QACrBkO,EAAS/M,MAAQ,2BACjB8M,EAAGzR,YAAY0R,EAGf,IAAIE,GAAatO,SAASC,cAAc,QACxCmO,GAAS1R,YAAY4R,EACrB,IAAIC,GAAcvO,SAASC,cAAc,QACzCqO,GAAW5R,YAAY6R,GACvBL,EAAKlO,SAASC,cAAc,MAC5BsO,EAAY7R,YAAYwR,EAExB,IAAIM,GAAgBxO,SAASC,cAAc,SAC3CuO,GAActO,UAAY,UAC1BiO,EAAKnO,SAASC,cAAc,MAC5BkO,EAAGzR,YAAY8R,GACfN,EAAGxR,YAAYyR,EAEf,IAAIvS,GAASoE,SAASC,cAAc,QACpC/I,MAAK+D,IAAIW,OAASA,EAClBA,EAAO2E,QAAU,SAAUT,GACzBiC,EAAU0M,iBAAiB3O,IAE7BlE,EAAO4E,SAAW,SAAUV,GAC1BiC,EAAU2M,UAAU5O,IAEtBlE,EAAO6E,UAAY,SAAUX,GAC3BiC,EAAUE,WAAWnC,IAEvBlE,EAAO8E,QAAU,SAAUZ,GACzBiC,EAAU4M,SAAS7O,IAErB0O,EAAcrO,QAAU,WACtBvE,EAAO8G,UAITyL,EAAKnO,SAASC,cAAc,MAC5BkO,EAAGzR,YAAYd,GACfsS,EAAGxR,YAAYyR,EAEf,IAAIS,GAAa5O,SAASC,cAAc,SACxC2O,GAAWvN,MAAQ,sBACnBuN,EAAW1O,UAAY,OACvB0O,EAAWzO,QAAU,WACnB4B,EAAUa,QAEZuL,EAAKnO,SAASC,cAAc,MAC5BkO,EAAGzR,YAAYkS,GACfV,EAAGxR,YAAYyR,EAEf,IAAIU,GAAiB7O,SAASC,cAAc,SAC5C4O,GAAexN,MAAQ,gCACvBwN,EAAe3O,UAAY,WAC3B2O,EAAe1O,QAAU,WACvB4B,EAAUY,YAEZwL,EAAKnO,SAASC,cAAc,MAC5BkO,EAAGzR,YAAYmS,GACfX,EAAGxR,YAAYyR,GAQjBrT,EAAUnC,UAAUiK,KAAO,SAAS/D,GAClC,GAAoBzD,QAAhBlE,KAAKgG,QAAsB,CAC7B,GAAIwK,GAA6BtM,QAApBlE,KAAK4X,YAA4B5X,KAAK4X,YAAc,EAAI,CACjEpH,GAAQxQ,KAAKgG,QAAQ5E,OAAS,IAChCoP,EAAQ,GAEVxQ,KAAK6X,iBAAiBrH,EAAO7I,KASjC/D,EAAUnC,UAAUgK,SAAW,SAAS9D,GACtC,GAAoBzD,QAAhBlE,KAAKgG,QAAsB,CAC7B,GAAIsC,GAAMtI,KAAKgG,QAAQ5E,OAAS,EAC5BoP,EAA6BtM,QAApBlE,KAAK4X,YAA4B5X,KAAK4X,YAAc,EAAItP,CACzD,GAARkI,IACFA,EAAQlI,GAEVtI,KAAK6X,iBAAiBrH,EAAO7I,KAWjC/D,EAAUnC,UAAUoW,iBAAmB,SAASrH,EAAO7I,GAErD,GAAI3H,KAAK8X,aAAc,CACrB,GAAIC,GAAW/X,KAAK8X,aAAa1S,KAC7B4S,EAAWhY,KAAK8X,aAAalI,IACjB,UAAZoI,QACKD,GAASE,wBAGTF,GAASG,kBAElBH,EAASI,YAGX,IAAKnY,KAAKgG,UAAYhG,KAAKgG,QAAQwK,GAIjC,MAFAxQ,MAAK4X,YAAc1T,YACnBlE,KAAK8X,aAAe5T,OAItBlE,MAAK4X,YAAcpH,CAGnB,IAAIpL,GAAOpF,KAAKgG,QAAQhG,KAAK4X,aAAaxS,KACtCwK,EAAO5P,KAAKgG,QAAQhG,KAAK4X,aAAahI,IAC9B,UAARA,EACFxK,EAAK6S,mBAAoB,EAGzB7S,EAAK8S,mBAAoB,EAE3BlY,KAAK8X,aAAe9X,KAAKgG,QAAQhG,KAAK4X,aACtCxS,EAAK+S,YAGL/S,EAAK0C,SAAS,WACRH,GACFvC,EAAKuC,MAAMiI,MASjBhM,EAAUnC,UAAU2W,YAAc,WACZlU,QAAhBlE,KAAK6W,UACPtP,aAAavH,KAAK6W,eACX7W,MAAK6W,UAUhBjT,EAAUnC,UAAU8V,iBAAmB,WAGrCvX,KAAKoY,aACL,IAAIvN,GAAY7K,IAChBA,MAAK6W,QAAUnO,WAAW,SAAUE,GAC9BiC,EAAU2M,UAAU5O,IAEtB5I,KAAK8W,QAWXlT,EAAUnC,UAAU+V,UAAY,SAAU5O,EAAOyP,GAC/CrY,KAAKoY,aAEL,IAAIjT,GAAQnF,KAAK+D,IAAIW,OAAOS,MACxBY,EAAQZ,EAAM/D,OAAS,EAAK+D,EAAQjB,MACxC,IAAI6B,GAAQ/F,KAAK+W,UAAYsB,EAO3B,GALArY,KAAK+W,SAAWhR,EAChB/F,KAAKgG,QAAUhG,KAAKgI,OAAOtD,OAAOqB,GAClC/F,KAAK6X,iBAAiB3T,QAGVA,QAAR6B,EAAmB,CACrB,GAAIuS,GAActY,KAAKgG,QAAQ5E,MAC/B,QAAQkX,GACN,IAAK,GAAGtY,KAAK+D,IAAIiC,QAAQuS,UAAY,iBAAmB,MACxD,KAAK,GAAGvY,KAAK+D,IAAIiC,QAAQuS,UAAY,eAAiB,MACtD,SAASvY,KAAK+D,IAAIiC,QAAQuS,UAAYD,EAAc,qBAItDtY,MAAK+D,IAAIiC,QAAQuS,UAAY,IAUnC3U,EAAUnC,UAAUsJ,WAAa,SAAUnC,GACzC,GAAIqC,GAASrC,EAAMsC,KACL,KAAVD,GACFjL,KAAK+D,IAAIW,OAAOS,MAAQ,GACxBnF,KAAKwX,UAAU5O,GACfA,EAAMQ,iBACNR,EAAM+C,mBAEW,IAAVV,IACHrC,EAAMwC,QAERpL,KAAKwX,UAAU5O,GAAO,GAEfA,EAAMyC,SAEbrL,KAAKyL,WAILzL,KAAK0L,OAEP9C,EAAMQ,iBACNR,EAAM+C,oBASV/H,EAAUnC,UAAUgW,SAAW,SAAU7O,GACvC,GAAIqC,GAASrC,EAAMuC,OACL,KAAVF,GAA0B,IAAVA,GAClBjL,KAAKuX,iBAAiB3O,IAI1B/I,EAAOD,QAAUgE,GAKZ,SAAS/D,EAAQD,EAASM,GAiB/B,QAAS2D,GAAMmE,EAAQ/C,GAErBjF,KAAKgI,OAASA,EACdhI,KAAK+D,OACL/D,KAAKwY,UAAW,EAEbvT,GAAWA,YAAkBwK,SAC9BzP,KAAKyY,SAASxT,EAAOC,MAAOD,EAAOyT,eACnC1Y,KAAK6N,SAAS5I,EAAOE,MAAOF,EAAO6F,QAGnC9K,KAAKyY,SAAS,IACdzY,KAAK6N,SAAS,OA3BlB,GAAI8K,GAAczY,EAAoB,GAClC0Y,EAAoB1Y,EAAoB,IACxCe,EAAOf,EAAoB,EAiC/B2D,GAAKpC,UAAUoX,mBAAqB,WAMlC,GALA7Y,KAAK8Y,UACH5T,OAAO,EACPC,OAAO,GAGLnF,KAAKgI,SACPhI,KAAK8Y,SAAS5T,MAAqC,SAA7BlF,KAAKgI,OAAOnH,QAAQa,KAC1C1B,KAAK8Y,SAAS3T,MAAqC,SAA7BnF,KAAKgI,OAAOnH,QAAQa,KAET,SAA7B1B,KAAKgI,OAAOnH,QAAQa,MAA4D,kBAAjC1B,MAAKgI,OAAOnH,QAAQiY,UAA0B,CAC/F,GAAIA,GAAW9Y,KAAKgI,OAAOnH,QAAQiY,UACjC5T,MAAOlF,KAAKkF,MACZC,MAAOnF,KAAKmF,MACZ4T,KAAM/Y,KAAK+Y,QAGW,kBAAbD,IACT9Y,KAAK8Y,SAAS5T,MAAQ4T,EACtB9Y,KAAK8Y,SAAS3T,MAAQ2T,IAGQ,iBAAnBA,GAAS5T,QAAqBlF,KAAK8Y,SAAS5T,MAAQ4T,EAAS5T,OAC1C,iBAAnB4T,GAAS3T,QAAqBnF,KAAK8Y,SAAS3T,MAAQ2T,EAAS3T,UAUhFtB,EAAKpC,UAAUsX,KAAO,WAGpB,IAFA,GAAI3T,GAAOpF,KACP+Y,KACG3T,GAAM,CACX,GAAIF,GAAsBhB,QAAdkB,EAAKF,MAAqBE,EAAKF,MAAQE,EAAKoL,KAC1CtM,UAAVgB,GACF6T,EAAKC,QAAQ9T,GAEfE,EAAOA,EAAK0P,OAEd,MAAOiE,IAOTlV,EAAKpC,UAAUwX,UAAY,SAASnE,GAClC9U,KAAK8U,OAASA,GAQhBjR,EAAKpC,UAAUgX,SAAW,SAASvT,EAAOwT,GACxC1Y,KAAKkF,MAAQA,EACblF,KAAK0Y,cAAkC,GAAjBA,GAOxB7U,EAAKpC,UAAUyX,SAAW,WAKxB,MAJmBhV,UAAflE,KAAKkF,OACPlF,KAAKmZ,eAGAnZ,KAAKkF,OASdrB,EAAKpC,UAAUoM,SAAW,SAAS1I,EAAO2F,GACxC,GAAIsO,GAAYrI,EAGZH,EAAS5Q,KAAK4Q,MAClB,IAAIA,EACF,KAAOA,EAAOxP,QACZpB,KAAKyE,YAAYmM,EAAO,GAS5B,IAHA5Q,KAAK8K,KAAO9K,KAAKqZ,SAASlU,GAGtB2F,GAAQA,GAAQ9K,KAAK8K,KAAM,CAC7B,GAAY,UAARA,GAAiC,QAAb9K,KAAK8K,KAI3B,KAAM,IAAI/J,OAAM,6CACoBf,KAAK8K,KACrC,2BAA6BA,EAAO,IALxC9K,MAAK8K,KAAOA,EAShB,GAAiB,SAAb9K,KAAK8K,KAAiB,CAExB9K,KAAK4Q,SACL,KAAK,GAAItN,GAAI,EAAGwN,EAAO3L,EAAM/D,OAAY0P,EAAJxN,EAAUA,IAC7C8V,EAAajU,EAAM7B,GACAY,SAAfkV,GAA8BA,YAAsBtU,YAEtDiM,EAAQ,GAAIlN,GAAK7D,KAAKgI,QACpB7C,MAAOiU,IAETpZ,KAAKwF,YAAYuL,GAGrB/Q,MAAKmF,MAAQ,OAEV,IAAiB,UAAbnF,KAAK8K,KAAkB,CAE9B9K,KAAK4Q,SACL,KAAK,GAAI0I,KAAcnU,GACjBA,EAAMR,eAAe2U,KACvBF,EAAajU,EAAMmU,GACApV,SAAfkV,GAA8BA,YAAsBtU,YAEtDiM,EAAQ,GAAIlN,GAAK7D,KAAKgI,QACpB9C,MAAOoU,EACPnU,MAAOiU,IAETpZ,KAAKwF,YAAYuL,IAIvB/Q,MAAKmF,MAAQ,OAIbnF,MAAK4Q,OAAS1M,OACdlE,KAAKmF,MAAQA,GAkBjBtB,EAAKpC,UAAUiE,SAAW,WAGxB,GAAiB,SAAb1F,KAAK8K,KAAiB,CACxB,GAAIyO,KAIJ,OAHAvZ,MAAK4Q,OAAO4I,QAAS,SAAUzI,GAC7BwI,EAAIlL,KAAK0C,EAAMrL,cAEV6T,EAEJ,GAAiB,UAAbvZ,KAAK8K,KAAkB,CAC9B,GAAI0E,KAIJ,OAHAxP,MAAK4Q,OAAO4I,QAAS,SAAUzI,GAC7BvB,EAAIuB,EAAMmI,YAAcnI,EAAMrL,aAEzB8J,EAOP,MAJmBtL,UAAflE,KAAKmF,OACPnF,KAAKyZ,eAGAzZ,KAAKmF,OAQhBtB,EAAKpC,UAAUiY,SAAW,WACxB,MAAQ1Z,MAAK8U,OAAS9U,KAAK8U,OAAO4E,WAAa,EAAI,GASrD7V,EAAKpC,UAAU+T,MAAQ,WACrB,GAAIA,GAAQ,GAAI3R,GAAK7D,KAAKgI,OAS1B,IARAwN,EAAM1K,KAAO9K,KAAK8K,KAClB0K,EAAMtQ,MAAQlF,KAAKkF,MACnBsQ,EAAMmE,eAAiB3Z,KAAK2Z,eAC5BnE,EAAMkD,cAAgB1Y,KAAK0Y,cAC3BlD,EAAMrQ,MAAQnF,KAAKmF,MACnBqQ,EAAMoE,eAAiB5Z,KAAK4Z,eAC5BpE,EAAMgD,SAAWxY,KAAKwY,SAElBxY,KAAK4Q,OAAQ,CAEf,GAAIiJ,KACJ7Z,MAAK4Q,OAAO4I,QAAQ,SAAUzI,GAC5B,GAAI+I,GAAa/I,EAAMyE,OACvBsE,GAAWb,UAAUzD,GACrBqE,EAAYxL,KAAKyL,KAEnBtE,EAAM5E,OAASiJ,MAIfrE,GAAM5E,OAAS1M,MAGjB,OAAOsR,IAQT3R,EAAKpC,UAAU8D,OAAS,SAASD,GAC1BtF,KAAK4Q,SAKV5Q,KAAKwY,UAAW,EACZxY,KAAK+D,IAAIwB,SACXvF,KAAK+D,IAAIwB,OAAOyD,UAAY,YAG9BhJ,KAAKsW,aAEU,GAAXhR,GACFtF,KAAK4Q,OAAO4I,QAAQ,SAAUzI,GAC5BA,EAAMxL,OAAOD,OAUnBzB,EAAKpC,UAAUmE,SAAW,SAASN,GAC5BtF,KAAK4Q,SAIV5Q,KAAKmW,aAGU,GAAX7Q,GACFtF,KAAK4Q,OAAO4I,QAAQ,SAAUzI,GAC5BA,EAAMnL,SAASN,KAMftF,KAAK+D,IAAIwB,SACXvF,KAAK+D,IAAIwB,OAAOyD,UAAY,aAE9BhJ,KAAKwY,UAAW,IAMlB3U,EAAKpC,UAAU6U,WAAa,WAC1B,GAAI1F,GAAS5Q,KAAK4Q,MAClB,IAAKA,GAGA5Q,KAAKwY,SAAV,CAIA,GAAIxB,GAAKhX,KAAK+D,IAAIiT,GACdhS,EAAQgS,EAAKA,EAAGxS,WAAaN,MACjC,IAAIc,EAAO,CAET,GAAIsQ,GAAStV,KAAK+Z,YACdC,EAAShD,EAAGiD,WACZD,GACFhV,EAAMgQ,aAAaM,EAAQ0E,GAG3BhV,EAAMQ,YAAY8P,GAIpBtV,KAAK4Q,OAAO4I,QAAQ,SAAUzI,GAC5B/L,EAAMgQ,aAAajE,EAAMjL,SAAUwP,GACnCvE,EAAMuF,kBAQZzS,EAAKpC,UAAUyY,KAAO,WACpB,GAAIlD,GAAKhX,KAAK+D,IAAIiT,GACdhS,EAAQgS,EAAKA,EAAGxS,WAAaN,MAC7Bc,IACFA,EAAMP,YAAYuS,GAEpBhX,KAAKmW,cAOPtS,EAAKpC,UAAU0U,WAAa,WAC1B,GAAIvF,GAAS5Q,KAAK4Q,MAClB,IAAKA,GAGA5Q,KAAKwY,SAAV,CAKA,GAAIlD,GAAStV,KAAK+Z,WACdzE,GAAO9Q,YACT8Q,EAAO9Q,WAAWC,YAAY6Q,GAIhCtV,KAAK4Q,OAAO4I,QAAQ,SAAUzI,GAC5BA,EAAMmJ,WAUVrW,EAAKpC,UAAU+D,YAAc,SAASJ,GACpC,GAAIpF,KAAKma,aAAc,CASrB,GAPA/U,EAAK6T,UAAUjZ,MACfoF,EAAKsT,cAA8B,UAAb1Y,KAAK8K,KACV,SAAb9K,KAAK8K,OACP1F,EAAKoL,MAAQxQ,KAAK4Q,OAAOxP,QAE3BpB,KAAK4Q,OAAOvC,KAAKjJ,GAEbpF,KAAKwY,SAAU,CAEjB,GAAI4B,GAAQhV,EAAKU,SACbuU,EAAWra,KAAK+Z,YAChB/U,EAAQqV,EAAWA,EAAS7V,WAAaN,MACzCmW,IAAYrV,GACdA,EAAMgQ,aAAaoF,EAAOC,GAG5BjV,EAAKkR,aAGPtW,KAAKmY,WAAWmC,eAAiB,IACjClV,EAAK+S,WAAW7S,SAAW,MAW/BzB,EAAKpC,UAAU8Y,WAAa,SAASnV,EAAM6P,GACzC,GAAIjV,KAAKma,aAAc,CAGrB,GAAItU,GAAS7F,KAAK+D,IAAM,GAAI/D,KAAK+D,IAAIiT,GAAGxS,WAAaN,MACrD,IAAI2B,EAAO,CACT,GAAI2U,GAAS1R,SAASC,cAAc,KACpCyR,GAAO9N,MAAM9F,OAASf,EAAMgB,aAAe,KAC3ChB,EAAML,YAAYgV,GAGhBpV,EAAK0P,QACP1P,EAAK0P,OAAOrQ,YAAYW,GAGtB6P,YAAsBwF,GACxBza,KAAKwF,YAAYJ,GAGjBpF,KAAKgV,aAAa5P,EAAM6P,GAGtBpP,GACFA,EAAMpB,YAAY+V,KAYxB3W,EAAKpC,UAAUqU,OAAS,SAAU1Q,EAAMoL,GACtC,GAAIpL,EAAK0P,QAAU9U,KAAM,CAEvB,GAAI0a,GAAe1a,KAAK4Q,OAAON,QAAQlL,EACpBoL,GAAfkK,GAEFlK,IAIJ,GAAIyE,GAAajV,KAAK4Q,OAAOJ,IAAUxQ,KAAKsV,MAC5CtV,MAAKua,WAAWnV,EAAM6P,IASxBpR,EAAKpC,UAAUuT,aAAe,SAAS5P,EAAM6P,GAC3C,GAAIjV,KAAKma,aAAc,CACrB,GAAIlF,GAAcjV,KAAKsV,OAIrBlQ,EAAK6T,UAAUjZ,MACfoF,EAAKsT,cAA8B,UAAb1Y,KAAK8K,KAC3B9K,KAAK4Q,OAAOvC,KAAKjJ,OAEd,CAEH,GAAIoL,GAAQxQ,KAAK4Q,OAAON,QAAQ2E,EAChC,IAAa,IAATzE,EACF,KAAM,IAAIzP,OAAM,iBAIlBqE,GAAK6T,UAAUjZ,MACfoF,EAAKsT,cAA8B,UAAb1Y,KAAK8K,KAC3B9K,KAAK4Q,OAAOH,OAAOD,EAAO,EAAGpL,GAG/B,GAAIpF,KAAKwY,SAAU,CAEjB,GAAI4B,GAAQhV,EAAKU,SACbkU,EAAS/E,EAAWnP,SACpBd,EAAQgV,EAASA,EAAOxV,WAAaN,MACrC8V,IAAUhV,GACZA,EAAMgQ,aAAaoF,EAAOJ,GAG5B5U,EAAKkR,aAGPtW,KAAKmY,WAAWmC,eAAiB,IACjClV,EAAK+S,WAAW7S,SAAW,MAU/BzB,EAAKpC,UAAU0T,YAAc,SAAS/P,EAAMgQ,GAC1C,GAAIpV,KAAKma,aAAc,CACrB,GAAI3J,GAAQxQ,KAAK4Q,OAAON,QAAQ8E,GAC5BH,EAAajV,KAAK4Q,OAAOJ,EAAQ,EACjCyE,GACFjV,KAAKgV,aAAa5P,EAAM6P,GAGxBjV,KAAKwF,YAAYJ,KAYvBvB,EAAKpC,UAAUiD,OAAS,SAASqB,GAC/B,GACIyK,GADAxK,KAEAtB,EAASqB,EAAOA,EAAK4U,cAAgBzW,MAOzC,UAJOlE,MAAK4a,kBACL5a,MAAK6a,YAGM3W,QAAdlE,KAAKkF,MAAoB,CAC3B,GAAIA,GAAQgK,OAAOlP,KAAKkF,OAAOyV,aAC/BnK,GAAQtL,EAAMoL,QAAQ5L,GACT,IAAT8L,IACFxQ,KAAK4a,aAAc,EACnB5U,EAAQqI,MACNjJ,KAAQpF,KACR4P,KAAQ,WAKZ5P,KAAK8a,kBAIP,GAAI9a,KAAKma,aAAc,CAIrB,GAAIna,KAAK4Q,OAAQ,CACf,GAAImK,KACJ/a,MAAK4Q,OAAO4I,QAAQ,SAAUzI,GAC5BgK,EAAeA,EAAaC,OAAOjK,EAAMrM,OAAOqB,MAElDC,EAAUA,EAAQgV,OAAOD,GAI3B,GAAc7W,QAAVQ,EAAqB,CACvB,GAAIY,IAAU,CACa,IAAvByV,EAAa3Z,OACfpB,KAAK4F,SAASN,GAGdtF,KAAKuF,OAAOD,QAIb,CAEH,GAAkBpB,QAAdlE,KAAKmF,MAAqB,CAC5B,GAAIA,GAAQ+J,OAAOlP,KAAKmF,OAAOwV,aAC/BnK,GAAQrL,EAAMmL,QAAQ5L,GACT,IAAT8L,IACFxQ,KAAK6a,aAAc,EACnB7U,EAAQqI,MACNjJ,KAAQpF,KACR4P,KAAQ,WAMd5P,KAAKib,kBAGP,MAAOjV,IAQTnC,EAAKpC,UAAUqG,SAAW,SAASC,GACjC,IAAK/H,KAAK+D,IAAIiT,KAAOhX,KAAK+D,IAAIiT,GAAGxS,WAI/B,IAFA,GAAIsQ,GAAS9U,KAAK8U,OACdxP,GAAU,EACPwP,GACLA,EAAOvP,OAAOD,GACdwP,EAASA,EAAOA,MAIhB9U,MAAK+D,IAAIiT,IAAMhX,KAAK+D,IAAIiT,GAAGxS,YAC7BxE,KAAKgI,OAAOF,SAAS9H,KAAK+D,IAAIiT,GAAGkE,UAAWnT,IAMhDlE,EAAKsX,aAAejX,OAQpBL,EAAKpC,UAAUkG,MAAQ,SAASyT,GAG9B,GAFAvX,EAAKsX,aAAeC,EAEhBpb,KAAK+D,IAAIiT,IAAMhX,KAAK+D,IAAIiT,GAAGxS,WAAY,CACzC,GAAIT,GAAM/D,KAAK+D,GAEf,QAAQqX,GACN,IAAK,OACCrX,EAAIsX,KACNtX,EAAIsX,KAAK1T,QAGT5D,EAAImG,KAAKvC,OAEX,MAEF,KAAK,OACH5D,EAAImG,KAAKvC,OACT,MAEF,KAAK,SACC3H,KAAKma,aACPpW,EAAIwB,OAAOoC,QAEJ5D,EAAImB,OAASlF,KAAK0Y,eACzB3U,EAAImB,MAAMyC,QACV1G,EAAKsK,sBAAsBxH,EAAImB,QAExBnB,EAAIoB,QAAUnF,KAAKma,cAC1BpW,EAAIoB,MAAMwC,QACV1G,EAAKsK,sBAAsBxH,EAAIoB,QAG/BpB,EAAImG,KAAKvC,OAEX,MAEF,KAAK,QACC5D,EAAImB,OAASlF,KAAK0Y,eACpB3U,EAAImB,MAAMyC,QACV1G,EAAKsK,sBAAsBxH,EAAImB,QAExBnB,EAAIoB,QAAUnF,KAAKma,cAC1BpW,EAAIoB,MAAMwC,QACV1G,EAAKsK,sBAAsBxH,EAAIoB,QAExBnF,KAAKma,aACZpW,EAAIwB,OAAOoC,QAGX5D,EAAImG,KAAKvC,OAEX,MAEF,KAAK,QACL,QACM5D,EAAIoB,QAAUnF,KAAKma,cACrBpW,EAAIoB,MAAMwC,QACV1G,EAAKsK,sBAAsBxH,EAAIoB,QAExBpB,EAAImB,OAASlF,KAAK0Y,eACzB3U,EAAImB,MAAMyC,QACV1G,EAAKsK,sBAAsBxH,EAAImB,QAExBlF,KAAKma,aACZpW,EAAIwB,OAAOoC,QAGX5D,EAAImG,KAAKvC,WAWnB9D,EAAK2H,OAAS,SAAS8P,GACrB5S,WAAW,WACTzH,EAAKsK,sBAAsB+P,IAC1B,IAMLzX,EAAKpC,UAAUgE,KAAO,WAEpBzF,KAAKyZ,cAAa,GAClBzZ,KAAKmZ,cAAa,IAUpBtV,EAAKpC,UAAU8Z,WAAa,SAASnW,GACnC,GAAIoQ,GAAQpQ,EAAKoQ,OASjB,OAFAxV,MAAKmV,YAAYK,EAAOpQ,GAEjBoQ,GAST3R,EAAKpC,UAAU+Z,aAAe,SAASpW,GACrC,GAAIpF,MAAQoF,EACV,OAAO,CAGT,IAAIwL,GAAS5Q,KAAK4Q,MAClB,IAAIA,EAEF,IAAK,GAAItN,GAAI,EAAGwN,EAAOF,EAAOxP,OAAY0P,EAAJxN,EAAUA,IAC9C,GAAIsN,EAAOtN,GAAGkY,aAAapW,GACzB,OAAO,CAKb,QAAO,GAWTvB,EAAKpC,UAAUga,MAAQ,SAASrW,EAAM6P,GACpC,GAAI7P,GAAQ6P,EAAZ,CAMA,GAAI7P,EAAKoW,aAAaxb,MACpB,KAAM,IAAIe,OAAM,6CAIdqE;EAAK0P,QACP1P,EAAK0P,OAAOrQ,YAAYW,EAI1B,IAAIoQ,GAAQpQ,EAAKoQ,OACjBpQ,GAAKsW,WAGDzG,EACFjV,KAAKgV,aAAaQ,EAAOP,GAGzBjV,KAAKwF,YAAYgQ,KAgBrB3R,EAAKpC,UAAUgD,YAAc,SAASW,GACpC,GAAIpF,KAAK4Q,OAAQ,CACf,GAAIJ,GAAQxQ,KAAK4Q,OAAON,QAAQlL,EAEhC,IAAa,IAAToL,EAAa,CACfpL,EAAK8U,aAGE9U,GAAKwV,kBACLxV,GAAKyV,WAEZ,IAAIc,GAAc3b,KAAK4Q,OAAOH,OAAOD,EAAO,GAAG,EAI/C,OAFAxQ,MAAKmY,WAAWmC,eAAiB,IAE1BqB,GAIX,MAAOzX,SAUTL,EAAKpC,UAAUma,QAAU,SAAUxW,GACjCpF,KAAKyE,YAAYW,IAOnBvB,EAAKpC,UAAUgU,WAAa,SAAUE,GACpC,GAAID,GAAU1V,KAAK8K,IAEnB,IAAI4K,GAAWC,EAAf,CAKA,GAAgB,UAAXA,GAAkC,QAAXA,GACZ,UAAXD,GAAkC,QAAXA,EAIvB,CAEH,GACImG,GADA7W,EAAQhF,KAAK+D,IAAIiT,GAAKhX,KAAK+D,IAAIiT,GAAGxS,WAAaN,MAGjD2X,GADE7b,KAAKwY,SACExY,KAAK+Z,YAGL/Z,KAAK8F,QAEhB,IAAIkU,GAAU6B,GAAUA,EAAOrX,WAAcqX,EAAO5B,YAAc/V,MAGlElE,MAAKka,OACLla,KAAK0b,WAGL1b,KAAK8K,KAAO6K,EAGG,UAAXA,GACG3V,KAAK4Q,SACR5Q,KAAK4Q,WAGP5Q,KAAK4Q,OAAO4I,QAAQ,SAAUzI,GAC5BA,EAAM2K,iBACC3K,GAAMP,MACbO,EAAM2H,eAAgB,EACHxU,QAAf6M,EAAM7L,QACR6L,EAAM7L,MAAQ,OAIH,UAAXwQ,GAAkC,QAAXA,KACzB1V,KAAKwY,UAAW,IAGA,SAAX7C,GACF3V,KAAK4Q,SACR5Q,KAAK4Q,WAGP5Q,KAAK4Q,OAAO4I,QAAQ,SAAUzI,EAAOP,GACnCO,EAAM2K,WACN3K,EAAM2H,eAAgB,EACtB3H,EAAMP,MAAQA,KAGD,UAAXkF,GAAkC,QAAXA,KACzB1V,KAAKwY,UAAW,IAIlBxY,KAAKwY,UAAW,EAIdxT,IACEgV,EACFhV,EAAMgQ,aAAahV,KAAK8F,SAAUkU,GAGlChV,EAAMQ,YAAYxF,KAAK8F,WAG3B9F,KAAKsW,iBApELtW,MAAK8K,KAAO6K,GAuEC,QAAXA,GAAgC,UAAXA,KAGrB3V,KAAKmF,MADQ,UAAXwQ,EACWzG,OAAOlP,KAAKmF,OAGZnF,KAAK8b,YAAY5M,OAAOlP,KAAKmF,QAG5CnF,KAAK2H,SAGP3H,KAAKmY,WAAWmC,eAAiB,MASnCzW,EAAKpC,UAAUgY,aAAe,SAASsC,GAKrC,GAJI/b,KAAK+D,IAAIoB,OAAsB,SAAbnF,KAAK8K,MAAgC,UAAb9K,KAAK8K,OACjD9K,KAAK4Z,eAAiB3Y,EAAKoR,aAAarS,KAAK+D,IAAIoB,QAGxBjB,QAAvBlE,KAAK4Z,eACP,IAEE,GAAIzU,EACJ,IAAiB,UAAbnF,KAAK8K,KACP3F,EAAQnF,KAAKgc,cAAchc,KAAK4Z,oBAE7B,CACH,GAAIqC,GAAMjc,KAAKgc,cAAchc,KAAK4Z,eAClCzU,GAAQnF,KAAK8b,YAAYG,GAE3B,GAAI9W,IAAUnF,KAAKmF,MAAO,CACxB,GAAIsP,GAAWzU,KAAKmF,KACpBnF,MAAKmF,MAAQA,EACbnF,KAAKgI,OAAO7B,UAAU,aACpBf,KAAQpF,KACRyU,SAAYA,EACZC,SAAYvP,EACZwR,aAAgB3W,KAAKgI,OAAO/D,UAC5B2S,aAAgB5W,KAAKgI,OAAOJ,kBAIlC,MAAO5E,GAGL,GAFAhD,KAAKmF,MAAQjB,OAEC,GAAV6X,EACF,KAAM/Y,KAada,EAAKpC,UAAUwZ,gBAAkB,WAC/B,GAAIiB,GAAWlc,KAAK+D,IAAIoB,KACxB,IAAI+W,EAAU,CAGZ,GAAIC,GAAInc,KAAKmF,MACTiX,EAAkB,QAAbpc,KAAK8K,KAAkB7J,EAAK6J,KAAKqR,GAAKnc,KAAK8K,KAChDwE,EAAc,UAAL8M,GAAiBnb,EAAKqO,MAAM6M,GACrCE,EAAQ,EAEVA,GADE/M,IAAUtP,KAAK8Y,SAAS3T,MAClB,GAEI,UAALiX,EACC,QAEI,UAALA,EACC,MAEI,WAALA,EACC,aAEDpc,KAAKma,aACJ,GAEK,OAANgC,EACC,UAIA,QAEVD,EAASxP,MAAM2P,MAAQA,CAGvB,IAAIC,GAAiC,IAAtBpN,OAAOlP,KAAKmF,QAA6B,SAAbnF,KAAK8K,MAAgC,UAAb9K,KAAK8K,IAiBxE,IAhBIwR,EACFrb,EAAKkP,aAAa+L,EAAU,SAG5Bjb,EAAKsP,gBAAgB2L,EAAU,SAI7B5M,EACFrO,EAAKkP,aAAa+L,EAAU,OAG5Bjb,EAAKsP,gBAAgB2L,EAAU,OAIxB,SAALE,GAAqB,UAALA,EAAe,CACjC,GAAIG,GAAQvc,KAAK4Q,OAAS5Q,KAAK4Q,OAAOxP,OAAS,CAC/C8a,GAAS/R,MAAQnK,KAAK8K,KAAO,eAAiByR,EAAQ,aAE1C,UAALH,GAAiBnb,EAAKqO,MAAM6M,GAC/Bnc,KAAK8Y,SAAS3T,QAChB+W,EAAS/R,MAAQ,sDAInB+R,EAAS/R,MAAQ,EAIfnK,MAAKkY,kBACPjX,EAAKkP,aAAa+L,EAAU,oBAG5Bjb,EAAKsP,gBAAgB2L,EAAU,oBAE7Blc,KAAK6a,YACP5Z,EAAKkP,aAAa+L,EAAU,aAG5Bjb,EAAKsP,gBAAgB2L,EAAU,aAIjCjb,EAAKyP,gBAAgBwL,KAWzBrY,EAAKpC,UAAUqZ,gBAAkB,WAC/B,GAAI0B,GAAWxc,KAAK+D,IAAImB,KACxB,IAAIsX,EAAU,CAEZ,GAAIF,GAAiC,IAAtBpN,OAAOlP,KAAKkF,QAAoC,SAApBlF,KAAK8U,OAAOhK,IACnDwR,GACFrb,EAAKkP,aAAaqM,EAAU,SAG5Bvb,EAAKsP,gBAAgBiM,EAAU,SAI7Bxc,KAAKiY,kBACPhX,EAAKkP,aAAaqM,EAAU,oBAG5Bvb,EAAKsP,gBAAgBiM,EAAU,oBAE7Bxc,KAAK4a,YACP3Z,EAAKkP,aAAaqM,EAAU,aAG5Bvb,EAAKsP,gBAAgBiM,EAAU,aAIjCvb,EAAKyP,gBAAgB8L,KAUzB3Y,EAAKpC,UAAU0X,aAAe,SAAS4C,GAKrC,GAJI/b,KAAK+D,IAAImB,OAASlF,KAAK0Y,gBACzB1Y,KAAK2Z,eAAiB1Y,EAAKoR,aAAarS,KAAK+D,IAAImB,QAGxBhB,QAAvBlE,KAAK2Z,eACP,IACE,GAAIzU,GAAQlF,KAAKgc,cAAchc,KAAK2Z,eAEpC,IAAIzU,IAAUlF,KAAKkF,MAAO,CACxB,GAAIuX,GAAWzc,KAAKkF,KACpBlF,MAAKkF,MAAQA,EACblF,KAAKgI,OAAO7B,UAAU,aACpBf,KAAQpF,KACRyU,SAAYgI,EACZ/H,SAAYxP,EACZyR,aAAgB3W,KAAKgI,OAAO/D,UAC5B2S,aAAgB5W,KAAKgI,OAAOJ,kBAIlC,MAAO5E,GAGL,GAFAhD,KAAKkF,MAAQhB,OAEC,GAAV6X,EACF,KAAM/Y,KASda,EAAKpC,UAAUia,SAAW,WAKxB1b,KAAK+D,QAQPF,EAAKpC,UAAUqE,OAAS,WACtB,GAAI/B,GAAM/D,KAAK+D,GACf,IAAIA,EAAIiT,GACN,MAAOjT,GAAIiT,EASb,IANAhX,KAAK6Y,qBAGL9U,EAAIiT,GAAKlO,SAASC,cAAc,MAChChF,EAAIiT,GAAG5R,KAAOpF,KAEmB,SAA7BA,KAAKgI,OAAOnH,QAAQa,KAAiB,CACvC,GAAIgb,GAAS5T,SAASC,cAAc,KACpC,IAAI/I,KAAK8Y,SAAS5T,OAEZlF,KAAK8U,OAAQ,CACf,GAAI6H,GAAU7T,SAASC,cAAc,SACrChF,GAAIsX,KAAOsB,EACXA,EAAQ3T,UAAY,WACpB2T,EAAQxS,MAAQ,6CAChBuS,EAAOlX,YAAYmX,GAGvB5Y,EAAIiT,GAAGxR,YAAYkX,EAGnB,IAAIE,GAAS9T,SAASC,cAAc,MAChCmB,EAAOpB,SAASC,cAAc,SAClChF,GAAImG,KAAOA,EACXA,EAAKlB,UAAY,cACjBkB,EAAKC,MAAQ,0CACbyS,EAAOpX,YAAYzB,EAAImG,MACvBnG,EAAIiT,GAAGxR,YAAYoX,GAIrB,GAAIC,GAAU/T,SAASC,cAAc,KAOrC,OANAhF,GAAIiT,GAAGxR,YAAYqX,GACnB9Y,EAAI+Y,KAAO9c,KAAK+c,iBAChBF,EAAQrX,YAAYzB,EAAI+Y,MAExB9c,KAAKmY,WAAWmC,eAAiB,IAE1BvW,EAAIiT,IAQbnT,EAAKpC,UAAUub,aAAe,SAAUpU,GACtC,GAAIxD,GAAOpF,IACNA,MAAKid,YACRjd,KAAKid,UAAYhc,EAAK8I,iBAAiBjB,SAAU,YAC7C,SAAUF,GACRxD,EAAK8X,QAAQtU,MAIhB5I,KAAKmd,UACRnd,KAAKmd,QAAUlc,EAAK8I,iBAAiBjB,SAAU,UAC3C,SAAUF,GACRxD,EAAKgY,WAAWxU,MAIxB5I,KAAKgI,OAAOhE,YAAYqQ,OACxBrU,KAAKqb,MACHgC,UAAavU,SAASwU,KAAK5Q,MAAM6Q,OACjC1H,YAAe7V,KAAK8U,OACpBiB,WAAc/V,KAAK8U,OAAOlE,OAAON,QAAQtQ,MACzCwd,OAAU5U,EAAM6U,MAChBC,MAAS1d,KAAK0Z,YAEhB5Q,SAASwU,KAAK5Q,MAAM6Q,OAAS,OAE7B3U,EAAMQ,kBAQRvF,EAAKpC,UAAUyb,QAAU,SAAUtU,GAEjC,GAGI+U,GAAQC,EAAQC,EAAQC,EAASC,EAAQC,EACzCC,EAAUC,EACVC,EAASC,EAASC,EAAUC,EAAYC,EAAYC,EALpDhY,EAASoC,EAAM6V,MACfjB,EAAS5U,EAAM6U,MAKfiB,GAAQ,CAQZ,IAHAf,EAAS3d,KAAK+D,IAAIiT,GAClBmH,EAAUld,EAAK0F,eAAegX,GAC9BW,EAAaX,EAAOgB,aACPR,EAAT3X,EAAkB,CAEpBoX,EAASD,CACT,GACEC,GAASA,EAAOgB,gBAChBX,EAAWpa,EAAKmH,kBAAkB4S,GAClCQ,EAAUR,EAAS3c,EAAK0F,eAAeiX,GAAU,QAE5CA,GAAmBQ,EAAT5X,EAEbyX,KAAaA,EAASnJ,SACxBmJ,EAAW/Z,QAGR+Z,IAEHD,EAASL,EAAOnZ,WAAW2N,WAC3ByL,EAASI,EAASA,EAAO/D,YAAc/V,OACvC+Z,EAAWpa,EAAKmH,kBAAkB4S,GAC9BK,GAAYje,OACdie,EAAW/Z,SAIX+Z,IAEFL,EAASK,EAASla,IAAIiT,GACtBoH,EAAUR,EAAS3c,EAAK0F,eAAeiX,GAAU,EAC7CpX,EAAS4X,EAAUE,IACrBL,EAAW/Z,SAIX+Z,IACFA,EAASnJ,OAAOyF,WAAWva,KAAMie,GACjCS,GAAQ,OAOV,IAFAX,EAAU/d,KAAKwY,UAAYxY,KAAKsV,OAAUtV,KAAKsV,OAAOxP,SAAW9F,KAAK+D,IAAIiT,GAC1E8G,EAAUC,EAASA,EAAO9D,YAAc/V,OAC3B,CACXma,EAAWpd,EAAK0F,eAAemX,GAC/BD,EAASC,CACT,GACEI,GAAWra,EAAKmH,kBAAkB6S,GAC9BA,IACFU,EAAaV,EAAO5D,YAChBhZ,EAAK0F,eAAekX,EAAO5D,aAAe,EAC9CuE,EAAaX,EAAUU,EAAaF,EAAY,EAEX,GAAjCH,EAASpJ,OAAOlE,OAAOxP,QAAe8c,EAASpJ,OAAOlE,OAAO,IAAM5Q,OAGrEme,GAAW,KAKfN,EAASA,EAAO5D,kBAEX4D,GAAUrX,EAAS2X,EAAUK,EAEpC,IAAIN,GAAYA,EAASpJ,OAAQ,CAE/B,GAAI+J,GAASrB,EAASxd,KAAKqb,KAAKmC,OAC5BsB,EAAY1W,KAAK2W,MAAMF,EAAQ,GAAK,GACpCnB,EAAQ1d,KAAKqb,KAAKqC,MAAQoB,EAC1BE,EAAYd,EAASxE,UAIzB,KADAkE,EAASM,EAASna,IAAIiT,GAAG4H,gBACNlB,EAAZsB,GAAqBpB,GAAQ,CAElC,GADAK,EAAWpa,EAAKmH,kBAAkB4S,GAC9BK,GAAYje,MAAQie,EAASgB,WAAWjf,WAGvC,CAAA,KAAIie,YAAoBxD,IAgB3B,KAfA,IAAI7J,GAASqN,EAASnJ,OAAOlE,MAC7B,MAAIA,EAAOxP,OAAS,GACE,GAAjBwP,EAAOxP,QAAewP,EAAO,IAAM5Q,MAStC,KAJAke,GAAWra,EAAKmH,kBAAkB4S,GAClCoB,EAAYd,EAASxE,WAUzBkE,EAASA,EAAOgB,gBAIdb,EAAO9D,aAAeiE,EAASna,IAAIiT,KACrCkH,EAASpJ,OAAOyF,WAAWva,KAAMke,GACjCQ,GAAQ,IAMZA,IAEF1e,KAAKqb,KAAKmC,OAASA,EACnBxd,KAAKqb,KAAKqC,MAAQ1d,KAAK0Z,YAIzB1Z,KAAKgI,OAAOzB,gBAAgBC,GAE5BoC,EAAMQ,kBAQRvF,EAAKpC,UAAU2b,WAAa,SAAUxU,GACpC,GAAI3D,IACFG,KAAQpF,KACR6V,YAAe7V,KAAKqb,KAAKxF,YACzBE,WAAc/V,KAAKqb,KAAKtF,WACxBC,UAAahW,KAAK8U,OAClBmB,SAAYjW,KAAK8U,OAAOlE,OAAON,QAAQtQ,QAEpCiF,EAAO4Q,aAAe5Q,EAAO+Q,WAC7B/Q,EAAO8Q,YAAc9Q,EAAOgR,WAE/BjW,KAAKgI,OAAO7B,UAAU,WAAYlB,GAGpC6D,SAASwU,KAAK5Q,MAAM6Q,OAASvd,KAAKqb,KAAKgC,UACvCrd,KAAKgI,OAAOhE,YAAYsQ,eACjBtU,MAAKqb,KAERrb,KAAKid,YACPhc,EAAK4S,oBAAoB/K,SAAU,YAAa9I,KAAKid,iBAC9Cjd,MAAKid,WACVjd,KAAKmd,UACPlc,EAAK4S,oBAAoB/K,SAAU,UAAW9I,KAAKmd,eAC5Cnd,MAAKmd,SAIdnd,KAAKgI,OAAOV,iBAEZsB,EAAMQ,kBASRvF,EAAKpC,UAAUwd,WAAa,SAAU7Z,GAEpC,IADA,GAAI8Z,GAAIlf,KAAK8U,OACNoK,GAAG,CACR,GAAIA,GAAK9Z,EACP,OAAO,CAET8Z,GAAIA,EAAEpK,OAGR,OAAO,GAQTjR,EAAKpC,UAAU0d,gBAAkB,WAC/B,MAAOrW,UAASC,cAAc,QAQhClF,EAAKpC,UAAUwS,aAAe,SAAUD,GAClChU,KAAK+D,IAAIiT,KACXhX,KAAK+D,IAAIiT,GAAGhO,UAAagL,EAAY,YAAc,GAE/ChU,KAAKsV,QACPtV,KAAKsV,OAAOrB,aAAaD,GAGvBhU,KAAK4Q,QACP5Q,KAAK4Q,OAAO4I,QAAQ,SAAUzI,GAC5BA,EAAMkD,aAAaD,OAW3BnQ,EAAKpC,UAAUmT,YAAc,SAAUzP,GACrCnF,KAAKmF,MAAQA,EACbnF,KAAKmY,aAOPtU,EAAKpC,UAAUkE,YAAc,SAAUT,GACrClF,KAAKkF,MAAQA,EACblF,KAAKmY,aAaPtU,EAAKpC,UAAU0W,UAAY,SAAUtX,GAEnC,GAAIue,GAAUpf,KAAK+D,IAAI+Y,IACnBsC,KACFA,EAAQ1S,MAAM2S,WAA+B,GAAlBrf,KAAK0Z,WAAkB,KAIpD,IAAI8C,GAAWxc,KAAK+D,IAAImB,KACxB,IAAIsX,EAAU,CACRxc,KAAK0Y,eAEP8D,EAAS8C,gBAAkBtf,KAAK8Y,SAAS5T,MACzCsX,EAAS/O,YAAa,EACtB+O,EAASxT,UAAY,SAIrBwT,EAASxT,UAAY,UAGvB,IAAI9D,EAEFA,GADgBhB,QAAdlE,KAAKwQ,MACCxQ,KAAKwQ,MAEQtM,QAAdlE,KAAKkF,MACJlF,KAAKkF,MAENlF,KAAKma,aACJna,KAAK8K,KAGL,GAEV0R,EAASjE,UAAYvY,KAAKuf,YAAYra,GAIxC,GAAIgX,GAAWlc,KAAK+D,IAAIoB,KACxB,IAAI+W,EAAU,CACZ,GAAIK,GAAQvc,KAAK4Q,OAAS5Q,KAAK4Q,OAAOxP,OAAS,CAE7C8a,GAAS3D,UADM,SAAbvY,KAAK8K,KACc,IAAMyR,EAAQ,IAEf,UAAbvc,KAAK8K,KACS,IAAMyR,EAAQ,IAGdvc,KAAKuf,YAAYvf,KAAKmF,OAK/CnF,KAAK8a,kBACL9a,KAAKib,kBAGDpa,GAAoC,GAAzBA,EAAQyZ,eAErBta,KAAKwf,oBAGH3e,GAA8B,GAAnBA,EAAQyE,SAEjBtF,KAAK4Q,QACP5Q,KAAK4Q,OAAO4I,QAAQ,SAAUzI,GAC5BA,EAAMoH,UAAUtX,KAMlBb,KAAKsV,QACPtV,KAAKsV,OAAO6C,aAUhBtU,EAAKpC,UAAU+d,kBAAoB,WACjC,GAAItD,GAAWlc,KAAK+D,IAAIoB,MACpByL,EAAS5Q,KAAK4Q,MACdsL,IAAYtL,IACG,SAAb5Q,KAAK8K,KACP8F,EAAO4I,QAAQ,SAAUzI,EAAOP,GAC9BO,EAAMP,MAAQA,CACd,IAAI8I,GAAavI,EAAMhN,IAAImB,KACvBoU,KACFA,EAAWf,UAAY/H,KAIP,UAAbxQ,KAAK8K,MACZ8F,EAAO4I,QAAQ,SAAUzI,GACJ7M,QAAf6M,EAAMP,cACDO,GAAMP,MAEMtM,QAAf6M,EAAM7L,QACR6L,EAAM7L,MAAQ,SAY1BrB,EAAKpC,UAAUge,gBAAkB,WAC/B,GAAIvD,EA+BJ,OA7BiB,SAAblc,KAAK8K,MACPoR,EAAWpT,SAASC,cAAc,OAClCmT,EAASlT,UAAY,WACrBkT,EAAS3D,UAAY,SAED,UAAbvY,KAAK8K,MACZoR,EAAWpT,SAASC,cAAc,OAClCmT,EAASlT,UAAY,WACrBkT,EAAS3D,UAAY,UAGhBvY,KAAK8Y,SAAS3T,OAASlE,EAAKqO,MAAMtP,KAAKmF,QAE1C+W,EAAWpT,SAASC,cAAc,KAClCmT,EAASlT,UAAY,QACrBkT,EAAS7O,KAAOrN,KAAKmF,MACrB+W,EAAShT,OAAS,SAClBgT,EAAS3D,UAAYvY,KAAKuf,YAAYvf,KAAKmF,SAI3C+W,EAAWpT,SAASC,cAAc,OAClCmT,EAASoD,gBAAkBtf,KAAK8Y,SAAS3T,MACzC+W,EAASzO,YAAa,EACtByO,EAASlT,UAAY,QACrBkT,EAAS3D,UAAYvY,KAAKuf,YAAYvf,KAAKmF,QAIxC+W,GAQTrY,EAAKpC,UAAUie,uBAAyB,WAEtC,GAAIna,GAASuD,SAASC,cAAc,SAYpC,OAXI/I,MAAKma,cACP5U,EAAOyD,UAAYhJ,KAAKwY,SAAW,WAAa,YAChDjT,EAAO4E,MACH,wGAIJ5E,EAAOyD,UAAY,YACnBzD,EAAO4E,MAAQ,IAGV5E,GAST1B,EAAKpC,UAAUsb,eAAiB,WAC9B,GAAIhZ,GAAM/D,KAAK+D,IACXqb,EAAUtW,SAASC,cAAc,SACjClD,EAAQiD,SAASC,cAAc,QACnCqW,GAAQ1S,MAAMiT,eAAiB,WAC/BP,EAAQpW,UAAY,SACpBoW,EAAQ5Z,YAAYK,EACpB,IAAImR,GAAKlO,SAASC,cAAc,KAChClD,GAAML,YAAYwR,EAGlB,IAAI4I,GAAW9W,SAASC,cAAc,KACtC6W,GAAS5W,UAAY,OACrBgO,EAAGxR,YAAYoa,GACf7b,EAAIwB,OAASvF,KAAK0f,yBAClBE,EAASpa,YAAYzB,EAAIwB,QACzBxB,EAAI6b,SAAWA,CAGf,IAAI/C,GAAU/T,SAASC,cAAc,KACrC8T,GAAQ7T,UAAY,OACpBgO,EAAGxR,YAAYqX,GACf9Y,EAAImB,MAAQlF,KAAKmf,kBACjBtC,EAAQrX,YAAYzB,EAAImB,OACxBnB,EAAI8Y,QAAUA,CAGd,IAAIgD,GAAc/W,SAASC,cAAc,KACzC8W,GAAY7W,UAAY,OACxBgO,EAAGxR,YAAYqa,GACE,UAAb7f,KAAK8K,MAAiC,SAAb9K,KAAK8K,OAChC+U,EAAYra,YAAYsD,SAASsE,eAAe,MAChDyS,EAAY7W,UAAY,aAE1BjF,EAAI8b,YAAcA,CAGlB,IAAIC,GAAUhX,SAASC,cAAc,KAOrC,OANA+W,GAAQ9W,UAAY,OACpBgO,EAAGxR,YAAYsa,GACf/b,EAAIoB,MAAQnF,KAAKyf,kBACjBK,EAAQta,YAAYzB,EAAIoB,OACxBpB,EAAI+b,QAAUA,EAEPV,GAOTvb,EAAKpC,UAAUkH,QAAU,SAAUC,GACjC,GAIIhE,GAJAkG,EAAOlC,EAAMkC,KACb5B,EAASN,EAAMM,QAAUN,EAAMmX,WAC/Bhc,EAAM/D,KAAK+D,IACXqB,EAAOpF,KAEPggB,EAAahgB,KAAKma,YAmBtB,KAfIjR,GAAUnF,EAAIsX,MAAQnS,GAAUnF,EAAImG,QAC1B,aAARY,EACF9K,KAAKgI,OAAOhE,YAAYgQ,UAAUhU,MAEnB,YAAR8K,GACP9K,KAAKgI,OAAOhE,YAAYmQ,eAKhB,aAARrJ,GAAuB5B,GAAUnF,EAAIsX,MACvCrb,KAAKgd,aAAapU,GAIR,SAARkC,GAAmB5B,GAAUnF,EAAImG,KAAM,CACzC,GAAIlG,GAAcoB,EAAK4C,OAAOhE,WAC9BA,GAAYgQ,UAAU5O,GACtBpB,EAAYqQ,OACZpT,EAAKkP,aAAapM,EAAImG,KAAM,YAC5BlK,KAAKigB,gBAAgBlc,EAAImG,KAAM,WAC7BjJ,EAAKsP,gBAAgBxM,EAAImG,KAAM,YAC/BlG,EAAYsQ,SACZtQ,EAAYmQ,gBAKhB,GAAY,SAARrJ,GAAmB5B,GAAUnF,EAAIwB,QAC/Bya,EAAY,CACd,GAAI1a,GAAUsD,EAAMwC,OACpBpL,MAAKkgB,UAAU5a,GAKnB,GAAI4W,GAAWnY,EAAIoB,KACnB,IAAI+D,GAAUgT,EAEZ,OAAQpR,GACN,IAAK,QACHlG,EAAY5E,IACZ,MAEF,KAAK,OACL,IAAK,SACHA,KAAKyZ,cAAa,GAClBzZ,KAAKib,kBACDjb,KAAKmF,QACP+W,EAAS3D,UAAYvY,KAAKuf,YAAYvf,KAAKmF,OAE7C,MAEF,KAAK,QACHnF,KAAKyZ,cAAa,GAClBzZ,KAAKib,iBACL,MAEF,KAAK,UACL,IAAK,YACHjb,KAAKgI,OAAO/D,UAAYjE,KAAKgI,OAAOJ,cACpC,MAEF,KAAK,SACCgB,EAAMwC,UAAYpL,KAAK8Y,SAAS3T,QAC9BlE,EAAKqO,MAAMtP,KAAKmF,QAClBmI,OAAOC,KAAKvN,KAAKmF,MAAO,SAG5B,MAEF,KAAK,QACHnF,KAAKyZ,cAAa,GAClBzZ,KAAKib,iBACL,MAEF,KAAK,MACL,IAAK,QACHvS,WAAW,WACTtD,EAAKqU,cAAa,GAClBrU,EAAK6V,mBACJ,GAMT,GAAIuB,GAAWzY,EAAImB,KACnB,IAAIgE,GAAUsT,EACZ,OAAQ1R,GACN,IAAK,QACHlG,EAAY5E,IACZ,MAEF,KAAK,OACL,IAAK,SACHA,KAAKmZ,cAAa,GAClBnZ,KAAK8a,kBACD9a,KAAKkF,QACPsX,EAASjE,UAAYvY,KAAKuf,YAAYvf,KAAKkF,OAE7C,MAEF,KAAK,QACHlF,KAAKmZ,cAAa,GAClBnZ,KAAK8a,iBACL,MAEF,KAAK,UACL,IAAK,YACH9a,KAAKgI,OAAO/D,UAAYjE,KAAKgI,OAAOJ,cACpC,MAEF,KAAK,QACH5H,KAAKmZ,cAAa,GAClBnZ,KAAK8a,iBACL,MAEF,KAAK,MACL,IAAK,QACHpS,WAAW,WACTtD,EAAK+T,cAAa,GAClB/T,EAAK0V,mBACJ,GAOT,GAAIsE,GAAUrb,EAAI+Y,IAClB,IAAI5T,GAAUkW,EAAQ5a,WACpB,OAAQsG,GACN,IAAK,QACH,GAAIiF,GAAyB7L,QAAjB0E,EAAMuX,QACbvX,EAAMuX,QAAkC,IAAvBngB,KAAK0Z,WAAa,GACnC9Q,EAAM6U,MAAQxc,EAAK0O,gBAAgB5L,EAAI8b,YACxC9P,IAAQiQ,EAENxD,IACFvb,EAAKoQ,wBAAwBmL,GAC7BA,EAAS7U,SAIPuU,IACFjb,EAAKoQ,wBAAwB6K,GAC7BA,EAASvU,SAMnB,GAAKuB,GAAUnF,EAAI6b,WAAaI,GAAe9W,GAAUnF,EAAI8Y,SACzD3T,GAAUnF,EAAI8b,YAChB,OAAQ/U,GACN,IAAK,QACC0R,IACFvb,EAAKoQ,wBAAwBmL,GAC7BA,EAAS7U,SAML,WAARmD,GACF9K,KAAKogB,UAAUxX,IAQnB/E,EAAKpC,UAAU2e,UAAY,SAAUxX,GACnC,GAMImP,GAAUsI,EAAUC,EAASC,EAN7BtV,EAASrC,EAAMsC,OAAStC,EAAMuC,QAC9BjC,EAASN,EAAMM,QAAUN,EAAMmX,WAC/B3U,EAAUxC,EAAMwC,QAChBC,EAAWzC,EAAMyC,SACjBmV,EAAS5X,EAAM4X,OACflV,GAAU,EAEVwN,EAAwC,SAA7B9Y,KAAKgI,OAAOnH,QAAQa,IAGnC,IAAc,IAAVuJ,GACF,GAAI/B,GAAUlJ,KAAK+D,IAAIoB,QAChBnF,KAAK8Y,SAAS3T,OAASyD,EAAMwC,UAC5BnK,EAAKqO,MAAMtP,KAAKmF,SAClBmI,OAAOC,KAAKvN,KAAKmF,MAAO,UACxBmG,GAAU,OAIX,IAAIpC,GAAUlJ,KAAK+D,IAAIwB,OAAQ,CAClC,GAAIya,GAAahgB,KAAKma,YACtB,IAAI6F,EAAY,CACd,GAAI1a,GAAUsD,EAAMwC,OACpBpL,MAAKkgB,UAAU5a,GACf4D,EAAOvB,QACP2D,GAAU,QAIX,IAAc,IAAVL,EACHG,GAAW0N,IACb9Y,KAAKygB,eACLnV,GAAU,OAGT,IAAc,IAAVL,EACHG,IACFpL,KAAKkgB,UAAU7U,GACfnC,EAAOvB,QACP2D,GAAU,OAGT,IAAc,IAAVL,GAAgB6N,EACnB1N,IACFpL,KAAKigB,gBAAgB/W,GACrBoC,GAAU,OAGT,IAAc,IAAVL,GAAgB6N,EACnB1N,IACFpL,KAAK0gB,YACLpV,GAAU,OAGT,IAAc,IAAVL,GAAgB6N,EACnB1N,IAAYC,GACdrL,KAAK2gB,kBACLrV,GAAU,GAEHF,GAAWC,IAClBrL,KAAK4gB,iBACLtV,GAAU,OAGT,IAAc,IAAVL,GACP,GAAIuV,EAAQ,CAEV,GAAIK,GAAW7gB,KAAK8gB,WAChBD,IACFA,EAASlZ,MAAM9D,EAAKsX,cAAgBnb,KAAK+gB,gBAAgB7X,IAE3DoC,GAAU,OAGT,IAAc,IAAVL,GACP,GAAIuV,EAAQ,CAEV,GAAIQ,GAAYhhB,KAAKihB,YACjBD,IACFA,EAAUrZ,MAAM9D,EAAKsX,cAAgBnb,KAAK+gB,gBAAgB7X,IAE5DoC,GAAU,OAGT,IAAc,IAAVL,GACP,GAAIuV,IAAWnV,EAAU,CAEvB,GAAI6V,GAAclhB,KAAKmhB,iBAAiBjY,EACpCgY,IACFlhB,KAAK2H,MAAM3H,KAAK+gB,gBAAgBG,IAElC5V,GAAU,MAEP,IAAIkV,GAAUnV,GAAYyN,EAAU,CACvC,GAAI9Y,KAAKwY,SAAU,CACjB,GAAI4I,GAAYphB,KAAK+Z,WACrBuG,GAAUc,EAAYA,EAAUnH,YAAc/V,WAE3C,CACH,GAAIH,GAAM/D,KAAK8F,QACfwa,GAAUvc,EAAIkW,YAEZqG,IACFD,EAAWxc,EAAKmH,kBAAkBsV,GAClCC,EAAWD,EAAQrG,YACnBoH,EAAYxd,EAAKmH,kBAAkBuV,GAC/BF,GAAYA,YAAoB5F,IACD,GAA7Bza,KAAK8U,OAAOlE,OAAOxP,QACrBigB,GAAaA,EAAUvM,SACzBuM,EAAUvM,OAAOyF,WAAWva,KAAMqhB,GAClCrhB,KAAK2H,MAAM9D,EAAKsX,cAAgBnb,KAAK+gB,gBAAgB7X,WAKxD,IAAc,IAAV+B,EACHuV,IAAWnV,GAEb0M,EAAW/X,KAAKshB,gBACZvJ,GACFA,EAASpQ,MAAM9D,EAAKsX,cAAgBnb,KAAK+gB,gBAAgB7X,IAE3DoC,GAAU,GAEHkV,GAAUnV,IAEjB0M,EAAW/X,KAAKshB,gBACZvJ,GAAYA,EAASjD,SACvBiD,EAASjD,OAAOyF,WAAWva,KAAM+X,GACjC/X,KAAK2H,MAAM9D,EAAKsX,cAAgBnb,KAAK+gB,gBAAgB7X,KAEvDoC,GAAU,OAGT,IAAc,IAAVL,GACP,GAAIuV,IAAWnV,EAAU,CAEvB,GAAIkW,GAAcvhB,KAAKwhB,aAAatY,EAChCqY,IACFvhB,KAAK2H,MAAM3H,KAAK+gB,gBAAgBQ,IAElCjW,GAAU,MAEP,IAAIkV,GAAUnV,EAAU,CAC3BtH,EAAM/D,KAAK8F,QACX,IAAI2b,GAAU1d,EAAI6a,eACd6C,KACF1J,EAAWlU,EAAKmH,kBAAkByW,GAC9B1J,GAAYA,EAASjD,QACpBiD,YAAoB0C,KACjB1C,EAAS2J,cACf3J,EAASjD,OAAOyF,WAAWva,KAAM+X,GACjC/X,KAAK2H,MAAM9D,EAAKsX,cAAgBnb,KAAK+gB,gBAAgB7X,WAKxD,IAAc,IAAV+B,EACP,GAAIuV,IAAWnV,EAEbgV,EAAWrgB,KAAK2hB,YACZtB,GACFA,EAAS1Y,MAAM9D,EAAKsX,cAAgBnb,KAAK+gB,gBAAgB7X,IAE3DoC,GAAU,MAEP,IAAIkV,GAAUnV,GAAYyN,EAAU,CAGrCuH,EADErgB,KAAKwY,SACIxY,KAAKsV,OAAStV,KAAKsV,OAAOqM,YAAczd,OAGxClE,KAAK2hB,YAElBrB,EAAUD,EAAWA,EAASva,SAAW5B,OAEvCqc,EAD+B,GAA7BvgB,KAAK8U,OAAOlE,OAAOxP,OACVkf,EAGAA,EAAUA,EAAQrG,YAAc/V,MAE7C,IAAImd,GAAYxd,EAAKmH,kBAAkBuV,EACnCc,IAAaA,EAAUvM,SACzBuM,EAAUvM,OAAOyF,WAAWva,KAAMqhB,GAClCrhB,KAAK2H,MAAM9D,EAAKsX,cAAgBnb,KAAK+gB,gBAAgB7X,KAEvDoC,GAAU,EAIVA,IACF1C,EAAMQ,iBACNR,EAAM+C,oBASV9H,EAAKpC,UAAUye,UAAY,SAAU5a,GACnC,GAAIA,EAAS,CAEX,GAAIN,GAAQhF,KAAK+D,IAAIiT,GAAGxS,WACpBD,EAAQS,EAAMR,WACd0C,EAAY3C,EAAM2C,SACtB3C,GAAME,YAAYO,GAGhBhF,KAAKwY,SACPxY,KAAK4F,SAASN,GAGdtF,KAAKuF,OAAOD,GAGVA,IAEFf,EAAMiB,YAAYR,GAClBT,EAAM2C,UAAYA,IAQtBrD,EAAKpC,UAAUif,UAAY,WACzB1gB,KAAKgI,OAAOhE,YAAYmQ,aACxB,IAAIvD,GAAS5Q,KAAK8U,OAAOlE,OACrBJ,EAAQI,EAAON,QAAQtQ,MAGvB2W,EAAe3W,KAAKgI,OAAOJ,cAC3BgJ,GAAOJ,EAAQ,GACjBI,EAAOJ,EAAQ,GAAG7I,QAEXiJ,EAAOJ,EAAQ,GACtBI,EAAOJ,EAAQ,GAAG7I,QAGlB3H,KAAK8U,OAAOnN,OAEd,IAAIiP,GAAe5W,KAAKgI,OAAOJ,cAG/B5H,MAAK8U,OAAO8G,QAAQ5b,MAGpBA,KAAKgI,OAAO7B,UAAU,cACpBf,KAAMpF,KACN8U,OAAQ9U,KAAK8U,OACbtE,MAAOA,EACPmG,aAAcA,EACdC,aAAcA,KAQlB/S,EAAKpC,UAAUgf,aAAe,WAC5B,GAAI9J,GAAe3W,KAAKgI,OAAOJ,eAC3B4N,EAAQxV,KAAK8U,OAAOyG,WAAWvb,KACnCwV,GAAM7N,OACN,IAAIiP,GAAe5W,KAAKgI,OAAOJ,cAE/B5H,MAAKgI,OAAO7B,UAAU,iBACpBf,KAAMpF,KACNwV,MAAOA,EACPV,OAAQ9U,KAAK8U,OACb6B,aAAcA,EACdC,aAAcA,KAWlB/S,EAAKpC,UAAUkf,gBAAkB,SAAUzb,EAAOC,EAAO2F,GACvD,GAAI6L,GAAe3W,KAAKgI,OAAOJ,eAE3Bga,EAAU,GAAI/d,GAAK7D,KAAKgI,QAC1B9C,MAAiBhB,QAATgB,EAAsBA,EAAQ,GACtCC,MAAiBjB,QAATiB,EAAsBA,EAAQ,GACtC2F,KAAMA,GAER8W,GAAQrc,QAAO,GACfvF,KAAK8U,OAAOE,aAAa4M,EAAS5hB,MAClCA,KAAKgI,OAAOhE,YAAYmQ,cACxByN,EAAQja,MAAM,QACd,IAAIiP,GAAe5W,KAAKgI,OAAOJ,cAE/B5H,MAAKgI,OAAO7B,UAAU,oBACpBf,KAAMwc,EACN3M,WAAYjV,KACZ8U,OAAQ9U,KAAK8U,OACb6B,aAAcA,EACdC,aAAcA,KAWlB/S,EAAKpC,UAAUmf,eAAiB,SAAU1b,EAAOC,EAAO2F,GACtD,GAAI6L,GAAe3W,KAAKgI,OAAOJ,eAE3Bga,EAAU,GAAI/d,GAAK7D,KAAKgI,QAC1B9C,MAAiBhB,QAATgB,EAAsBA,EAAQ,GACtCC,MAAiBjB,QAATiB,EAAsBA,EAAQ,GACtC2F,KAAMA,GAER8W,GAAQrc,QAAO,GACfvF,KAAK8U,OAAOK,YAAYyM,EAAS5hB,MACjCA,KAAKgI,OAAOhE,YAAYmQ,cACxByN,EAAQja,MAAM,QACd,IAAIiP,GAAe5W,KAAKgI,OAAOJ,cAE/B5H,MAAKgI,OAAO7B,UAAU,mBACpBf,KAAMwc,EACNxM,UAAWpV,KACX8U,OAAQ9U,KAAK8U,OACb6B,aAAcA,EACdC,aAAcA,KAWlB/S,EAAKpC,UAAUogB,UAAY,SAAU3c,EAAOC,EAAO2F,GACjD,GAAI6L,GAAe3W,KAAKgI,OAAOJ,eAE3Bga,EAAU,GAAI/d,GAAK7D,KAAKgI,QAC1B9C,MAAiBhB,QAATgB,EAAsBA,EAAQ,GACtCC,MAAiBjB,QAATiB,EAAsBA,EAAQ,GACtC2F,KAAMA,GAER8W,GAAQrc,QAAO,GACfvF,KAAK8U,OAAOtP,YAAYoc,GACxB5hB,KAAKgI,OAAOhE,YAAYmQ,cACxByN,EAAQja,MAAM,QACd,IAAIiP,GAAe5W,KAAKgI,OAAOJ,cAE/B5H,MAAKgI,OAAO7B,UAAU,cACpBf,KAAMwc,EACN9M,OAAQ9U,KAAK8U,OACb6B,aAAcA,EACdC,aAAcA,KASlB/S,EAAKpC,UAAUqgB,cAAgB,SAAUnM,GACvC,GAAID,GAAU1V,KAAK8K,IACnB,IAAI6K,GAAWD,EAAS,CACtB,GAAIiB,GAAe3W,KAAKgI,OAAOJ,cAC/B5H,MAAKyV,WAAWE,EAChB,IAAIiB,GAAe5W,KAAKgI,OAAOJ,cAE/B5H,MAAKgI,OAAO7B,UAAU,cACpBf,KAAMpF,KACN0V,QAASA,EACTC,QAASA,EACTgB,aAAcA,EACdC,aAAcA,MAWpB/S,EAAKpC,UAAUsgB,QAAU,SAAUC,GACjC,GAAIhiB,KAAKma,aAAc,CACrB,GAAI8H,GAAsB,QAAbD,EAAuB,GAAK,EACrCze,EAAqB,SAAbvD,KAAK8K,KAAmB,QAAS,OAC7C9K,MAAKmW,YAEL,IAAIE,GAAYrW,KAAK4Q,OACjBwF,EAAUpW,KAAKkW,IAGnBlW,MAAK4Q,OAAS5Q,KAAK4Q,OAAOoK,SAG1Bhb,KAAK4Q,OAAOsF,KAAK,SAAUrH,EAAGC,GAC5B,MAAID,GAAEtL,GAAQuL,EAAEvL,GAAc0e,EAC1BpT,EAAEtL,GAAQuL,EAAEvL,IAAe0e,EACxB,IAETjiB,KAAKkW,KAAiB,GAAT+L,EAAc,MAAQ,OAEnCjiB,KAAKgI,OAAO7B,UAAU,QACpBf,KAAMpF,KACNqW,UAAWA,EACXD,QAASA,EACTI,UAAWxW,KAAK4Q,OAChB2F,QAASvW,KAAKkW,OAGhBlW,KAAKsW,eAQTzS,EAAKpC,UAAUsY,UAAY,WAKzB,MAJK/Z,MAAKsV,SACRtV,KAAKsV,OAAS,GAAImF,GAAWza,KAAKgI,QAClChI,KAAKsV,OAAO2D,UAAUjZ,OAEjBA,KAAKsV,OAAOxP,UASrBjC,EAAKmH,kBAAoB,SAAU9B,GACjC,KAAOA,GAAQ,CACb,GAAIA,EAAO9D,KACT,MAAO8D,GAAO9D,IAEhB8D,GAASA,EAAO1E,WAGlB,MAAON,SAQTL,EAAKpC,UAAU6f,cAAgB,WAC7B,GAAIvJ,GAAW,KACXhU,EAAM/D,KAAK8F,QACf,IAAI/B,GAAOA,EAAIS,WAAY,CAEzB,GAAIid,GAAU1d,CACd,GACE0d,GAAUA,EAAQ7C,gBAClB7G,EAAWlU,EAAKmH,kBAAkByW,SAE7BA,GAAY1J,YAAoB0C,KAAe1C,EAAS2J,aAEjE,MAAO3J,IAQTlU,EAAKpC,UAAUkgB,UAAY,WACzB,GAAItB,GAAW,KACXtc,EAAM/D,KAAK8F,QACf,IAAI/B,GAAOA,EAAIS,WAAY,CAEzB,GAAI8b,GAAUvc,CACd,GACEuc,GAAUA,EAAQrG,YAClBoG,EAAWxc,EAAKmH,kBAAkBsV,SAE7BA,GAAYD,YAAoB5F,KAAe4F,EAASqB,aAGjE,MAAOrB,IAQTxc,EAAKpC,UAAUwf,WAAa,WAC1B,GAAID,GAAY,KACZjd,EAAM/D,KAAK8F,QACf,IAAI/B,GAAOA,EAAIS,WAAY,CACzB,GAAI0d,GAAWne,EAAIS,WAAW2N,UAC9B6O,GAAYnd,EAAKmH,kBAAkBkX,GAGrC,MAAOlB,IAQTnd,EAAKpC,UAAUqf,UAAY,WACzB,GAAID,GAAW,KACX9c,EAAM/D,KAAK8F,QACf,IAAI/B,GAAOA,EAAIS,WAAY,CACzB,GAAI2d,GAAUpe,EAAIS,WAAW4d,SAE7B,KADAvB,EAAYhd,EAAKmH,kBAAkBmX,GAC5BA,GAAYtB,YAAoBpG,KAAeoG,EAASa,aAC7DS,EAAUA,EAAQvD,gBAClBiC,EAAYhd,EAAKmH,kBAAkBmX,GAGvC,MAAOtB,IASThd,EAAKpC,UAAU0f,iBAAmB,SAAUvR,GAC1C,GAAI7L,GAAM/D,KAAK+D,GAEf,QAAQ6L,GACN,IAAK7L,GAAIoB,MACP,GAAInF,KAAK0Y,cACP,MAAO3U,GAAImB,KAGf,KAAKnB,GAAImB,MACP,GAAIlF,KAAKma,aACP,MAAOpW,GAAIwB,MAGf,KAAKxB,GAAIwB,OACP,MAAOxB,GAAImG,IACb,KAAKnG,GAAImG,KACP,GAAInG,EAAIsX,KACN,MAAOtX,GAAIsX,IAGf,SACE,MAAO,QAUbxX,EAAKpC,UAAU+f,aAAe,SAAU5R,GACtC,GAAI7L,GAAM/D,KAAK+D,GAEf,QAAQ6L,GACN,IAAK7L,GAAIsX,KACP,MAAOtX,GAAImG,IACb,KAAKnG,GAAImG,KACP,GAAIlK,KAAKma,aACP,MAAOpW,GAAIwB,MAGf,KAAKxB,GAAIwB,OACP,GAAIvF,KAAK0Y,cACP,MAAO3U,GAAImB,KAGf,KAAKnB,GAAImB,MACP,IAAKlF,KAAKma,aACR,MAAOpW,GAAIoB,KAEf,SACE,MAAO,QAYbtB,EAAKpC,UAAUsf,gBAAkB,SAAUzO,GACzC,GAAIvO,GAAM/D,KAAK+D,GACf,KAAK,GAAIzB,KAAQyB,GACf,GAAIA,EAAIY,eAAerC,IACjByB,EAAIzB,IAASgQ,EACf,MAAOhQ,EAIb,OAAO,OASTuB,EAAKpC,UAAU0Y,WAAa,WAC1B,MAAoB,SAAbna,KAAK8K,MAAgC,UAAb9K,KAAK8K,MAItCjH,EAAKwe,aACHC,KAAQ,8HAGRrT,OAAU,+EAEVsT,MAAS,yEAETC,OAAU,oGAWZ3e,EAAKpC,UAAUwe,gBAAkB,SAAUwC,EAAQC,GACjD,GAAItd,GAAOpF,KACP2iB,EAAS9e,EAAKwe,YACdO,IAgDJ,IA9CI5iB,KAAK8Y,SAAS3T,OAChByd,EAAMvU,MACJtI,KAAM,OACNoE,MAAO,gCACPnB,UAAW,QAAUhJ,KAAK8K,KAC1B+X,UAEI9c,KAAM,OACNiD,UAAW,aACO,QAAbhJ,KAAK8K,KAAiB,YAAc,IACzCX,MAAOwY,EAAOL,KACdQ,MAAO,WACL1d,EAAK0c,cAAc,WAIrB/b,KAAM,QACNiD,UAAW,cACO,SAAbhJ,KAAK8K,KAAkB,YAAc,IAC1CX,MAAOwY,EAAOJ,MACdO,MAAO,WACL1d,EAAK0c,cAAc,YAIrB/b,KAAM,SACNiD,UAAW,eACO,UAAbhJ,KAAK8K,KAAmB,YAAc,IAC3CX,MAAOwY,EAAO1T,OACd6T,MAAO,WACL1d,EAAK0c,cAAc,aAIrB/b,KAAM,SACNiD,UAAW,eACO,UAAbhJ,KAAK8K,KAAmB,YAAc,IAC3CX,MAAOwY,EAAOH,OACdM,MAAO,WACL1d,EAAK0c,cAAc,eAOzB9hB,KAAKma,aAAc,CACrB,GAAI6H,GAA2B,OAAbhiB,KAAKkW,KAAiB,OAAQ,KAChD0M,GAAMvU,MACJtI,KAAM,OACNoE,MAAO,2BAA6BnK,KAAK8K,KACzC9B,UAAW,QAAUgZ,EACrBc,MAAO,WACL1d,EAAK2c,QAAQC,IAEfa,UAEI9c,KAAM,YACNiD,UAAW,WACXmB,MAAO,2BAA6BnK,KAAK8K,KAAO,sBAChDgY,MAAO,WACL1d,EAAK2c,QAAQ,UAIfhc,KAAM,aACNiD,UAAW,YACXmB,MAAO,2BAA6BnK,KAAK8K,KAAM,uBAC/CgY,MAAO,WACL1d,EAAK2c,QAAQ,aAOvB,GAAI/hB,KAAK8U,QAAU9U,KAAK8U,OAAOqF,aAAc,CACvCyI,EAAMxhB,QAERwhB,EAAMvU,MACJvD,KAAQ,aAKZ,IAAI8F,GAASxL,EAAK0P,OAAOlE,MACrBxL,IAAQwL,EAAOA,EAAOxP,OAAS,IACjCwhB,EAAMvU,MACJtI,KAAM,SACNoE,MAAO,wEACP4Y,aAAc,8CACd/Z,UAAW,SACX8Z,MAAO,WACL1d,EAAKyc,UAAU,GAAI,GAAI,SAEzBgB,UAEI9c,KAAM,OACNiD,UAAW,YACXmB,MAAOwY,EAAOL,KACdQ,MAAO,WACL1d,EAAKyc,UAAU,GAAI,GAAI,WAIzB9b,KAAM,QACNiD,UAAW,aACXmB,MAAOwY,EAAOJ,MACdO,MAAO,WACL1d,EAAKyc,UAAU,UAIjB9b,KAAM,SACNiD,UAAW,cACXmB,MAAOwY,EAAO1T,OACd6T,MAAO,WACL1d,EAAKyc,UAAU,UAIjB9b,KAAM,SACNiD,UAAW,cACXmB,MAAOwY,EAAOH,OACdM,MAAO,WACL1d,EAAKyc,UAAU,GAAI,GAAI,eAQjCe,EAAMvU,MACJtI,KAAM,SACNoE,MAAO,mEACP4Y,aAAc,8CACd/Z,UAAW,SACX8Z,MAAO,WACL1d,EAAKub,gBAAgB,GAAI,GAAI,SAE/BkC,UAEI9c,KAAM,OACNiD,UAAW,YACXmB,MAAOwY,EAAOL,KACdQ,MAAO,WACL1d,EAAKub,gBAAgB,GAAI,GAAI,WAI/B5a,KAAM,QACNiD,UAAW,aACXmB,MAAOwY,EAAOJ,MACdO,MAAO,WACL1d,EAAKub,gBAAgB,UAIvB5a,KAAM,SACNiD,UAAW,cACXmB,MAAOwY,EAAO1T,OACd6T,MAAO,WACL1d,EAAKub,gBAAgB,UAIvB5a,KAAM,SACNiD,UAAW,cACXmB,MAAOwY,EAAOH,OACdM,MAAO,WACL1d,EAAKub,gBAAgB,GAAI,GAAI,eAMjC3gB,KAAK8Y,SAAS5T,QAEhB0d,EAAMvU,MACJtI,KAAM,YACNoE,MAAO,gCACPnB,UAAW,YACX8Z,MAAO,WACL1d,EAAKqb,kBAKTmC,EAAMvU,MACJtI,KAAM,SACNoE,MAAO,+BACPnB,UAAW,SACX8Z,MAAO,WACL1d,EAAKsb,gBAMb,GAAIxW,GAAO,GAAIyO,GAAYiK,GAAQI,MAAON,GAC1CxY,GAAK+Y,KAAKR,IASZ5e,EAAKpC,UAAU4X,SAAW,SAASlU,GACjC,MAAIA,aAAiB+d,OACZ,QAEL/d,YAAiBsK,QACZ,SAEY,gBAAX,IAA0D,gBAA5BzP,MAAK8b,YAAY3W,GAChD,SAGF,QAUTtB,EAAKpC,UAAUqa,YAAc,SAASG,GACpC,GAAIkH,GAAQlH,EAAItB,cACZyI,EAAMnX,OAAOgQ,GACboH,EAAW9P,WAAW0I,EAE1B,OAAW,IAAPA,EACK,GAES,QAATkH,EACA,KAES,QAATA,GACA,EAES,SAATA,GACA,EAECG,MAAMF,IAASE,MAAMD,GAItBpH,EAHAmH,GAaXvf,EAAKpC,UAAU8d,YAAc,SAAUxZ,GACrC,GAAIwd,GAAcrU,OAAOnJ,GACpBwI,QAAQ,KAAM,QACdA,QAAQ,KAAM,QACdA,QAAQ,MAAO,WACfA,QAAQ,KAAM,UACdA,QAAQ,KAAM,UAEfzN,EAAOqB,KAAKC,UAAUmhB,EAC1B,OAAOziB,GAAK0iB,UAAU,EAAG1iB,EAAKM,OAAS,IASzCyC,EAAKpC,UAAUua,cAAgB,SAAUyH,GACvC,GAAI3iB,GAAO,IAAMd,KAAK0jB,YAAYD,GAAe,IAC7CF,EAActiB,EAAKgB,MAAMnB,EAC7B,OAAOyiB,GACFhV,QAAQ,QAAS,KACjBA,QAAQ,QAAS,KACjBA,QAAQ,iBAAkB,MAYjC1K,EAAKpC,UAAUiiB,YAAc,SAAU3d,GAIrC,IAFA,GAAI4d,GAAU,GACVrgB,EAAI,EAAGwN,EAAO/K,EAAK3E,OACZ0P,EAAJxN,GAAU,CACf,GAAI7C,GAAIsF,EAAKoI,OAAO7K,EACX,OAAL7C,EACFkjB,GAAW,MAEC,MAALljB,GACPkjB,GAAWljB,EACX6C,IAEA7C,EAAIsF,EAAKoI,OAAO7K,GACe,IAA3B,aAAagN,QAAQ7P,KACvBkjB,GAAW,MAEbA,GAAWljB,GAGXkjB,GADY,KAALljB,EACI,MAGAA,EAEb6C,IAGF,MAAOqgB,GAIT,IAAIlJ,GAAa7B,EAAkB/U,EAEnChE,GAAOD,QAAUiE,GAKZ,SAAShE,EAAQD,EAASM,GAW/B,QAAS0jB,GAAmB5b,EAAQxG,EAAOqiB,GAOzC,QAASC,GAAWpiB,GAElBsG,EAAOrG,QAAQD,EAGf,IAAIkJ,GAAU5C,EAAOjE,KAAOiE,EAAOjE,IAAI6G,OACnCA,IACFA,EAAQjD,QA6CZ,IAAK,GAxCDoc,IACFC,MACEje,KAAQ,OACRoE,MAAS,6BACT2Y,MAAS,WACPgB,EAAW,UAGfG,MACEle,KAAQ,OACRoE,MAAS,wBACT2Y,MAAS,WACPgB,EAAW,UAGf/d,MACEA,KAAQ,OACRoE,MAAS,8BACT2Y,MAAS,WACPgB,EAAW,UAGfhH,MACE/W,KAAQ,OACRoE,MAAS,wBACT2Y,MAAS,WACPgB,EAAW,UAGfI,MACEne,KAAQ,OACRoE,MAAS,sBACT2Y,MAAS,WACPgB,EAAW,WAMblB,KACKtf,EAAI,EAAGA,EAAI9B,EAAMJ,OAAQkC,IAAK,CACrC,GAAI5B,GAAOF,EAAM8B,GACb6gB,EAAOJ,EAAeriB,EAC1B,KAAKyiB,EACH,KAAM,IAAIpjB,OAAM,iBAAmBW,EAAO,IAG5CyiB,GAAKnb,UAAY,cAAiB6a,GAAWniB,EAAQ,YAAc,IACnEkhB,EAAMvU,KAAK8V,GAIb,GAAIC,GAAcL,EAAeF,EACjC,KAAKO,EACH,KAAM,IAAIrjB,OAAM,iBAAmB8iB,EAAU,IAE/C,IAAIQ,GAAeD,EAAYre,KAG3Bue,EAAMxb,SAASC,cAAc,SASjC,OARAub,GAAItb,UAAY,kBAChBsb,EAAI/L,UAAY8L,EAAe,YAC/BC,EAAIna,MAAQ,qBACZma,EAAIrb,QAAU,WACZ,GAAIiB,GAAO,GAAIyO,GAAYiK,EAC3B1Y,GAAK+Y,KAAKqB,IAGLA,EAhGT,GAAI3L,GAAczY,EAAoB,EAmGtCN,GAAQkD,OAAS8gB,GAKZ,SAAS/jB,EAAQD,EAASM,GAa/B,QAASyY,GAAaiK,EAAO/hB,GAiC3B,QAAS0jB,GAAiBC,EAAMC,EAAU7B,GACxCA,EAAMpJ,QAAQ,SAAU2K,GACtB,GAAiB,aAAbA,EAAKrZ,KAAqB,CAE5B,GAAI4Z,GAAY5b,SAASC,cAAc,MACvC2b,GAAU1b,UAAY,YACtB2b,EAAK7b,SAASC,cAAc,MAC5B4b,EAAGnf,YAAYkf,GACfF,EAAKhf,YAAYmf,OAEd,CACH,GAAIC,MAGAD,EAAK7b,SAASC,cAAc,KAChCyb,GAAKhf,YAAYmf,EAGjB,IAAIE,GAAS/b,SAASC,cAAc,SAepC,IAdA8b,EAAO7b,UAAYmb,EAAKnb,UACxB4b,EAAQC,OAASA,EACbV,EAAKha,QACP0a,EAAO1a,MAAQga,EAAKha,OAElBga,EAAKrB,QACP+B,EAAO5b,QAAU,WACfxC,EAAGyT,OACHiK,EAAKrB,UAGT6B,EAAGnf,YAAYqf,GAGXV,EAAKtB,QAAS,CAEhB,GAAIiC,GAAUhc,SAASC,cAAc,MACrC+b,GAAQ9b,UAAY,OACpB6b,EAAOrf,YAAYsf,GACnBD,EAAOrf,YAAYsD,SAASsE,eAAe+W,EAAKpe,MAEhD,IAAIgf,EACJ,IAAIZ,EAAKrB,MAAO,CAEd+B,EAAO7b,WAAa,UAEpB,IAAIgc,GAAelc,SAASC,cAAc,SAC1C6b,GAAQI,aAAeA,EACvBA,EAAahc,UAAY,SACzBgc,EAAazM,UAAY,6BACzBoM,EAAGnf,YAAYwf,GACXb,EAAKpB,eACPiC,EAAa7a,MAAQga,EAAKpB,cAG5BgC,EAAgBC,MAEb,CAEH,GAAIC,GAAYnc,SAASC,cAAc,MACvCkc,GAAUjc,UAAY,SACtB6b,EAAOrf,YAAYyf,GAEnBF,EAAgBF,EAIlBE,EAAc9b,QAAU,WACtBxC,EAAGye,cAAcN,GACjBG,EAAcpd,QAIhB,IAAIwd,KACJP,GAAQQ,SAAWD,CACnB,IAAIE,GAAKvc,SAASC,cAAc,KAChC6b,GAAQS,GAAKA,EACbA,EAAGrc,UAAY,OACfqc,EAAG3Y,MAAM9F,OAAS,IAClB+d,EAAGnf,YAAY6f,GACfd,EAAgBc,EAAIF,EAAahB,EAAKtB,aAItCgC,GAAOtM,UAAY,2BAA6B4L,EAAKpe,IAGvD0e,GAASpW,KAAKuW,MAtHpB5kB,KAAK+D,MAEL,IAAI0C,GAAKzG,KACL+D,EAAM/D,KAAK+D,GACf/D,MAAKyiB,OAASve,OACdlE,KAAK4iB,MAAQA,EACb5iB,KAAKslB,kBACLtlB,KAAKiE,UAAYC,OACjBlE,KAAKulB,eAAiBrhB,OACtBlE,KAAK0iB,QAAU7hB,EAAUA,EAAQmiB,MAAQ9e,MAGzC,IAAIgG,GAAOpB,SAASC,cAAc,MAClCmB,GAAKlB,UAAY,yBACjBjF,EAAImG,KAAOA,CAGX,IAAIsa,GAAO1b,SAASC,cAAc,KAClCyb,GAAKxb,UAAY,OACjBkB,EAAK1E,YAAYgf,GACjBzgB,EAAIygB,KAAOA,EACXzgB,EAAI6e,QAGJ,IAAI4C,GAAc1c,SAASC,cAAc,SACzChF,GAAIyhB,YAAcA,CAClB,IAAIb,GAAK7b,SAASC,cAAc,KAChC4b,GAAGjY,MAAM+Y,SAAW,SACpBd,EAAGjY,MAAM9F,OAAS,IAClB+d,EAAGnf,YAAYggB,GACfhB,EAAKhf,YAAYmf,GA4FjBJ,EAAgBC,EAAMxkB,KAAK+D,IAAI6e,MAAOA,GAKtC5iB,KAAK0lB,UAAY,EACjB9C,EAAMpJ,QAAQ,SAAU2K,GACtB,GAAIvd,GAAqE,IAA3Dgc,EAAMxhB,QAAU+iB,EAAKtB,QAAUsB,EAAKtB,QAAQzhB,OAAS,GACnEqF,GAAGif,UAAYtd,KAAKE,IAAI7B,EAAGif,UAAW9e,KA9I1C,GAAI3F,GAAOf,EAAoB,EAuJ/ByY,GAAYlX,UAAUkkB,mBAAqB,WACzC,GAAIC,MACAnf,EAAKzG,IAiBT,OAhBAA,MAAK+D,IAAI6e,MAAMpJ,QAAQ,SAAU2K,GAC/ByB,EAAQvX,KAAK8V,EAAKU,QACdV,EAAKa,cACPY,EAAQvX,KAAK8V,EAAKa,cAEhBb,EAAKiB,UAAYjB,GAAQ1d,EAAGof,cAC9B1B,EAAKiB,SAAS5L,QAAQ,SAAUsM,GAC9BF,EAAQvX,KAAKyX,EAAQjB,QACjBiB,EAAQd,cACVY,EAAQvX,KAAKyX,EAAQd,kBAOtBY,GAITjN,EAAYoN,YAAc7hB,OAM1ByU,EAAYlX,UAAUwhB,KAAO,SAAUR,GACrCziB,KAAKka,MAGL,IAAI8L,GAAe1Y,OAAO2Y,YACtBC,EAAgB5Y,OAAO4C,aAAepH,SAAS5B,WAAa,EAC5Dif,EAAeH,EAAeE,EAC9BE,EAAe3D,EAAO9D,aACtB0H,EAAarmB,KAAK0lB,UAGlB3V,EAAO9O,EAAK0O,gBAAgB8S,GAC5B/b,EAAMzF,EAAK0F,eAAe8b,EACQ0D,GAAlCzf,EAAM0f,EAAeC,GAEvBrmB,KAAK+D,IAAImG,KAAKwC,MAAMqD,KAAOA,EAAO,KAClC/P,KAAK+D,IAAImG,KAAKwC,MAAMhG,IAAOA,EAAM0f,EAAgB,KACjDpmB,KAAK+D,IAAImG,KAAKwC,MAAM5F,OAAS,KAI7B9G,KAAK+D,IAAImG,KAAKwC,MAAMqD,KAAOA,EAAO,KAClC/P,KAAK+D,IAAImG,KAAKwC,MAAMhG,IAAM,GAC1B1G,KAAK+D,IAAImG,KAAKwC,MAAM5F,OAAUkf,EAAetf,EAAO,MAItDoC,SAASwU,KAAK9X,YAAYxF,KAAK+D,IAAImG,KAGnC,IAAIzD,GAAKzG,KACLwkB,EAAOxkB,KAAK+D,IAAIygB,IACpBxkB,MAAKslB,eAAegB,UAAYrlB,EAAK8I,iBACjCjB,SAAU,YAAa,SAAUF,GAE/B,GAAIM,GAASN,EAAMM,MACdA,IAAUsb,GAAU/d,EAAGwY,WAAW/V,EAAQsb,KAC7C/d,EAAGyT,OACHtR,EAAM+C,kBACN/C,EAAMQ,oBAGdpJ,KAAKslB,eAAeiB,WAAatlB,EAAK8I,iBAClCjB,SAAU,aAAc,SAAUF,GAEhCA,EAAM+C,kBACN/C,EAAMQ,mBAEZpJ,KAAKslB,eAAekB,QAAUvlB,EAAK8I,iBAC/BjB,SAAU,UAAW,SAAUF,GAC7BnC,EAAGsE,WAAWnC,KAIpB5I,KAAKiE,UAAYhD,EAAK2G,eACtB5H,KAAKyiB,OAASA,EACd/Z,WAAW,WACTjC,EAAG1C,IAAIyhB,YAAY7d,SAClB,GAECgR,EAAYoN,aACdpN,EAAYoN,YAAY7L,OAE1BvB,EAAYoN,YAAc/lB,MAM5B2Y,EAAYlX,UAAUyY,KAAO,WAEvBla,KAAK+D,IAAImG,KAAK1F,aAChBxE,KAAK+D,IAAImG,KAAK1F,WAAWC,YAAYzE,KAAK+D,IAAImG,MAC1ClK,KAAK0iB,SACP1iB,KAAK0iB,UAMT,KAAK,GAAIpgB,KAAQtC,MAAKslB,eACpB,GAAItlB,KAAKslB,eAAe3gB,eAAerC,GAAO,CAC5C,GAAImkB,GAAKzmB,KAAKslB,eAAehjB,EACzBmkB,IACFxlB,EAAK4S,oBAAoB/K,SAAUxG,EAAMmkB,SAEpCzmB,MAAKslB,eAAehjB,GAI3BqW,EAAYoN,aAAe/lB,OAC7B2Y,EAAYoN,YAAc7hB,SAU9ByU,EAAYlX,UAAUyjB,cAAgB,SAAUN,GAC9C,GAAIne,GAAKzG,KACL0mB,EAAkB9B,GAAW5kB,KAAK6lB,aAGlCA,EAAe7lB,KAAK6lB,YAcxB,IAbIA,IAEFA,EAAaR,GAAG3Y,MAAM9F,OAAS,IAC/Bif,EAAaR,GAAG3Y,MAAMia,QAAU,GAChCje,WAAW,WACLjC,EAAGof,cAAgBA,IACrBA,EAAaR,GAAG3Y,MAAMka,QAAU,GAChC3lB,EAAKsP,gBAAgBsV,EAAaR,GAAG7gB,WAAY,cAElD,KACHxE,KAAK6lB,aAAe3hB,SAGjBwiB,EAAgB,CACnB,GAAIrB,GAAKT,EAAQS,EACjBA,GAAG3Y,MAAMka,QAAU,OACnB,EAAavB,EAAGxe,aAChB6B,WAAW,WACLjC,EAAGof,cAAgBjB,IACrBS,EAAG3Y,MAAM9F,OAAiC,GAAvBye,EAAGxU,WAAWzP,OAAe,KAChDikB,EAAG3Y,MAAMia,QAAU,aAEpB,GACH1lB,EAAKkP,aAAakV,EAAG7gB,WAAY,YACjCxE,KAAK6lB,aAAejB,IASxBjM,EAAYlX,UAAUsJ,WAAa,SAAUnC,GAC3C,GAGIgd,GAASiB,EAAaC,EAAYC,EAHlC7d,EAASN,EAAMM,OACf+B,EAASrC,EAAMsC,MACfI,GAAU,CAGA,KAAVL,GAIEjL,KAAKiE,WACPhD,EAAKuG,aAAaxH,KAAKiE,WAErBjE,KAAKyiB,QACPziB,KAAKyiB,OAAO9a,QAGd3H,KAAKka,OAEL5O,GAAU,GAEO,GAAVL,EACFrC,EAAMyC,UAUTua,EAAU5lB,KAAK2lB,qBACfkB,EAAcjB,EAAQtV,QAAQpH,GACX,GAAf2d,IAEFjB,EAAQA,EAAQxkB,OAAS,GAAGuG,QAC5B2D,GAAU,KAdZsa,EAAU5lB,KAAK2lB,qBACfkB,EAAcjB,EAAQtV,QAAQpH,GAC1B2d,GAAejB,EAAQxkB,OAAS,IAElCwkB,EAAQ,GAAGje,QACX2D,GAAU,IAaG,IAAVL,GACiB,UAApB/B,EAAOF,YACT4c,EAAU5lB,KAAK2lB,qBACfkB,EAAcjB,EAAQtV,QAAQpH,GAC9B4d,EAAalB,EAAQiB,EAAc,GAC/BC,GACFA,EAAWnf,SAGf2D,GAAU,GAEO,IAAVL,GACP2a,EAAU5lB,KAAK2lB,qBACfkB,EAAcjB,EAAQtV,QAAQpH,GAC9B4d,EAAalB,EAAQiB,EAAc,GAC/BC,GAAsC,UAAxBA,EAAW9d,YAE3B8d,EAAalB,EAAQiB,EAAc,IAEhCC,IAEHA,EAAalB,EAAQA,EAAQxkB,OAAS,IAEpC0lB,GACFA,EAAWnf,QAEb2D,GAAU,GAEO,IAAVL,GACP2a,EAAU5lB,KAAK2lB,qBACfkB,EAAcjB,EAAQtV,QAAQpH,GAC9B6d,EAAanB,EAAQiB,EAAc,GAC/BE,GAAsC,UAAxBA,EAAW/d,WAC3B+d,EAAWpf,QAEb2D,GAAU,GAEO,IAAVL,IACP2a,EAAU5lB,KAAK2lB,qBACfkB,EAAcjB,EAAQtV,QAAQpH,GAC9B6d,EAAanB,EAAQiB,EAAc,GAC/BE,GAAsC,UAAxBA,EAAW/d,YAE3B+d,EAAanB,EAAQiB,EAAc,IAEhCE,IAEHA,EAAanB,EAAQ,IAEnBmB,IACFA,EAAWpf,QACX2D,GAAU,GAEZA,GAAU,GAIRA,IACF1C,EAAM+C,kBACN/C,EAAMQ,mBAUVuP,EAAYlX,UAAUwd,WAAa,SAAUlO,EAAO+D,GAElD,IADA,GAAIkS,GAAIjW,EAAMvM,WACPwiB,GAAG,CACR,GAAIA,GAAKlS,EACP,OAAO,CAETkS,GAAIA,EAAExiB,WAGR,OAAO,GAGT3E,EAAOD,QAAU+Y,GAKZ,SAAS9Y,EAAQD,EAASM,GAS/B,QAAS0Y,GAAkB/U,GAQzB,QAAS4W,GAAYzS,GAEnBhI,KAAKgI,OAASA,EACdhI,KAAK+D,OA4MP,MAzMA0W,GAAWhZ,UAAY,GAAIoC,GAM3B4W,EAAWhZ,UAAUqE,OAAS,WAE5B,GAAI/B,GAAM/D,KAAK+D,GAEf,IAAIA,EAAIiT,GACN,MAAOjT,GAAIiT,EAGbhX,MAAK6Y,oBAGL,IAAIoO,GAAWne,SAASC,cAAc,KAMtC,IALAke,EAAS7hB,KAAOpF,KAChB+D,EAAIiT,GAAKiQ,EAILjnB,KAAK8Y,SAAS5T,MAAO,CAEvBnB,EAAI2Y,OAAS5T,SAASC,cAAc,KAGpC,IAAI6T,GAAS9T,SAASC,cAAc,KACpChF,GAAI6Y,OAASA,CACb,IAAI1S,GAAOpB,SAASC,cAAc,SAClCmB,GAAKlB,UAAY,cACjBkB,EAAKC,MAAQ,0CACbpG,EAAImG,KAAOA,EACX0S,EAAOpX,YAAYzB,EAAImG,MAIzB,GAAIgd,GAAWpe,SAASC,cAAc,MAClCoe,EAAUre,SAASC,cAAc,MASrC,OARAoe,GAAQ5O,UAAY,UACpB4O,EAAQne,UAAY,WACpBke,EAAS1hB,YAAY2hB,GACrBpjB,EAAIkT,GAAKiQ,EACTnjB,EAAIgC,KAAOohB,EAEXnnB,KAAKmY,YAEE8O,GAMTxM,EAAWhZ,UAAU0W,UAAY,WAC/B,GAAIpU,GAAM/D,KAAK+D,IACXmjB,EAAWnjB,EAAIkT,EACfiQ,KACFA,EAASxa,MAAM0a,YAAiC,GAAlBpnB,KAAK0Z,WAAkB,GAAM,KAI7D,IAAIyN,GAAUpjB,EAAIgC,IACdohB,KACFA,EAAQ5O,UAAY,UAAYvY,KAAK8U,OAAOhK,KAAO,IAKrD,IAAImc,GAAWljB,EAAIiT,EACdhX,MAAK0hB,YAYH3d,EAAIiT,GAAG7E,aACNpO,EAAI2Y,QACNuK,EAASzhB,YAAYzB,EAAI2Y,QAEvB3Y,EAAI6Y,QACNqK,EAASzhB,YAAYzB,EAAI6Y,QAE3BqK,EAASzhB,YAAY0hB,IAlBnBnjB,EAAIiT,GAAG7E,aACLpO,EAAI2Y,QACNuK,EAASxiB,YAAYV,EAAI2Y,QAEvB3Y,EAAI6Y,QACNqK,EAASxiB,YAAYV,EAAI6Y,QAE3BqK,EAASxiB,YAAYyiB,KAqB3BzM,EAAWhZ,UAAUigB,UAAY,WAC/B,MAAqC,IAA7B1hB,KAAK8U,OAAOlE,OAAOxP,QAS7BqZ,EAAWhZ,UAAUwe,gBAAkB,SAAUwC,EAAQC,GACvD,GAAItd,GAAOpF,KACP2iB,EAAS9e,EAAKwe,YACdO,IAGA7c,KAAQ,SACRoE,MAAS,uDACT4Y,aAAgB,8CAChB/Z,UAAa,SACb8Z,MAAS,WACP1d,EAAKyc,UAAU,GAAI,GAAI,SAEzBgB,UAEI9c,KAAQ,OACRiD,UAAa,YACbmB,MAASwY,EAAOL,KAChBQ,MAAS,WACP1d,EAAKyc,UAAU,GAAI,GAAI,WAIzB9b,KAAQ,QACRiD,UAAa,aACbmB,MAASwY,EAAOJ,MAChBO,MAAS,WACP1d,EAAKyc,UAAU,UAIjB9b,KAAQ,SACRiD,UAAa,cACbmB,MAASwY,EAAO1T,OAChB6T,MAAS,WACP1d,EAAKyc,UAAU,UAIjB9b,KAAQ,SACRiD,UAAa,cACbmB,MAASwY,EAAOH,OAChBM,MAAS,WACP1d,EAAKyc,UAAU,GAAI,GAAI,eAO7B3X,EAAO,GAAIyO,GAAYiK,GAAQI,MAAON,GAC1CxY,GAAK+Y,KAAKR,IAOZhI,EAAWhZ,UAAUkH,QAAU,SAAUC,GACvC,GAAIkC,GAAOlC,EAAMkC,KACb5B,EAASN,EAAMM,QAAUN,EAAMmX,WAC/Bhc,EAAM/D,KAAK+D,IAGXmG,EAAOnG,EAAImG,IAWf,IAVIhB,GAAUgB,IACA,aAARY,EACF9K,KAAKgI,OAAOhE,YAAYgQ,UAAUhU,KAAK8U,QAExB,YAARhK,GACP9K,KAAKgI,OAAOhE,YAAYmQ,eAKhB,SAARrJ,GAAmB5B,GAAUnF,EAAImG,KAAM,CACzC,GAAIlG,GAAchE,KAAKgI,OAAOhE,WAC9BA,GAAYgQ,UAAUhU,KAAK8U,QAC3B9Q,EAAYqQ,OACZpT,EAAKkP,aAAapM,EAAImG,KAAM,YAC5BlK,KAAKigB,gBAAgBlc,EAAImG,KAAM,WAC7BjJ,EAAKsP,gBAAgBxM,EAAImG,KAAM,YAC/BlG,EAAYsQ,SACZtQ,EAAYmQ,gBAIJ,WAARrJ,GACF9K,KAAKogB,UAAUxX,IAIZ6R,EA9NT,GAAIxZ,GAAOf,EAAoB,GAC3ByY,EAAczY,EAAoB,EAgOtCL,GAAOD,QAAUgZ"} \ No newline at end of file diff --git a/jsoneditor.min.js b/jsoneditor.min.js index 19e74f0..03876bb 100644 --- a/jsoneditor.min.js +++ b/jsoneditor.min.js @@ -24,9 +24,8 @@ * * @author Jos de Jong, * @version 3.2.0 - * @date 2015-01-25 + * @date 2015-02-27 */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):"object"==typeof exports?exports.JSONEditor=t():e.JSONEditor=t()}(this,function(){return function(e){function t(n){if(i[n])return i[n].exports;var o=i[n]={exports:{},id:n,loaded:!1};return e[n].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var i={};return t.m=e,t.c=i,t.p="",t(0)}([function(e,t,i){var n,o;n=[i(1),i(2),i(3)],o=function(e,t,i){function n(e,t,o){if(!(this instanceof n))throw new Error('JSONEditor constructor called without "new".');var s=i.getInternetExplorerVersion();if(-1!=s&&9>s)throw new Error("Unsupported browser, IE9 or newer required. Please install the newest version of your browser.");arguments.length&&this._create(e,t,o)}return n.modes={},n.prototype._create=function(e,t,i){this.container=e,this.options=t||{},this.json=i||{};var n=this.options.mode||"tree";this.setMode(n)},n.prototype._delete=function(){},n.prototype.set=function(e){this.json=e},n.prototype.get=function(){return this.json},n.prototype.setText=function(e){this.json=i.parse(e)},n.prototype.getText=function(){return JSON.stringify(this.json)},n.prototype.setName=function(e){this.options||(this.options={}),this.options.name=e},n.prototype.getName=function(){return this.options&&this.options.name},n.prototype.setMode=function(e){var t,o,s=this.container,r=i.extend({},this.options);r.mode=e;var a=n.modes[e];if(!a)throw new Error('Unknown mode "'+r.mode+'"');try{var d="text"==a.data;if(o=this.getName(),t=this[d?"getText":"get"](),this._delete(),i.clear(this),i.extend(this,a.mixin),this.create(s,r),this.setName(o),this[d?"setText":"set"](t),"function"==typeof a.load)try{a.load.call(this)}catch(h){}}catch(h){this._onError(h)}},n.prototype._onError=function(e){if("function"==typeof this.onError&&(i.log("WARNING: JSONEditor.onError is deprecated. Use options.error instead."),this.onError(e)),!this.options||"function"!=typeof this.options.error)throw e;this.options.error(e)},n.registerMode=function(e){var t,o;if(i.isArray(e))for(t=0;te&&i.scrollTop>0?(n+a-e)/3:e>r-a&&o+i.scrollTop3?(i.scrollTop+=o/3,n.animateCallback=t,n.animateTimeout=setTimeout(a,50)):(t&&t(!0),i.scrollTop=r,delete n.animateTimeout,delete n.animateCallback)};a()}else t&&t(!1)},r._createFrame=function(){function e(e){t._onEvent(e)}this.frame=document.createElement("div"),this.frame.className="jsoneditor",this.container.appendChild(this.frame);var t=this;this.frame.onclick=function(t){var i=t.target;e(t),"BUTTON"==i.nodeName&&t.preventDefault()},this.frame.oninput=e,this.frame.onchange=e,this.frame.onkeydown=e,this.frame.onkeyup=e,this.frame.oncut=e,this.frame.onpaste=e,this.frame.onmousedown=e,this.frame.onmouseup=e,this.frame.onmouseover=e,this.frame.onmouseout=e,s.addEventListener(this.frame,"focus",e,!0),s.addEventListener(this.frame,"blur",e,!0),this.frame.onfocusin=e,this.frame.onfocusout=e,this.menu=document.createElement("div"),this.menu.className="menu",this.frame.appendChild(this.menu);var n=document.createElement("button");n.className="expand-all",n.title="Expand all fields",n.onclick=function(){t.expandAll()},this.menu.appendChild(n);var r=document.createElement("button");if(r.title="Collapse all fields",r.className="collapse-all",r.onclick=function(){t.collapseAll()},this.menu.appendChild(r),this.history){var a=document.createElement("button");a.className="undo separator",a.title="Undo last action (Ctrl+Z)",a.onclick=function(){t._onUndo()},this.menu.appendChild(a),this.dom.undo=a;var d=document.createElement("button");d.className="redo",d.title="Redo (Ctrl+Shift+Z)",d.onclick=function(){t._onRedo()},this.menu.appendChild(d),this.dom.redo=d,this.history.onChange=function(){a.disabled=!t.history.canUndo(),d.disabled=!t.history.canRedo()},this.history.onChange()}if(this.options&&this.options.modes&&this.options.modes.length){var h=o.create(this,this.options.modes,this.options.mode);this.menu.appendChild(h),this.dom.modeBox=h}this.options.search&&(this.searchBox=new i(this,this.menu))},r._onUndo=function(){this.history&&(this.history.undo(),this.options.change&&this.options.change())},r._onRedo=function(){this.history&&(this.history.redo(),this.options.change&&this.options.change())},r._onEvent=function(e){var t=e.target;"keydown"==e.type&&this._onKeyDown(e),"focus"==e.type&&(d=t);var i=n.getNodeFromTarget(t);i&&i.onEvent(e)},r._onKeyDown=function(e){var t=e.which||e.keyCode,i=e.ctrlKey,n=e.shiftKey,o=!1;if(9==t&&setTimeout(function(){s.selectContentEditable(d)},0),this.searchBox)if(i&&70==t)this.searchBox.dom.search.focus(),this.searchBox.dom.search.select(),o=!0;else if(114==t||i&&71==t){var r=!0;n?this.searchBox.previous(r):this.searchBox.next(r),o=!0}this.history&&(i&&!n&&90==t?(this._onUndo(),o=!0):i&&n&&90==t&&(this._onRedo(),o=!0)),o&&(e.preventDefault(),e.stopPropagation())},r._createTable=function(){var e=document.createElement("div");e.className="outer",this.contentOuter=e,this.content=document.createElement("div"),this.content.className="tree",e.appendChild(this.content),this.table=document.createElement("table"),this.table.className="tree",this.content.appendChild(this.table);var t;this.colgroupContent=document.createElement("colgroup"),"tree"===this.options.mode&&(t=document.createElement("col"),t.width="24px",this.colgroupContent.appendChild(t)),t=document.createElement("col"),t.width="24px",this.colgroupContent.appendChild(t),t=document.createElement("col"),this.colgroupContent.appendChild(t),this.table.appendChild(this.colgroupContent),this.tbody=document.createElement("tbody"),this.table.appendChild(this.tbody),this.frame.appendChild(e)},[{mode:"tree",mixin:r,data:"json"},{mode:"view",mixin:r,data:"json"},{mode:"form",mixin:r,data:"json"}]}.apply(t,n),!(void 0!==o&&(e.exports=o))},function(e,t,i){var n,o;n=[i(8),i(3)],o=function(e,t){var i={};return i.create=function(i,n){n=n||{},this.options=n,this.indentation=n.indentation?Number(n.indentation):2,this.mode="code"==n.mode?"code":"text","code"==this.mode&&"undefined"==typeof ace&&(this.mode="text",t.log("WARNING: Cannot load code editor, Ace library not loaded. Falling back to plain text editor"));var o=this;this.container=i,this.dom={},this.editor=void 0,this.textarea=void 0,this.width=i.clientWidth,this.height=i.clientHeight,this.frame=document.createElement("div"),this.frame.className="jsoneditor",this.frame.onclick=function(e){e.preventDefault()},this.frame.onkeydown=function(e){o._onKeyDown(e)},this.menu=document.createElement("div"),this.menu.className="menu",this.frame.appendChild(this.menu);var s=document.createElement("button");s.className="format",s.title="Format JSON data, with proper indentation and line feeds (Ctrl+\\)",this.menu.appendChild(s),s.onclick=function(){try{o.format()}catch(e){o._onError(e)}};var r=document.createElement("button");if(r.className="compact",r.title="Compact JSON data, remove all whitespaces (Ctrl+Shift+\\)",this.menu.appendChild(r),r.onclick=function(){try{o.compact()}catch(e){o._onError(e)}},this.options&&this.options.modes&&this.options.modes.length){var a=e.create(this,this.options.modes,this.options.mode);this.menu.appendChild(a),this.dom.modeBox=a}if(this.content=document.createElement("div"),this.content.className="outer",this.frame.appendChild(this.content),this.container.appendChild(this.frame),"code"==this.mode){this.editorDom=document.createElement("div"),this.editorDom.style.height="100%",this.editorDom.style.width="100%",this.content.appendChild(this.editorDom);var d=ace.edit(this.editorDom);d.setTheme("ace/theme/jsoneditor"),d.setShowPrintMargin(!1),d.setFontSize(13),d.getSession().setMode("ace/mode/json"),d.getSession().setTabSize(this.indentation),d.getSession().setUseSoftTabs(!0),d.getSession().setUseWrapMode(!0),this.editor=d;var h=document.createElement("a");h.appendChild(document.createTextNode("powered by ace")),h.href="http://ace.ajax.org",h.target="_blank",h.className="poweredBy",h.onclick=function(){window.open(h.href,h.target)},this.menu.appendChild(h),n.change&&d.on("change",function(){n.change()})}else{var l=document.createElement("textarea");l.className="text",l.spellcheck=!1,this.content.appendChild(l),this.textarea=l,n.change&&(null===this.textarea.oninput?this.textarea.oninput=function(){n.change()}:this.textarea.onchange=function(){n.change()})}},i._onKeyDown=function(e){var t=e.which||e.keyCode,i=!1;220==t&&e.ctrlKey&&(e.shiftKey?this.compact():this.format(),i=!0),i&&(e.preventDefault(),e.stopPropagation())},i._delete=function(){this.frame&&this.container&&this.frame.parentNode==this.container&&this.container.removeChild(this.frame)},i._onError=function(e){if("function"==typeof this.onError&&(t.log("WARNING: JSONEditor.onError is deprecated. Use options.error instead."),this.onError(e)),!this.options||"function"!=typeof this.options.error)throw e;this.options.error(e)},i.compact=function(){var e=this.get(),t=JSON.stringify(e);this.setText(t)},i.format=function(){var e=this.get(),t=JSON.stringify(e,null,this.indentation);this.setText(t)},i.focus=function(){this.textarea&&this.textarea.focus(),this.editor&&this.editor.focus()},i.resize=function(){if(this.editor){var e=!1;this.editor.resize(e)}},i.set=function(e){this.setText(JSON.stringify(e,null,this.indentation))},i.get=function(){var e,i=this.getText();try{e=t.parse(i)}catch(n){i=t.sanitize(i),this.setText(i),e=t.parse(i)}return e},i.getText=function(){return this.textarea?this.textarea.value:this.editor?this.editor.getValue():""},i.setText=function(e){this.textarea&&(this.textarea.value=e),this.editor&&this.editor.setValue(e,-1)},[{mode:"text",mixin:i,data:"text",load:i.format},{mode:"code",mixin:i,data:"text",load:i.format}]}.apply(t,n),!(void 0!==o&&(e.exports=o))},function(e,t,i){var n;n=function(){var e={};e.parse=function(t){try{return JSON.parse(t)}catch(i){throw e.validate(t),i}},e.sanitize=function(e){for(var t=[],i=!1,n=0;nn;n++){var s=i[n];s.style&&s.removeAttribute("style");var r=s.attributes;if(r)for(var a=r.length-1;a>=0;a--){var d=r[a];1==d.specified&&s.removeAttribute(d.name)}e.stripFormatting(s)}},e.setEndOfContentEditable=function(e){var t,i;document.createRange&&(t=document.createRange(),t.selectNodeContents(e),t.collapse(!1),i=window.getSelection(),i.removeAllRanges(),i.addRange(t))},e.selectContentEditable=function(e){if(e&&"DIV"==e.nodeName){var t,i;window.getSelection&&document.createRange&&(i=document.createRange(),i.selectNodeContents(e),t=window.getSelection(),t.removeAllRanges(),t.addRange(i))}},e.getSelection=function(){if(window.getSelection){var e=window.getSelection();if(e.getRangeAt&&e.rangeCount)return e.getRangeAt(0)}return null},e.setSelection=function(e){if(e&&window.getSelection){var t=window.getSelection();t.removeAllRanges(),t.addRange(e)}},e.getSelectionOffset=function(){var t=e.getSelection();return t&&"startOffset"in t&&"endOffset"in t&&t.startContainer&&t.startContainer==t.endContainer?{startOffset:t.startOffset,endOffset:t.endOffset,container:t.startContainer.parentNode}:null},e.setSelectionOffset=function(t){if(document.createRange&&window.getSelection){var i=window.getSelection();if(i){var n=document.createRange();n.setStart(t.container.firstChild,t.startOffset),n.setEnd(t.container.firstChild,t.endOffset),e.setSelection(n)}}},e.getInnerText=function(t,i){var n=void 0==i;if(n&&(i={text:"",flush:function(){var e=this.text;return this.text="",e},set:function(e){this.text=e}}),t.nodeValue)return i.flush()+t.nodeValue;if(t.hasChildNodes()){for(var o=t.childNodes,s="",r=0,a=o.length;a>r;r++){var d=o[r];if("DIV"==d.nodeName||"P"==d.nodeName){var h=o[r-1],l=h?h.nodeName:void 0;l&&"DIV"!=l&&"P"!=l&&"BR"!=l&&(s+="\n",i.flush()),s+=e.getInnerText(d,i),i.set("\n")}else"BR"==d.nodeName?(s+=i.flush(),i.set("\n")):s+=e.getInnerText(d,i)}return s}return"P"==t.nodeName&&-1!=e.getInternetExplorerVersion()?i.flush():""},e.getInternetExplorerVersion=function(){if(-1==i){var e=-1;if("Microsoft Internet Explorer"==navigator.appName){var t=navigator.userAgent,n=new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})");null!=n.exec(t)&&(e=parseFloat(RegExp.$1))}i=e}return i},e.isFirefox=function(){return-1!=navigator.userAgent.indexOf("Firefox")};var i=-1;return e.addEventListener=function(t,i,n,o){if(t.addEventListener)return void 0===o&&(o=!1),"mousewheel"===i&&e.isFirefox()&&(i="DOMMouseScroll"),t.addEventListener(i,n,o),n;if(t.attachEvent){var s=function(){return n.call(t,window.event)};return t.attachEvent("on"+i,s),s}},e.removeEventListener=function(t,i,n,o){t.removeEventListener?(void 0===o&&(o=!1),"mousewheel"===i&&e.isFirefox()&&(i="DOMMouseScroll"),t.removeEventListener(i,n,o)):t.detachEvent&&t.detachEvent("on"+i,n)},e}.call(t,i,t,e),!(void 0!==n&&(e.exports=n))},function(e,t,i){var n;n=function(){function e(){this.locked=!1}return e.prototype.highlight=function(e){this.locked||(this.node!=e&&(this.node&&this.node.setHighlight(!1),this.node=e,this.node.setHighlight(!0)),this._cancelUnhighlight())},e.prototype.unhighlight=function(){if(!this.locked){var e=this;this.node&&(this._cancelUnhighlight(),this.unhighlightTimer=setTimeout(function(){e.node.setHighlight(!1),e.node=void 0,e.unhighlightTimer=void 0},0))}},e.prototype._cancelUnhighlight=function(){this.unhighlightTimer&&(clearTimeout(this.unhighlightTimer),this.unhighlightTimer=void 0)},e.prototype.lock=function(){this.locked=!0},e.prototype.unlock=function(){this.locked=!1},e}.call(t,i,t,e),!(void 0!==n&&(e.exports=n))},function(e,t,i){var n,o;n=[i(3)],o=function(e){function t(e){this.editor=e,this.clear(),this.actions={editField:{undo:function(e){e.node.updateField(e.oldValue)},redo:function(e){e.node.updateField(e.newValue)}},editValue:{undo:function(e){e.node.updateValue(e.oldValue)},redo:function(e){e.node.updateValue(e.newValue)}},appendNode:{undo:function(e){e.parent.removeChild(e.node)},redo:function(e){e.parent.appendChild(e.node)}},insertBeforeNode:{undo:function(e){e.parent.removeChild(e.node)},redo:function(e){e.parent.insertBefore(e.node,e.beforeNode)}},insertAfterNode:{undo:function(e){e.parent.removeChild(e.node)},redo:function(e){e.parent.insertAfter(e.node,e.afterNode)}},removeNode:{undo:function(e){var t=e.parent,i=t.childs[e.index]||t.append;t.insertBefore(e.node,i)},redo:function(e){e.parent.removeChild(e.node)}},duplicateNode:{undo:function(e){e.parent.removeChild(e.clone)},redo:function(e){e.parent.insertAfter(e.clone,e.node)}},changeType:{undo:function(e){e.node.changeType(e.oldType)},redo:function(e){e.node.changeType(e.newType)}},moveNode:{undo:function(e){e.startParent.moveTo(e.node,e.startIndex)},redo:function(e){e.endParent.moveTo(e.node,e.endIndex)}},sort:{undo:function(e){var t=e.node;t.hideChilds(),t.sort=e.oldSort,t.childs=e.oldChilds,t.showChilds()},redo:function(e){var t=e.node;t.hideChilds(),t.sort=e.newSort,t.childs=e.newChilds,t.showChilds()}}}}return t.prototype.onChange=function(){},t.prototype.add=function(e,t){this.index++,this.history[this.index]={action:e,params:t,timestamp:new Date},this.index=0},t.prototype.canRedo=function(){return this.indexthis.results.length-1&&(t=0),this._setActiveResult(t,e)}},e.prototype.previous=function(e){if(void 0!=this.results){var t=this.results.length-1,i=void 0!=this.resultIndex?this.resultIndex-1:t;0>i&&(i=t),this._setActiveResult(i,e)}},e.prototype._setActiveResult=function(e,t){if(this.activeResult){var i=this.activeResult.node,n=this.activeResult.elem;"field"==n?delete i.searchFieldActive:delete i.searchValueActive,i.updateDom()}if(!this.results||!this.results[e])return this.resultIndex=void 0,void(this.activeResult=void 0);this.resultIndex=e;var o=this.results[this.resultIndex].node,s=this.results[this.resultIndex].elem;"field"==s?o.searchFieldActive=!0:o.searchValueActive=!0,this.activeResult=this.results[this.resultIndex],o.updateDom(),o.scrollTo(function(){t&&o.focus(s)})},e.prototype._clearDelay=function(){void 0!=this.timeout&&(clearTimeout(this.timeout),delete this.timeout)},e.prototype._onDelayedSearch=function(){this._clearDelay();var e=this;this.timeout=setTimeout(function(t){e._onSearch(t)},this.delay)},e.prototype._onSearch=function(e,t){this._clearDelay();var i=this.dom.search.value,n=i.length>0?i:void 0;if(n!=this.lastText||t)if(this.lastText=n,this.results=this.editor.search(n),this._setActiveResult(void 0),void 0!=n){var o=this.results.length;switch(o){case 0:this.dom.results.innerHTML="no results";break;case 1:this.dom.results.innerHTML="1 result";break;default:this.dom.results.innerHTML=o+" results"}}else this.dom.results.innerHTML=""},e.prototype._onKeyDown=function(e){var t=e.which;27==t?(this.dom.search.value="",this._onSearch(e),e.preventDefault(),e.stopPropagation()):13==t&&(e.ctrlKey?this._onSearch(e,!0):e.shiftKey?this.previous():this.next(),e.preventDefault(),e.stopPropagation())},e.prototype._onKeyUp=function(e){var t=e.keyCode;27!=t&&13!=t&&this._onDelayedSearch(e)},e}.call(t,i,t,e),!(void 0!==n&&(e.exports=n))},function(e,t,i){var n,o;n=[i(9),i(10),i(3)],o=function(e,t,i){function n(e,t){this.editor=e,this.dom={},this.expanded=!1,t&&t instanceof Object?(this.setField(t.field,t.fieldEditable),this.setValue(t.value,t.type)):(this.setField(""),this.setValue(null))}n.prototype._updateEditability=function(){if(this.editable={field:!0,value:!0},this.editor&&(this.editable.field="tree"===this.editor.options.mode,this.editable.value="view"!==this.editor.options.mode,"tree"===this.editor.options.mode&&"function"==typeof this.editor.options.editable)){var e=this.editor.options.editable({field:this.field,value:this.value,path:this.path()});"boolean"==typeof e?(this.editable.field=e,this.editable.value=e):("boolean"==typeof e.field&&(this.editable.field=e.field),"boolean"==typeof e.value&&(this.editable.value=e.value))}},n.prototype.path=function(){for(var e=this,t=[];e;){var i=void 0!=e.field?e.field:e.index;void 0!==i&&t.unshift(i),e=e.parent}return t},n.prototype.setParent=function(e){this.parent=e},n.prototype.setField=function(e,t){this.field=e,this.fieldEditable=1==t},n.prototype.getField=function(){return void 0===this.field&&this._getDomField(),this.field},n.prototype.setValue=function(e,t){var i,o,s=this.childs;if(s)for(;s.length;)this.removeChild(s[0]);if(this.type=this._getType(e),t&&t!=this.type){if("string"!=t||"auto"!=this.type)throw new Error('Type mismatch: cannot cast value of type "'+this.type+' to the specified type "'+t+'"');this.type=t}if("array"==this.type){this.childs=[];for(var r=0,a=e.length;a>r;r++)i=e[r],void 0===i||i instanceof Function||(o=new n(this.editor,{value:i}),this.appendChild(o));this.value=""}else if("object"==this.type){this.childs=[];for(var d in e)e.hasOwnProperty(d)&&(i=e[d],void 0===i||i instanceof Function||(o=new n(this.editor,{field:d,value:i}),this.appendChild(o)));this.value=""}else this.childs=void 0,this.value=e},n.prototype.getValue=function(){if("array"==this.type){var e=[];return this.childs.forEach(function(t){e.push(t.getValue())}),e}if("object"==this.type){var t={};return this.childs.forEach(function(e){t[e.getField()]=e.getValue()}),t}return void 0===this.value&&this._getDomValue(),this.value},n.prototype.getLevel=function(){return this.parent?this.parent.getLevel()+1:0},n.prototype.clone=function(){var e=new n(this.editor);if(e.type=this.type,e.field=this.field,e.fieldInnerText=this.fieldInnerText,e.fieldEditable=this.fieldEditable,e.value=this.value,e.valueInnerText=this.valueInnerText,e.expanded=this.expanded,this.childs){var t=[];this.childs.forEach(function(i){var n=i.clone();n.setParent(e),t.push(n)}),e.childs=t}else e.childs=void 0;return e},n.prototype.expand=function(e){this.childs&&(this.expanded=!0,this.dom.expand&&(this.dom.expand.className="expanded"),this.showChilds(),0!=e&&this.childs.forEach(function(t){t.expand(e)}))},n.prototype.collapse=function(e){this.childs&&(this.hideChilds(),0!=e&&this.childs.forEach(function(t){t.collapse(e)}),this.dom.expand&&(this.dom.expand.className="collapsed"),this.expanded=!1)},n.prototype.showChilds=function(){var e=this.childs;if(e&&this.expanded){var t=this.dom.tr,i=t?t.parentNode:void 0;if(i){var n=this.getAppend(),o=t.nextSibling;o?i.insertBefore(n,o):i.appendChild(n),this.childs.forEach(function(e){i.insertBefore(e.getDom(),n),e.showChilds()})}}},n.prototype.hide=function(){var e=this.dom.tr,t=e?e.parentNode:void 0;t&&t.removeChild(e),this.hideChilds()},n.prototype.hideChilds=function(){var e=this.childs;if(e&&this.expanded){var t=this.getAppend();t.parentNode&&t.parentNode.removeChild(t),this.childs.forEach(function(e){e.hide()})}},n.prototype.appendChild=function(e){if(this._hasChilds()){if(e.setParent(this),e.fieldEditable="object"==this.type,"array"==this.type&&(e.index=this.childs.length),this.childs.push(e),this.expanded){var t=e.getDom(),i=this.getAppend(),n=i?i.parentNode:void 0;i&&n&&n.insertBefore(t,i),e.showChilds()}this.updateDom({updateIndexes:!0}),e.updateDom({recurse:!0})}},n.prototype.moveBefore=function(e,t){if(this._hasChilds()){var i=this.dom.tr?this.dom.tr.parentNode:void 0;if(i){var n=document.createElement("tr");n.style.height=i.clientHeight+"px",i.appendChild(n)}e.parent&&e.parent.removeChild(e),t instanceof o?this.appendChild(e):this.insertBefore(e,t),i&&i.removeChild(n)}},n.prototype.moveTo=function(e,t){if(e.parent==this){var i=this.childs.indexOf(e);t>i&&t++}var n=this.childs[t]||this.append;this.moveBefore(e,n)},n.prototype.insertBefore=function(e,t){if(this._hasChilds()){if(t==this.append)e.setParent(this),e.fieldEditable="object"==this.type,this.childs.push(e);else{var i=this.childs.indexOf(t);if(-1==i)throw new Error("Node not found");e.setParent(this),e.fieldEditable="object"==this.type,this.childs.splice(i,0,e)}if(this.expanded){var n=e.getDom(),o=t.getDom(),s=o?o.parentNode:void 0;o&&s&&s.insertBefore(n,o),e.showChilds()}this.updateDom({updateIndexes:!0}),e.updateDom({recurse:!0})}},n.prototype.insertAfter=function(e,t){if(this._hasChilds()){var i=this.childs.indexOf(t),n=this.childs[i+1];n?this.insertBefore(e,n):this.appendChild(e)}},n.prototype.search=function(e){var t,i=[],n=e?e.toLowerCase():void 0;if(delete this.searchField,delete this.searchValue,void 0!=this.field){var o=String(this.field).toLowerCase();t=o.indexOf(n),-1!=t&&(this.searchField=!0,i.push({node:this,elem:"field"})),this._updateDomField()}if(this._hasChilds()){if(this.childs){var s=[];this.childs.forEach(function(t){s=s.concat(t.search(e))}),i=i.concat(s)}if(void 0!=n){var r=!1;0==s.length?this.collapse(r):this.expand(r)}}else{if(void 0!=this.value){var a=String(this.value).toLowerCase();t=a.indexOf(n),-1!=t&&(this.searchValue=!0,i.push({node:this,elem:"value"}))}this._updateDomValue()}return i},n.prototype.scrollTo=function(e){if(!this.dom.tr||!this.dom.tr.parentNode)for(var t=this.parent,i=!1;t;)t.expand(i),t=t.parent;this.dom.tr&&this.dom.tr.parentNode&&this.editor.scrollTo(this.dom.tr.offsetTop,e)},n.focusElement=void 0,n.prototype.focus=function(e){if(n.focusElement=e,this.dom.tr&&this.dom.tr.parentNode){var t=this.dom;switch(e){case"drag":t.drag?t.drag.focus():t.menu.focus();break;case"menu":t.menu.focus();break;case"expand":this._hasChilds()?t.expand.focus():t.field&&this.fieldEditable?(t.field.focus(),i.selectContentEditable(t.field)):t.value&&!this._hasChilds()?(t.value.focus(),i.selectContentEditable(t.value)):t.menu.focus();break;case"field":t.field&&this.fieldEditable?(t.field.focus(),i.selectContentEditable(t.field)):t.value&&!this._hasChilds()?(t.value.focus(),i.selectContentEditable(t.value)):this._hasChilds()?t.expand.focus():t.menu.focus();break;case"value":default:t.value&&!this._hasChilds()?(t.value.focus(),i.selectContentEditable(t.value)):t.field&&this.fieldEditable?(t.field.focus(),i.selectContentEditable(t.field)):this._hasChilds()?t.expand.focus():t.menu.focus() -}}},n.select=function(e){setTimeout(function(){i.selectContentEditable(e)},0)},n.prototype.blur=function(){this._getDomValue(!1),this._getDomField(!1)},n.prototype._duplicate=function(e){var t=e.clone();return this.insertAfter(t,e),t},n.prototype.containsNode=function(e){if(this==e)return!0;var t=this.childs;if(t)for(var i=0,n=t.length;n>i;i++)if(t[i].containsNode(e))return!0;return!1},n.prototype._move=function(e,t){if(e!=t){if(e.containsNode(this))throw new Error("Cannot move a field into a child of itself");e.parent&&e.parent.removeChild(e);var i=e.clone();e.clearDom(),t?this.insertBefore(i,t):this.appendChild(i)}},n.prototype.removeChild=function(e){if(this.childs){var t=this.childs.indexOf(e);if(-1!=t){e.hide(),delete e.searchField,delete e.searchValue;var i=this.childs.splice(t,1)[0];return this.updateDom({updateIndexes:!0}),i}}return void 0},n.prototype._remove=function(e){this.removeChild(e)},n.prototype.changeType=function(e){var t=this.type;if(t!=e){if("string"!=e&&"auto"!=e||"string"!=t&&"auto"!=t){var i,n=this.dom.tr?this.dom.tr.parentNode:void 0;i=this.expanded?this.getAppend():this.getDom();var o=i&&i.parentNode?i.nextSibling:void 0;this.hide(),this.clearDom(),this.type=e,"object"==e?(this.childs||(this.childs=[]),this.childs.forEach(function(e){e.clearDom(),delete e.index,e.fieldEditable=!0,void 0==e.field&&(e.field="")}),("string"==t||"auto"==t)&&(this.expanded=!0)):"array"==e?(this.childs||(this.childs=[]),this.childs.forEach(function(e,t){e.clearDom(),e.fieldEditable=!1,e.index=t}),("string"==t||"auto"==t)&&(this.expanded=!0)):this.expanded=!1,n&&(o?n.insertBefore(this.getDom(),o):n.appendChild(this.getDom())),this.showChilds()}else this.type=e;("auto"==e||"string"==e)&&(this.value="string"==e?String(this.value):this._stringCast(String(this.value)),this.focus()),this.updateDom({updateIndexes:!0})}},n.prototype._getDomValue=function(e){if(this.dom.value&&"array"!=this.type&&"object"!=this.type&&(this.valueInnerText=i.getInnerText(this.dom.value)),void 0!=this.valueInnerText)try{var t;if("string"==this.type)t=this._unescapeHTML(this.valueInnerText);else{var n=this._unescapeHTML(this.valueInnerText);t=this._stringCast(n)}if(t!==this.value){var o=this.value;this.value=t,this.editor._onAction("editValue",{node:this,oldValue:o,newValue:t,oldSelection:this.editor.selection,newSelection:this.editor.getSelection()})}}catch(s){if(this.value=void 0,1!=e)throw s}},n.prototype._updateDomValue=function(){var e=this.dom.value;if(e){var t=this.value,n="auto"==this.type?i.type(t):this.type,o="string"==n&&i.isUrl(t),s="";s=o&&!this.editable.value?"":"string"==n?"green":"number"==n?"red":"boolean"==n?"darkorange":this._hasChilds()?"":null===t?"#004ED0":"black",e.style.color=s;var r=""==String(this.value)&&"array"!=this.type&&"object"!=this.type;if(r?i.addClassName(e,"empty"):i.removeClassName(e,"empty"),o?i.addClassName(e,"url"):i.removeClassName(e,"url"),"array"==n||"object"==n){var a=this.childs?this.childs.length:0;e.title=this.type+" containing "+a+" items"}else"string"==n&&i.isUrl(t)?this.editable.value&&(e.title="Ctrl+Click or Ctrl+Enter to open url in new window"):e.title="";this.searchValueActive?i.addClassName(e,"highlight-active"):i.removeClassName(e,"highlight-active"),this.searchValue?i.addClassName(e,"highlight"):i.removeClassName(e,"highlight"),i.stripFormatting(e)}},n.prototype._updateDomField=function(){var e=this.dom.field;if(e){var t=""==String(this.field)&&"array"!=this.parent.type;t?i.addClassName(e,"empty"):i.removeClassName(e,"empty"),this.searchFieldActive?i.addClassName(e,"highlight-active"):i.removeClassName(e,"highlight-active"),this.searchField?i.addClassName(e,"highlight"):i.removeClassName(e,"highlight"),i.stripFormatting(e)}},n.prototype._getDomField=function(e){if(this.dom.field&&this.fieldEditable&&(this.fieldInnerText=i.getInnerText(this.dom.field)),void 0!=this.fieldInnerText)try{var t=this._unescapeHTML(this.fieldInnerText);if(t!==this.field){var n=this.field;this.field=t,this.editor._onAction("editField",{node:this,oldValue:n,newValue:t,oldSelection:this.editor.selection,newSelection:this.editor.getSelection()})}}catch(o){if(this.field=void 0,1!=e)throw o}},n.prototype.clearDom=function(){this.dom={}},n.prototype.getDom=function(){var e=this.dom;if(e.tr)return e.tr;if(this._updateEditability(),e.tr=document.createElement("tr"),e.tr.node=this,"tree"===this.editor.options.mode){var t=document.createElement("td");if(this.editable.field&&this.parent){var i=document.createElement("button");e.drag=i,i.className="dragarea",i.title="Drag to move this field (Alt+Shift+Arrows)",t.appendChild(i)}e.tr.appendChild(t);var n=document.createElement("td"),o=document.createElement("button");e.menu=o,o.className="contextmenu",o.title="Click to open the actions menu (Ctrl+M)",n.appendChild(e.menu),e.tr.appendChild(n)}var s=document.createElement("td");return e.tr.appendChild(s),e.tree=this._createDomTree(),s.appendChild(e.tree),this.updateDom({updateIndexes:!0}),e.tr},n.prototype._onDragStart=function(e){var t=this;this.mousemove||(this.mousemove=i.addEventListener(document,"mousemove",function(e){t._onDrag(e)})),this.mouseup||(this.mouseup=i.addEventListener(document,"mouseup",function(e){t._onDragEnd(e)})),this.editor.highlighter.lock(),this.drag={oldCursor:document.body.style.cursor,startParent:this.parent,startIndex:this.parent.childs.indexOf(this),mouseX:e.pageX,level:this.getLevel()},document.body.style.cursor="move",e.preventDefault()},n.prototype._onDrag=function(e){var t,s,r,a,d,h,l,c,u,p,f,m,v,g,y=e.pageY,x=e.pageX,b=!1;if(t=this.dom.tr,u=i.getAbsoluteTop(t),m=t.offsetHeight,u>y){s=t;do s=s.previousSibling,l=n.getNodeFromTarget(s),p=s?i.getAbsoluteTop(s):0;while(s&&p>y);l&&!l.parent&&(l=void 0),l||(h=t.parentNode.firstChild,s=h?h.nextSibling:void 0,l=n.getNodeFromTarget(s),l==this&&(l=void 0)),l&&(s=l.dom.tr,p=s?i.getAbsoluteTop(s):0,y>p+m&&(l=void 0)),l&&(l.parent.moveBefore(this,l),b=!0)}else if(d=this.expanded&&this.append?this.append.getDom():this.dom.tr,a=d?d.nextSibling:void 0){f=i.getAbsoluteTop(a),r=a;do c=n.getNodeFromTarget(r),r&&(v=r.nextSibling?i.getAbsoluteTop(r.nextSibling):0,g=r?v-f:0,1==c.parent.childs.length&&c.parent.childs[0]==this&&(u+=23)),r=r.nextSibling;while(r&&y>u+g);if(c&&c.parent){var C=x-this.drag.mouseX,E=Math.round(C/24/2),N=this.drag.level+E,_=c.getLevel();for(s=c.dom.tr.previousSibling;N>_&&s;){if(l=n.getNodeFromTarget(s),l==this||l._isChildOf(this));else{if(!(l instanceof o))break;var w=l.parent.childs;if(!(w.length>1||1==w.length&&w[0]!=this))break;c=n.getNodeFromTarget(s),_=c.getLevel()}s=s.previousSibling}d.nextSibling!=c.dom.tr&&(c.parent.moveBefore(this,c),b=!0)}}b&&(this.drag.mouseX=x,this.drag.level=this.getLevel()),this.editor.startAutoScroll(y),e.preventDefault()},n.prototype._onDragEnd=function(e){var t={node:this,startParent:this.drag.startParent,startIndex:this.drag.startIndex,endParent:this.parent,endIndex:this.parent.childs.indexOf(this)};(t.startParent!=t.endParent||t.startIndex!=t.endIndex)&&this.editor._onAction("moveNode",t),document.body.style.cursor=this.drag.oldCursor,this.editor.highlighter.unlock(),delete this.drag,this.mousemove&&(i.removeEventListener(document,"mousemove",this.mousemove),delete this.mousemove),this.mouseup&&(i.removeEventListener(document,"mouseup",this.mouseup),delete this.mouseup),this.editor.stopAutoScroll(),e.preventDefault()},n.prototype._isChildOf=function(e){for(var t=this.parent;t;){if(t==e)return!0;t=t.parent}return!1},n.prototype._createDomField=function(){return document.createElement("div")},n.prototype.setHighlight=function(e){this.dom.tr&&(this.dom.tr.className=e?"highlight":"",this.append&&this.append.setHighlight(e),this.childs&&this.childs.forEach(function(t){t.setHighlight(e)}))},n.prototype.updateValue=function(e){this.value=e,this.updateDom()},n.prototype.updateField=function(e){this.field=e,this.updateDom()},n.prototype.updateDom=function(e){var t=this.dom.tree;t&&(t.style.marginLeft=24*this.getLevel()+"px");var i=this.dom.field;if(i){this.fieldEditable?(i.contentEditable=this.editable.field,i.spellcheck=!1,i.className="field"):i.className="readonly";var n;n=void 0!=this.index?this.index:void 0!=this.field?this.field:this._hasChilds()?this.type:"",i.innerHTML=this._escapeHTML(n)}var o=this.dom.value;if(o){var s=this.childs?this.childs.length:0;o.innerHTML="array"==this.type?"["+s+"]":"object"==this.type?"{"+s+"}":this._escapeHTML(this.value)}this._updateDomField(),this._updateDomValue(),e&&1==e.updateIndexes&&this._updateDomIndexes(),e&&1==e.recurse&&this.childs&&this.childs.forEach(function(t){t.updateDom(e)}),this.append&&this.append.updateDom()},n.prototype._updateDomIndexes=function(){var e=this.dom.value,t=this.childs;e&&t&&("array"==this.type?t.forEach(function(e,t){e.index=t;var i=e.dom.field;i&&(i.innerHTML=t)}):"object"==this.type&&t.forEach(function(e){void 0!=e.index&&(delete e.index,void 0==e.field&&(e.field=""))}))},n.prototype._createDomValue=function(){var e;return"array"==this.type?(e=document.createElement("div"),e.className="readonly",e.innerHTML="[...]"):"object"==this.type?(e=document.createElement("div"),e.className="readonly",e.innerHTML="{...}"):!this.editable.value&&i.isUrl(this.value)?(e=document.createElement("a"),e.className="value",e.href=this.value,e.target="_blank",e.innerHTML=this._escapeHTML(this.value)):(e=document.createElement("div"),e.contentEditable=this.editable.value,e.spellcheck=!1,e.className="value",e.innerHTML=this._escapeHTML(this.value)),e},n.prototype._createDomExpandButton=function(){var e=document.createElement("button");return this._hasChilds()?(e.className=this.expanded?"expanded":"collapsed",e.title="Click to expand/collapse this field (Ctrl+E). \nCtrl+Click to expand/collapse including all childs."):(e.className="invisible",e.title=""),e},n.prototype._createDomTree=function(){var e=this.dom,t=document.createElement("table"),i=document.createElement("tbody");t.style.borderCollapse="collapse",t.className="values",t.appendChild(i);var n=document.createElement("tr");i.appendChild(n);var o=document.createElement("td");o.className="tree",n.appendChild(o),e.expand=this._createDomExpandButton(),o.appendChild(e.expand),e.tdExpand=o;var s=document.createElement("td");s.className="tree",n.appendChild(s),e.field=this._createDomField(),s.appendChild(e.field),e.tdField=s;var r=document.createElement("td");r.className="tree",n.appendChild(r),"object"!=this.type&&"array"!=this.type&&(r.appendChild(document.createTextNode(":")),r.className="separator"),e.tdSeparator=r;var a=document.createElement("td");return a.className="tree",n.appendChild(a),e.value=this._createDomValue(),a.appendChild(e.value),e.tdValue=a,t},n.prototype.onEvent=function(e){var t,n=e.type,o=e.target||e.srcElement,s=this.dom,r=this,a=this._hasChilds();if((o==s.drag||o==s.menu)&&("mouseover"==n?this.editor.highlighter.highlight(this):"mouseout"==n&&this.editor.highlighter.unhighlight()),"mousedown"==n&&o==s.drag&&this._onDragStart(e),"click"==n&&o==s.menu){var d=r.editor.highlighter;d.highlight(r),d.lock(),i.addClassName(s.menu,"selected"),this.showContextMenu(s.menu,function(){i.removeClassName(s.menu,"selected"),d.unlock(),d.unhighlight()})}if("click"==n&&o==s.expand&&a){var h=e.ctrlKey;this._onExpand(h)}var l=s.value;if(o==l)switch(n){case"focus":t=this;break;case"blur":case"change":this._getDomValue(!0),this._updateDomValue(),this.value&&(l.innerHTML=this._escapeHTML(this.value));break;case"input":this._getDomValue(!0),this._updateDomValue();break;case"keydown":case"mousedown":this.editor.selection=this.editor.getSelection();break;case"click":(e.ctrlKey||!this.editable.value)&&i.isUrl(this.value)&&window.open(this.value,"_blank");break;case"keyup":this._getDomValue(!0),this._updateDomValue();break;case"cut":case"paste":setTimeout(function(){r._getDomValue(!0),r._updateDomValue()},1)}var c=s.field;if(o==c)switch(n){case"focus":t=this;break;case"blur":case"change":this._getDomField(!0),this._updateDomField(),this.field&&(c.innerHTML=this._escapeHTML(this.field));break;case"input":this._getDomField(!0),this._updateDomField();break;case"keydown":case"mousedown":this.editor.selection=this.editor.getSelection();break;case"keyup":this._getDomField(!0),this._updateDomField();break;case"cut":case"paste":setTimeout(function(){r._getDomField(!0),r._updateDomField()},1)}var u=s.tree;if(o==u.parentNode)switch(n){case"click":var p=void 0!=e.offsetX?e.offsetX<24*(this.getLevel()+1):e.pageXn[i]?t:e[i]/g,">").replace(/ /g,"  ").replace(/^ /," ").replace(/ $/," "),i=JSON.stringify(t);return i.substring(1,i.length-1)},n.prototype._unescapeHTML=function(e){var t='"'+this._escapeJSON(e)+'"',n=i.parse(t);return n.replace(/</g,"<").replace(/>/g,">").replace(/ |\u00A0/g," ")},n.prototype._escapeJSON=function(e){for(var t="",i=0,n=e.length;n>i;){var o=e.charAt(i);"\n"==o?t+="\\n":"\\"==o?(t+=o,i++,o=e.charAt(i),-1=='"\\/bfnrtu'.indexOf(o)&&(t+="\\"),t+=o):t+='"'==o?'\\"':o,i++}return t};var o=t(n);return n}.apply(t,n),!(void 0!==o&&(e.exports=o))},function(e,t,i){var n,o;n=[i(9)],o=function(e){function t(t,i,n){function o(e){t.setMode(e);var i=t.dom&&t.dom.modeBox;i&&i.focus()}for(var s={code:{text:"Code",title:"Switch to code highlighter",click:function(){o("code")}},form:{text:"Form",title:"Switch to form editor",click:function(){o("form")}},text:{text:"Text",title:"Switch to plain text editor",click:function(){o("text")}},tree:{text:"Tree",title:"Switch to tree editor",click:function(){o("tree")}},view:{text:"View",title:"Switch to tree view",click:function(){o("view")}}},r=[],a=0;a',a.appendChild(c),o.submenuTitle&&(c.title=o.submenuTitle),l=c}else{var u=document.createElement("div");u.className="expand",d.appendChild(u),l=d}l.onclick=function(){n._onExpandItem(r),l.focus()};var p=[];r.subItems=p;var f=document.createElement("ul");r.ul=f,f.className="menu",f.style.height="0",a.appendChild(f),i(f,p,o.submenu)}else d.innerHTML='
'+o.text;t.push(r)}})}this.dom={};var n=this,o=this.dom;this.anchor=void 0,this.items=e,this.eventListeners={},this.selection=void 0,this.visibleSubmenu=void 0,this.onClose=t?t.close:void 0;var s=document.createElement("div");s.className="jsoneditor-contextmenu",o.menu=s;var r=document.createElement("ul");r.className="menu",s.appendChild(r),o.list=r,o.items=[];var a=document.createElement("button");o.focusButton=a;var d=document.createElement("li");d.style.overflow="hidden",d.style.height="0",d.appendChild(a),r.appendChild(d),i(r,this.dom.items,e),this.maxHeight=0,e.forEach(function(t){var i=24*(e.length+(t.submenu?t.submenu.length:0));n.maxHeight=Math.max(n.maxHeight,i)})}return t.prototype._getVisibleButtons=function(){var e=[],t=this;return this.dom.items.forEach(function(i){e.push(i.button),i.buttonExpand&&e.push(i.buttonExpand),i.subItems&&i==t.expandedItem&&i.subItems.forEach(function(t){e.push(t.button),t.buttonExpand&&e.push(t.buttonExpand)})}),e},t.visibleMenu=void 0,t.prototype.show=function(i){this.hide();var n=window.innerHeight,o=window.pageYOffset||document.scrollTop||0,s=n+o,r=i.offsetHeight,a=this.maxHeight,d=e.getAbsoluteLeft(i),h=e.getAbsoluteTop(i);s>h+r+a?(this.dom.menu.style.left=d+"px",this.dom.menu.style.top=h+r+"px",this.dom.menu.style.bottom=""):(this.dom.menu.style.left=d+"px",this.dom.menu.style.top="",this.dom.menu.style.bottom=n-h+"px"),document.body.appendChild(this.dom.menu);var l=this,c=this.dom.list;this.eventListeners.mousedown=e.addEventListener(document,"mousedown",function(e){var t=e.target;t==c||l._isChildOf(t,c)||(l.hide(),e.stopPropagation(),e.preventDefault())}),this.eventListeners.mousewheel=e.addEventListener(document,"mousewheel",function(e){e.stopPropagation(),e.preventDefault()}),this.eventListeners.keydown=e.addEventListener(document,"keydown",function(e){l._onKeyDown(e)}),this.selection=e.getSelection(),this.anchor=i,setTimeout(function(){l.dom.focusButton.focus()},0),t.visibleMenu&&t.visibleMenu.hide(),t.visibleMenu=this},t.prototype.hide=function(){this.dom.menu.parentNode&&(this.dom.menu.parentNode.removeChild(this.dom.menu),this.onClose&&this.onClose());for(var i in this.eventListeners)if(this.eventListeners.hasOwnProperty(i)){var n=this.eventListeners[i];n&&e.removeEventListener(document,i,n),delete this.eventListeners[i]}t.visibleMenu==this&&(t.visibleMenu=void 0)},t.prototype._onExpandItem=function(t){var i=this,n=t==this.expandedItem,o=this.expandedItem;if(o&&(o.ul.style.height="0",o.ul.style.padding="",setTimeout(function(){i.expandedItem!=o&&(o.ul.style.display="",e.removeClassName(o.ul.parentNode,"selected"))},300),this.expandedItem=void 0),!n){var s=t.ul;s.style.display="block";{s.clientHeight}setTimeout(function(){i.expandedItem==t&&(s.style.height=24*s.childNodes.length+"px",s.style.padding="5px 10px")},0),e.addClassName(s.parentNode,"selected"),this.expandedItem=t}},t.prototype._onKeyDown=function(t){var i,n,o,s,r=t.target,a=t.which,d=!1;27==a?(this.selection&&e.setSelection(this.selection),this.anchor&&this.anchor.focus(),this.hide(),d=!0):9==a?t.shiftKey?(i=this._getVisibleButtons(),n=i.indexOf(r),0==n&&(i[i.length-1].focus(),d=!0)):(i=this._getVisibleButtons(),n=i.indexOf(r),n==i.length-1&&(i[0].focus(),d=!0)):37==a?("expand"==r.className&&(i=this._getVisibleButtons(),n=i.indexOf(r),o=i[n-1],o&&o.focus()),d=!0):38==a?(i=this._getVisibleButtons(),n=i.indexOf(r),o=i[n-1],o&&"expand"==o.className&&(o=i[n-2]),o||(o=i[i.length-1]),o&&o.focus(),d=!0):39==a?(i=this._getVisibleButtons(),n=i.indexOf(r),s=i[n+1],s&&"expand"==s.className&&s.focus(),d=!0):40==a&&(i=this._getVisibleButtons(),n=i.indexOf(r),s=i[n+1],s&&"expand"==s.className&&(s=i[n+2]),s||(s=i[0]),s&&(s.focus(),d=!0),d=!0),d&&(t.stopPropagation(),t.preventDefault())},t.prototype._isChildOf=function(e,t){for(var i=e.parentNode;i;){if(i==t)return!0;i=i.parentNode}return!1},t}.apply(t,n),!(void 0!==o&&(e.exports=o))},function(e,t,i){var n,o;n=[i(9),i(3)],o=function(e,t){function i(i){function n(e){this.editor=e,this.dom={}}return n.prototype=new i,n.prototype.getDom=function(){var e=this.dom;if(e.tr)return e.tr;this._updateEditability();var t=document.createElement("tr");if(t.node=this,e.tr=t,this.editable.field){e.tdDrag=document.createElement("td");var i=document.createElement("td");e.tdMenu=i;var n=document.createElement("button");n.className="contextmenu",n.title="Click to open the actions menu (Ctrl+M)",e.menu=n,i.appendChild(e.menu)}var o=document.createElement("td"),s=document.createElement("div");return s.innerHTML="(empty)",s.className="readonly",o.appendChild(s),e.td=o,e.text=s,this.updateDom(),t},n.prototype.updateDom=function(){var e=this.dom,t=e.td;t&&(t.style.paddingLeft=24*this.getLevel()+26+"px");var i=e.text;i&&(i.innerHTML="(empty "+this.parent.type+")");var n=e.tr;this.isVisible()?e.tr.firstChild||(e.tdDrag&&n.appendChild(e.tdDrag),e.tdMenu&&n.appendChild(e.tdMenu),n.appendChild(t)):e.tr.firstChild&&(e.tdDrag&&n.removeChild(e.tdDrag),e.tdMenu&&n.removeChild(e.tdMenu),n.removeChild(t))},n.prototype.isVisible=function(){return 0==this.parent.childs.length},n.prototype.showContextMenu=function(t,n){var o=this,s=i.TYPE_TITLES,r=[{text:"Append",title:"Append a new field with type 'auto' (Ctrl+Shift+Ins)",submenuTitle:"Select the type of the field to be appended",className:"insert",click:function(){o._onAppend("","","auto")},submenu:[{text:"Auto",className:"type-auto",title:s.auto,click:function(){o._onAppend("","","auto")}},{text:"Array",className:"type-array",title:s.array,click:function(){o._onAppend("",[])}},{text:"Object",className:"type-object",title:s.object,click:function(){o._onAppend("",{})}},{text:"String",className:"type-string",title:s.string,click:function(){o._onAppend("","","string")}}]}],a=new e(r,{close:n});a.show(t)},n.prototype.onEvent=function(e){var i=e.type,n=e.target||e.srcElement,o=this.dom,s=o.menu;if(n==s&&("mouseover"==i?this.editor.highlighter.highlight(this.parent):"mouseout"==i&&this.editor.highlighter.unhighlight()),"click"==i&&n==o.menu){var r=this.editor.highlighter;r.highlight(this.parent),r.lock(),t.addClassName(o.menu,"selected"),this.showContextMenu(o.menu,function(){t.removeClassName(o.menu,"selected"),r.unlock(),r.unhighlight()})}"keydown"==i&&this.onKeyDown(e) -},n}return i}.apply(t,n),!(void 0!==o&&(e.exports=o))}])}); +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):"object"==typeof exports?exports.JSONEditor=t():e.JSONEditor=t()}(this,function(){return function(e){function t(n){if(i[n])return i[n].exports;var o=i[n]={exports:{},id:n,loaded:!1};return e[n].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var i={};return t.m=e,t.c=i,t.p="",t(0)}([function(e,t,i){function n(e,t,i){if(!(this instanceof n))throw new Error('JSONEditor constructor called without "new".');var o=r.getInternetExplorerVersion();if(-1!=o&&9>o)throw new Error("Unsupported browser, IE9 or newer required. Please install the newest version of your browser.");arguments.length&&this._create(e,t,i)}var o=i(1),s=i(2),r=i(3);n.modes={},n.prototype._create=function(e,t,i){this.container=e,this.options=t||{},this.json=i||{};var n=this.options.mode||"tree";this.setMode(n)},n.prototype._delete=function(){},n.prototype.set=function(e){this.json=e},n.prototype.get=function(){return this.json},n.prototype.setText=function(e){this.json=r.parse(e)},n.prototype.getText=function(){return JSON.stringify(this.json)},n.prototype.setName=function(e){this.options||(this.options={}),this.options.name=e},n.prototype.getName=function(){return this.options&&this.options.name},n.prototype.setMode=function(e){var t,i,o=this.container,s=r.extend({},this.options);s.mode=e;var a=n.modes[e];if(!a)throw new Error('Unknown mode "'+s.mode+'"');try{var d="text"==a.data;if(i=this.getName(),t=this[d?"getText":"get"](),this._delete(),r.clear(this),r.extend(this,a.mixin),this.create(o,s),this.setName(i),this[d?"setText":"set"](t),"function"==typeof a.load)try{a.load.call(this)}catch(h){}}catch(h){this._onError(h)}},n.prototype._onError=function(e){if("function"==typeof this.onError&&(r.log("WARNING: JSONEditor.onError is deprecated. Use options.error instead."),this.onError(e)),!this.options||"function"!=typeof this.options.error)throw e;this.options.error(e)},n.registerMode=function(e){var t,i;if(r.isArray(e))for(t=0;te&&i.scrollTop>0?(n+r-e)/3:e>s-r&&o+i.scrollTop3?(i.scrollTop+=o/3,n.animateCallback=t,n.animateTimeout=setTimeout(a,50)):(t&&t(!0),i.scrollTop=r,delete n.animateTimeout,delete n.animateCallback)};a()}else t&&t(!1)},h._createFrame=function(){function e(e){t._onEvent(e)}this.frame=document.createElement("div"),this.frame.className="jsoneditor",this.container.appendChild(this.frame);var t=this;this.frame.onclick=function(t){var i=t.target;e(t),"BUTTON"==i.nodeName&&t.preventDefault()},this.frame.oninput=e,this.frame.onchange=e,this.frame.onkeydown=e,this.frame.onkeyup=e,this.frame.oncut=e,this.frame.onpaste=e,this.frame.onmousedown=e,this.frame.onmouseup=e,this.frame.onmouseover=e,this.frame.onmouseout=e,d.addEventListener(this.frame,"focus",e,!0),d.addEventListener(this.frame,"blur",e,!0),this.frame.onfocusin=e,this.frame.onfocusout=e,this.menu=document.createElement("div"),this.menu.className="menu",this.frame.appendChild(this.menu);var i=document.createElement("button");i.className="expand-all",i.title="Expand all fields",i.onclick=function(){t.expandAll()},this.menu.appendChild(i);var n=document.createElement("button");if(n.title="Collapse all fields",n.className="collapse-all",n.onclick=function(){t.collapseAll()},this.menu.appendChild(n),this.history){var o=document.createElement("button");o.className="undo separator",o.title="Undo last action (Ctrl+Z)",o.onclick=function(){t._onUndo()},this.menu.appendChild(o),this.dom.undo=o;var r=document.createElement("button");r.className="redo",r.title="Redo (Ctrl+Shift+Z)",r.onclick=function(){t._onRedo()},this.menu.appendChild(r),this.dom.redo=r,this.history.onChange=function(){o.disabled=!t.history.canUndo(),r.disabled=!t.history.canRedo()},this.history.onChange()}if(this.options&&this.options.modes&&this.options.modes.length){var h=a.create(this,this.options.modes,this.options.mode);this.menu.appendChild(h),this.dom.modeBox=h}this.options.search&&(this.searchBox=new s(this,this.menu))},h._onUndo=function(){this.history&&(this.history.undo(),this.options.change&&this.options.change())},h._onRedo=function(){this.history&&(this.history.redo(),this.options.change&&this.options.change())},h._onEvent=function(e){var t=e.target;"keydown"==e.type&&this._onKeyDown(e),"focus"==e.type&&(c=t);var i=r.getNodeFromTarget(t);i&&i.onEvent(e)},h._onKeyDown=function(e){var t=e.which||e.keyCode,i=e.ctrlKey,n=e.shiftKey,o=!1;if(9==t&&setTimeout(function(){d.selectContentEditable(c)},0),this.searchBox)if(i&&70==t)this.searchBox.dom.search.focus(),this.searchBox.dom.search.select(),o=!0;else if(114==t||i&&71==t){var s=!0;n?this.searchBox.previous(s):this.searchBox.next(s),o=!0}this.history&&(i&&!n&&90==t?(this._onUndo(),o=!0):i&&n&&90==t&&(this._onRedo(),o=!0)),o&&(e.preventDefault(),e.stopPropagation())},h._createTable=function(){var e=document.createElement("div");e.className="outer",this.contentOuter=e,this.content=document.createElement("div"),this.content.className="tree",e.appendChild(this.content),this.table=document.createElement("table"),this.table.className="tree",this.content.appendChild(this.table);var t;this.colgroupContent=document.createElement("colgroup"),"tree"===this.options.mode&&(t=document.createElement("col"),t.width="24px",this.colgroupContent.appendChild(t)),t=document.createElement("col"),t.width="24px",this.colgroupContent.appendChild(t),t=document.createElement("col"),this.colgroupContent.appendChild(t),this.table.appendChild(this.colgroupContent),this.tbody=document.createElement("tbody"),this.table.appendChild(this.tbody),this.frame.appendChild(e)},e.exports=[{mode:"tree",mixin:h,data:"json"},{mode:"view",mixin:h,data:"json"},{mode:"form",mixin:h,data:"json"}]},function(e,t,i){var n=i(8),o=i(3),s={};s.create=function(e,t){t=t||{},this.options=t,this.indentation=t.indentation?Number(t.indentation):2,this.mode="code"==t.mode?"code":"text","code"==this.mode&&"undefined"==typeof ace&&(this.mode="text",o.log("WARNING: Cannot load code editor, Ace library not loaded. Falling back to plain text editor"));var i=this;this.container=e,this.dom={},this.editor=void 0,this.textarea=void 0,this.width=e.clientWidth,this.height=e.clientHeight,this.frame=document.createElement("div"),this.frame.className="jsoneditor",this.frame.onclick=function(e){e.preventDefault()},this.frame.onkeydown=function(e){i._onKeyDown(e)},this.menu=document.createElement("div"),this.menu.className="menu",this.frame.appendChild(this.menu);var s=document.createElement("button");s.className="format",s.title="Format JSON data, with proper indentation and line feeds (Ctrl+\\)",this.menu.appendChild(s),s.onclick=function(){try{i.format()}catch(e){i._onError(e)}};var r=document.createElement("button");if(r.className="compact",r.title="Compact JSON data, remove all whitespaces (Ctrl+Shift+\\)",this.menu.appendChild(r),r.onclick=function(){try{i.compact()}catch(e){i._onError(e)}},this.options&&this.options.modes&&this.options.modes.length){var a=n.create(this,this.options.modes,this.options.mode);this.menu.appendChild(a),this.dom.modeBox=a}if(this.content=document.createElement("div"),this.content.className="outer",this.frame.appendChild(this.content),this.container.appendChild(this.frame),"code"==this.mode){this.editorDom=document.createElement("div"),this.editorDom.style.height="100%",this.editorDom.style.width="100%",this.content.appendChild(this.editorDom);var d=ace.edit(this.editorDom);d.setTheme("ace/theme/jsoneditor"),d.setShowPrintMargin(!1),d.setFontSize(13),d.getSession().setMode("ace/mode/json"),d.getSession().setTabSize(this.indentation),d.getSession().setUseSoftTabs(!0),d.getSession().setUseWrapMode(!0),this.editor=d;var h=document.createElement("a");h.appendChild(document.createTextNode("powered by ace")),h.href="http://ace.ajax.org",h.target="_blank",h.className="poweredBy",h.onclick=function(){window.open(h.href,h.target)},this.menu.appendChild(h),t.change&&d.on("change",function(){t.change()})}else{var l=document.createElement("textarea");l.className="text",l.spellcheck=!1,this.content.appendChild(l),this.textarea=l,t.change&&(null===this.textarea.oninput?this.textarea.oninput=function(){t.change()}:this.textarea.onchange=function(){t.change()})}},s._onKeyDown=function(e){var t=e.which||e.keyCode,i=!1;220==t&&e.ctrlKey&&(e.shiftKey?this.compact():this.format(),i=!0),i&&(e.preventDefault(),e.stopPropagation())},s._delete=function(){this.frame&&this.container&&this.frame.parentNode==this.container&&this.container.removeChild(this.frame)},s._onError=function(e){if("function"==typeof this.onError&&(o.log("WARNING: JSONEditor.onError is deprecated. Use options.error instead."),this.onError(e)),!this.options||"function"!=typeof this.options.error)throw e;this.options.error(e)},s.compact=function(){var e=this.get(),t=JSON.stringify(e);this.setText(t)},s.format=function(){var e=this.get(),t=JSON.stringify(e,null,this.indentation);this.setText(t)},s.focus=function(){this.textarea&&this.textarea.focus(),this.editor&&this.editor.focus()},s.resize=function(){if(this.editor){var e=!1;this.editor.resize(e)}},s.set=function(e){this.setText(JSON.stringify(e,null,this.indentation))},s.get=function(){var e,t=this.getText();try{e=o.parse(t)}catch(i){t=o.sanitize(t),this.setText(t),e=o.parse(t)}return e},s.getText=function(){return this.textarea?this.textarea.value:this.editor?this.editor.getValue():""},s.setText=function(e){this.textarea&&(this.textarea.value=e),this.editor&&this.editor.setValue(e,-1)},e.exports=[{mode:"text",mixin:s,data:"text",load:s.format},{mode:"code",mixin:s,data:"text",load:s.format}]},function(e,t){t.parse=function(e){try{return JSON.parse(e)}catch(i){throw t.validate(e),i}},t.sanitize=function(e){for(var t=[],i=!1,n=0;nn;n++){var s=i[n];s.style&&s.removeAttribute("style");var r=s.attributes;if(r)for(var a=r.length-1;a>=0;a--){var d=r[a];1==d.specified&&s.removeAttribute(d.name)}t.stripFormatting(s)}},t.setEndOfContentEditable=function(e){var t,i;document.createRange&&(t=document.createRange(),t.selectNodeContents(e),t.collapse(!1),i=window.getSelection(),i.removeAllRanges(),i.addRange(t))},t.selectContentEditable=function(e){if(e&&"DIV"==e.nodeName){var t,i;window.getSelection&&document.createRange&&(i=document.createRange(),i.selectNodeContents(e),t=window.getSelection(),t.removeAllRanges(),t.addRange(i))}},t.getSelection=function(){if(window.getSelection){var e=window.getSelection();if(e.getRangeAt&&e.rangeCount)return e.getRangeAt(0)}return null},t.setSelection=function(e){if(e&&window.getSelection){var t=window.getSelection();t.removeAllRanges(),t.addRange(e)}},t.getSelectionOffset=function(){var e=t.getSelection();return e&&"startOffset"in e&&"endOffset"in e&&e.startContainer&&e.startContainer==e.endContainer?{startOffset:e.startOffset,endOffset:e.endOffset,container:e.startContainer.parentNode}:null},t.setSelectionOffset=function(e){if(document.createRange&&window.getSelection){var i=window.getSelection();if(i){var n=document.createRange();n.setStart(e.container.firstChild,e.startOffset),n.setEnd(e.container.firstChild,e.endOffset),t.setSelection(n)}}},t.getInnerText=function(e,i){var n=void 0==i;if(n&&(i={text:"",flush:function(){var e=this.text;return this.text="",e},set:function(e){this.text=e}}),e.nodeValue)return i.flush()+e.nodeValue;if(e.hasChildNodes()){for(var o=e.childNodes,s="",r=0,a=o.length;a>r;r++){var d=o[r];if("DIV"==d.nodeName||"P"==d.nodeName){var h=o[r-1],l=h?h.nodeName:void 0;l&&"DIV"!=l&&"P"!=l&&"BR"!=l&&(s+="\n",i.flush()),s+=t.getInnerText(d,i),i.set("\n")}else"BR"==d.nodeName?(s+=i.flush(),i.set("\n")):s+=t.getInnerText(d,i)}return s}return"P"==e.nodeName&&-1!=t.getInternetExplorerVersion()?i.flush():""},t.getInternetExplorerVersion=function(){if(-1==n){var e=-1;if("Microsoft Internet Explorer"==navigator.appName){var t=navigator.userAgent,i=new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})");null!=i.exec(t)&&(e=parseFloat(RegExp.$1))}n=e}return n},t.isFirefox=function(){return-1!=navigator.userAgent.indexOf("Firefox")};var n=-1;t.addEventListener=function(e,i,n,o){if(e.addEventListener)return void 0===o&&(o=!1),"mousewheel"===i&&t.isFirefox()&&(i="DOMMouseScroll"),e.addEventListener(i,n,o),n;if(e.attachEvent){var s=function(){return n.call(e,window.event)};return e.attachEvent("on"+i,s),s}},t.removeEventListener=function(e,i,n,o){e.removeEventListener?(void 0===o&&(o=!1),"mousewheel"===i&&t.isFirefox()&&(i="DOMMouseScroll"),e.removeEventListener(i,n,o)):e.detachEvent&&e.detachEvent("on"+i,n)}},function(e){function t(){this.locked=!1}t.prototype.highlight=function(e){this.locked||(this.node!=e&&(this.node&&this.node.setHighlight(!1),this.node=e,this.node.setHighlight(!0)),this._cancelUnhighlight())},t.prototype.unhighlight=function(){if(!this.locked){var e=this;this.node&&(this._cancelUnhighlight(),this.unhighlightTimer=setTimeout(function(){e.node.setHighlight(!1),e.node=void 0,e.unhighlightTimer=void 0},0))}},t.prototype._cancelUnhighlight=function(){this.unhighlightTimer&&(clearTimeout(this.unhighlightTimer),this.unhighlightTimer=void 0)},t.prototype.lock=function(){this.locked=!0},t.prototype.unlock=function(){this.locked=!1},e.exports=t},function(e,t,i){function n(e){this.editor=e,this.clear(),this.actions={editField:{undo:function(e){e.node.updateField(e.oldValue)},redo:function(e){e.node.updateField(e.newValue)}},editValue:{undo:function(e){e.node.updateValue(e.oldValue)},redo:function(e){e.node.updateValue(e.newValue)}},appendNode:{undo:function(e){e.parent.removeChild(e.node)},redo:function(e){e.parent.appendChild(e.node)}},insertBeforeNode:{undo:function(e){e.parent.removeChild(e.node)},redo:function(e){e.parent.insertBefore(e.node,e.beforeNode)}},insertAfterNode:{undo:function(e){e.parent.removeChild(e.node)},redo:function(e){e.parent.insertAfter(e.node,e.afterNode)}},removeNode:{undo:function(e){var t=e.parent,i=t.childs[e.index]||t.append;t.insertBefore(e.node,i)},redo:function(e){e.parent.removeChild(e.node)}},duplicateNode:{undo:function(e){e.parent.removeChild(e.clone)},redo:function(e){e.parent.insertAfter(e.clone,e.node)}},changeType:{undo:function(e){e.node.changeType(e.oldType)},redo:function(e){e.node.changeType(e.newType)}},moveNode:{undo:function(e){e.startParent.moveTo(e.node,e.startIndex)},redo:function(e){e.endParent.moveTo(e.node,e.endIndex)}},sort:{undo:function(e){var t=e.node;t.hideChilds(),t.sort=e.oldSort,t.childs=e.oldChilds,t.showChilds()},redo:function(e){var t=e.node;t.hideChilds(),t.sort=e.newSort,t.childs=e.newChilds,t.showChilds()}}}}var o=i(3);n.prototype.onChange=function(){},n.prototype.add=function(e,t){this.index++,this.history[this.index]={action:e,params:t,timestamp:new Date},this.index=0},n.prototype.canRedo=function(){return this.indexthis.results.length-1&&(t=0),this._setActiveResult(t,e)}},t.prototype.previous=function(e){if(void 0!=this.results){var t=this.results.length-1,i=void 0!=this.resultIndex?this.resultIndex-1:t;0>i&&(i=t),this._setActiveResult(i,e)}},t.prototype._setActiveResult=function(e,t){if(this.activeResult){var i=this.activeResult.node,n=this.activeResult.elem;"field"==n?delete i.searchFieldActive:delete i.searchValueActive,i.updateDom()}if(!this.results||!this.results[e])return this.resultIndex=void 0,void(this.activeResult=void 0);this.resultIndex=e;var o=this.results[this.resultIndex].node,s=this.results[this.resultIndex].elem;"field"==s?o.searchFieldActive=!0:o.searchValueActive=!0,this.activeResult=this.results[this.resultIndex],o.updateDom(),o.scrollTo(function(){t&&o.focus(s)})},t.prototype._clearDelay=function(){void 0!=this.timeout&&(clearTimeout(this.timeout),delete this.timeout)},t.prototype._onDelayedSearch=function(){this._clearDelay();var e=this;this.timeout=setTimeout(function(t){e._onSearch(t)},this.delay)},t.prototype._onSearch=function(e,t){this._clearDelay();var i=this.dom.search.value,n=i.length>0?i:void 0;if(n!=this.lastText||t)if(this.lastText=n,this.results=this.editor.search(n),this._setActiveResult(void 0),void 0!=n){var o=this.results.length;switch(o){case 0:this.dom.results.innerHTML="no results";break;case 1:this.dom.results.innerHTML="1 result";break;default:this.dom.results.innerHTML=o+" results"}}else this.dom.results.innerHTML=""},t.prototype._onKeyDown=function(e){var t=e.which;27==t?(this.dom.search.value="",this._onSearch(e),e.preventDefault(),e.stopPropagation()):13==t&&(e.ctrlKey?this._onSearch(e,!0):e.shiftKey?this.previous():this.next(),e.preventDefault(),e.stopPropagation())},t.prototype._onKeyUp=function(e){var t=e.keyCode;27!=t&&13!=t&&this._onDelayedSearch(e)},e.exports=t},function(e,t,i){function n(e,t){this.editor=e,this.dom={},this.expanded=!1,t&&t instanceof Object?(this.setField(t.field,t.fieldEditable),this.setValue(t.value,t.type)):(this.setField(""),this.setValue(null))}var o=i(9),s=i(10),r=i(3);n.prototype._updateEditability=function(){if(this.editable={field:!0,value:!0},this.editor&&(this.editable.field="tree"===this.editor.options.mode,this.editable.value="view"!==this.editor.options.mode,"tree"===this.editor.options.mode&&"function"==typeof this.editor.options.editable)){var e=this.editor.options.editable({field:this.field,value:this.value,path:this.path()});"boolean"==typeof e?(this.editable.field=e,this.editable.value=e):("boolean"==typeof e.field&&(this.editable.field=e.field),"boolean"==typeof e.value&&(this.editable.value=e.value))}},n.prototype.path=function(){for(var e=this,t=[];e;){var i=void 0!=e.field?e.field:e.index;void 0!==i&&t.unshift(i),e=e.parent}return t},n.prototype.setParent=function(e){this.parent=e},n.prototype.setField=function(e,t){this.field=e,this.fieldEditable=1==t},n.prototype.getField=function(){return void 0===this.field&&this._getDomField(),this.field},n.prototype.setValue=function(e,t){var i,o,s=this.childs;if(s)for(;s.length;)this.removeChild(s[0]);if(this.type=this._getType(e),t&&t!=this.type){if("string"!=t||"auto"!=this.type)throw new Error('Type mismatch: cannot cast value of type "'+this.type+' to the specified type "'+t+'"');this.type=t}if("array"==this.type){this.childs=[];for(var r=0,a=e.length;a>r;r++)i=e[r],void 0===i||i instanceof Function||(o=new n(this.editor,{value:i}),this.appendChild(o));this.value=""}else if("object"==this.type){this.childs=[];for(var d in e)e.hasOwnProperty(d)&&(i=e[d],void 0===i||i instanceof Function||(o=new n(this.editor,{field:d,value:i}),this.appendChild(o)));this.value=""}else this.childs=void 0,this.value=e},n.prototype.getValue=function(){if("array"==this.type){var e=[];return this.childs.forEach(function(t){e.push(t.getValue())}),e}if("object"==this.type){var t={};return this.childs.forEach(function(e){t[e.getField()]=e.getValue()}),t}return void 0===this.value&&this._getDomValue(),this.value},n.prototype.getLevel=function(){return this.parent?this.parent.getLevel()+1:0},n.prototype.clone=function(){var e=new n(this.editor);if(e.type=this.type,e.field=this.field,e.fieldInnerText=this.fieldInnerText,e.fieldEditable=this.fieldEditable,e.value=this.value,e.valueInnerText=this.valueInnerText,e.expanded=this.expanded,this.childs){var t=[];this.childs.forEach(function(i){var n=i.clone();n.setParent(e),t.push(n)}),e.childs=t}else e.childs=void 0;return e},n.prototype.expand=function(e){this.childs&&(this.expanded=!0,this.dom.expand&&(this.dom.expand.className="expanded"),this.showChilds(),0!=e&&this.childs.forEach(function(t){t.expand(e)}))},n.prototype.collapse=function(e){this.childs&&(this.hideChilds(),0!=e&&this.childs.forEach(function(t){t.collapse(e)}),this.dom.expand&&(this.dom.expand.className="collapsed"),this.expanded=!1)},n.prototype.showChilds=function(){var e=this.childs;if(e&&this.expanded){var t=this.dom.tr,i=t?t.parentNode:void 0;if(i){var n=this.getAppend(),o=t.nextSibling;o?i.insertBefore(n,o):i.appendChild(n),this.childs.forEach(function(e){i.insertBefore(e.getDom(),n),e.showChilds()})}}},n.prototype.hide=function(){var e=this.dom.tr,t=e?e.parentNode:void 0;t&&t.removeChild(e),this.hideChilds()},n.prototype.hideChilds=function(){var e=this.childs;if(e&&this.expanded){var t=this.getAppend();t.parentNode&&t.parentNode.removeChild(t),this.childs.forEach(function(e){e.hide()})}},n.prototype.appendChild=function(e){if(this._hasChilds()){if(e.setParent(this),e.fieldEditable="object"==this.type,"array"==this.type&&(e.index=this.childs.length),this.childs.push(e),this.expanded){var t=e.getDom(),i=this.getAppend(),n=i?i.parentNode:void 0;i&&n&&n.insertBefore(t,i),e.showChilds()}this.updateDom({updateIndexes:!0}),e.updateDom({recurse:!0})}},n.prototype.moveBefore=function(e,t){if(this._hasChilds()){var i=this.dom.tr?this.dom.tr.parentNode:void 0;if(i){var n=document.createElement("tr");n.style.height=i.clientHeight+"px",i.appendChild(n)}e.parent&&e.parent.removeChild(e),t instanceof a?this.appendChild(e):this.insertBefore(e,t),i&&i.removeChild(n)}},n.prototype.moveTo=function(e,t){if(e.parent==this){var i=this.childs.indexOf(e);t>i&&t++}var n=this.childs[t]||this.append;this.moveBefore(e,n)},n.prototype.insertBefore=function(e,t){if(this._hasChilds()){if(t==this.append)e.setParent(this),e.fieldEditable="object"==this.type,this.childs.push(e);else{var i=this.childs.indexOf(t);if(-1==i)throw new Error("Node not found");e.setParent(this),e.fieldEditable="object"==this.type,this.childs.splice(i,0,e)}if(this.expanded){var n=e.getDom(),o=t.getDom(),s=o?o.parentNode:void 0;o&&s&&s.insertBefore(n,o),e.showChilds()}this.updateDom({updateIndexes:!0}),e.updateDom({recurse:!0})}},n.prototype.insertAfter=function(e,t){if(this._hasChilds()){var i=this.childs.indexOf(t),n=this.childs[i+1];n?this.insertBefore(e,n):this.appendChild(e)}},n.prototype.search=function(e){var t,i=[],n=e?e.toLowerCase():void 0;if(delete this.searchField,delete this.searchValue,void 0!=this.field){var o=String(this.field).toLowerCase();t=o.indexOf(n),-1!=t&&(this.searchField=!0,i.push({node:this,elem:"field"})),this._updateDomField()}if(this._hasChilds()){if(this.childs){var s=[];this.childs.forEach(function(t){s=s.concat(t.search(e))}),i=i.concat(s)}if(void 0!=n){var r=!1;0==s.length?this.collapse(r):this.expand(r)}}else{if(void 0!=this.value){var a=String(this.value).toLowerCase();t=a.indexOf(n),-1!=t&&(this.searchValue=!0,i.push({node:this,elem:"value"}))}this._updateDomValue()}return i},n.prototype.scrollTo=function(e){if(!this.dom.tr||!this.dom.tr.parentNode)for(var t=this.parent,i=!1;t;)t.expand(i),t=t.parent;this.dom.tr&&this.dom.tr.parentNode&&this.editor.scrollTo(this.dom.tr.offsetTop,e)},n.focusElement=void 0,n.prototype.focus=function(e){if(n.focusElement=e,this.dom.tr&&this.dom.tr.parentNode){var t=this.dom;switch(e){case"drag":t.drag?t.drag.focus():t.menu.focus();break;case"menu":t.menu.focus();break;case"expand":this._hasChilds()?t.expand.focus():t.field&&this.fieldEditable?(t.field.focus(),r.selectContentEditable(t.field)):t.value&&!this._hasChilds()?(t.value.focus(),r.selectContentEditable(t.value)):t.menu.focus();break;case"field":t.field&&this.fieldEditable?(t.field.focus(),r.selectContentEditable(t.field)):t.value&&!this._hasChilds()?(t.value.focus(),r.selectContentEditable(t.value)):this._hasChilds()?t.expand.focus():t.menu.focus();break;case"value":default:t.value&&!this._hasChilds()?(t.value.focus(),r.selectContentEditable(t.value)):t.field&&this.fieldEditable?(t.field.focus(),r.selectContentEditable(t.field)):this._hasChilds()?t.expand.focus():t.menu.focus()}}},n.select=function(e){setTimeout(function(){r.selectContentEditable(e)},0)},n.prototype.blur=function(){this._getDomValue(!1),this._getDomField(!1)},n.prototype._duplicate=function(e){var t=e.clone();return this.insertAfter(t,e),t},n.prototype.containsNode=function(e){if(this==e)return!0;var t=this.childs;if(t)for(var i=0,n=t.length;n>i;i++)if(t[i].containsNode(e))return!0;return!1},n.prototype._move=function(e,t){if(e!=t){if(e.containsNode(this))throw new Error("Cannot move a field into a child of itself"); +e.parent&&e.parent.removeChild(e);var i=e.clone();e.clearDom(),t?this.insertBefore(i,t):this.appendChild(i)}},n.prototype.removeChild=function(e){if(this.childs){var t=this.childs.indexOf(e);if(-1!=t){e.hide(),delete e.searchField,delete e.searchValue;var i=this.childs.splice(t,1)[0];return this.updateDom({updateIndexes:!0}),i}}return void 0},n.prototype._remove=function(e){this.removeChild(e)},n.prototype.changeType=function(e){var t=this.type;if(t!=e){if("string"!=e&&"auto"!=e||"string"!=t&&"auto"!=t){var i,n=this.dom.tr?this.dom.tr.parentNode:void 0;i=this.expanded?this.getAppend():this.getDom();var o=i&&i.parentNode?i.nextSibling:void 0;this.hide(),this.clearDom(),this.type=e,"object"==e?(this.childs||(this.childs=[]),this.childs.forEach(function(e){e.clearDom(),delete e.index,e.fieldEditable=!0,void 0==e.field&&(e.field="")}),("string"==t||"auto"==t)&&(this.expanded=!0)):"array"==e?(this.childs||(this.childs=[]),this.childs.forEach(function(e,t){e.clearDom(),e.fieldEditable=!1,e.index=t}),("string"==t||"auto"==t)&&(this.expanded=!0)):this.expanded=!1,n&&(o?n.insertBefore(this.getDom(),o):n.appendChild(this.getDom())),this.showChilds()}else this.type=e;("auto"==e||"string"==e)&&(this.value="string"==e?String(this.value):this._stringCast(String(this.value)),this.focus()),this.updateDom({updateIndexes:!0})}},n.prototype._getDomValue=function(e){if(this.dom.value&&"array"!=this.type&&"object"!=this.type&&(this.valueInnerText=r.getInnerText(this.dom.value)),void 0!=this.valueInnerText)try{var t;if("string"==this.type)t=this._unescapeHTML(this.valueInnerText);else{var i=this._unescapeHTML(this.valueInnerText);t=this._stringCast(i)}if(t!==this.value){var n=this.value;this.value=t,this.editor._onAction("editValue",{node:this,oldValue:n,newValue:t,oldSelection:this.editor.selection,newSelection:this.editor.getSelection()})}}catch(o){if(this.value=void 0,1!=e)throw o}},n.prototype._updateDomValue=function(){var e=this.dom.value;if(e){var t=this.value,i="auto"==this.type?r.type(t):this.type,n="string"==i&&r.isUrl(t),o="";o=n&&!this.editable.value?"":"string"==i?"green":"number"==i?"red":"boolean"==i?"darkorange":this._hasChilds()?"":null===t?"#004ED0":"black",e.style.color=o;var s=""==String(this.value)&&"array"!=this.type&&"object"!=this.type;if(s?r.addClassName(e,"empty"):r.removeClassName(e,"empty"),n?r.addClassName(e,"url"):r.removeClassName(e,"url"),"array"==i||"object"==i){var a=this.childs?this.childs.length:0;e.title=this.type+" containing "+a+" items"}else"string"==i&&r.isUrl(t)?this.editable.value&&(e.title="Ctrl+Click or Ctrl+Enter to open url in new window"):e.title="";this.searchValueActive?r.addClassName(e,"highlight-active"):r.removeClassName(e,"highlight-active"),this.searchValue?r.addClassName(e,"highlight"):r.removeClassName(e,"highlight"),r.stripFormatting(e)}},n.prototype._updateDomField=function(){var e=this.dom.field;if(e){var t=""==String(this.field)&&"array"!=this.parent.type;t?r.addClassName(e,"empty"):r.removeClassName(e,"empty"),this.searchFieldActive?r.addClassName(e,"highlight-active"):r.removeClassName(e,"highlight-active"),this.searchField?r.addClassName(e,"highlight"):r.removeClassName(e,"highlight"),r.stripFormatting(e)}},n.prototype._getDomField=function(e){if(this.dom.field&&this.fieldEditable&&(this.fieldInnerText=r.getInnerText(this.dom.field)),void 0!=this.fieldInnerText)try{var t=this._unescapeHTML(this.fieldInnerText);if(t!==this.field){var i=this.field;this.field=t,this.editor._onAction("editField",{node:this,oldValue:i,newValue:t,oldSelection:this.editor.selection,newSelection:this.editor.getSelection()})}}catch(n){if(this.field=void 0,1!=e)throw n}},n.prototype.clearDom=function(){this.dom={}},n.prototype.getDom=function(){var e=this.dom;if(e.tr)return e.tr;if(this._updateEditability(),e.tr=document.createElement("tr"),e.tr.node=this,"tree"===this.editor.options.mode){var t=document.createElement("td");if(this.editable.field&&this.parent){var i=document.createElement("button");e.drag=i,i.className="dragarea",i.title="Drag to move this field (Alt+Shift+Arrows)",t.appendChild(i)}e.tr.appendChild(t);var n=document.createElement("td"),o=document.createElement("button");e.menu=o,o.className="contextmenu",o.title="Click to open the actions menu (Ctrl+M)",n.appendChild(e.menu),e.tr.appendChild(n)}var s=document.createElement("td");return e.tr.appendChild(s),e.tree=this._createDomTree(),s.appendChild(e.tree),this.updateDom({updateIndexes:!0}),e.tr},n.prototype._onDragStart=function(e){var t=this;this.mousemove||(this.mousemove=r.addEventListener(document,"mousemove",function(e){t._onDrag(e)})),this.mouseup||(this.mouseup=r.addEventListener(document,"mouseup",function(e){t._onDragEnd(e)})),this.editor.highlighter.lock(),this.drag={oldCursor:document.body.style.cursor,startParent:this.parent,startIndex:this.parent.childs.indexOf(this),mouseX:e.pageX,level:this.getLevel()},document.body.style.cursor="move",e.preventDefault()},n.prototype._onDrag=function(e){var t,i,o,s,d,h,l,c,u,p,f,m,v,g,y=e.pageY,x=e.pageX,b=!1;if(t=this.dom.tr,u=r.getAbsoluteTop(t),m=t.offsetHeight,u>y){i=t;do i=i.previousSibling,l=n.getNodeFromTarget(i),p=i?r.getAbsoluteTop(i):0;while(i&&p>y);l&&!l.parent&&(l=void 0),l||(h=t.parentNode.firstChild,i=h?h.nextSibling:void 0,l=n.getNodeFromTarget(i),l==this&&(l=void 0)),l&&(i=l.dom.tr,p=i?r.getAbsoluteTop(i):0,y>p+m&&(l=void 0)),l&&(l.parent.moveBefore(this,l),b=!0)}else if(d=this.expanded&&this.append?this.append.getDom():this.dom.tr,s=d?d.nextSibling:void 0){f=r.getAbsoluteTop(s),o=s;do c=n.getNodeFromTarget(o),o&&(v=o.nextSibling?r.getAbsoluteTop(o.nextSibling):0,g=o?v-f:0,1==c.parent.childs.length&&c.parent.childs[0]==this&&(u+=23)),o=o.nextSibling;while(o&&y>u+g);if(c&&c.parent){var C=x-this.drag.mouseX,E=Math.round(C/24/2),N=this.drag.level+E,_=c.getLevel();for(i=c.dom.tr.previousSibling;N>_&&i;){if(l=n.getNodeFromTarget(i),l==this||l._isChildOf(this));else{if(!(l instanceof a))break;var w=l.parent.childs;if(!(w.length>1||1==w.length&&w[0]!=this))break;c=n.getNodeFromTarget(i),_=c.getLevel()}i=i.previousSibling}d.nextSibling!=c.dom.tr&&(c.parent.moveBefore(this,c),b=!0)}}b&&(this.drag.mouseX=x,this.drag.level=this.getLevel()),this.editor.startAutoScroll(y),e.preventDefault()},n.prototype._onDragEnd=function(e){var t={node:this,startParent:this.drag.startParent,startIndex:this.drag.startIndex,endParent:this.parent,endIndex:this.parent.childs.indexOf(this)};(t.startParent!=t.endParent||t.startIndex!=t.endIndex)&&this.editor._onAction("moveNode",t),document.body.style.cursor=this.drag.oldCursor,this.editor.highlighter.unlock(),delete this.drag,this.mousemove&&(r.removeEventListener(document,"mousemove",this.mousemove),delete this.mousemove),this.mouseup&&(r.removeEventListener(document,"mouseup",this.mouseup),delete this.mouseup),this.editor.stopAutoScroll(),e.preventDefault()},n.prototype._isChildOf=function(e){for(var t=this.parent;t;){if(t==e)return!0;t=t.parent}return!1},n.prototype._createDomField=function(){return document.createElement("div")},n.prototype.setHighlight=function(e){this.dom.tr&&(this.dom.tr.className=e?"highlight":"",this.append&&this.append.setHighlight(e),this.childs&&this.childs.forEach(function(t){t.setHighlight(e)}))},n.prototype.updateValue=function(e){this.value=e,this.updateDom()},n.prototype.updateField=function(e){this.field=e,this.updateDom()},n.prototype.updateDom=function(e){var t=this.dom.tree;t&&(t.style.marginLeft=24*this.getLevel()+"px");var i=this.dom.field;if(i){this.fieldEditable?(i.contentEditable=this.editable.field,i.spellcheck=!1,i.className="field"):i.className="readonly";var n;n=void 0!=this.index?this.index:void 0!=this.field?this.field:this._hasChilds()?this.type:"",i.innerHTML=this._escapeHTML(n)}var o=this.dom.value;if(o){var s=this.childs?this.childs.length:0;o.innerHTML="array"==this.type?"["+s+"]":"object"==this.type?"{"+s+"}":this._escapeHTML(this.value)}this._updateDomField(),this._updateDomValue(),e&&1==e.updateIndexes&&this._updateDomIndexes(),e&&1==e.recurse&&this.childs&&this.childs.forEach(function(t){t.updateDom(e)}),this.append&&this.append.updateDom()},n.prototype._updateDomIndexes=function(){var e=this.dom.value,t=this.childs;e&&t&&("array"==this.type?t.forEach(function(e,t){e.index=t;var i=e.dom.field;i&&(i.innerHTML=t)}):"object"==this.type&&t.forEach(function(e){void 0!=e.index&&(delete e.index,void 0==e.field&&(e.field=""))}))},n.prototype._createDomValue=function(){var e;return"array"==this.type?(e=document.createElement("div"),e.className="readonly",e.innerHTML="[...]"):"object"==this.type?(e=document.createElement("div"),e.className="readonly",e.innerHTML="{...}"):!this.editable.value&&r.isUrl(this.value)?(e=document.createElement("a"),e.className="value",e.href=this.value,e.target="_blank",e.innerHTML=this._escapeHTML(this.value)):(e=document.createElement("div"),e.contentEditable=this.editable.value,e.spellcheck=!1,e.className="value",e.innerHTML=this._escapeHTML(this.value)),e},n.prototype._createDomExpandButton=function(){var e=document.createElement("button");return this._hasChilds()?(e.className=this.expanded?"expanded":"collapsed",e.title="Click to expand/collapse this field (Ctrl+E). \nCtrl+Click to expand/collapse including all childs."):(e.className="invisible",e.title=""),e},n.prototype._createDomTree=function(){var e=this.dom,t=document.createElement("table"),i=document.createElement("tbody");t.style.borderCollapse="collapse",t.className="values",t.appendChild(i);var n=document.createElement("tr");i.appendChild(n);var o=document.createElement("td");o.className="tree",n.appendChild(o),e.expand=this._createDomExpandButton(),o.appendChild(e.expand),e.tdExpand=o;var s=document.createElement("td");s.className="tree",n.appendChild(s),e.field=this._createDomField(),s.appendChild(e.field),e.tdField=s;var r=document.createElement("td");r.className="tree",n.appendChild(r),"object"!=this.type&&"array"!=this.type&&(r.appendChild(document.createTextNode(":")),r.className="separator"),e.tdSeparator=r;var a=document.createElement("td");return a.className="tree",n.appendChild(a),e.value=this._createDomValue(),a.appendChild(e.value),e.tdValue=a,t},n.prototype.onEvent=function(e){var t,i=e.type,n=e.target||e.srcElement,o=this.dom,s=this,a=this._hasChilds();if((n==o.drag||n==o.menu)&&("mouseover"==i?this.editor.highlighter.highlight(this):"mouseout"==i&&this.editor.highlighter.unhighlight()),"mousedown"==i&&n==o.drag&&this._onDragStart(e),"click"==i&&n==o.menu){var d=s.editor.highlighter;d.highlight(s),d.lock(),r.addClassName(o.menu,"selected"),this.showContextMenu(o.menu,function(){r.removeClassName(o.menu,"selected"),d.unlock(),d.unhighlight()})}if("click"==i&&n==o.expand&&a){var h=e.ctrlKey;this._onExpand(h)}var l=o.value;if(n==l)switch(i){case"focus":t=this;break;case"blur":case"change":this._getDomValue(!0),this._updateDomValue(),this.value&&(l.innerHTML=this._escapeHTML(this.value));break;case"input":this._getDomValue(!0),this._updateDomValue();break;case"keydown":case"mousedown":this.editor.selection=this.editor.getSelection();break;case"click":(e.ctrlKey||!this.editable.value)&&r.isUrl(this.value)&&window.open(this.value,"_blank");break;case"keyup":this._getDomValue(!0),this._updateDomValue();break;case"cut":case"paste":setTimeout(function(){s._getDomValue(!0),s._updateDomValue()},1)}var c=o.field;if(n==c)switch(i){case"focus":t=this;break;case"blur":case"change":this._getDomField(!0),this._updateDomField(),this.field&&(c.innerHTML=this._escapeHTML(this.field));break;case"input":this._getDomField(!0),this._updateDomField();break;case"keydown":case"mousedown":this.editor.selection=this.editor.getSelection();break;case"keyup":this._getDomField(!0),this._updateDomField();break;case"cut":case"paste":setTimeout(function(){s._getDomField(!0),s._updateDomField()},1)}var u=o.tree;if(n==u.parentNode)switch(i){case"click":var p=void 0!=e.offsetX?e.offsetX<24*(this.getLevel()+1):e.pageXn[i]?t:e[i]/g,">").replace(/ /g,"  ").replace(/^ /," ").replace(/ $/," "),i=JSON.stringify(t);return i.substring(1,i.length-1)},n.prototype._unescapeHTML=function(e){var t='"'+this._escapeJSON(e)+'"',i=r.parse(t);return i.replace(/</g,"<").replace(/>/g,">").replace(/ |\u00A0/g," ")},n.prototype._escapeJSON=function(e){for(var t="",i=0,n=e.length;n>i;){var o=e.charAt(i);"\n"==o?t+="\\n":"\\"==o?(t+=o,i++,o=e.charAt(i),-1=='"\\/bfnrtu'.indexOf(o)&&(t+="\\"),t+=o):t+='"'==o?'\\"':o,i++}return t};var a=s(n);e.exports=n},function(e,t,i){function n(e,t,i){function n(t){e.setMode(t);var i=e.dom&&e.dom.modeBox;i&&i.focus()}for(var s={code:{text:"Code",title:"Switch to code highlighter",click:function(){n("code")}},form:{text:"Form",title:"Switch to form editor",click:function(){n("form")}},text:{text:"Text",title:"Switch to plain text editor",click:function(){n("text")}},tree:{text:"Tree",title:"Switch to tree editor",click:function(){n("tree")}},view:{text:"View",title:"Switch to tree view",click:function(){n("view")}}},r=[],a=0;a',a.appendChild(c),o.submenuTitle&&(c.title=o.submenuTitle),l=c}else{var u=document.createElement("div");u.className="expand",d.appendChild(u),l=d}l.onclick=function(){n._onExpandItem(r),l.focus()};var p=[];r.subItems=p;var f=document.createElement("ul");r.ul=f,f.className="menu",f.style.height="0",a.appendChild(f),i(f,p,o.submenu)}else d.innerHTML='
'+o.text;t.push(r)}})}this.dom={};var n=this,o=this.dom;this.anchor=void 0,this.items=e,this.eventListeners={},this.selection=void 0,this.visibleSubmenu=void 0,this.onClose=t?t.close:void 0;var s=document.createElement("div");s.className="jsoneditor-contextmenu",o.menu=s;var r=document.createElement("ul");r.className="menu",s.appendChild(r),o.list=r,o.items=[];var a=document.createElement("button");o.focusButton=a;var d=document.createElement("li");d.style.overflow="hidden",d.style.height="0",d.appendChild(a),r.appendChild(d),i(r,this.dom.items,e),this.maxHeight=0,e.forEach(function(t){var i=24*(e.length+(t.submenu?t.submenu.length:0));n.maxHeight=Math.max(n.maxHeight,i)})}var o=i(3);n.prototype._getVisibleButtons=function(){var e=[],t=this;return this.dom.items.forEach(function(i){e.push(i.button),i.buttonExpand&&e.push(i.buttonExpand),i.subItems&&i==t.expandedItem&&i.subItems.forEach(function(t){e.push(t.button),t.buttonExpand&&e.push(t.buttonExpand)})}),e},n.visibleMenu=void 0,n.prototype.show=function(e){this.hide();var t=window.innerHeight,i=window.pageYOffset||document.scrollTop||0,s=t+i,r=e.offsetHeight,a=this.maxHeight,d=o.getAbsoluteLeft(e),h=o.getAbsoluteTop(e);s>h+r+a?(this.dom.menu.style.left=d+"px",this.dom.menu.style.top=h+r+"px",this.dom.menu.style.bottom=""):(this.dom.menu.style.left=d+"px",this.dom.menu.style.top="",this.dom.menu.style.bottom=t-h+"px"),document.body.appendChild(this.dom.menu);var l=this,c=this.dom.list;this.eventListeners.mousedown=o.addEventListener(document,"mousedown",function(e){var t=e.target;t==c||l._isChildOf(t,c)||(l.hide(),e.stopPropagation(),e.preventDefault())}),this.eventListeners.mousewheel=o.addEventListener(document,"mousewheel",function(e){e.stopPropagation(),e.preventDefault()}),this.eventListeners.keydown=o.addEventListener(document,"keydown",function(e){l._onKeyDown(e)}),this.selection=o.getSelection(),this.anchor=e,setTimeout(function(){l.dom.focusButton.focus()},0),n.visibleMenu&&n.visibleMenu.hide(),n.visibleMenu=this},n.prototype.hide=function(){this.dom.menu.parentNode&&(this.dom.menu.parentNode.removeChild(this.dom.menu),this.onClose&&this.onClose());for(var e in this.eventListeners)if(this.eventListeners.hasOwnProperty(e)){var t=this.eventListeners[e];t&&o.removeEventListener(document,e,t),delete this.eventListeners[e]}n.visibleMenu==this&&(n.visibleMenu=void 0)},n.prototype._onExpandItem=function(e){var t=this,i=e==this.expandedItem,n=this.expandedItem;if(n&&(n.ul.style.height="0",n.ul.style.padding="",setTimeout(function(){t.expandedItem!=n&&(n.ul.style.display="",o.removeClassName(n.ul.parentNode,"selected"))},300),this.expandedItem=void 0),!i){var s=e.ul;s.style.display="block";{s.clientHeight}setTimeout(function(){t.expandedItem==e&&(s.style.height=24*s.childNodes.length+"px",s.style.padding="5px 10px")},0),o.addClassName(s.parentNode,"selected"),this.expandedItem=e}},n.prototype._onKeyDown=function(e){var t,i,n,s,r=e.target,a=e.which,d=!1;27==a?(this.selection&&o.setSelection(this.selection),this.anchor&&this.anchor.focus(),this.hide(),d=!0):9==a?e.shiftKey?(t=this._getVisibleButtons(),i=t.indexOf(r),0==i&&(t[t.length-1].focus(),d=!0)):(t=this._getVisibleButtons(),i=t.indexOf(r),i==t.length-1&&(t[0].focus(),d=!0)):37==a?("expand"==r.className&&(t=this._getVisibleButtons(),i=t.indexOf(r),n=t[i-1],n&&n.focus()),d=!0):38==a?(t=this._getVisibleButtons(),i=t.indexOf(r),n=t[i-1],n&&"expand"==n.className&&(n=t[i-2]),n||(n=t[t.length-1]),n&&n.focus(),d=!0):39==a?(t=this._getVisibleButtons(),i=t.indexOf(r),s=t[i+1],s&&"expand"==s.className&&s.focus(),d=!0):40==a&&(t=this._getVisibleButtons(),i=t.indexOf(r),s=t[i+1],s&&"expand"==s.className&&(s=t[i+2]),s||(s=t[0]),s&&(s.focus(),d=!0),d=!0),d&&(e.stopPropagation(),e.preventDefault())},n.prototype._isChildOf=function(e,t){for(var i=e.parentNode;i;){if(i==t)return!0;i=i.parentNode}return!1},e.exports=n},function(e,t,i){function n(e){function t(e){this.editor=e,this.dom={}}return t.prototype=new e,t.prototype.getDom=function(){var e=this.dom;if(e.tr)return e.tr;this._updateEditability();var t=document.createElement("tr");if(t.node=this,e.tr=t,this.editable.field){e.tdDrag=document.createElement("td");var i=document.createElement("td");e.tdMenu=i;var n=document.createElement("button");n.className="contextmenu",n.title="Click to open the actions menu (Ctrl+M)",e.menu=n,i.appendChild(e.menu)}var o=document.createElement("td"),s=document.createElement("div");return s.innerHTML="(empty)",s.className="readonly",o.appendChild(s),e.td=o,e.text=s,this.updateDom(),t},t.prototype.updateDom=function(){var e=this.dom,t=e.td;t&&(t.style.paddingLeft=24*this.getLevel()+26+"px");var i=e.text;i&&(i.innerHTML="(empty "+this.parent.type+")");var n=e.tr;this.isVisible()?e.tr.firstChild||(e.tdDrag&&n.appendChild(e.tdDrag),e.tdMenu&&n.appendChild(e.tdMenu),n.appendChild(t)):e.tr.firstChild&&(e.tdDrag&&n.removeChild(e.tdDrag),e.tdMenu&&n.removeChild(e.tdMenu),n.removeChild(t))},t.prototype.isVisible=function(){return 0==this.parent.childs.length},t.prototype.showContextMenu=function(t,i){var n=this,o=e.TYPE_TITLES,r=[{text:"Append",title:"Append a new field with type 'auto' (Ctrl+Shift+Ins)",submenuTitle:"Select the type of the field to be appended",className:"insert",click:function(){n._onAppend("","","auto")},submenu:[{text:"Auto",className:"type-auto",title:o.auto,click:function(){n._onAppend("","","auto")}},{text:"Array",className:"type-array",title:o.array,click:function(){n._onAppend("",[])}},{text:"Object",className:"type-object",title:o.object,click:function(){n._onAppend("",{})}},{text:"String",className:"type-string",title:o.string,click:function(){n._onAppend("","","string")}}]}],a=new s(r,{close:i});a.show(t)},t.prototype.onEvent=function(e){var t=e.type,i=e.target||e.srcElement,n=this.dom,s=n.menu;if(i==s&&("mouseover"==t?this.editor.highlighter.highlight(this.parent):"mouseout"==t&&this.editor.highlighter.unhighlight()),"click"==t&&i==n.menu){var r=this.editor.highlighter;r.highlight(this.parent),r.lock(),o.addClassName(n.menu,"selected"),this.showContextMenu(n.menu,function(){o.removeClassName(n.menu,"selected"),r.unlock(),r.unhighlight()})}"keydown"==t&&this.onKeyDown(e)},t}var o=i(3),s=i(9);e.exports=n}])}); //# sourceMappingURL=jsoneditor.map \ No newline at end of file diff --git a/src/js/ContextMenu.js b/src/js/ContextMenu.js index 8bb3b51..5fa9935 100644 --- a/src/js/ContextMenu.js +++ b/src/js/ContextMenu.js @@ -1,444 +1,443 @@ -define(['./util'], function (util) { +var util = require('./util'); - /** - * A context menu - * @param {Object[]} items Array containing the menu structure - * TODO: describe structure - * @param {Object} [options] Object with options. Available options: - * {function} close Callback called when the - * context menu is being closed. - * @constructor - */ - function ContextMenu (items, options) { - this.dom = {}; +/** + * A context menu + * @param {Object[]} items Array containing the menu structure + * TODO: describe structure + * @param {Object} [options] Object with options. Available options: + * {function} close Callback called when the + * context menu is being closed. + * @constructor + */ +function ContextMenu (items, options) { + this.dom = {}; - var me = this; - var dom = this.dom; - this.anchor = undefined; - this.items = items; - this.eventListeners = {}; - this.selection = undefined; // holds the selection before the menu was opened - this.visibleSubmenu = undefined; - this.onClose = options ? options.close : undefined; + var me = this; + var dom = this.dom; + this.anchor = undefined; + this.items = items; + this.eventListeners = {}; + this.selection = undefined; // holds the selection before the menu was opened + this.visibleSubmenu = undefined; + this.onClose = options ? options.close : undefined; - // create a container element - var menu = document.createElement('div'); - menu.className = 'jsoneditor-contextmenu'; - dom.menu = menu; + // create a container element + var menu = document.createElement('div'); + menu.className = 'jsoneditor-contextmenu'; + dom.menu = menu; - // create a list to hold the menu items - var list = document.createElement('ul'); - list.className = 'menu'; - menu.appendChild(list); - dom.list = list; - dom.items = []; // list with all buttons + // create a list to hold the menu items + var list = document.createElement('ul'); + list.className = 'menu'; + menu.appendChild(list); + dom.list = list; + dom.items = []; // list with all buttons - // create a (non-visible) button to set the focus to the menu - var focusButton = document.createElement('button'); - dom.focusButton = focusButton; - var li = document.createElement('li'); - li.style.overflow = 'hidden'; - li.style.height = '0'; - li.appendChild(focusButton); - list.appendChild(li); + // create a (non-visible) button to set the focus to the menu + var focusButton = document.createElement('button'); + dom.focusButton = focusButton; + var li = document.createElement('li'); + li.style.overflow = 'hidden'; + li.style.height = '0'; + li.appendChild(focusButton); + list.appendChild(li); - function createMenuItems (list, domItems, items) { - items.forEach(function (item) { - if (item.type == 'separator') { - // create a separator - var separator = document.createElement('div'); - separator.className = 'separator'; - li = document.createElement('li'); - li.appendChild(separator); - list.appendChild(li); + function createMenuItems (list, domItems, items) { + items.forEach(function (item) { + if (item.type == 'separator') { + // create a separator + var separator = document.createElement('div'); + separator.className = 'separator'; + li = document.createElement('li'); + li.appendChild(separator); + list.appendChild(li); + } + else { + var domItem = {}; + + // create a menu item + var li = document.createElement('li'); + list.appendChild(li); + + // create a button in the menu item + var button = document.createElement('button'); + button.className = item.className; + domItem.button = button; + if (item.title) { + button.title = item.title; } - else { - var domItem = {}; + if (item.click) { + button.onclick = function () { + me.hide(); + item.click(); + }; + } + li.appendChild(button); - // create a menu item - var li = document.createElement('li'); - list.appendChild(li); + // create the contents of the button + if (item.submenu) { + // add the icon to the button + var divIcon = document.createElement('div'); + divIcon.className = 'icon'; + button.appendChild(divIcon); + button.appendChild(document.createTextNode(item.text)); - // create a button in the menu item - var button = document.createElement('button'); - button.className = item.className; - domItem.button = button; - if (item.title) { - button.title = item.title; - } + var buttonSubmenu; if (item.click) { - button.onclick = function () { - me.hide(); - item.click(); - }; - } - li.appendChild(button); + // submenu and a button with a click handler + button.className += ' default'; - // create the contents of the button - if (item.submenu) { - // add the icon to the button - var divIcon = document.createElement('div'); - divIcon.className = 'icon'; - button.appendChild(divIcon); - button.appendChild(document.createTextNode(item.text)); - - var buttonSubmenu; - if (item.click) { - // submenu and a button with a click handler - button.className += ' default'; - - var buttonExpand = document.createElement('button'); - domItem.buttonExpand = buttonExpand; - buttonExpand.className = 'expand'; - buttonExpand.innerHTML = '
'; - li.appendChild(buttonExpand); - if (item.submenuTitle) { - buttonExpand.title = item.submenuTitle; - } - - buttonSubmenu = buttonExpand; - } - else { - // submenu and a button without a click handler - var divExpand = document.createElement('div'); - divExpand.className = 'expand'; - button.appendChild(divExpand); - - buttonSubmenu = button; + var buttonExpand = document.createElement('button'); + domItem.buttonExpand = buttonExpand; + buttonExpand.className = 'expand'; + buttonExpand.innerHTML = '
'; + li.appendChild(buttonExpand); + if (item.submenuTitle) { + buttonExpand.title = item.submenuTitle; } - // attach a handler to expand/collapse the submenu - buttonSubmenu.onclick = function () { - me._onExpandItem(domItem); - buttonSubmenu.focus(); - }; - - // create the submenu - var domSubItems = []; - domItem.subItems = domSubItems; - var ul = document.createElement('ul'); - domItem.ul = ul; - ul.className = 'menu'; - ul.style.height = '0'; - li.appendChild(ul); - createMenuItems(ul, domSubItems, item.submenu); + buttonSubmenu = buttonExpand; } else { - // no submenu, just a button with clickhandler - button.innerHTML = '
' + item.text; + // submenu and a button without a click handler + var divExpand = document.createElement('div'); + divExpand.className = 'expand'; + button.appendChild(divExpand); + + buttonSubmenu = button; } - domItems.push(domItem); + // attach a handler to expand/collapse the submenu + buttonSubmenu.onclick = function () { + me._onExpandItem(domItem); + buttonSubmenu.focus(); + }; + + // create the submenu + var domSubItems = []; + domItem.subItems = domSubItems; + var ul = document.createElement('ul'); + domItem.ul = ul; + ul.className = 'menu'; + ul.style.height = '0'; + li.appendChild(ul); + createMenuItems(ul, domSubItems, item.submenu); + } + else { + // no submenu, just a button with clickhandler + button.innerHTML = '
' + item.text; } - }); - } - createMenuItems(list, this.dom.items, items); - // TODO: when the editor is small, show the submenu on the right instead of inline? - - // calculate the max height of the menu with one submenu expanded - this.maxHeight = 0; // height in pixels - items.forEach(function (item) { - var height = (items.length + (item.submenu ? item.submenu.length : 0)) * 24; - me.maxHeight = Math.max(me.maxHeight, height); + domItems.push(domItem); + } }); } + createMenuItems(list, this.dom.items, items); - /** - * Get the currently visible buttons - * @return {Array.} buttons - * @private - */ - ContextMenu.prototype._getVisibleButtons = function () { - var buttons = []; - var me = this; - this.dom.items.forEach(function (item) { - buttons.push(item.button); - if (item.buttonExpand) { - buttons.push(item.buttonExpand); - } - if (item.subItems && item == me.expandedItem) { - item.subItems.forEach(function (subItem) { - buttons.push(subItem.button); - if (subItem.buttonExpand) { - buttons.push(subItem.buttonExpand); - } - // TODO: change to fully recursive method - }); - } - }); + // TODO: when the editor is small, show the submenu on the right instead of inline? - return buttons; - }; + // calculate the max height of the menu with one submenu expanded + this.maxHeight = 0; // height in pixels + items.forEach(function (item) { + var height = (items.length + (item.submenu ? item.submenu.length : 0)) * 24; + me.maxHeight = Math.max(me.maxHeight, height); + }); +} + +/** + * Get the currently visible buttons + * @return {Array.} buttons + * @private + */ +ContextMenu.prototype._getVisibleButtons = function () { + var buttons = []; + var me = this; + this.dom.items.forEach(function (item) { + buttons.push(item.button); + if (item.buttonExpand) { + buttons.push(item.buttonExpand); + } + if (item.subItems && item == me.expandedItem) { + item.subItems.forEach(function (subItem) { + buttons.push(subItem.button); + if (subItem.buttonExpand) { + buttons.push(subItem.buttonExpand); + } + // TODO: change to fully recursive method + }); + } + }); + + return buttons; +}; // currently displayed context menu, a singleton. We may only have one visible context menu - ContextMenu.visibleMenu = undefined; +ContextMenu.visibleMenu = undefined; - /** - * Attach the menu to an anchor - * @param {HTMLElement} anchor - */ - ContextMenu.prototype.show = function (anchor) { - this.hide(); +/** + * Attach the menu to an anchor + * @param {HTMLElement} anchor + */ +ContextMenu.prototype.show = function (anchor) { + this.hide(); - // calculate whether the menu fits below the anchor - var windowHeight = window.innerHeight, - windowScroll = (window.pageYOffset || document.scrollTop || 0), - windowBottom = windowHeight + windowScroll, - anchorHeight = anchor.offsetHeight, - menuHeight = this.maxHeight; + // calculate whether the menu fits below the anchor + var windowHeight = window.innerHeight, + windowScroll = (window.pageYOffset || document.scrollTop || 0), + windowBottom = windowHeight + windowScroll, + anchorHeight = anchor.offsetHeight, + menuHeight = this.maxHeight; - // position the menu - var left = util.getAbsoluteLeft(anchor); - var top = util.getAbsoluteTop(anchor); - if (top + anchorHeight + menuHeight < windowBottom) { - // display the menu below the anchor - this.dom.menu.style.left = left + 'px'; - this.dom.menu.style.top = (top + anchorHeight) + 'px'; - this.dom.menu.style.bottom = ''; - } - else { - // display the menu above the anchor - this.dom.menu.style.left = left + 'px'; - this.dom.menu.style.top = ''; - this.dom.menu.style.bottom = (windowHeight - top) + 'px'; - } + // position the menu + var left = util.getAbsoluteLeft(anchor); + var top = util.getAbsoluteTop(anchor); + if (top + anchorHeight + menuHeight < windowBottom) { + // display the menu below the anchor + this.dom.menu.style.left = left + 'px'; + this.dom.menu.style.top = (top + anchorHeight) + 'px'; + this.dom.menu.style.bottom = ''; + } + else { + // display the menu above the anchor + this.dom.menu.style.left = left + 'px'; + this.dom.menu.style.top = ''; + this.dom.menu.style.bottom = (windowHeight - top) + 'px'; + } - // attach the menu to the document - document.body.appendChild(this.dom.menu); + // attach the menu to the document + document.body.appendChild(this.dom.menu); - // create and attach event listeners - var me = this; - var list = this.dom.list; - this.eventListeners.mousedown = util.addEventListener( - document, 'mousedown', function (event) { - // hide menu on click outside of the menu - var target = event.target; - if ((target != list) && !me._isChildOf(target, list)) { - me.hide(); - event.stopPropagation(); - event.preventDefault(); - } - }); - this.eventListeners.mousewheel = util.addEventListener( - document, 'mousewheel', function (event) { - // block scrolling when context menu is visible + // create and attach event listeners + var me = this; + var list = this.dom.list; + this.eventListeners.mousedown = util.addEventListener( + document, 'mousedown', function (event) { + // hide menu on click outside of the menu + var target = event.target; + if ((target != list) && !me._isChildOf(target, list)) { + me.hide(); event.stopPropagation(); event.preventDefault(); - }); - this.eventListeners.keydown = util.addEventListener( - document, 'keydown', function (event) { - me._onKeyDown(event); - }); + } + }); + this.eventListeners.mousewheel = util.addEventListener( + document, 'mousewheel', function (event) { + // block scrolling when context menu is visible + event.stopPropagation(); + event.preventDefault(); + }); + this.eventListeners.keydown = util.addEventListener( + document, 'keydown', function (event) { + me._onKeyDown(event); + }); - // move focus to the first button in the context menu - this.selection = util.getSelection(); - this.anchor = anchor; + // move focus to the first button in the context menu + this.selection = util.getSelection(); + this.anchor = anchor; + setTimeout(function () { + me.dom.focusButton.focus(); + }, 0); + + if (ContextMenu.visibleMenu) { + ContextMenu.visibleMenu.hide(); + } + ContextMenu.visibleMenu = this; +}; + +/** + * Hide the context menu if visible + */ +ContextMenu.prototype.hide = function () { + // remove the menu from the DOM + if (this.dom.menu.parentNode) { + this.dom.menu.parentNode.removeChild(this.dom.menu); + if (this.onClose) { + this.onClose(); + } + } + + // remove all event listeners + // all event listeners are supposed to be attached to document. + for (var name in this.eventListeners) { + if (this.eventListeners.hasOwnProperty(name)) { + var fn = this.eventListeners[name]; + if (fn) { + util.removeEventListener(document, name, fn); + } + delete this.eventListeners[name]; + } + } + + if (ContextMenu.visibleMenu == this) { + ContextMenu.visibleMenu = undefined; + } +}; + +/** + * Expand a submenu + * Any currently expanded submenu will be hided. + * @param {Object} domItem + * @private + */ +ContextMenu.prototype._onExpandItem = function (domItem) { + var me = this; + var alreadyVisible = (domItem == this.expandedItem); + + // hide the currently visible submenu + var expandedItem = this.expandedItem; + if (expandedItem) { + //var ul = expandedItem.ul; + expandedItem.ul.style.height = '0'; + expandedItem.ul.style.padding = ''; setTimeout(function () { - me.dom.focusButton.focus(); + if (me.expandedItem != expandedItem) { + expandedItem.ul.style.display = ''; + util.removeClassName(expandedItem.ul.parentNode, 'selected'); + } + }, 300); // timeout duration must match the css transition duration + this.expandedItem = undefined; + } + + if (!alreadyVisible) { + var ul = domItem.ul; + ul.style.display = 'block'; + var height = ul.clientHeight; // force a reflow in Firefox + setTimeout(function () { + if (me.expandedItem == domItem) { + ul.style.height = (ul.childNodes.length * 24) + 'px'; + ul.style.padding = '5px 10px'; + } }, 0); + util.addClassName(ul.parentNode, 'selected'); + this.expandedItem = domItem; + } +}; - if (ContextMenu.visibleMenu) { - ContextMenu.visibleMenu.hide(); +/** + * Handle onkeydown event + * @param {Event} event + * @private + */ +ContextMenu.prototype._onKeyDown = function (event) { + var target = event.target; + var keynum = event.which; + var handled = false; + var buttons, targetIndex, prevButton, nextButton; + + if (keynum == 27) { // ESC + // hide the menu on ESC key + + // restore previous selection and focus + if (this.selection) { + util.setSelection(this.selection); + } + if (this.anchor) { + this.anchor.focus(); } - ContextMenu.visibleMenu = this; - }; - /** - * Hide the context menu if visible - */ - ContextMenu.prototype.hide = function () { - // remove the menu from the DOM - if (this.dom.menu.parentNode) { - this.dom.menu.parentNode.removeChild(this.dom.menu); - if (this.onClose) { - this.onClose(); + this.hide(); + + handled = true; + } + else if (keynum == 9) { // Tab + if (!event.shiftKey) { // Tab + buttons = this._getVisibleButtons(); + targetIndex = buttons.indexOf(target); + if (targetIndex == buttons.length - 1) { + // move to first button + buttons[0].focus(); + handled = true; } } - - // remove all event listeners - // all event listeners are supposed to be attached to document. - for (var name in this.eventListeners) { - if (this.eventListeners.hasOwnProperty(name)) { - var fn = this.eventListeners[name]; - if (fn) { - util.removeEventListener(document, name, fn); - } - delete this.eventListeners[name]; + else { // Shift+Tab + buttons = this._getVisibleButtons(); + targetIndex = buttons.indexOf(target); + if (targetIndex == 0) { + // move to last button + buttons[buttons.length - 1].focus(); + handled = true; } } - - if (ContextMenu.visibleMenu == this) { - ContextMenu.visibleMenu = undefined; - } - }; - - /** - * Expand a submenu - * Any currently expanded submenu will be hided. - * @param {Object} domItem - * @private - */ - ContextMenu.prototype._onExpandItem = function (domItem) { - var me = this; - var alreadyVisible = (domItem == this.expandedItem); - - // hide the currently visible submenu - var expandedItem = this.expandedItem; - if (expandedItem) { - //var ul = expandedItem.ul; - expandedItem.ul.style.height = '0'; - expandedItem.ul.style.padding = ''; - setTimeout(function () { - if (me.expandedItem != expandedItem) { - expandedItem.ul.style.display = ''; - util.removeClassName(expandedItem.ul.parentNode, 'selected'); - } - }, 300); // timeout duration must match the css transition duration - this.expandedItem = undefined; - } - - if (!alreadyVisible) { - var ul = domItem.ul; - ul.style.display = 'block'; - var height = ul.clientHeight; // force a reflow in Firefox - setTimeout(function () { - if (me.expandedItem == domItem) { - ul.style.height = (ul.childNodes.length * 24) + 'px'; - ul.style.padding = '5px 10px'; - } - }, 0); - util.addClassName(ul.parentNode, 'selected'); - this.expandedItem = domItem; - } - }; - - /** - * Handle onkeydown event - * @param {Event} event - * @private - */ - ContextMenu.prototype._onKeyDown = function (event) { - var target = event.target; - var keynum = event.which; - var handled = false; - var buttons, targetIndex, prevButton, nextButton; - - if (keynum == 27) { // ESC - // hide the menu on ESC key - - // restore previous selection and focus - if (this.selection) { - util.setSelection(this.selection); - } - if (this.anchor) { - this.anchor.focus(); - } - - this.hide(); - - handled = true; - } - else if (keynum == 9) { // Tab - if (!event.shiftKey) { // Tab - buttons = this._getVisibleButtons(); - targetIndex = buttons.indexOf(target); - if (targetIndex == buttons.length - 1) { - // move to first button - buttons[0].focus(); - handled = true; - } - } - else { // Shift+Tab - buttons = this._getVisibleButtons(); - targetIndex = buttons.indexOf(target); - if (targetIndex == 0) { - // move to last button - buttons[buttons.length - 1].focus(); - handled = true; - } - } - } - else if (keynum == 37) { // Arrow Left - if (target.className == 'expand') { - buttons = this._getVisibleButtons(); - targetIndex = buttons.indexOf(target); - prevButton = buttons[targetIndex - 1]; - if (prevButton) { - prevButton.focus(); - } - } - handled = true; - } - else if (keynum == 38) { // Arrow Up + } + else if (keynum == 37) { // Arrow Left + if (target.className == 'expand') { buttons = this._getVisibleButtons(); targetIndex = buttons.indexOf(target); prevButton = buttons[targetIndex - 1]; - if (prevButton && prevButton.className == 'expand') { - // skip expand button - prevButton = buttons[targetIndex - 2]; - } - if (!prevButton) { - // move to last button - prevButton = buttons[buttons.length - 1]; - } if (prevButton) { prevButton.focus(); } + } + handled = true; + } + else if (keynum == 38) { // Arrow Up + buttons = this._getVisibleButtons(); + targetIndex = buttons.indexOf(target); + prevButton = buttons[targetIndex - 1]; + if (prevButton && prevButton.className == 'expand') { + // skip expand button + prevButton = buttons[targetIndex - 2]; + } + if (!prevButton) { + // move to last button + prevButton = buttons[buttons.length - 1]; + } + if (prevButton) { + prevButton.focus(); + } + handled = true; + } + else if (keynum == 39) { // Arrow Right + buttons = this._getVisibleButtons(); + targetIndex = buttons.indexOf(target); + nextButton = buttons[targetIndex + 1]; + if (nextButton && nextButton.className == 'expand') { + nextButton.focus(); + } + handled = true; + } + else if (keynum == 40) { // Arrow Down + buttons = this._getVisibleButtons(); + targetIndex = buttons.indexOf(target); + nextButton = buttons[targetIndex + 1]; + if (nextButton && nextButton.className == 'expand') { + // skip expand button + nextButton = buttons[targetIndex + 2]; + } + if (!nextButton) { + // move to first button + nextButton = buttons[0]; + } + if (nextButton) { + nextButton.focus(); handled = true; } - else if (keynum == 39) { // Arrow Right - buttons = this._getVisibleButtons(); - targetIndex = buttons.indexOf(target); - nextButton = buttons[targetIndex + 1]; - if (nextButton && nextButton.className == 'expand') { - nextButton.focus(); - } - handled = true; - } - else if (keynum == 40) { // Arrow Down - buttons = this._getVisibleButtons(); - targetIndex = buttons.indexOf(target); - nextButton = buttons[targetIndex + 1]; - if (nextButton && nextButton.className == 'expand') { - // skip expand button - nextButton = buttons[targetIndex + 2]; - } - if (!nextButton) { - // move to first button - nextButton = buttons[0]; - } - if (nextButton) { - nextButton.focus(); - handled = true; - } - handled = true; - } - // TODO: arrow left and right + handled = true; + } + // TODO: arrow left and right - if (handled) { - event.stopPropagation(); - event.preventDefault(); + if (handled) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +/** + * Test if an element is a child of a parent element. + * @param {Element} child + * @param {Element} parent + * @return {boolean} isChild + */ +ContextMenu.prototype._isChildOf = function (child, parent) { + var e = child.parentNode; + while (e) { + if (e == parent) { + return true; } - }; + e = e.parentNode; + } - /** - * Test if an element is a child of a parent element. - * @param {Element} child - * @param {Element} parent - * @return {boolean} isChild - */ - ContextMenu.prototype._isChildOf = function (child, parent) { - var e = child.parentNode; - while (e) { - if (e == parent) { - return true; - } - e = e.parentNode; - } + return false; +}; - return false; - }; - - return ContextMenu; -}); +module.exports = ContextMenu; diff --git a/src/js/Highlighter.js b/src/js/Highlighter.js index d2bcf33..ece99e6 100644 --- a/src/js/Highlighter.js +++ b/src/js/Highlighter.js @@ -1,87 +1,84 @@ -define(function () { +/** + * The highlighter can highlight/unhighlight a node, and + * animate the visibility of a context menu. + * @constructor Highlighter + */ +function Highlighter () { + this.locked = false; +} - /** - * The highlighter can highlight/unhighlight a node, and - * animate the visibility of a context menu. - * @constructor Highlighter - */ - function Highlighter () { - this.locked = false; +/** + * Hightlight given node and its childs + * @param {Node} node + */ +Highlighter.prototype.highlight = function (node) { + if (this.locked) { + return; } - /** - * Hightlight given node and its childs - * @param {Node} node - */ - Highlighter.prototype.highlight = function (node) { - if (this.locked) { - return; - } - - if (this.node != node) { - // unhighlight current node - if (this.node) { - this.node.setHighlight(false); - } - - // highlight new node - this.node = node; - this.node.setHighlight(true); - } - - // cancel any current timeout - this._cancelUnhighlight(); - }; - - /** - * Unhighlight currently highlighted node. - * Will be done after a delay - */ - Highlighter.prototype.unhighlight = function () { - if (this.locked) { - return; - } - - var me = this; + if (this.node != node) { + // unhighlight current node if (this.node) { - this._cancelUnhighlight(); - - // do the unhighlighting after a small delay, to prevent re-highlighting - // the same node when moving from the drag-icon to the contextmenu-icon - // or vice versa. - this.unhighlightTimer = setTimeout(function () { - me.node.setHighlight(false); - me.node = undefined; - me.unhighlightTimer = undefined; - }, 0); + this.node.setHighlight(false); } - }; - /** - * Cancel an unhighlight action (if before the timeout of the unhighlight action) - * @private - */ - Highlighter.prototype._cancelUnhighlight = function () { - if (this.unhighlightTimer) { - clearTimeout(this.unhighlightTimer); - this.unhighlightTimer = undefined; - } - }; + // highlight new node + this.node = node; + this.node.setHighlight(true); + } - /** - * Lock highlighting or unhighlighting nodes. - * methods highlight and unhighlight do not work while locked. - */ - Highlighter.prototype.lock = function () { - this.locked = true; - }; + // cancel any current timeout + this._cancelUnhighlight(); +}; - /** - * Unlock highlighting or unhighlighting nodes - */ - Highlighter.prototype.unlock = function () { - this.locked = false; - }; +/** + * Unhighlight currently highlighted node. + * Will be done after a delay + */ +Highlighter.prototype.unhighlight = function () { + if (this.locked) { + return; + } - return Highlighter; -}); \ No newline at end of file + var me = this; + if (this.node) { + this._cancelUnhighlight(); + + // do the unhighlighting after a small delay, to prevent re-highlighting + // the same node when moving from the drag-icon to the contextmenu-icon + // or vice versa. + this.unhighlightTimer = setTimeout(function () { + me.node.setHighlight(false); + me.node = undefined; + me.unhighlightTimer = undefined; + }, 0); + } +}; + +/** + * Cancel an unhighlight action (if before the timeout of the unhighlight action) + * @private + */ +Highlighter.prototype._cancelUnhighlight = function () { + if (this.unhighlightTimer) { + clearTimeout(this.unhighlightTimer); + this.unhighlightTimer = undefined; + } +}; + +/** + * Lock highlighting or unhighlighting nodes. + * methods highlight and unhighlight do not work while locked. + */ +Highlighter.prototype.lock = function () { + this.locked = true; +}; + +/** + * Unlock highlighting or unhighlighting nodes + */ +Highlighter.prototype.unlock = function () { + this.locked = false; +}; + +module.exports = Highlighter; diff --git a/src/js/History.js b/src/js/History.js index 72da3f8..d195a36 100644 --- a/src/js/History.js +++ b/src/js/History.js @@ -1,223 +1,222 @@ -define(['./util'], function (util) { +var util = require('./util'); - /** - * @constructor History - * Store action history, enables undo and redo - * @param {JSONEditor} editor - */ - function History (editor) { - this.editor = editor; - this.clear(); +/** + * @constructor History + * Store action history, enables undo and redo + * @param {JSONEditor} editor + */ +function History (editor) { + this.editor = editor; + this.clear(); - // map with all supported actions - this.actions = { - 'editField': { - 'undo': function (params) { - params.node.updateField(params.oldValue); - }, - 'redo': function (params) { - params.node.updateField(params.newValue); - } + // map with all supported actions + this.actions = { + 'editField': { + 'undo': function (params) { + params.node.updateField(params.oldValue); }, - 'editValue': { - 'undo': function (params) { - params.node.updateValue(params.oldValue); - }, - 'redo': function (params) { - params.node.updateValue(params.newValue); - } - }, - 'appendNode': { - 'undo': function (params) { - params.parent.removeChild(params.node); - }, - 'redo': function (params) { - params.parent.appendChild(params.node); - } - }, - 'insertBeforeNode': { - 'undo': function (params) { - params.parent.removeChild(params.node); - }, - 'redo': function (params) { - params.parent.insertBefore(params.node, params.beforeNode); - } - }, - 'insertAfterNode': { - 'undo': function (params) { - params.parent.removeChild(params.node); - }, - 'redo': function (params) { - params.parent.insertAfter(params.node, params.afterNode); - } - }, - 'removeNode': { - 'undo': function (params) { - var parent = params.parent; - var beforeNode = parent.childs[params.index] || parent.append; - parent.insertBefore(params.node, beforeNode); - }, - 'redo': function (params) { - params.parent.removeChild(params.node); - } - }, - 'duplicateNode': { - 'undo': function (params) { - params.parent.removeChild(params.clone); - }, - 'redo': function (params) { - params.parent.insertAfter(params.clone, params.node); - } - }, - 'changeType': { - 'undo': function (params) { - params.node.changeType(params.oldType); - }, - 'redo': function (params) { - params.node.changeType(params.newType); - } - }, - 'moveNode': { - 'undo': function (params) { - params.startParent.moveTo(params.node, params.startIndex); - }, - 'redo': function (params) { - params.endParent.moveTo(params.node, params.endIndex); - } - }, - 'sort': { - 'undo': function (params) { - var node = params.node; - node.hideChilds(); - node.sort = params.oldSort; - node.childs = params.oldChilds; - node.showChilds(); - }, - 'redo': function (params) { - var node = params.node; - node.hideChilds(); - node.sort = params.newSort; - node.childs = params.newChilds; - node.showChilds(); - } + 'redo': function (params) { + params.node.updateField(params.newValue); } + }, + 'editValue': { + 'undo': function (params) { + params.node.updateValue(params.oldValue); + }, + 'redo': function (params) { + params.node.updateValue(params.newValue); + } + }, + 'appendNode': { + 'undo': function (params) { + params.parent.removeChild(params.node); + }, + 'redo': function (params) { + params.parent.appendChild(params.node); + } + }, + 'insertBeforeNode': { + 'undo': function (params) { + params.parent.removeChild(params.node); + }, + 'redo': function (params) { + params.parent.insertBefore(params.node, params.beforeNode); + } + }, + 'insertAfterNode': { + 'undo': function (params) { + params.parent.removeChild(params.node); + }, + 'redo': function (params) { + params.parent.insertAfter(params.node, params.afterNode); + } + }, + 'removeNode': { + 'undo': function (params) { + var parent = params.parent; + var beforeNode = parent.childs[params.index] || parent.append; + parent.insertBefore(params.node, beforeNode); + }, + 'redo': function (params) { + params.parent.removeChild(params.node); + } + }, + 'duplicateNode': { + 'undo': function (params) { + params.parent.removeChild(params.clone); + }, + 'redo': function (params) { + params.parent.insertAfter(params.clone, params.node); + } + }, + 'changeType': { + 'undo': function (params) { + params.node.changeType(params.oldType); + }, + 'redo': function (params) { + params.node.changeType(params.newType); + } + }, + 'moveNode': { + 'undo': function (params) { + params.startParent.moveTo(params.node, params.startIndex); + }, + 'redo': function (params) { + params.endParent.moveTo(params.node, params.endIndex); + } + }, + 'sort': { + 'undo': function (params) { + var node = params.node; + node.hideChilds(); + node.sort = params.oldSort; + node.childs = params.oldChilds; + node.showChilds(); + }, + 'redo': function (params) { + var node = params.node; + node.hideChilds(); + node.sort = params.newSort; + node.childs = params.newChilds; + node.showChilds(); + } + } - // TODO: restore the original caret position and selection with each undo - // TODO: implement history for actions "expand", "collapse", "scroll", "setDocument" - }; + // 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. + */ +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. + */ +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); } - /** - * The method onChange is executed when the History is changed, and can - * be overloaded. - */ - History.prototype.onChange = function () {}; + // fire onchange event + this.onChange(); +}; - /** - * 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. - */ - History.prototype.add = function (action, params) { +/** + * Clear history + */ +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 + */ +History.prototype.canUndo = function () { + return (this.index >= 0); +}; + +/** + * Check if there is an action available for redo + * @return {Boolean} canRedo + */ +History.prototype.canRedo = function () { + return (this.index < this.history.length - 1); +}; + +/** + * Undo the last action + */ +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.params); + if (obj.params.oldSelection) { + this.editor.setSelection(obj.params.oldSelection); + } + } + else { + util.log('Error: unknown action "' + obj.action + '"'); + } + } + this.index--; + + // fire onchange event + this.onChange(); + } +}; + +/** + * Redo the last action + */ +History.prototype.redo = function () { + if (this.canRedo()) { 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); + var obj = this.history[this.index]; + if (obj) { + var action = this.actions[obj.action]; + if (action && action.redo) { + action.redo(obj.params); + if (obj.params.newSelection) { + this.editor.setSelection(obj.params.newSelection); + } + } + else { + util.log('Error: unknown action "' + obj.action + '"'); + } } // fire onchange event this.onChange(); - }; + } +}; - /** - * Clear history - */ - 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 - */ - History.prototype.canUndo = function () { - return (this.index >= 0); - }; - - /** - * Check if there is an action available for redo - * @return {Boolean} canRedo - */ - History.prototype.canRedo = function () { - return (this.index < this.history.length - 1); - }; - - /** - * Undo the last action - */ - 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.params); - if (obj.params.oldSelection) { - this.editor.setSelection(obj.params.oldSelection); - } - } - else { - util.log('Error: unknown action "' + obj.action + '"'); - } - } - this.index--; - - // fire onchange event - this.onChange(); - } - }; - - /** - * Redo the last action - */ - History.prototype.redo = function () { - if (this.canRedo()) { - this.index++; - - var obj = this.history[this.index]; - if (obj) { - var action = this.actions[obj.action]; - if (action && action.redo) { - action.redo(obj.params); - if (obj.params.newSelection) { - this.editor.setSelection(obj.params.newSelection); - } - } - else { - util.log('Error: unknown action "' + obj.action + '"'); - } - } - - // fire onchange event - this.onChange(); - } - }; - - return History; -}); +module.exports = History; diff --git a/src/js/JSONEditor.js b/src/js/JSONEditor.js index fff5ad2..a23e769 100644 --- a/src/js/JSONEditor.js +++ b/src/js/JSONEditor.js @@ -1,261 +1,262 @@ -define(['./treemode', './textmode', './util'], function (treemode, textmode, util) { +var treemode = require('./treemode'); +var textmode = require('./textmode'); +var util = require('./util'); - /** - * @constructor JSONEditor - * @param {Element} container Container element - * @param {Object} [options] Object with options. available options: - * {String} mode Editor mode. Available values: - * 'tree' (default), 'view', - * 'form', 'text', and 'code'. - * {function} change Callback method, triggered - * on change of contents - * {Boolean} search Enable search box. - * True by default - * Only applicable for modes - * 'tree', 'view', and 'form' - * {Boolean} history Enable history (undo/redo). - * True by default - * Only applicable for modes - * 'tree', 'view', and 'form' - * {String} name Field name for the root node. - * Only applicable for modes - * 'tree', 'view', and 'form' - * {Number} indentation Number of indentation - * spaces. 4 by default. - * Only applicable for - * modes 'text' and 'code' - * @param {Object | undefined} json JSON object - */ - function JSONEditor (container, options, json) { - if (!(this instanceof JSONEditor)) { - throw new Error('JSONEditor constructor called without "new".'); - } - - // check for unsupported browser (IE8 and older) - var ieVersion = util.getInternetExplorerVersion(); - if (ieVersion != -1 && ieVersion < 9) { - throw new Error('Unsupported browser, IE9 or newer required. ' + - 'Please install the newest version of your browser.'); - } - - if (arguments.length) { - this._create(container, options, json); - } +/** + * @constructor JSONEditor + * @param {Element} container Container element + * @param {Object} [options] Object with options. available options: + * {String} mode Editor mode. Available values: + * 'tree' (default), 'view', + * 'form', 'text', and 'code'. + * {function} change Callback method, triggered + * on change of contents + * {Boolean} search Enable search box. + * True by default + * Only applicable for modes + * 'tree', 'view', and 'form' + * {Boolean} history Enable history (undo/redo). + * True by default + * Only applicable for modes + * 'tree', 'view', and 'form' + * {String} name Field name for the root node. + * Only applicable for modes + * 'tree', 'view', and 'form' + * {Number} indentation Number of indentation + * spaces. 4 by default. + * Only applicable for + * modes 'text' and 'code' + * @param {Object | undefined} json JSON object + */ +function JSONEditor (container, options, json) { + if (!(this instanceof JSONEditor)) { + throw new Error('JSONEditor constructor called without "new".'); } - /** - * Configuration for all registered modes. Example: - * { - * tree: { - * mixin: TreeEditor, - * data: 'json' - * }, - * text: { - * mixin: TextEditor, - * data: 'text' - * } - * } - * - * @type { Object. } - */ - JSONEditor.modes = {}; + // check for unsupported browser (IE8 and older) + var ieVersion = util.getInternetExplorerVersion(); + if (ieVersion != -1 && ieVersion < 9) { + throw new Error('Unsupported browser, IE9 or newer required. ' + + 'Please install the newest version of your browser.'); + } - /** - * Create the JSONEditor - * @param {Element} container Container element - * @param {Object} [options] See description in constructor - * @param {Object | undefined} json JSON object - * @private - */ - JSONEditor.prototype._create = function (container, options, json) { - this.container = container; - this.options = options || {}; - this.json = json || {}; + if (arguments.length) { + this._create(container, options, json); + } +} - var mode = this.options.mode || 'tree'; - this.setMode(mode); - }; +/** + * Configuration for all registered modes. Example: + * { + * tree: { + * mixin: TreeEditor, + * data: 'json' + * }, + * text: { + * mixin: TextEditor, + * data: 'text' + * } + * } + * + * @type { Object. } + */ +JSONEditor.modes = {}; - /** - * Detach the editor from the DOM - * @private - */ - JSONEditor.prototype._delete = function () {}; +/** + * Create the JSONEditor + * @param {Element} container Container element + * @param {Object} [options] See description in constructor + * @param {Object | undefined} json JSON object + * @private + */ +JSONEditor.prototype._create = function (container, options, json) { + this.container = container; + this.options = options || {}; + this.json = json || {}; - /** - * Set JSON object in editor - * @param {Object | undefined} json JSON data - */ - JSONEditor.prototype.set = function (json) { - this.json = json; - }; + var mode = this.options.mode || 'tree'; + this.setMode(mode); +}; - /** - * Get JSON from the editor - * @returns {Object} json - */ - JSONEditor.prototype.get = function () { - return this.json; - }; +/** + * Detach the editor from the DOM + * @private + */ +JSONEditor.prototype._delete = function () {}; - /** - * Set string containing JSON for the editor - * @param {String | undefined} jsonText - */ - JSONEditor.prototype.setText = function (jsonText) { - this.json = util.parse(jsonText); - }; +/** + * Set JSON object in editor + * @param {Object | undefined} json JSON data + */ +JSONEditor.prototype.set = function (json) { + this.json = json; +}; - /** - * Get stringified JSON contents from the editor - * @returns {String} jsonText - */ - JSONEditor.prototype.getText = function () { - return JSON.stringify(this.json); - }; +/** + * Get JSON from the editor + * @returns {Object} json + */ +JSONEditor.prototype.get = function () { + return this.json; +}; - /** - * Set a field name for the root node. - * @param {String | undefined} name - */ - JSONEditor.prototype.setName = function (name) { - if (!this.options) { - this.options = {}; - } - this.options.name = name; - }; +/** + * Set string containing JSON for the editor + * @param {String | undefined} jsonText + */ +JSONEditor.prototype.setText = function (jsonText) { + this.json = util.parse(jsonText); +}; - /** - * Get the field name for the root node. - * @return {String | undefined} name - */ - JSONEditor.prototype.getName = function () { - return this.options && this.options.name; - }; +/** + * Get stringified JSON contents from the editor + * @returns {String} jsonText + */ +JSONEditor.prototype.getText = function () { + return JSON.stringify(this.json); +}; - /** - * Change the mode of the editor. - * JSONEditor will be extended with all methods needed for the chosen mode. - * @param {String} mode Available modes: 'tree' (default), 'view', 'form', - * 'text', and 'code'. - */ - JSONEditor.prototype.setMode = function (mode) { - var container = this.container, - options = util.extend({}, this.options), - data, - name; +/** + * Set a field name for the root node. + * @param {String | undefined} name + */ +JSONEditor.prototype.setName = function (name) { + if (!this.options) { + this.options = {}; + } + this.options.name = name; +}; - options.mode = mode; - var config = JSONEditor.modes[mode]; - if (config) { - try { - var asText = (config.data == 'text'); - name = this.getName(); - data = this[asText ? 'getText' : 'get'](); // get text or json +/** + * Get the field name for the root node. + * @return {String | undefined} name + */ +JSONEditor.prototype.getName = function () { + return this.options && this.options.name; +}; - this._delete(); - util.clear(this); - util.extend(this, config.mixin); - this.create(container, options); +/** + * Change the mode of the editor. + * JSONEditor will be extended with all methods needed for the chosen mode. + * @param {String} mode Available modes: 'tree' (default), 'view', 'form', + * 'text', and 'code'. + */ +JSONEditor.prototype.setMode = function (mode) { + var container = this.container, + options = util.extend({}, this.options), + data, + name; - this.setName(name); - this[asText ? 'setText' : 'set'](data); // set text or json + options.mode = mode; + var config = JSONEditor.modes[mode]; + if (config) { + try { + var asText = (config.data == 'text'); + name = this.getName(); + data = this[asText ? 'getText' : 'get'](); // get text or json - if (typeof config.load === 'function') { - try { - config.load.call(this); - } - catch (err) {} + this._delete(); + util.clear(this); + util.extend(this, config.mixin); + this.create(container, options); + + this.setName(name); + this[asText ? 'setText' : 'set'](data); // set text or json + + if (typeof config.load === 'function') { + try { + config.load.call(this); } - } - catch (err) { - this._onError(err); + catch (err) {} } } - else { - throw new Error('Unknown mode "' + options.mode + '"'); + catch (err) { + this._onError(err); } - }; + } + else { + throw new Error('Unknown mode "' + options.mode + '"'); + } +}; - /** - * Throw an error. If an error callback is configured in options.error, this - * callback will be invoked. Else, a regular error is thrown. - * @param {Error} err - * @private - */ - JSONEditor.prototype._onError = function(err) { - // TODO: onError is deprecated since version 2.2.0. cleanup some day - if (typeof this.onError === 'function') { - util.log('WARNING: JSONEditor.onError is deprecated. ' + - 'Use options.error instead.'); - this.onError(err); +/** + * Throw an error. If an error callback is configured in options.error, this + * callback will be invoked. Else, a regular error is thrown. + * @param {Error} err + * @private + */ +JSONEditor.prototype._onError = function(err) { + // TODO: onError is deprecated since version 2.2.0. cleanup some day + if (typeof this.onError === 'function') { + util.log('WARNING: JSONEditor.onError is deprecated. ' + + 'Use options.error instead.'); + this.onError(err); + } + + if (this.options && typeof this.options.error === 'function') { + this.options.error(err); + } + else { + throw err; + } +}; + +/** + * Register a plugin with one ore multiple modes for the JSON Editor. + * + * A mode is described as an object with properties: + * + * - `mode: String` The name of the mode. + * - `mixin: Object` An object containing the mixin functions which + * will be added to the JSONEditor. Must contain functions + * create, get, getText, set, and setText. May have + * additional functions. + * When the JSONEditor switches to a mixin, all mixin + * functions are added to the JSONEditor, and then + * the function `create(container, options)` is executed. + * - `data: 'text' | 'json'` The type of data that will be used to load the mixin. + * - `[load: function]` An optional function called after the mixin + * has been loaded. + * + * @param {Object | Array} mode A mode object or an array with multiple mode objects. + */ +JSONEditor.registerMode = function (mode) { + var i, prop; + + if (util.isArray(mode)) { + // multiple modes + for (i = 0; i < mode.length; i++) { + JSONEditor.registerMode(mode[i]); + } + } + else { + // validate the new mode + if (!('mode' in mode)) throw new Error('Property "mode" missing'); + if (!('mixin' in mode)) throw new Error('Property "mixin" missing'); + if (!('data' in mode)) throw new Error('Property "data" missing'); + var name = mode.mode; + if (name in JSONEditor.modes) { + throw new Error('Mode "' + name + '" already registered'); } - if (this.options && typeof this.options.error === 'function') { - this.options.error(err); + // validate the mixin + if (typeof mode.mixin.create !== 'function') { + throw new Error('Required function "create" missing on mixin'); } - else { - throw err; - } - }; - - /** - * Register a plugin with one ore multiple modes for the JSON Editor. - * - * A mode is described as an object with properties: - * - * - `mode: String` The name of the mode. - * - `mixin: Object` An object containing the mixin functions which - * will be added to the JSONEditor. Must contain functions - * create, get, getText, set, and setText. May have - * additional functions. - * When the JSONEditor switches to a mixin, all mixin - * functions are added to the JSONEditor, and then - * the function `create(container, options)` is executed. - * - `data: 'text' | 'json'` The type of data that will be used to load the mixin. - * - `[load: function]` An optional function called after the mixin - * has been loaded. - * - * @param {Object | Array} mode A mode object or an array with multiple mode objects. - */ - JSONEditor.registerMode = function (mode) { - var i, prop; - - if (util.isArray(mode)) { - // multiple modes - for (i = 0; i < mode.length; i++) { - JSONEditor.registerMode(mode[i]); + var reserved = ['setMode', 'registerMode', 'modes']; + for (i = 0; i < reserved.length; i++) { + prop = reserved[i]; + if (prop in mode.mixin) { + throw new Error('Reserved property "' + prop + '" not allowed in mixin'); } } - else { - // validate the new mode - if (!('mode' in mode)) throw new Error('Property "mode" missing'); - if (!('mixin' in mode)) throw new Error('Property "mixin" missing'); - if (!('data' in mode)) throw new Error('Property "data" missing'); - var name = mode.mode; - if (name in JSONEditor.modes) { - throw new Error('Mode "' + name + '" already registered'); - } - // validate the mixin - if (typeof mode.mixin.create !== 'function') { - throw new Error('Required function "create" missing on mixin'); - } - var reserved = ['setMode', 'registerMode', 'modes']; - for (i = 0; i < reserved.length; i++) { - prop = reserved[i]; - if (prop in mode.mixin) { - throw new Error('Reserved property "' + prop + '" not allowed in mixin'); - } - } + JSONEditor.modes[name] = mode; + } +}; - JSONEditor.modes[name] = mode; - } - }; +// register tree and text modes +JSONEditor.registerMode(treemode); +JSONEditor.registerMode(textmode); - // register tree and text modes - JSONEditor.registerMode(treemode); - JSONEditor.registerMode(textmode); - - return JSONEditor; -}); \ No newline at end of file +module.exports = JSONEditor; diff --git a/src/js/Node.js b/src/js/Node.js index 192959f..370d11f 100644 --- a/src/js/Node.js +++ b/src/js/Node.js @@ -1,2737 +1,2693 @@ -define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu, appendNodeFactory, util) { +var ContextMenu = require('./ContextMenu'); +var appendNodeFactory = require('./appendNodeFactory'); +var util = require('./util'); - /** - * @constructor Node - * Create a new Node - * @param {TreeEditor} editor - * @param {Object} [params] Can contain parameters: - * {string} field - * {boolean} fieldEditable - * {*} value - * {String} type Can have values 'auto', 'array', - * 'object', or 'string'. - */ - function Node (editor, params) { - /** @type {TreeEditor} */ - this.editor = editor; - this.dom = {}; - this.expanded = false; +/** + * @constructor Node + * Create a new Node + * @param {TreeEditor} editor + * @param {Object} [params] Can contain parameters: + * {string} field + * {boolean} fieldEditable + * {*} value + * {String} type Can have values 'auto', 'array', + * 'object', or 'string'. + */ +function Node (editor, params) { + /** @type {TreeEditor} */ + this.editor = editor; + this.dom = {}; + this.expanded = false; - if(params && (params instanceof Object)) { - this.setField(params.field, params.fieldEditable); - this.setValue(params.value, params.type); + if(params && (params instanceof Object)) { + this.setField(params.field, params.fieldEditable); + this.setValue(params.value, params.type); + } + else { + this.setField(''); + this.setValue(null); + } +} + +/** + * Determine whether the field and/or value of this node are editable + * @private + */ +Node.prototype._updateEditability = function () { + this.editable = { + field: true, + value: true + }; + + if (this.editor) { + this.editable.field = this.editor.options.mode === 'tree'; + this.editable.value = this.editor.options.mode !== 'view'; + + if (this.editor.options.mode === 'tree' && (typeof this.editor.options.editable === 'function')) { + var editable = this.editor.options.editable({ + field: this.field, + value: this.value, + path: this.path() + }); + + if (typeof editable === 'boolean') { + this.editable.field = editable; + this.editable.value = editable; + } + else { + if (typeof editable.field === 'boolean') this.editable.field = editable.field; + if (typeof editable.value === 'boolean') this.editable.value = editable.value; + } } - else { - this.setField(''); - this.setValue(null); + } +}; + +/** + * Get the path of this node + * @return {String[]} Array containing the path to this node + */ +Node.prototype.path = function () { + var node = this; + var path = []; + while (node) { + var field = node.field != undefined ? node.field : node.index; + if (field !== undefined) { + path.unshift(field); + } + node = node.parent; + } + return path; +}; + +/** + * Set parent node + * @param {Node} parent + */ +Node.prototype.setParent = function(parent) { + this.parent = parent; +}; + +/** + * Set field + * @param {String} field + * @param {boolean} [fieldEditable] + */ +Node.prototype.setField = function(field, fieldEditable) { + this.field = field; + this.fieldEditable = (fieldEditable == true); +}; + +/** + * Get field + * @return {String} + */ +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 + * @param {String} [type] Specify the type of the value. Can be 'auto', + * 'array', 'object', or 'string' + */ +Node.prototype.setValue = function(value, type) { + var childValue, child; + + // first clear all current childs (if any) + var childs = this.childs; + if (childs) { + while (childs.length) { + this.removeChild(childs[0]); } } - /** - * Determine whether the field and/or value of this node are editable - * @private - */ - Node.prototype._updateEditability = function () { - this.editable = { - field: true, - value: true - }; + // TODO: remove the DOM of this Node - if (this.editor) { - this.editable.field = this.editor.options.mode === 'tree'; - this.editable.value = this.editor.options.mode !== 'view'; + this.type = this._getType(value); - if (this.editor.options.mode === 'tree' && (typeof this.editor.options.editable === 'function')) { - var editable = this.editor.options.editable({ - field: this.field, - value: this.value, - path: this.path() + // check if type corresponds with the provided type + if (type && type != this.type) { + if (type == 'string' && this.type == 'auto') { + this.type = type; + } + else { + throw new Error('Type mismatch: ' + + 'cannot cast value of type "' + this.type + + ' to the specified type "' + type + '"'); + } + } + + 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 Node(this.editor, { + value: childValue }); - - if (typeof editable === 'boolean') { - this.editable.field = editable; - this.editable.value = editable; - } - else { - if (typeof editable.field === 'boolean') this.editable.field = editable.field; - if (typeof editable.value === 'boolean') this.editable.value = editable.value; - } + this.appendChild(child); } } - }; - - /** - * Get the path of this node - * @return {String[]} Array containing the path to this node - */ - Node.prototype.path = function () { - var node = this; - var path = []; - while (node) { - var field = node.field != undefined ? node.field : node.index; - if (field !== undefined) { - path.unshift(field); - } - node = node.parent; - } - return path; - }; - - /** - * Set parent node - * @param {Node} parent - */ - Node.prototype.setParent = function(parent) { - this.parent = parent; - }; - - /** - * Set field - * @param {String} field - * @param {boolean} [fieldEditable] - */ - Node.prototype.setField = function(field, fieldEditable) { - this.field = field; - this.fieldEditable = (fieldEditable == true); - }; - - /** - * Get field - * @return {String} - */ - 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 - * @param {String} [type] Specify the type of the value. Can be 'auto', - * 'array', 'object', or 'string' - */ - Node.prototype.setValue = function(value, type) { - 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); - - // check if type corresponds with the provided type - if (type && type != this.type) { - if (type == 'string' && this.type == 'auto') { - this.type = type; - } - else { - throw new Error('Type mismatch: ' + - 'cannot cast value of type "' + this.type + - ' to the specified type "' + type + '"'); - } - } - - if (this.type == 'array') { - // array - this.childs = []; - for (var i = 0, iMax = value.length; i < iMax; i++) { - childValue = value[i]; + 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 Node(this.editor, { + field: childField, 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 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); - util.log('check', value, this.value); - } - else { - this.value = value; - } - */ - } - }; - - /** - * Get value. Value is a JSON structure - * @return {*} value - */ - 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 - */ - 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 {Node} clone - */ - Node.prototype.clone = function() { - var clone = new 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 - */ - Node.prototype.expand = function(recurse) { - if (!this.childs) { - return; - } - - // set this node expanded - this.expanded = true; - if (this.dom.expand) { - this.dom.expand.className = 'expanded'; - } - - this.showChilds(); - - if (recurse != false) { - this.childs.forEach(function (child) { - child.expand(recurse); - }); - } - }; - - /** - * Collapse this node and optionally its childs. - * @param {boolean} [recurse] Optional recursion, true by default. When - * true, all childs will be collapsed recursively - */ - 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 = 'collapsed'; - } - this.expanded = false; - }; - - /** - * Recursively show all childs when they are expanded - */ - 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 - */ - 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 - */ - 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 {Node} node - */ - Node.prototype.appendChild = function(node) { - if (this._hasChilds()) { - // 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 {Node} node - * @param {Node} beforeNode - */ - Node.prototype.moveBefore = function(node, beforeNode) { - if (this._hasChilds()) { - // 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); - } - - if (node.parent) { - node.parent.removeChild(node); - } - - if (beforeNode instanceof 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 {Node} node - * @param {Number} index - */ - 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 {Node} node - * @param {Node} beforeNode - */ - Node.prototype.insertBefore = function(node, beforeNode) { - if (this._hasChilds()) { - 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('Node not found'); - } - - // 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}); - } - }; - - /** - * Insert a new child before a given node - * Only applicable when Node value is of type array or object - * @param {Node} node - * @param {Node} afterNode - */ - Node.prototype.insertAfter = function(node, afterNode) { - if (this._hasChilds()) { - var index = this.childs.indexOf(afterNode); - var beforeNode = this.childs[index + 1]; - if (beforeNode) { - this.insertBefore(node, beforeNode); - } - else { - this.appendChild(node); - } - } - }; - - /** - * 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 {Node[]} results Array with nodes containing the search text - */ - 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._hasChilds()) { - // 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 - * @param {function(boolean)} [callback] - */ - Node.prototype.scrollTo = function(callback) { - 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, callback); - } - }; - - -// stores the element name currently having the focus - Node.focusElement = undefined; - - /** - * Set focus to this node - * @param {String} [elementName] The field name of the element to get the - * focus available values: 'drag', 'menu', - * 'expand', 'field', 'value' (default) - */ - Node.prototype.focus = function(elementName) { - Node.focusElement = elementName; - - if (this.dom.tr && this.dom.tr.parentNode) { - var dom = this.dom; - - switch (elementName) { - case 'drag': - if (dom.drag) { - dom.drag.focus(); - } - else { - dom.menu.focus(); - } - break; - - case 'menu': - dom.menu.focus(); - break; - - case 'expand': - if (this._hasChilds()) { - dom.expand.focus(); - } - else if (dom.field && this.fieldEditable) { - dom.field.focus(); - util.selectContentEditable(dom.field); - } - else if (dom.value && !this._hasChilds()) { - dom.value.focus(); - util.selectContentEditable(dom.value); - } - else { - dom.menu.focus(); - } - break; - - case 'field': - if (dom.field && this.fieldEditable) { - dom.field.focus(); - util.selectContentEditable(dom.field); - } - else if (dom.value && !this._hasChilds()) { - dom.value.focus(); - util.selectContentEditable(dom.value); - } - else if (this._hasChilds()) { - dom.expand.focus(); - } - else { - dom.menu.focus(); - } - break; - - case 'value': - default: - if (dom.value && !this._hasChilds()) { - dom.value.focus(); - util.selectContentEditable(dom.value); - } - else if (dom.field && this.fieldEditable) { - dom.field.focus(); - util.selectContentEditable(dom.field); - } - else if (this._hasChilds()) { - dom.expand.focus(); - } - else { - dom.menu.focus(); - } - break; - } - } - }; - - /** - * Select all text in an editable div after a delay of 0 ms - * @param {Element} editableDiv - */ - Node.select = function(editableDiv) { - setTimeout(function () { - util.selectContentEditable(editableDiv); - }, 0); - }; - - /** - * Update the values from the DOM field and value of this node - */ - 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 {Node} node the childNode to be duplicated - * @return {Node} clone the clone of the node - * @private - */ - Node.prototype._duplicate = function(node) { - var clone = node.clone(); - - /* TODO: adjust the field name (to prevent equal field names) - if (this.type == 'object') { + 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); + util.log('check', value, this.value); + } + else { + this.value = value; } */ + } +}; - this.insertAfter(clone, node); +/** + * Get value. Value is a JSON structure + * @return {*} value + */ +Node.prototype.getValue = function() { + //var childs, i, iMax; - return clone; - }; - - /** - * Check if given node is a child. The method will check recursively to find - * this node. - * @param {Node} node - * @return {boolean} containsNode - */ - Node.prototype.containsNode = function(node) { - if (this == node) { - return true; + 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(); } - var childs = this.childs; - if (childs) { - // TODO: use the js5 Array.some() here? - for (var i = 0, iMax = childs.length; i < iMax; i++) { - if (childs[i].containsNode(node)) { - return true; - } + return this.value; + } +}; + +/** + * Get the nesting level of this node + * @return {Number} level + */ +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 {Node} clone + */ +Node.prototype.clone = function() { + var clone = new 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 + */ +Node.prototype.expand = function(recurse) { + if (!this.childs) { + return; + } + + // set this node expanded + this.expanded = true; + if (this.dom.expand) { + this.dom.expand.className = 'expanded'; + } + + this.showChilds(); + + if (recurse != false) { + this.childs.forEach(function (child) { + child.expand(recurse); + }); + } +}; + +/** + * Collapse this node and optionally its childs. + * @param {boolean} [recurse] Optional recursion, true by default. When + * true, all childs will be collapsed recursively + */ +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 = 'collapsed'; + } + this.expanded = false; +}; + +/** + * Recursively show all childs when they are expanded + */ +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 + */ +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 + */ +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 {Node} node + */ +Node.prototype.appendChild = function(node) { + if (this._hasChilds()) { + // 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(); } - return false; - }; + this.updateDom({'updateIndexes': true}); + node.updateDom({'recurse': true}); + } +}; - /** - * Move given node into this node - * @param {Node} node the childNode to be moved - * @param {Node} beforeNode node will be inserted before given - * node. If no beforeNode is given, - * the node is appended at the end - * @private - */ - Node.prototype._move = function(node, beforeNode) { - if (node == beforeNode) { - // nothing to do... - return; + +/** + * Move a node from its current parent to this node + * Only applicable when Node value is of type array or object + * @param {Node} node + * @param {Node} beforeNode + */ +Node.prototype.moveBefore = function(node, beforeNode) { + if (this._hasChilds()) { + // 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); } - // check if this node is not a child of the node to be moved here - if (node.containsNode(this)) { - throw new Error('Cannot move a field into a child of itself'); - } - - // remove the original node if (node.parent) { node.parent.removeChild(node); } - // create a clone of the node - var clone = node.clone(); - node.clearDom(); + if (beforeNode instanceof AppendNode) { + this.appendChild(node); + } + else { + this.insertBefore(node, beforeNode); + } - // insert or append the node + 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 {Node} node + * @param {Number} index + */ +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 {Node} node + * @param {Node} beforeNode + */ +Node.prototype.insertBefore = function(node, beforeNode) { + if (this._hasChilds()) { + 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('Node not found'); + } + + // 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}); + } +}; + +/** + * Insert a new child before a given node + * Only applicable when Node value is of type array or object + * @param {Node} node + * @param {Node} afterNode + */ +Node.prototype.insertAfter = function(node, afterNode) { + if (this._hasChilds()) { + var index = this.childs.indexOf(afterNode); + var beforeNode = this.childs[index + 1]; if (beforeNode) { - this.insertBefore(clone, beforeNode); + this.insertBefore(node, beforeNode); } else { - this.appendChild(clone); + this.appendChild(node); + } + } +}; + +/** + * 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 {Node[]} results Array with nodes containing the search text + */ +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' + }); } - /* TODO: adjust the field name (to prevent equal field names) - if (this.type == 'object') { - } - */ - }; + // update dom + this._updateDomField(); + } - /** - * Remove a child from the node. - * Only applicable when Node value is of type array or object - * @param {Node} node The child node to be removed; - * @return {Node | undefined} node The removed node on success, - * else undefined - */ - Node.prototype.removeChild = function(node) { + // search in value + if (this._hasChilds()) { + // array, object + + // search the nodes childs if (this.childs) { - var index = this.childs.indexOf(node); + 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) { - 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; + this.searchValue = true; + results.push({ + 'node': this, + 'elem': 'value' + }); } } - return undefined; - }; + // update dom + this._updateDomValue(); + } - /** - * Remove a child node node from this node - * This method is equal to Node.removeChild, except that _remove firex an - * onChange event. - * @param {Node} node - * @private - */ - Node.prototype._remove = function (node) { - this.removeChild(node); - }; + return results; +}; - /** - * Change the type of the value of this Node - * @param {String} newType - */ - Node.prototype.changeType = function (newType) { - var oldType = this.type; - - if (oldType == newType) { - // type is not changed - return; +/** + * Move the scroll position such that this node is in the visible area. + * The node will not get the focus + * @param {function(boolean)} [callback] + */ +Node.prototype.scrollTo = function(callback) { + 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 ((newType == 'string' || newType == 'auto') && - (oldType == 'string' || oldType == 'auto')) { - // this is an easy change - this.type = newType; + if (this.dom.tr && this.dom.tr.parentNode) { + this.editor.scrollTo(this.dom.tr.offsetTop, callback); + } +}; + + +// stores the element name currently having the focus +Node.focusElement = undefined; + +/** + * Set focus to this node + * @param {String} [elementName] The field name of the element to get the + * focus available values: 'drag', 'menu', + * 'expand', 'field', 'value' (default) + */ +Node.prototype.focus = function(elementName) { + Node.focusElement = elementName; + + if (this.dom.tr && this.dom.tr.parentNode) { + var dom = this.dom; + + switch (elementName) { + case 'drag': + if (dom.drag) { + dom.drag.focus(); + } + else { + dom.menu.focus(); + } + break; + + case 'menu': + dom.menu.focus(); + break; + + case 'expand': + if (this._hasChilds()) { + dom.expand.focus(); + } + else if (dom.field && this.fieldEditable) { + dom.field.focus(); + util.selectContentEditable(dom.field); + } + else if (dom.value && !this._hasChilds()) { + dom.value.focus(); + util.selectContentEditable(dom.value); + } + else { + dom.menu.focus(); + } + break; + + case 'field': + if (dom.field && this.fieldEditable) { + dom.field.focus(); + util.selectContentEditable(dom.field); + } + else if (dom.value && !this._hasChilds()) { + dom.value.focus(); + util.selectContentEditable(dom.value); + } + else if (this._hasChilds()) { + dom.expand.focus(); + } + else { + dom.menu.focus(); + } + break; + + case 'value': + default: + if (dom.value && !this._hasChilds()) { + dom.value.focus(); + util.selectContentEditable(dom.value); + } + else if (dom.field && this.fieldEditable) { + dom.field.focus(); + util.selectContentEditable(dom.field); + } + else if (this._hasChilds()) { + dom.expand.focus(); + } + else { + dom.menu.focus(); + } + break; + } + } +}; + +/** + * Select all text in an editable div after a delay of 0 ms + * @param {Element} editableDiv + */ +Node.select = function(editableDiv) { + setTimeout(function () { + util.selectContentEditable(editableDiv); + }, 0); +}; + +/** + * Update the values from the DOM field and value of this node + */ +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 {Node} node the childNode to be duplicated + * @return {Node} clone the clone of the node + * @private + */ +Node.prototype._duplicate = function(node) { + var clone = node.clone(); + + /* TODO: adjust the field name (to prevent equal field names) + if (this.type == 'object') { + } + */ + + this.insertAfter(clone, node); + + return clone; +}; + +/** + * Check if given node is a child. The method will check recursively to find + * this node. + * @param {Node} node + * @return {boolean} containsNode + */ +Node.prototype.containsNode = function(node) { + if (this == node) { + return true; + } + + var childs = this.childs; + if (childs) { + // TODO: 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 {Node} node the childNode to be moved + * @param {Node} beforeNode node will be inserted before given + * node. If no beforeNode is given, + * the node is appended at the end + * @private + */ +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('Cannot move a field into a child of itself'); + } + + // 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 {Node} node The child node to be removed; + * @return {Node | undefined} node The removed node on success, + * else undefined + */ +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 {Node} node + * @private + */ +Node.prototype._remove = function (node) { + this.removeChild(node); +}; + +/** + * Change the type of the value of this Node + * @param {String} newType + */ +Node.prototype.changeType = function (newType) { + var oldType = this.type; + + if (oldType == newType) { + // type is not changed + return; + } + + 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 { - // 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(); + 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 = ''; + } + }); + + 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 { - lastTr = this.getDom(); + table.appendChild(this.getDom()); } - var nextTr = (lastTr && lastTr.parentNode) ? lastTr.nextSibling : undefined; + } + this.showChilds(); + } - // hide current field and all its childs - this.hide(); - this.clearDom(); + 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)); + } - // adjust the field and the value - this.type = newType; + this.focus(); + } - // adjust childs - if (newType == 'object') { - if (!this.childs) { - this.childs = []; - } + this.updateDom({'updateIndexes': true}); +}; - this.childs.forEach(function (child, index) { - child.clearDom(); - delete child.index; - child.fieldEditable = true; - if (child.field == undefined) { - child.field = ''; - } +/** + * Retrieve value from DOM + * @param {boolean} [silent] If true (default), no errors will be thrown in + * case of invalid data + * @private + */ +Node.prototype._getDomValue = function(silent) { + if (this.dom.value && this.type != 'array' && this.type != 'object') { + this.valueInnerText = util.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, + 'oldSelection': this.editor.selection, + 'newSelection': this.editor.getSelection() }); - - if (oldType == 'string' || oldType == 'auto') { - this.expanded = true; - } } - else if (newType == 'array') { - if (!this.childs) { - this.childs = []; - } + } + catch (err) { + this.value = undefined; + // TODO: sent an action with the new, invalid value? + if (silent != true) { + throw err; + } + } + } +}; - this.childs.forEach(function (child, index) { - child.clearDom(); - child.fieldEditable = false; - child.index = index; +/** + * 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 + */ +Node.prototype._updateDomValue = function () { + var domValue = this.dom.value; + if (domValue) { + // set text color depending on value type + // TODO: put colors in css + var v = this.value; + var t = (this.type == 'auto') ? util.type(v) : this.type; + var isUrl = (t == 'string' && util.isUrl(v)); + var color = ''; + if (isUrl && !this.editable.value) { // TODO: when to apply this? + color = ''; + } + else if (t == 'string') { + color = 'green'; + } + else if (t == 'number') { + color = 'red'; + } + else if (t == 'boolean') { + color = 'darkorange'; + } + else if (this._hasChilds()) { + color = ''; + } + else if (v === null) { + color = '#004ED0'; // blue + } + else { + // invalid value + color = 'black'; + } + domValue.style.color = color; + + // make background color light-gray when empty + var isEmpty = (String(this.value) == '' && this.type != 'array' && this.type != 'object'); + if (isEmpty) { + util.addClassName(domValue, 'empty'); + } + else { + util.removeClassName(domValue, 'empty'); + } + + // underline url + if (isUrl) { + util.addClassName(domValue, 'url'); + } + else { + util.removeClassName(domValue, 'url'); + } + + // update title + if (t == 'array' || t == 'object') { + var count = this.childs ? this.childs.length : 0; + domValue.title = this.type + ' containing ' + count + ' items'; + } + else if (t == 'string' && util.isUrl(v)) { + if (this.editable.value) { + domValue.title = 'Ctrl+Click or Ctrl+Enter to open url in new window'; + } + } + else { + domValue.title = ''; + } + + // highlight when there is a search result + if (this.searchValueActive) { + util.addClassName(domValue, 'highlight-active'); + } + else { + util.removeClassName(domValue, 'highlight-active'); + } + if (this.searchValue) { + util.addClassName(domValue, 'highlight'); + } + else { + util.removeClassName(domValue, 'highlight'); + } + + // strip formatting from the contents of the editable div + util.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 + */ +Node.prototype._updateDomField = function () { + var domField = this.dom.field; + if (domField) { + // make backgound color lightgray when empty + var isEmpty = (String(this.field) == '' && this.parent.type != 'array'); + if (isEmpty) { + util.addClassName(domField, 'empty'); + } + else { + util.removeClassName(domField, 'empty'); + } + + // highlight when there is a search result + if (this.searchFieldActive) { + util.addClassName(domField, 'highlight-active'); + } + else { + util.removeClassName(domField, 'highlight-active'); + } + if (this.searchField) { + util.addClassName(domField, 'highlight'); + } + else { + util.removeClassName(domField, 'highlight'); + } + + // strip formatting from the contents of the editable div + util.stripFormatting(domField); + } +}; + +/** + * Retrieve field from DOM + * @param {boolean} [silent] If true (default), no errors will be thrown in + * case of invalid data + * @private + */ +Node.prototype._getDomField = function(silent) { + if (this.dom.field && this.fieldEditable) { + this.fieldInnerText = util.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, + 'oldSelection': this.editor.selection, + 'newSelection': this.editor.getSelection() }); - - 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 - */ - Node.prototype._getDomValue = function(silent) { - if (this.dom.value && this.type != 'array' && this.type != 'object') { - this.valueInnerText = util.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, - 'oldSelection': this.editor.selection, - 'newSelection': this.editor.getSelection() - }); - } - } - 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 - */ - Node.prototype._updateDomValue = function () { - var domValue = this.dom.value; - if (domValue) { - // set text color depending on value type - // TODO: put colors in css - var v = this.value; - var t = (this.type == 'auto') ? util.type(v) : this.type; - var isUrl = (t == 'string' && util.isUrl(v)); - var color = ''; - if (isUrl && !this.editable.value) { // TODO: when to apply this? - color = ''; - } - else if (t == 'string') { - color = 'green'; - } - else if (t == 'number') { - color = 'red'; - } - else if (t == 'boolean') { - color = 'darkorange'; - } - else if (this._hasChilds()) { - color = ''; - } - else if (v === null) { - color = '#004ED0'; // blue - } - else { - // invalid value - color = 'black'; - } - domValue.style.color = color; - - // make background color light-gray when empty - var isEmpty = (String(this.value) == '' && this.type != 'array' && this.type != 'object'); - if (isEmpty) { - util.addClassName(domValue, 'empty'); - } - else { - util.removeClassName(domValue, 'empty'); - } - - // underline url - if (isUrl) { - util.addClassName(domValue, 'url'); - } - else { - util.removeClassName(domValue, 'url'); - } - - // update title - if (t == 'array' || t == 'object') { - var count = this.childs ? this.childs.length : 0; - domValue.title = this.type + ' containing ' + count + ' items'; - } - else if (t == 'string' && util.isUrl(v)) { - if (this.editable.value) { - domValue.title = 'Ctrl+Click or Ctrl+Enter to open url in new window'; - } - } - else { - domValue.title = ''; - } - - // highlight when there is a search result - if (this.searchValueActive) { - util.addClassName(domValue, 'highlight-active'); - } - else { - util.removeClassName(domValue, 'highlight-active'); - } - if (this.searchValue) { - util.addClassName(domValue, 'highlight'); - } - else { - util.removeClassName(domValue, 'highlight'); - } - - // strip formatting from the contents of the editable div - util.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 - */ - Node.prototype._updateDomField = function () { - var domField = this.dom.field; - if (domField) { - // make backgound color lightgray when empty - var isEmpty = (String(this.field) == '' && this.parent.type != 'array'); - if (isEmpty) { - util.addClassName(domField, 'empty'); - } - else { - util.removeClassName(domField, 'empty'); - } - - // highlight when there is a search result - if (this.searchFieldActive) { - util.addClassName(domField, 'highlight-active'); - } - else { - util.removeClassName(domField, 'highlight-active'); - } - if (this.searchField) { - util.addClassName(domField, 'highlight'); - } - else { - util.removeClassName(domField, 'highlight'); - } - - // strip formatting from the contents of the editable div - util.stripFormatting(domField); - } - }; - - /** - * Retrieve field from DOM - * @param {boolean} [silent] If true (default), no errors will be thrown in - * case of invalid data - * @private - */ - Node.prototype._getDomField = function(silent) { - if (this.dom.field && this.fieldEditable) { - this.fieldInnerText = util.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, - 'oldSelection': this.editor.selection, - 'newSelection': this.editor.getSelection() - }); - } - } - catch (err) { - this.field = undefined; - // TODO: sent an action here, with the new, invalid value? - if (silent != true) { - throw err; - } + 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 - */ - Node.prototype.clearDom = function() { - // TODO: hide the node first? - //this.hide(); - // TODO: recursively clear dom? +/** + * Clear the dom of the node + */ +Node.prototype.clearDom = function() { + // TODO: hide the node first? + //this.hide(); + // TODO: 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 - */ - Node.prototype.getDom = function() { - var dom = this.dom; - if (dom.tr) { - return dom.tr; - } - - this._updateEditability(); - - // create row - dom.tr = document.createElement('tr'); - dom.tr.node = this; - - if (this.editor.options.mode === 'tree') { // note: we take here the global setting - var tdDrag = document.createElement('td'); - if (this.editable.field) { - // create draggable area - if (this.parent) { - var domDrag = document.createElement('button'); - dom.drag = domDrag; - domDrag.className = 'dragarea'; - domDrag.title = 'Drag to move this field (Alt+Shift+Arrows)'; - tdDrag.appendChild(domDrag); - } - } - dom.tr.appendChild(tdDrag); - - // create context menu - var tdMenu = document.createElement('td'); - var menu = document.createElement('button'); - dom.menu = menu; - menu.className = 'contextmenu'; - menu.title = 'Click to open the actions menu (Ctrl+M)'; - tdMenu.appendChild(dom.menu); - dom.tr.appendChild(tdMenu); - } - - // create tree and field - var tdField = document.createElement('td'); - dom.tr.appendChild(tdField); - dom.tree = this._createDomTree(); - tdField.appendChild(dom.tree); - - this.updateDom({'updateIndexes': true}); + 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 + */ +Node.prototype.getDom = function() { + var dom = this.dom; + if (dom.tr) { return dom.tr; - }; + } - /** - * DragStart event, fired on mousedown on the dragarea at the left side of a Node - * @param {Event} event - * @private - */ - Node.prototype._onDragStart = function (event) { - var node = this; - if (!this.mousemove) { - this.mousemove = util.addEventListener(document, 'mousemove', - function (event) { - node._onDrag(event); - }); - } + this._updateEditability(); - if (!this.mouseup) { - this.mouseup = util.addEventListener(document, 'mouseup', - function (event ) { - node._onDragEnd(event); - }); - } + // create row + dom.tr = document.createElement('tr'); + dom.tr.node = this; - this.editor.highlighter.lock(); - this.drag = { - 'oldCursor': document.body.style.cursor, - 'startParent': this.parent, - 'startIndex': this.parent.childs.indexOf(this), - 'mouseX': event.pageX, - 'level': this.getLevel() - }; - document.body.style.cursor = 'move'; - - event.preventDefault(); - }; - - /** - * Drag event, fired when moving the mouse while dragging a Node - * @param {Event} event - * @private - */ - Node.prototype._onDrag = function (event) { - // TODO: this method has grown too large. Split it in a number of methods - var mouseY = event.pageY; - var mouseX = event.pageX; - - var trThis, trPrev, trNext, trFirst, trLast, trRoot; - var nodePrev, nodeNext; - var topThis, topPrev, topFirst, heightThis, bottomNext, heightNext; - var moved = false; - - // TODO: add an ESC option, which resets to the original position - - // move up/down - trThis = this.dom.tr; - topThis = util.getAbsoluteTop(trThis); - heightThis = trThis.offsetHeight; - if (mouseY < topThis) { - // move up - trPrev = trThis; - do { - trPrev = trPrev.previousSibling; - nodePrev = Node.getNodeFromTarget(trPrev); - topPrev = trPrev ? util.getAbsoluteTop(trPrev) : 0; + if (this.editor.options.mode === 'tree') { // note: we take here the global setting + var tdDrag = document.createElement('td'); + if (this.editable.field) { + // create draggable area + if (this.parent) { + var domDrag = document.createElement('button'); + dom.drag = domDrag; + domDrag.className = 'dragarea'; + domDrag.title = 'Drag to move this field (Alt+Shift+Arrows)'; + tdDrag.appendChild(domDrag); } - while (trPrev && mouseY < topPrev); + } + dom.tr.appendChild(tdDrag); - if (nodePrev && !nodePrev.parent) { + // create context menu + var tdMenu = document.createElement('td'); + var menu = document.createElement('button'); + dom.menu = menu; + menu.className = 'contextmenu'; + menu.title = 'Click to open the actions menu (Ctrl+M)'; + tdMenu.appendChild(dom.menu); + dom.tr.appendChild(tdMenu); + } + + // create tree and field + var tdField = document.createElement('td'); + dom.tr.appendChild(tdField); + dom.tree = this._createDomTree(); + tdField.appendChild(dom.tree); + + this.updateDom({'updateIndexes': true}); + + return dom.tr; +}; + +/** + * DragStart event, fired on mousedown on the dragarea at the left side of a Node + * @param {Event} event + * @private + */ +Node.prototype._onDragStart = function (event) { + var node = this; + if (!this.mousemove) { + this.mousemove = util.addEventListener(document, 'mousemove', + function (event) { + node._onDrag(event); + }); + } + + if (!this.mouseup) { + this.mouseup = util.addEventListener(document, 'mouseup', + function (event ) { + node._onDragEnd(event); + }); + } + + this.editor.highlighter.lock(); + this.drag = { + 'oldCursor': document.body.style.cursor, + 'startParent': this.parent, + 'startIndex': this.parent.childs.indexOf(this), + 'mouseX': event.pageX, + 'level': this.getLevel() + }; + document.body.style.cursor = 'move'; + + event.preventDefault(); +}; + +/** + * Drag event, fired when moving the mouse while dragging a Node + * @param {Event} event + * @private + */ +Node.prototype._onDrag = function (event) { + // TODO: this method has grown too large. Split it in a number of methods + var mouseY = event.pageY; + var mouseX = event.pageX; + + var trThis, trPrev, trNext, trFirst, trLast, trRoot; + var nodePrev, nodeNext; + var topThis, topPrev, topFirst, heightThis, bottomNext, heightNext; + var moved = false; + + // TODO: add an ESC option, which resets to the original position + + // move up/down + trThis = this.dom.tr; + topThis = util.getAbsoluteTop(trThis); + heightThis = trThis.offsetHeight; + if (mouseY < topThis) { + // move up + trPrev = trThis; + do { + trPrev = trPrev.previousSibling; + nodePrev = Node.getNodeFromTarget(trPrev); + topPrev = trPrev ? util.getAbsoluteTop(trPrev) : 0; + } + while (trPrev && mouseY < topPrev); + + if (nodePrev && !nodePrev.parent) { + nodePrev = undefined; + } + + if (!nodePrev) { + // move to the first node + trRoot = trThis.parentNode.firstChild; + trPrev = trRoot ? trRoot.nextSibling : undefined; + nodePrev = Node.getNodeFromTarget(trPrev); + if (nodePrev == this) { nodePrev = undefined; } + } - if (!nodePrev) { - // move to the first node - trRoot = trThis.parentNode.firstChild; - trPrev = trRoot ? trRoot.nextSibling : undefined; - nodePrev = Node.getNodeFromTarget(trPrev); - if (nodePrev == this) { - nodePrev = undefined; - } - } - - if (nodePrev) { - // check if mouseY is really inside the found node - trPrev = nodePrev.dom.tr; - topPrev = trPrev ? util.getAbsoluteTop(trPrev) : 0; - if (mouseY > topPrev + heightThis) { - nodePrev = undefined; - } - } - - if (nodePrev) { - nodePrev.parent.moveBefore(this, nodePrev); - moved = true; + if (nodePrev) { + // check if mouseY is really inside the found node + trPrev = nodePrev.dom.tr; + topPrev = trPrev ? util.getAbsoluteTop(trPrev) : 0; + if (mouseY > topPrev + heightThis) { + nodePrev = undefined; } } - else { - // move down - trLast = (this.expanded && this.append) ? this.append.getDom() : this.dom.tr; - trFirst = trLast ? trLast.nextSibling : undefined; - if (trFirst) { - topFirst = util.getAbsoluteTop(trFirst); - trNext = trFirst; - do { - nodeNext = Node.getNodeFromTarget(trNext); - if (trNext) { - bottomNext = trNext.nextSibling ? - util.getAbsoluteTop(trNext.nextSibling) : 0; - heightNext = trNext ? (bottomNext - topFirst) : 0; - if (nodeNext.parent.childs.length == 1 && nodeNext.parent.childs[0] == this) { - // We are about to remove the last child of this parent, - // which will make the parents appendNode visible. - topThis += 24 - 1; - // TODO: dangerous to suppose the height of the appendNode a constant of 24-1 px. - } + if (nodePrev) { + nodePrev.parent.moveBefore(this, nodePrev); + moved = true; + } + } + else { + // move down + trLast = (this.expanded && this.append) ? this.append.getDom() : this.dom.tr; + trFirst = trLast ? trLast.nextSibling : undefined; + if (trFirst) { + topFirst = util.getAbsoluteTop(trFirst); + trNext = trFirst; + do { + nodeNext = Node.getNodeFromTarget(trNext); + if (trNext) { + bottomNext = trNext.nextSibling ? + util.getAbsoluteTop(trNext.nextSibling) : 0; + heightNext = trNext ? (bottomNext - topFirst) : 0; + + if (nodeNext.parent.childs.length == 1 && nodeNext.parent.childs[0] == this) { + // We are about to remove the last child of this parent, + // which will make the parents appendNode visible. + topThis += 24 - 1; + // TODO: dangerous to suppose the height of the appendNode a constant of 24-1 px. } - - trNext = trNext.nextSibling; } - while (trNext && mouseY > topThis + heightNext); - if (nodeNext && nodeNext.parent) { - // calculate the desired level - var diffX = (mouseX - this.drag.mouseX); - var diffLevel = Math.round(diffX / 24 / 2); - var level = this.drag.level + diffLevel; // desired level - var levelNext = nodeNext.getLevel(); // level to be + trNext = trNext.nextSibling; + } + while (trNext && mouseY > topThis + heightNext); - // find the best fitting level (move upwards over the append nodes) - trPrev = nodeNext.dom.tr.previousSibling; - while (levelNext < level && trPrev) { - nodePrev = Node.getNodeFromTarget(trPrev); - if (nodePrev == this || nodePrev._isChildOf(this)) { - // neglect itself and its childs - } - else if (nodePrev instanceof AppendNode) { - var childs = nodePrev.parent.childs; - if (childs.length > 1 || - (childs.length == 1 && childs[0] != this)) { - // non-visible append node of a list of childs - // consisting of not only this node (else the - // append node will change into a visible "empty" - // text when removing this node). - nodeNext = Node.getNodeFromTarget(trPrev); - levelNext = nodeNext.getLevel(); - } - else { - break; - } + if (nodeNext && nodeNext.parent) { + // calculate the desired level + var diffX = (mouseX - this.drag.mouseX); + var diffLevel = Math.round(diffX / 24 / 2); + var level = this.drag.level + diffLevel; // desired level + var levelNext = nodeNext.getLevel(); // level to be + + // find the best fitting level (move upwards over the append nodes) + trPrev = nodeNext.dom.tr.previousSibling; + while (levelNext < level && trPrev) { + nodePrev = Node.getNodeFromTarget(trPrev); + if (nodePrev == this || nodePrev._isChildOf(this)) { + // neglect itself and its childs + } + else if (nodePrev instanceof AppendNode) { + var childs = nodePrev.parent.childs; + if (childs.length > 1 || + (childs.length == 1 && childs[0] != this)) { + // non-visible append node of a list of childs + // consisting of not only this node (else the + // append node will change into a visible "empty" + // text when removing this node). + nodeNext = Node.getNodeFromTarget(trPrev); + levelNext = nodeNext.getLevel(); } else { break; } - - trPrev = trPrev.previousSibling; + } + else { + break; } - // move the node when its position is changed - if (trLast.nextSibling != nodeNext.dom.tr) { - nodeNext.parent.moveBefore(this, nodeNext); - moved = true; - } + trPrev = trPrev.previousSibling; + } + + // move the node when its position is changed + if (trLast.nextSibling != nodeNext.dom.tr) { + nodeNext.parent.moveBefore(this, nodeNext); + moved = true; } } } + } - if (moved) { - // update the dragging parameters when moved - this.drag.mouseX = mouseX; - this.drag.level = this.getLevel(); - } + if (moved) { + // update the dragging parameters when moved + this.drag.mouseX = mouseX; + this.drag.level = this.getLevel(); + } - // auto scroll when hovering around the top of the editor - this.editor.startAutoScroll(mouseY); + // auto scroll when hovering around the top of the editor + this.editor.startAutoScroll(mouseY); - event.preventDefault(); + event.preventDefault(); +}; + +/** + * Drag event, fired on mouseup after having dragged a node + * @param {Event} event + * @private + */ +Node.prototype._onDragEnd = function (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); + } - /** - * Drag event, fired on mouseup after having dragged a node - * @param {Event} event - * @private - */ - Node.prototype._onDragEnd = function (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; + this.editor.highlighter.unlock(); + delete this.drag; + + if (this.mousemove) { + util.removeEventListener(document, 'mousemove', this.mousemove); + delete this.mousemove;} + if (this.mouseup) { + util.removeEventListener(document, 'mouseup', this.mouseup); + delete this.mouseup; + } + + // Stop any running auto scroll + this.editor.stopAutoScroll(); + + event.preventDefault(); +}; + +/** + * Test if this node is a child of an other node + * @param {Node} node + * @return {boolean} isChild + * @private + */ +Node.prototype._isChildOf = function (node) { + var n = this.parent; + while (n) { + if (n == node) { + return true; } + n = n.parent; + } - document.body.style.cursor = this.drag.oldCursor; - this.editor.highlighter.unlock(); - delete this.drag; + return false; +}; - if (this.mousemove) { - util.removeEventListener(document, 'mousemove', this.mousemove); - delete this.mousemove;} - if (this.mouseup) { - util.removeEventListener(document, 'mouseup', this.mouseup); - delete this.mouseup; - } +/** + * Create an editable field + * @return {Element} domField + * @private + */ +Node.prototype._createDomField = function () { + return document.createElement('div'); +}; - // Stop any running auto scroll - this.editor.stopAutoScroll(); +/** + * Set highlighting for this node and all its childs. + * Only applied to the currently visible (expanded childs) + * @param {boolean} highlight + */ +Node.prototype.setHighlight = function (highlight) { + if (this.dom.tr) { + this.dom.tr.className = (highlight ? 'highlight' : ''); - event.preventDefault(); - }; - - /** - * Test if this node is a child of an other node - * @param {Node} node - * @return {boolean} isChild - * @private - */ - Node.prototype._isChildOf = function (node) { - var n = this.parent; - while (n) { - if (n == node) { - return true; - } - n = n.parent; - } - - return false; - }; - - /** - * Create an editable field - * @return {Element} domField - * @private - */ - 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 - */ - Node.prototype.setHighlight = function (highlight) { - if (this.dom.tr) { - this.dom.tr.className = (highlight ? '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 - */ - Node.prototype.updateValue = function (value) { - this.value = value; - this.updateDom(); - }; - - /** - * Update the field of the node. - * @param {String} field - */ - 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. - */ - 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) { - // parent is an object - domField.contentEditable = this.editable.field; - domField.spellcheck = false; - domField.className = 'field'; - } - else { - // parent is an array this is the root node - domField.className = 'readonly'; - } - - var field; - if (this.index != undefined) { - field = this.index; - } - else if (this.field != undefined) { - field = this.field; - } - else if (this._hasChilds()) { - field = this.type; - } - else { - 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 + ']'; - } - else if (this.type == 'object') { - domValue.innerHTML = '{' + count + '}'; - } - else { - domValue.innerHTML = this._escapeHTML(this.value); - } - } - - // 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 - */ - 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 = ''; - } - } - }); - } - } - }; - - /** - * Create an editable value - * @private - */ - Node.prototype._createDomValue = function () { - var domValue; - - if (this.type == 'array') { - domValue = document.createElement('div'); - domValue.className = 'readonly'; - domValue.innerHTML = '[...]'; - } - else if (this.type == 'object') { - domValue = document.createElement('div'); - domValue.className = 'readonly'; - domValue.innerHTML = '{...}'; - } - else { - if (!this.editable.value && util.isUrl(this.value)) { - // create a link in case of read-only editor and value containing an url - domValue = document.createElement('a'); - domValue.className = 'value'; - domValue.href = this.value; - domValue.target = '_blank'; - domValue.innerHTML = this._escapeHTML(this.value); - } - else { - // create an editable or read-only div - domValue = document.createElement('div'); - domValue.contentEditable = this.editable.value; - domValue.spellcheck = false; - domValue.className = 'value'; - domValue.innerHTML = this._escapeHTML(this.value); - } + this.append.setHighlight(highlight); } - return domValue; - }; - - /** - * Create an expand/collapse button - * @return {Element} expand - * @private - */ - Node.prototype._createDomExpandButton = function () { - // create expand button - var expand = document.createElement('button'); - if (this._hasChilds()) { - expand.className = this.expanded ? 'expanded' : 'collapsed'; - expand.title = - 'Click to expand/collapse this field (Ctrl+E). \n' + - 'Ctrl+Click to expand/collapse including all childs.'; - } - else { - expand.className = 'invisible'; - expand.title = ''; - } - - return expand; - }; - - - /** - * Create a DOM tree element, containing the expand/collapse button - * @return {Element} domTree - * @private - */ - Node.prototype._createDomTree = function () { - var dom = this.dom; - var domTree = document.createElement('table'); - var tbody = document.createElement('tbody'); - domTree.style.borderCollapse = 'collapse'; // TODO: put in css - domTree.className = 'values'; - domTree.appendChild(tbody); - var tr = document.createElement('tr'); - tbody.appendChild(tr); - - // create expand button - var tdExpand = document.createElement('td'); - tdExpand.className = 'tree'; - tr.appendChild(tdExpand); - dom.expand = this._createDomExpandButton(); - tdExpand.appendChild(dom.expand); - dom.tdExpand = tdExpand; - - // create the field - var tdField = document.createElement('td'); - tdField.className = 'tree'; - tr.appendChild(tdField); - dom.field = this._createDomField(); - tdField.appendChild(dom.field); - dom.tdField = tdField; - - // create a separator - var tdSeparator = document.createElement('td'); - tdSeparator.className = 'tree'; - tr.appendChild(tdSeparator); - if (this.type != 'object' && this.type != 'array') { - tdSeparator.appendChild(document.createTextNode(':')); - tdSeparator.className = 'separator'; - } - dom.tdSeparator = tdSeparator; - - // create the value - var tdValue = document.createElement('td'); - tdValue.className = 'tree'; - tr.appendChild(tdValue); - dom.value = this._createDomValue(); - tdValue.appendChild(dom.value); - dom.tdValue = tdValue; - - return domTree; - }; - - /** - * Handle an event. The event is catched centrally by the editor - * @param {Event} event - */ - Node.prototype.onEvent = function (event) { - var type = event.type, - target = event.target || event.srcElement, - dom = this.dom, - node = this, - focusNode, - expandable = this._hasChilds(); - - // check if mouse is on menu or on dragarea. - // If so, highlight current row and its childs - if (target == dom.drag || target == dom.menu) { - if (type == 'mouseover') { - this.editor.highlighter.highlight(this); - } - else if (type == 'mouseout') { - this.editor.highlighter.unhighlight(); - } - } - - // drag events - if (type == 'mousedown' && target == dom.drag) { - this._onDragStart(event); - } - - // context menu events - if (type == 'click' && target == dom.menu) { - var highlighter = node.editor.highlighter; - highlighter.highlight(node); - highlighter.lock(); - util.addClassName(dom.menu, 'selected'); - this.showContextMenu(dom.menu, function () { - util.removeClassName(dom.menu, 'selected'); - highlighter.unlock(); - highlighter.unhighlight(); + if (this.childs) { + this.childs.forEach(function (child) { + child.setHighlight(highlight); }); } + } +}; - // expand events - if (type == 'click' && target == dom.expand) { - if (expandable) { - var recurse = event.ctrlKey; // with ctrl-key, expand/collapse all - this._onExpand(recurse); - } +/** + * Update the value of the node. Only primitive types are allowed, no Object + * or Array is allowed. + * @param {String | Number | Boolean | null} value + */ +Node.prototype.updateValue = function (value) { + this.value = value; + this.updateDom(); +}; + +/** + * Update the field of the node. + * @param {String} field + */ +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. + */ +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) { + // parent is an object + domField.contentEditable = this.editable.field; + domField.spellcheck = false; + domField.className = 'field'; + } + else { + // parent is an array this is the root node + domField.className = 'readonly'; } - // value events - var domValue = dom.value; - if (target == domValue) { - //noinspection FallthroughInSwitchStatementJS - switch (type) { - case 'focus': - focusNode = this; - break; - - case 'blur': - case 'change': - this._getDomValue(true); - this._updateDomValue(); - if (this.value) { - domValue.innerHTML = this._escapeHTML(this.value); - } - break; - - case 'input': - this._getDomValue(true); - this._updateDomValue(); - break; - - case 'keydown': - case 'mousedown': - this.editor.selection = this.editor.getSelection(); - break; - - case 'click': - if (event.ctrlKey || !this.editable.value) { - if (util.isUrl(this.value)) { - window.open(this.value, '_blank'); - } - } - break; - - case 'keyup': - this._getDomValue(true); - this._updateDomValue(); - break; - - case 'cut': - case 'paste': - setTimeout(function () { - node._getDomValue(true); - node._updateDomValue(); - }, 1); - break; - } + var field; + if (this.index != undefined) { + field = this.index; } - - // field events - var domField = dom.field; - if (target == domField) { - switch (type) { - case 'focus': - focusNode = this; - break; - - case 'blur': - case 'change': - this._getDomField(true); - this._updateDomField(); - if (this.field) { - domField.innerHTML = this._escapeHTML(this.field); - } - break; - - case 'input': - this._getDomField(true); - this._updateDomField(); - break; - - case 'keydown': - case 'mousedown': - this.editor.selection = this.editor.getSelection(); - break; - - case 'keyup': - this._getDomField(true); - this._updateDomField(); - break; - - case 'cut': - case 'paste': - setTimeout(function () { - node._getDomField(true); - node._updateDomField(); - }, 1); - break; - } + else if (this.field != undefined) { + field = this.field; } - - // 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.pageX < util.getAbsoluteLeft(dom.tdSeparator));// for FF - if (left || expandable) { - // node is expandable when it is an object or array - if (domField) { - util.setEndOfContentEditable(domField); - domField.focus(); - } - } - else { - if (domValue) { - util.setEndOfContentEditable(domValue); - domValue.focus(); - } - } - break; - } + else if (this._hasChilds()) { + field = this.type; } - if ((target == dom.tdExpand && !expandable) || target == dom.tdField || - target == dom.tdSeparator) { - switch (type) { - case 'click': + else { + 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 + ']'; + } + else if (this.type == 'object') { + domValue.innerHTML = '{' + count + '}'; + } + else { + domValue.innerHTML = this._escapeHTML(this.value); + } + } + + // 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 + */ +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 = ''; + } + } + }); + } + } +}; + +/** + * Create an editable value + * @private + */ +Node.prototype._createDomValue = function () { + var domValue; + + if (this.type == 'array') { + domValue = document.createElement('div'); + domValue.className = 'readonly'; + domValue.innerHTML = '[...]'; + } + else if (this.type == 'object') { + domValue = document.createElement('div'); + domValue.className = 'readonly'; + domValue.innerHTML = '{...}'; + } + else { + if (!this.editable.value && util.isUrl(this.value)) { + // create a link in case of read-only editor and value containing an url + domValue = document.createElement('a'); + domValue.className = 'value'; + domValue.href = this.value; + domValue.target = '_blank'; + domValue.innerHTML = this._escapeHTML(this.value); + } + else { + // create an editable or read-only div + domValue = document.createElement('div'); + domValue.contentEditable = this.editable.value; + domValue.spellcheck = false; + domValue.className = 'value'; + domValue.innerHTML = this._escapeHTML(this.value); + } + } + + return domValue; +}; + +/** + * Create an expand/collapse button + * @return {Element} expand + * @private + */ +Node.prototype._createDomExpandButton = function () { + // create expand button + var expand = document.createElement('button'); + if (this._hasChilds()) { + expand.className = this.expanded ? 'expanded' : 'collapsed'; + expand.title = + 'Click to expand/collapse this field (Ctrl+E). \n' + + 'Ctrl+Click to expand/collapse including all childs.'; + } + else { + expand.className = 'invisible'; + expand.title = ''; + } + + return expand; +}; + + +/** + * Create a DOM tree element, containing the expand/collapse button + * @return {Element} domTree + * @private + */ +Node.prototype._createDomTree = function () { + var dom = this.dom; + var domTree = document.createElement('table'); + var tbody = document.createElement('tbody'); + domTree.style.borderCollapse = 'collapse'; // TODO: put in css + domTree.className = 'values'; + domTree.appendChild(tbody); + var tr = document.createElement('tr'); + tbody.appendChild(tr); + + // create expand button + var tdExpand = document.createElement('td'); + tdExpand.className = 'tree'; + tr.appendChild(tdExpand); + dom.expand = this._createDomExpandButton(); + tdExpand.appendChild(dom.expand); + dom.tdExpand = tdExpand; + + // create the field + var tdField = document.createElement('td'); + tdField.className = 'tree'; + tr.appendChild(tdField); + dom.field = this._createDomField(); + tdField.appendChild(dom.field); + dom.tdField = tdField; + + // create a separator + var tdSeparator = document.createElement('td'); + tdSeparator.className = 'tree'; + tr.appendChild(tdSeparator); + if (this.type != 'object' && this.type != 'array') { + tdSeparator.appendChild(document.createTextNode(':')); + tdSeparator.className = 'separator'; + } + dom.tdSeparator = tdSeparator; + + // create the value + var tdValue = document.createElement('td'); + tdValue.className = 'tree'; + tr.appendChild(tdValue); + dom.value = this._createDomValue(); + tdValue.appendChild(dom.value); + dom.tdValue = tdValue; + + return domTree; +}; + +/** + * Handle an event. The event is catched centrally by the editor + * @param {Event} event + */ +Node.prototype.onEvent = function (event) { + var type = event.type, + target = event.target || event.srcElement, + dom = this.dom, + node = this, + focusNode, + expandable = this._hasChilds(); + + // check if mouse is on menu or on dragarea. + // If so, highlight current row and its childs + if (target == dom.drag || target == dom.menu) { + if (type == 'mouseover') { + this.editor.highlighter.highlight(this); + } + else if (type == 'mouseout') { + this.editor.highlighter.unhighlight(); + } + } + + // drag events + if (type == 'mousedown' && target == dom.drag) { + this._onDragStart(event); + } + + // context menu events + if (type == 'click' && target == dom.menu) { + var highlighter = node.editor.highlighter; + highlighter.highlight(node); + highlighter.lock(); + util.addClassName(dom.menu, 'selected'); + this.showContextMenu(dom.menu, function () { + util.removeClassName(dom.menu, 'selected'); + highlighter.unlock(); + highlighter.unhighlight(); + }); + } + + // expand events + if (type == 'click' && target == dom.expand) { + if (expandable) { + var recurse = event.ctrlKey; // with ctrl-key, expand/collapse all + this._onExpand(recurse); + } + } + + // value events + var domValue = dom.value; + if (target == domValue) { + //noinspection FallthroughInSwitchStatementJS + switch (type) { + case 'focus': + focusNode = this; + break; + + case 'blur': + case 'change': + this._getDomValue(true); + this._updateDomValue(); + if (this.value) { + domValue.innerHTML = this._escapeHTML(this.value); + } + break; + + case 'input': + this._getDomValue(true); + this._updateDomValue(); + break; + + case 'keydown': + case 'mousedown': + this.editor.selection = this.editor.getSelection(); + break; + + case 'click': + if (event.ctrlKey || !this.editable.value) { + if (util.isUrl(this.value)) { + window.open(this.value, '_blank'); + } + } + 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': + focusNode = this; + break; + + case 'blur': + case 'change': + this._getDomField(true); + this._updateDomField(); + if (this.field) { + domField.innerHTML = this._escapeHTML(this.field); + } + break; + + case 'input': + this._getDomField(true); + this._updateDomField(); + break; + + case 'keydown': + case 'mousedown': + this.editor.selection = this.editor.getSelection(); + break; + + case 'keyup': + this._getDomField(true); + this._updateDomField(); + break; + + case 'cut': + case 'paste': + setTimeout(function () { + node._getDomField(true); + node._updateDomField(); + }, 1); + 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.pageX < util.getAbsoluteLeft(dom.tdSeparator));// for FF + if (left || expandable) { + // node is expandable when it is an object or array if (domField) { util.setEndOfContentEditable(domField); domField.focus(); } - break; - } - } - - if (type == 'keydown') { - this.onKeyDown(event); - } - }; - - /** - * Key down event handler - * @param {Event} event - */ - Node.prototype.onKeyDown = function (event) { - var keynum = event.which || event.keyCode; - var target = event.target || event.srcElement; - var ctrlKey = event.ctrlKey; - var shiftKey = event.shiftKey; - var altKey = event.altKey; - var handled = false; - var prevNode, nextNode, nextDom, nextDom2; - var editable = this.editor.options.mode === 'tree'; - - // util.log(ctrlKey, keynum, event.charCode); // TODO: cleanup - if (keynum == 13) { // Enter - if (target == this.dom.value) { - if (!this.editable.value || event.ctrlKey) { - if (util.isUrl(this.value)) { - window.open(this.value, '_blank'); - handled = true; + } + else { + if (domValue) { + util.setEndOfContentEditable(domValue); + domValue.focus(); } } - } - else if (target == this.dom.expand) { - var expandable = this._hasChilds(); - if (expandable) { - var recurse = event.ctrlKey; // with ctrl-key, expand/collapse all - this._onExpand(recurse); - target.focus(); + break; + } + } + if ((target == dom.tdExpand && !expandable) || target == dom.tdField || + target == dom.tdSeparator) { + switch (type) { + case 'click': + if (domField) { + util.setEndOfContentEditable(domField); + domField.focus(); + } + break; + } + } + + if (type == 'keydown') { + this.onKeyDown(event); + } +}; + +/** + * Key down event handler + * @param {Event} event + */ +Node.prototype.onKeyDown = function (event) { + var keynum = event.which || event.keyCode; + var target = event.target || event.srcElement; + var ctrlKey = event.ctrlKey; + var shiftKey = event.shiftKey; + var altKey = event.altKey; + var handled = false; + var prevNode, nextNode, nextDom, nextDom2; + var editable = this.editor.options.mode === 'tree'; + + // util.log(ctrlKey, keynum, event.charCode); // TODO: cleanup + if (keynum == 13) { // Enter + if (target == this.dom.value) { + if (!this.editable.value || event.ctrlKey) { + if (util.isUrl(this.value)) { + window.open(this.value, '_blank'); handled = true; } } } - else if (keynum == 68) { // D - if (ctrlKey && editable) { // Ctrl+D - this._onDuplicate(); + else if (target == this.dom.expand) { + var expandable = this._hasChilds(); + if (expandable) { + var recurse = event.ctrlKey; // with ctrl-key, expand/collapse all + this._onExpand(recurse); + target.focus(); handled = true; } } - else if (keynum == 69) { // E - if (ctrlKey) { // Ctrl+E and Ctrl+Shift+E - this._onExpand(shiftKey); // recurse = shiftKey - target.focus(); // TODO: should restore focus in case of recursing expand (which takes DOM offline) - handled = true; - } + } + else if (keynum == 68) { // D + if (ctrlKey && editable) { // Ctrl+D + this._onDuplicate(); + handled = true; } - else if (keynum == 77 && editable) { // M - if (ctrlKey) { // Ctrl+M - this.showContextMenu(target); - handled = true; - } + } + else if (keynum == 69) { // E + if (ctrlKey) { // Ctrl+E and Ctrl+Shift+E + this._onExpand(shiftKey); // recurse = shiftKey + target.focus(); // TODO: should restore focus in case of recursing expand (which takes DOM offline) + handled = true; } - else if (keynum == 46 && editable) { // Del - if (ctrlKey) { // Ctrl+Del - this._onRemove(); - handled = true; - } + } + else if (keynum == 77 && editable) { // M + if (ctrlKey) { // Ctrl+M + this.showContextMenu(target); + handled = true; } - else if (keynum == 45 && editable) { // Ins - if (ctrlKey && !shiftKey) { // Ctrl+Ins - this._onInsertBefore(); - handled = true; - } - else if (ctrlKey && shiftKey) { // Ctrl+Shift+Ins - this._onInsertAfter(); - handled = true; - } + } + else if (keynum == 46 && editable) { // Del + if (ctrlKey) { // Ctrl+Del + this._onRemove(); + handled = true; } - else if (keynum == 35) { // End - if (altKey) { // Alt+End - // find the last node - var lastNode = this._lastNode(); - if (lastNode) { - lastNode.focus(Node.focusElement || this._getElementName(target)); - } - handled = true; - } + } + else if (keynum == 45 && editable) { // Ins + if (ctrlKey && !shiftKey) { // Ctrl+Ins + this._onInsertBefore(); + handled = true; } - else if (keynum == 36) { // Home - if (altKey) { // Alt+Home - // find the first node - var firstNode = this._firstNode(); - if (firstNode) { - firstNode.focus(Node.focusElement || this._getElementName(target)); - } - handled = true; - } + else if (ctrlKey && shiftKey) { // Ctrl+Shift+Ins + this._onInsertAfter(); + handled = true; } - else if (keynum == 37) { // Arrow Left - if (altKey && !shiftKey) { // Alt + Arrow Left - // move to left element - var prevElement = this._previousElement(target); - if (prevElement) { - this.focus(this._getElementName(prevElement)); - } - handled = true; - } - else if (altKey && shiftKey && editable) { // Alt + Shift Arrow left - if (this.expanded) { - var appendDom = this.getAppend(); - nextDom = appendDom ? appendDom.nextSibling : undefined; - } - else { - var dom = this.getDom(); - nextDom = dom.nextSibling; - } - if (nextDom) { - nextNode = Node.getNodeFromTarget(nextDom); - nextDom2 = nextDom.nextSibling; - nextNode2 = Node.getNodeFromTarget(nextDom2); - if (nextNode && nextNode instanceof AppendNode && - !(this.parent.childs.length == 1) && - nextNode2 && nextNode2.parent) { - nextNode2.parent.moveBefore(this, nextNode2); - this.focus(Node.focusElement || this._getElementName(target)); - } - } + } + else if (keynum == 35) { // End + if (altKey) { // Alt+End + // find the last node + var lastNode = this._lastNode(); + if (lastNode) { + lastNode.focus(Node.focusElement || this._getElementName(target)); } + handled = true; } - else if (keynum == 38) { // Arrow Up - if (altKey && !shiftKey) { // Alt + Arrow Up - // find the previous node - prevNode = this._previousNode(); - if (prevNode) { - prevNode.focus(Node.focusElement || this._getElementName(target)); - } - handled = true; - } - else if (altKey && shiftKey) { // Alt + Shift + Arrow Up - // find the previous node - prevNode = this._previousNode(); - if (prevNode && prevNode.parent) { - prevNode.parent.moveBefore(this, prevNode); - this.focus(Node.focusElement || this._getElementName(target)); - } - handled = true; + } + else if (keynum == 36) { // Home + if (altKey) { // Alt+Home + // find the first node + var firstNode = this._firstNode(); + if (firstNode) { + firstNode.focus(Node.focusElement || this._getElementName(target)); } + handled = true; } - else if (keynum == 39) { // Arrow Right - if (altKey && !shiftKey) { // Alt + Arrow Right - // move to right element - var nextElement = this._nextElement(target); - if (nextElement) { - this.focus(this._getElementName(nextElement)); - } - handled = true; - } - else if (altKey && shiftKey) { // Alt + Shift Arrow Right - dom = this.getDom(); - var prevDom = dom.previousSibling; - if (prevDom) { - prevNode = Node.getNodeFromTarget(prevDom); - if (prevNode && prevNode.parent && - (prevNode instanceof AppendNode) - && !prevNode.isVisible()) { - prevNode.parent.moveBefore(this, prevNode); - this.focus(Node.focusElement || this._getElementName(target)); - } - } + } + else if (keynum == 37) { // Arrow Left + if (altKey && !shiftKey) { // Alt + Arrow Left + // move to left element + var prevElement = this._previousElement(target); + if (prevElement) { + this.focus(this._getElementName(prevElement)); } + handled = true; } - else if (keynum == 40) { // Arrow Down - if (altKey && !shiftKey) { // Alt + Arrow Down - // find the next node - nextNode = this._nextNode(); - if (nextNode) { - nextNode.focus(Node.focusElement || this._getElementName(target)); - } - handled = true; + else if (altKey && shiftKey && editable) { // Alt + Shift Arrow left + if (this.expanded) { + var appendDom = this.getAppend(); + nextDom = appendDom ? appendDom.nextSibling : undefined; } - else if (altKey && shiftKey && editable) { // Alt + Shift + Arrow Down - // find the 2nd next node and move before that one - if (this.expanded) { - nextNode = this.append ? this.append._nextNode() : undefined; - } - else { - nextNode = this._nextNode(); - } - nextDom = nextNode ? nextNode.getDom() : undefined; - if (this.parent.childs.length == 1) { - nextDom2 = nextDom; - } - else { - nextDom2 = nextDom ? nextDom.nextSibling : undefined; - } - var nextNode2 = Node.getNodeFromTarget(nextDom2); - if (nextNode2 && nextNode2.parent) { + else { + var dom = this.getDom(); + nextDom = dom.nextSibling; + } + if (nextDom) { + nextNode = Node.getNodeFromTarget(nextDom); + nextDom2 = nextDom.nextSibling; + nextNode2 = Node.getNodeFromTarget(nextDom2); + if (nextNode && nextNode instanceof AppendNode && + !(this.parent.childs.length == 1) && + nextNode2 && nextNode2.parent) { nextNode2.parent.moveBefore(this, nextNode2); this.focus(Node.focusElement || this._getElementName(target)); } - handled = true; } } - - if (handled) { - event.preventDefault(); - event.stopPropagation(); - } - }; - - /** - * Handle the expand event, when clicked on the expand button - * @param {boolean} recurse If true, child nodes will be expanded too - * @private - */ - Node.prototype._onExpand = function (recurse) { - 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; - } - }; - - /** - * Remove this node - * @private - */ - Node.prototype._onRemove = function() { - this.editor.highlighter.unhighlight(); - var childs = this.parent.childs; - var index = childs.indexOf(this); - - // adjust the focus - var oldSelection = this.editor.getSelection(); - if (childs[index + 1]) { - childs[index + 1].focus(); - } - else if (childs[index - 1]) { - childs[index - 1].focus(); - } - else { - this.parent.focus(); - } - var newSelection = this.editor.getSelection(); - - // remove the node - this.parent._remove(this); - - // store history action - this.editor._onAction('removeNode', { - node: this, - parent: this.parent, - index: index, - oldSelection: oldSelection, - newSelection: newSelection - }); - }; - - /** - * Duplicate this node - * @private - */ - Node.prototype._onDuplicate = function() { - var oldSelection = this.editor.getSelection(); - var clone = this.parent._duplicate(this); - clone.focus(); - var newSelection = this.editor.getSelection(); - - this.editor._onAction('duplicateNode', { - node: this, - clone: clone, - parent: this.parent, - oldSelection: oldSelection, - newSelection: newSelection - }); - }; - - /** - * Handle insert before event - * @param {String} [field] - * @param {*} [value] - * @param {String} [type] Can be 'auto', 'array', 'object', or 'string' - * @private - */ - Node.prototype._onInsertBefore = function (field, value, type) { - var oldSelection = this.editor.getSelection(); - - var newNode = new Node(this.editor, { - field: (field != undefined) ? field : '', - value: (value != undefined) ? value : '', - type: type - }); - newNode.expand(true); - this.parent.insertBefore(newNode, this); - this.editor.highlighter.unhighlight(); - newNode.focus('field'); - var newSelection = this.editor.getSelection(); - - this.editor._onAction('insertBeforeNode', { - node: newNode, - beforeNode: this, - parent: this.parent, - oldSelection: oldSelection, - newSelection: newSelection - }); - }; - - /** - * Handle insert after event - * @param {String} [field] - * @param {*} [value] - * @param {String} [type] Can be 'auto', 'array', 'object', or 'string' - * @private - */ - Node.prototype._onInsertAfter = function (field, value, type) { - var oldSelection = this.editor.getSelection(); - - var newNode = new Node(this.editor, { - field: (field != undefined) ? field : '', - value: (value != undefined) ? value : '', - type: type - }); - newNode.expand(true); - this.parent.insertAfter(newNode, this); - this.editor.highlighter.unhighlight(); - newNode.focus('field'); - var newSelection = this.editor.getSelection(); - - this.editor._onAction('insertAfterNode', { - node: newNode, - afterNode: this, - parent: this.parent, - oldSelection: oldSelection, - newSelection: newSelection - }); - }; - - /** - * Handle append event - * @param {String} [field] - * @param {*} [value] - * @param {String} [type] Can be 'auto', 'array', 'object', or 'string' - * @private - */ - Node.prototype._onAppend = function (field, value, type) { - var oldSelection = this.editor.getSelection(); - - var newNode = new Node(this.editor, { - field: (field != undefined) ? field : '', - value: (value != undefined) ? value : '', - type: type - }); - newNode.expand(true); - this.parent.appendChild(newNode); - this.editor.highlighter.unhighlight(); - newNode.focus('field'); - var newSelection = this.editor.getSelection(); - - this.editor._onAction('appendNode', { - node: newNode, - parent: this.parent, - oldSelection: oldSelection, - newSelection: newSelection - }); - }; - - /** - * Change the type of the node's value - * @param {String} newType - * @private - */ - Node.prototype._onChangeType = function (newType) { - var oldType = this.type; - if (newType != oldType) { - var oldSelection = this.editor.getSelection(); - this.changeType(newType); - var newSelection = this.editor.getSelection(); - - this.editor._onAction('changeType', { - node: this, - oldType: oldType, - newType: newType, - oldSelection: oldSelection, - newSelection: newSelection - }); - } - }; - - /** - * Sort the childs of the node. Only applicable when the node has type 'object' - * or 'array'. - * @param {String} direction Sorting direction. Available values: "asc", "desc" - * @private - */ - Node.prototype._onSort = function (direction) { - if (this._hasChilds()) { - var order = (direction == 'desc') ? -1 : 1; - var prop = (this.type == 'array') ? 'value': 'field'; - this.hideChilds(); - - var oldChilds = this.childs; - var oldSort = this.sort; - - // copy the array (the old one will be kept for an undo action - this.childs = this.childs.concat(); - - // sort the arrays - this.childs.sort(function (a, b) { - if (a[prop] > b[prop]) return order; - if (a[prop] < b[prop]) return -order; - return 0; - }); - this.sort = (order == 1) ? 'asc' : 'desc'; - - this.editor._onAction('sort', { - node: this, - oldChilds: oldChilds, - oldSort: oldSort, - newChilds: this.childs, - newSort: this.sort - }); - - this.showChilds(); - } - }; - - /** - * Create a table row with an append button. - * @return {HTMLElement | undefined} buttonAppend or undefined when inapplicable - */ - Node.prototype.getAppend = function () { - if (!this.append) { - this.append = new AppendNode(this.editor); - this.append.setParent(this); - } - return this.append.getDom(); - }; - - /** - * Find the node from an event target - * @param {Node} target - * @return {Node | undefined} node or undefined when not found - * @static - */ - Node.getNodeFromTarget = function (target) { - while (target) { - if (target.node) { - return target.node; + } + else if (keynum == 38) { // Arrow Up + if (altKey && !shiftKey) { // Alt + Arrow Up + // find the previous node + prevNode = this._previousNode(); + if (prevNode) { + prevNode.focus(Node.focusElement || this._getElementName(target)); } - target = target.parentNode; + handled = true; } - - return undefined; - }; - - /** - * Get the previously rendered node - * @return {Node | null} previousNode - * @private - */ - Node.prototype._previousNode = function () { - var prevNode = null; - var dom = this.getDom(); - if (dom && dom.parentNode) { - // find the previous field - var prevDom = dom; - do { - prevDom = prevDom.previousSibling; + else if (altKey && shiftKey) { // Alt + Shift + Arrow Up + // find the previous node + prevNode = this._previousNode(); + if (prevNode && prevNode.parent) { + prevNode.parent.moveBefore(this, prevNode); + this.focus(Node.focusElement || this._getElementName(target)); + } + handled = true; + } + } + else if (keynum == 39) { // Arrow Right + if (altKey && !shiftKey) { // Alt + Arrow Right + // move to right element + var nextElement = this._nextElement(target); + if (nextElement) { + this.focus(this._getElementName(nextElement)); + } + handled = true; + } + else if (altKey && shiftKey) { // Alt + Shift Arrow Right + dom = this.getDom(); + var prevDom = dom.previousSibling; + if (prevDom) { prevNode = Node.getNodeFromTarget(prevDom); + if (prevNode && prevNode.parent && + (prevNode instanceof AppendNode) + && !prevNode.isVisible()) { + prevNode.parent.moveBefore(this, prevNode); + this.focus(Node.focusElement || this._getElementName(target)); + } } - while (prevDom && (prevNode instanceof AppendNode && !prevNode.isVisible())); } - return prevNode; - }; - - /** - * Get the next rendered node - * @return {Node | null} nextNode - * @private - */ - Node.prototype._nextNode = function () { - var nextNode = null; - var dom = this.getDom(); - if (dom && dom.parentNode) { - // find the previous field - var nextDom = dom; - do { - nextDom = nextDom.nextSibling; - nextNode = Node.getNodeFromTarget(nextDom); + } + else if (keynum == 40) { // Arrow Down + if (altKey && !shiftKey) { // Alt + Arrow Down + // find the next node + nextNode = this._nextNode(); + if (nextNode) { + nextNode.focus(Node.focusElement || this._getElementName(target)); } - while (nextDom && (nextNode instanceof AppendNode && !nextNode.isVisible())); + handled = true; } - - return nextNode; - }; - - /** - * Get the first rendered node - * @return {Node | null} firstNode - * @private - */ - Node.prototype._firstNode = function () { - var firstNode = null; - var dom = this.getDom(); - if (dom && dom.parentNode) { - var firstDom = dom.parentNode.firstChild; - firstNode = Node.getNodeFromTarget(firstDom); + else if (altKey && shiftKey && editable) { // Alt + Shift + Arrow Down + // find the 2nd next node and move before that one + if (this.expanded) { + nextNode = this.append ? this.append._nextNode() : undefined; + } + else { + nextNode = this._nextNode(); + } + nextDom = nextNode ? nextNode.getDom() : undefined; + if (this.parent.childs.length == 1) { + nextDom2 = nextDom; + } + else { + nextDom2 = nextDom ? nextDom.nextSibling : undefined; + } + var nextNode2 = Node.getNodeFromTarget(nextDom2); + if (nextNode2 && nextNode2.parent) { + nextNode2.parent.moveBefore(this, nextNode2); + this.focus(Node.focusElement || this._getElementName(target)); + } + handled = true; } + } - return firstNode; - }; + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } +}; - /** - * Get the last rendered node - * @return {Node | null} lastNode - * @private - */ - Node.prototype._lastNode = function () { - var lastNode = null; - var dom = this.getDom(); - if (dom && dom.parentNode) { - var lastDom = dom.parentNode.lastChild; +/** + * Handle the expand event, when clicked on the expand button + * @param {boolean} recurse If true, child nodes will be expanded too + * @private + */ +Node.prototype._onExpand = function (recurse) { + 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; + } +}; + +/** + * Remove this node + * @private + */ +Node.prototype._onRemove = function() { + this.editor.highlighter.unhighlight(); + var childs = this.parent.childs; + var index = childs.indexOf(this); + + // adjust the focus + var oldSelection = this.editor.getSelection(); + if (childs[index + 1]) { + childs[index + 1].focus(); + } + else if (childs[index - 1]) { + childs[index - 1].focus(); + } + else { + this.parent.focus(); + } + var newSelection = this.editor.getSelection(); + + // remove the node + this.parent._remove(this); + + // store history action + this.editor._onAction('removeNode', { + node: this, + parent: this.parent, + index: index, + oldSelection: oldSelection, + newSelection: newSelection + }); +}; + +/** + * Duplicate this node + * @private + */ +Node.prototype._onDuplicate = function() { + var oldSelection = this.editor.getSelection(); + var clone = this.parent._duplicate(this); + clone.focus(); + var newSelection = this.editor.getSelection(); + + this.editor._onAction('duplicateNode', { + node: this, + clone: clone, + parent: this.parent, + oldSelection: oldSelection, + newSelection: newSelection + }); +}; + +/** + * Handle insert before event + * @param {String} [field] + * @param {*} [value] + * @param {String} [type] Can be 'auto', 'array', 'object', or 'string' + * @private + */ +Node.prototype._onInsertBefore = function (field, value, type) { + var oldSelection = this.editor.getSelection(); + + var newNode = new Node(this.editor, { + field: (field != undefined) ? field : '', + value: (value != undefined) ? value : '', + type: type + }); + newNode.expand(true); + this.parent.insertBefore(newNode, this); + this.editor.highlighter.unhighlight(); + newNode.focus('field'); + var newSelection = this.editor.getSelection(); + + this.editor._onAction('insertBeforeNode', { + node: newNode, + beforeNode: this, + parent: this.parent, + oldSelection: oldSelection, + newSelection: newSelection + }); +}; + +/** + * Handle insert after event + * @param {String} [field] + * @param {*} [value] + * @param {String} [type] Can be 'auto', 'array', 'object', or 'string' + * @private + */ +Node.prototype._onInsertAfter = function (field, value, type) { + var oldSelection = this.editor.getSelection(); + + var newNode = new Node(this.editor, { + field: (field != undefined) ? field : '', + value: (value != undefined) ? value : '', + type: type + }); + newNode.expand(true); + this.parent.insertAfter(newNode, this); + this.editor.highlighter.unhighlight(); + newNode.focus('field'); + var newSelection = this.editor.getSelection(); + + this.editor._onAction('insertAfterNode', { + node: newNode, + afterNode: this, + parent: this.parent, + oldSelection: oldSelection, + newSelection: newSelection + }); +}; + +/** + * Handle append event + * @param {String} [field] + * @param {*} [value] + * @param {String} [type] Can be 'auto', 'array', 'object', or 'string' + * @private + */ +Node.prototype._onAppend = function (field, value, type) { + var oldSelection = this.editor.getSelection(); + + var newNode = new Node(this.editor, { + field: (field != undefined) ? field : '', + value: (value != undefined) ? value : '', + type: type + }); + newNode.expand(true); + this.parent.appendChild(newNode); + this.editor.highlighter.unhighlight(); + newNode.focus('field'); + var newSelection = this.editor.getSelection(); + + this.editor._onAction('appendNode', { + node: newNode, + parent: this.parent, + oldSelection: oldSelection, + newSelection: newSelection + }); +}; + +/** + * Change the type of the node's value + * @param {String} newType + * @private + */ +Node.prototype._onChangeType = function (newType) { + var oldType = this.type; + if (newType != oldType) { + var oldSelection = this.editor.getSelection(); + this.changeType(newType); + var newSelection = this.editor.getSelection(); + + this.editor._onAction('changeType', { + node: this, + oldType: oldType, + newType: newType, + oldSelection: oldSelection, + newSelection: newSelection + }); + } +}; + +/** + * Sort the childs of the node. Only applicable when the node has type 'object' + * or 'array'. + * @param {String} direction Sorting direction. Available values: "asc", "desc" + * @private + */ +Node.prototype._onSort = function (direction) { + if (this._hasChilds()) { + var order = (direction == 'desc') ? -1 : 1; + var prop = (this.type == 'array') ? 'value': 'field'; + this.hideChilds(); + + var oldChilds = this.childs; + var oldSort = this.sort; + + // copy the array (the old one will be kept for an undo action + this.childs = this.childs.concat(); + + // sort the arrays + this.childs.sort(function (a, b) { + if (a[prop] > b[prop]) return order; + if (a[prop] < b[prop]) return -order; + return 0; + }); + this.sort = (order == 1) ? 'asc' : 'desc'; + + this.editor._onAction('sort', { + node: this, + oldChilds: oldChilds, + oldSort: oldSort, + newChilds: this.childs, + newSort: this.sort + }); + + this.showChilds(); + } +}; + +/** + * Create a table row with an append button. + * @return {HTMLElement | undefined} buttonAppend or undefined when inapplicable + */ +Node.prototype.getAppend = function () { + if (!this.append) { + this.append = new AppendNode(this.editor); + this.append.setParent(this); + } + return this.append.getDom(); +}; + +/** + * Find the node from an event target + * @param {Node} target + * @return {Node | undefined} node or undefined when not found + * @static + */ +Node.getNodeFromTarget = function (target) { + while (target) { + if (target.node) { + return target.node; + } + target = target.parentNode; + } + + return undefined; +}; + +/** + * Get the previously rendered node + * @return {Node | null} previousNode + * @private + */ +Node.prototype._previousNode = function () { + var prevNode = null; + var dom = this.getDom(); + if (dom && dom.parentNode) { + // find the previous field + var prevDom = dom; + do { + prevDom = prevDom.previousSibling; + prevNode = Node.getNodeFromTarget(prevDom); + } + while (prevDom && (prevNode instanceof AppendNode && !prevNode.isVisible())); + } + return prevNode; +}; + +/** + * Get the next rendered node + * @return {Node | null} nextNode + * @private + */ +Node.prototype._nextNode = function () { + var nextNode = null; + var dom = this.getDom(); + if (dom && dom.parentNode) { + // find the previous field + var nextDom = dom; + do { + nextDom = nextDom.nextSibling; + nextNode = Node.getNodeFromTarget(nextDom); + } + while (nextDom && (nextNode instanceof AppendNode && !nextNode.isVisible())); + } + + return nextNode; +}; + +/** + * Get the first rendered node + * @return {Node | null} firstNode + * @private + */ +Node.prototype._firstNode = function () { + var firstNode = null; + var dom = this.getDom(); + if (dom && dom.parentNode) { + var firstDom = dom.parentNode.firstChild; + firstNode = Node.getNodeFromTarget(firstDom); + } + + return firstNode; +}; + +/** + * Get the last rendered node + * @return {Node | null} lastNode + * @private + */ +Node.prototype._lastNode = function () { + var lastNode = null; + var dom = this.getDom(); + if (dom && dom.parentNode) { + var lastDom = dom.parentNode.lastChild; + lastNode = Node.getNodeFromTarget(lastDom); + while (lastDom && (lastNode instanceof AppendNode && !lastNode.isVisible())) { + lastDom = lastDom.previousSibling; lastNode = Node.getNodeFromTarget(lastDom); - while (lastDom && (lastNode instanceof AppendNode && !lastNode.isVisible())) { - lastDom = lastDom.previousSibling; - lastNode = Node.getNodeFromTarget(lastDom); + } + } + return lastNode; +}; + +/** + * Get the next element which can have focus. + * @param {Element} elem + * @return {Element | null} nextElem + * @private + */ +Node.prototype._previousElement = function (elem) { + var dom = this.dom; + // noinspection FallthroughInSwitchStatementJS + switch (elem) { + case dom.value: + if (this.fieldEditable) { + return dom.field; + } + // intentional fall through + case dom.field: + if (this._hasChilds()) { + return dom.expand; + } + // intentional fall through + case dom.expand: + return dom.menu; + case dom.menu: + if (dom.drag) { + return dom.drag; + } + // intentional fall through + default: + return null; + } +}; + +/** + * Get the next element which can have focus. + * @param {Element} elem + * @return {Element | null} nextElem + * @private + */ +Node.prototype._nextElement = function (elem) { + var dom = this.dom; + // noinspection FallthroughInSwitchStatementJS + switch (elem) { + case dom.drag: + return dom.menu; + case dom.menu: + if (this._hasChilds()) { + return dom.expand; + } + // intentional fall through + case dom.expand: + if (this.fieldEditable) { + return dom.field; + } + // intentional fall through + case dom.field: + if (!this._hasChilds()) { + return dom.value; + } + default: + return null; + } +}; + +/** + * Get the dom name of given element. returns null if not found. + * For example when element == dom.field, "field" is returned. + * @param {Element} element + * @return {String | null} elementName Available elements with name: 'drag', + * 'menu', 'expand', 'field', 'value' + * @private + */ +Node.prototype._getElementName = function (element) { + var dom = this.dom; + for (var name in dom) { + if (dom.hasOwnProperty(name)) { + if (dom[name] == element) { + return name; } } - return lastNode; - }; + } + return null; +}; - /** - * Get the next element which can have focus. - * @param {Element} elem - * @return {Element | null} nextElem - * @private - */ - Node.prototype._previousElement = function (elem) { - var dom = this.dom; - // noinspection FallthroughInSwitchStatementJS - switch (elem) { - case dom.value: - if (this.fieldEditable) { - return dom.field; - } - // intentional fall through - case dom.field: - if (this._hasChilds()) { - return dom.expand; - } - // intentional fall through - case dom.expand: - return dom.menu; - case dom.menu: - if (dom.drag) { - return dom.drag; - } - // intentional fall through - default: - return null; - } - }; - - /** - * Get the next element which can have focus. - * @param {Element} elem - * @return {Element | null} nextElem - * @private - */ - Node.prototype._nextElement = function (elem) { - var dom = this.dom; - // noinspection FallthroughInSwitchStatementJS - switch (elem) { - case dom.drag: - return dom.menu; - case dom.menu: - if (this._hasChilds()) { - return dom.expand; - } - // intentional fall through - case dom.expand: - if (this.fieldEditable) { - return dom.field; - } - // intentional fall through - case dom.field: - if (!this._hasChilds()) { - return dom.value; - } - default: - return null; - } - }; - - /** - * Get the dom name of given element. returns null if not found. - * For example when element == dom.field, "field" is returned. - * @param {Element} element - * @return {String | null} elementName Available elements with name: 'drag', - * 'menu', 'expand', 'field', 'value' - * @private - */ - Node.prototype._getElementName = function (element) { - var dom = this.dom; - for (var name in dom) { - if (dom.hasOwnProperty(name)) { - if (dom[name] == element) { - return name; - } - } - } - return null; - }; - - /** - * Test if this node has childs. This is the case when the node is an object - * or array. - * @return {boolean} hasChilds - * @private - */ - Node.prototype._hasChilds = function () { - return this.type == 'array' || this.type == 'object'; - }; +/** + * Test if this node has childs. This is the case when the node is an object + * or array. + * @return {boolean} hasChilds + * @private + */ +Node.prototype._hasChilds = function () { + return this.type == 'array' || this.type == 'object'; +}; // titles with explanation for the different types - Node.TYPE_TITLES = { - 'auto': 'Field type "auto". ' + - 'The field type is automatically determined from the value ' + - 'and can be a string, number, boolean, or null.', - 'object': 'Field type "object". ' + - 'An object contains an unordered set of key/value pairs.', - 'array': 'Field type "array". ' + - 'An array contains an ordered collection of values.', - 'string': 'Field type "string". ' + - 'Field type is not determined from the value, ' + - 'but always returned as string.' - }; +Node.TYPE_TITLES = { + 'auto': 'Field type "auto". ' + + 'The field type is automatically determined from the value ' + + 'and can be a string, number, boolean, or null.', + 'object': 'Field type "object". ' + + 'An object contains an unordered set of key/value pairs.', + 'array': 'Field type "array". ' + + 'An array contains an ordered collection of values.', + 'string': 'Field type "string". ' + + 'Field type is not determined from the value, ' + + 'but always returned as string.' +}; - /** - * Show a contextmenu for this node - * @param {HTMLElement} anchor Anchor element to attache the context menu to. - * @param {function} [onClose] Callback method called when the context menu - * is being closed. - */ - Node.prototype.showContextMenu = function (anchor, onClose) { - var node = this; - var titles = Node.TYPE_TITLES; - var items = []; +/** + * Show a contextmenu for this node + * @param {HTMLElement} anchor Anchor element to attache the context menu to. + * @param {function} [onClose] Callback method called when the context menu + * is being closed. + */ +Node.prototype.showContextMenu = function (anchor, onClose) { + var node = this; + var titles = Node.TYPE_TITLES; + var items = []; - if (this.editable.value) { - items.push({ - text: 'Type', - title: 'Change the type of this field', - className: 'type-' + this.type, - submenu: [ - { - text: 'Auto', - className: 'type-auto' + - (this.type == 'auto' ? ' selected' : ''), - title: titles.auto, - click: function () { - node._onChangeType('auto'); - } - }, - { - text: 'Array', - className: 'type-array' + - (this.type == 'array' ? ' selected' : ''), - title: titles.array, - click: function () { - node._onChangeType('array'); - } - }, - { - text: 'Object', - className: 'type-object' + - (this.type == 'object' ? ' selected' : ''), - title: titles.object, - click: function () { - node._onChangeType('object'); - } - }, - { - text: 'String', - className: 'type-string' + - (this.type == 'string' ? ' selected' : ''), - title: titles.string, - click: function () { - node._onChangeType('string'); - } - } - ] - }); - } - - if (this._hasChilds()) { - var direction = ((this.sort == 'asc') ? 'desc': 'asc'); - items.push({ - text: 'Sort', - title: 'Sort the childs of this ' + this.type, - className: 'sort-' + direction, - click: function () { - node._onSort(direction); - }, - submenu: [ - { - text: 'Ascending', - className: 'sort-asc', - title: 'Sort the childs of this ' + this.type + ' in ascending order', - click: function () { - node._onSort('asc'); - } - }, - { - text: 'Descending', - className: 'sort-desc', - title: 'Sort the childs of this ' + this.type +' in descending order', - click: function () { - node._onSort('desc'); - } - } - ] - }); - } - - if (this.parent && this.parent._hasChilds()) { - if (items.length) { - // create a separator - items.push({ - 'type': 'separator' - }); - } - - // create append button (for last child node only) - var childs = node.parent.childs; - if (node == childs[childs.length - 1]) { - items.push({ - text: 'Append', - title: 'Append a new field with type \'auto\' after this field (Ctrl+Shift+Ins)', - submenuTitle: 'Select the type of the field to be appended', - className: 'append', + if (this.editable.value) { + items.push({ + text: 'Type', + title: 'Change the type of this field', + className: 'type-' + this.type, + submenu: [ + { + text: 'Auto', + className: 'type-auto' + + (this.type == 'auto' ? ' selected' : ''), + title: titles.auto, click: function () { - node._onAppend('', '', 'auto'); - }, - submenu: [ - { - text: 'Auto', - className: 'type-auto', - title: titles.auto, - click: function () { - node._onAppend('', '', 'auto'); - } - }, - { - text: 'Array', - className: 'type-array', - title: titles.array, - click: function () { - node._onAppend('', []); - } - }, - { - text: 'Object', - className: 'type-object', - title: titles.object, - click: function () { - node._onAppend('', {}); - } - }, - { - text: 'String', - className: 'type-string', - title: titles.string, - click: function () { - node._onAppend('', '', 'string'); - } - } - ] - }); - } + node._onChangeType('auto'); + } + }, + { + text: 'Array', + className: 'type-array' + + (this.type == 'array' ? ' selected' : ''), + title: titles.array, + click: function () { + node._onChangeType('array'); + } + }, + { + text: 'Object', + className: 'type-object' + + (this.type == 'object' ? ' selected' : ''), + title: titles.object, + click: function () { + node._onChangeType('object'); + } + }, + { + text: 'String', + className: 'type-string' + + (this.type == 'string' ? ' selected' : ''), + title: titles.string, + click: function () { + node._onChangeType('string'); + } + } + ] + }); + } - // create insert button + if (this._hasChilds()) { + var direction = ((this.sort == 'asc') ? 'desc': 'asc'); + items.push({ + text: 'Sort', + title: 'Sort the childs of this ' + this.type, + className: 'sort-' + direction, + click: function () { + node._onSort(direction); + }, + submenu: [ + { + text: 'Ascending', + className: 'sort-asc', + title: 'Sort the childs of this ' + this.type + ' in ascending order', + click: function () { + node._onSort('asc'); + } + }, + { + text: 'Descending', + className: 'sort-desc', + title: 'Sort the childs of this ' + this.type +' in descending order', + click: function () { + node._onSort('desc'); + } + } + ] + }); + } + + if (this.parent && this.parent._hasChilds()) { + if (items.length) { + // create a separator items.push({ - text: 'Insert', - title: 'Insert a new field with type \'auto\' before this field (Ctrl+Ins)', - submenuTitle: 'Select the type of the field to be inserted', - className: 'insert', + 'type': 'separator' + }); + } + + // create append button (for last child node only) + var childs = node.parent.childs; + if (node == childs[childs.length - 1]) { + items.push({ + text: 'Append', + title: 'Append a new field with type \'auto\' after this field (Ctrl+Shift+Ins)', + submenuTitle: 'Select the type of the field to be appended', + className: 'append', click: function () { - node._onInsertBefore('', '', 'auto'); + node._onAppend('', '', 'auto'); }, submenu: [ { @@ -2739,7 +2695,7 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu className: 'type-auto', title: titles.auto, click: function () { - node._onInsertBefore('', '', 'auto'); + node._onAppend('', '', 'auto'); } }, { @@ -2747,7 +2703,7 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu className: 'type-array', title: titles.array, click: function () { - node._onInsertBefore('', []); + node._onAppend('', []); } }, { @@ -2755,7 +2711,7 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu className: 'type-object', title: titles.object, click: function () { - node._onInsertBefore('', {}); + node._onAppend('', {}); } }, { @@ -2763,166 +2719,211 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu className: 'type-string', title: titles.string, click: function () { - node._onInsertBefore('', '', 'string'); + node._onAppend('', '', 'string'); } } ] }); + } - if (this.editable.field) { - // create duplicate button - items.push({ - text: 'Duplicate', - title: 'Duplicate this field (Ctrl+D)', - className: 'duplicate', + // create insert button + items.push({ + text: 'Insert', + title: 'Insert a new field with type \'auto\' before this field (Ctrl+Ins)', + submenuTitle: 'Select the type of the field to be inserted', + className: 'insert', + click: function () { + node._onInsertBefore('', '', 'auto'); + }, + submenu: [ + { + text: 'Auto', + className: 'type-auto', + title: titles.auto, click: function () { - node._onDuplicate(); + node._onInsertBefore('', '', 'auto'); } - }); + }, + { + text: 'Array', + className: 'type-array', + title: titles.array, + click: function () { + node._onInsertBefore('', []); + } + }, + { + text: 'Object', + className: 'type-object', + title: titles.object, + click: function () { + node._onInsertBefore('', {}); + } + }, + { + text: 'String', + className: 'type-string', + title: titles.string, + click: function () { + node._onInsertBefore('', '', 'string'); + } + } + ] + }); - // create remove button - items.push({ - text: 'Remove', - title: 'Remove this field (Ctrl+Del)', - className: 'remove', - click: function () { - node._onRemove(); - } - }); + if (this.editable.field) { + // create duplicate button + items.push({ + text: 'Duplicate', + title: 'Duplicate this field (Ctrl+D)', + className: 'duplicate', + click: function () { + node._onDuplicate(); + } + }); + + // create remove button + items.push({ + text: 'Remove', + title: 'Remove this field (Ctrl+Del)', + className: 'remove', + click: function () { + node._onRemove(); + } + }); + } + } + + var menu = new ContextMenu(items, {close: onClose}); + menu.show(anchor); +}; + +/** + * get the type of a value + * @param {*} value + * @return {String} type Can be 'object', 'array', 'string', 'auto' + * @private + */ +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 + */ +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 + */ +Node.prototype._escapeHTML = function (text) { + var htmlEscaped = String(text) + .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 + */ +Node.prototype._unescapeHTML = function (escapedText) { + var json = '"' + this._escapeJSON(escapedText) + '"'; + var htmlEscaped = util.parse(json); + return htmlEscaped + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/ |\u00A0/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 + */ +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; } - - var menu = new ContextMenu(items, {close: onClose}); - menu.show(anchor); - }; - - /** - * get the type of a value - * @param {*} value - * @return {String} type Can be 'object', 'array', 'string', 'auto' - * @private - */ - 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 - */ - 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 if (c == '"') { + escaped += '\\"'; } else { - return str; + escaped += c; } - }; + i++; + } - /** - * escape a text, such that it can be displayed safely in an HTML element - * @param {String} text - * @return {String} escapedText - * @private - */ - Node.prototype._escapeHTML = function (text) { - var htmlEscaped = String(text) - .replace(//g, '>') - .replace(/ /g, '  ') // replace double space with an nbsp and space - .replace(/^ /, ' ') // space at start - .replace(/ $/, ' '); // space at end + return escaped; +}; - var json = JSON.stringify(htmlEscaped); - return json.substring(1, json.length - 1); - }; +// TODO: find a nicer solution to resolve this circular dependency between Node and AppendNode +var AppendNode = appendNodeFactory(Node); - /** - * unescape a string. - * @param {String} escapedText - * @return {String} text - * @private - */ - Node.prototype._unescapeHTML = function (escapedText) { - var json = '"' + this._escapeJSON(escapedText) + '"'; - var htmlEscaped = util.parse(json); - return htmlEscaped - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/ |\u00A0/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 - */ - 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; - }; - - // TODO: find a nicer solution to resolve this circular dependency between Node and AppendNode - var AppendNode = appendNodeFactory(Node); - - return Node; -}); \ No newline at end of file +module.exports = Node; diff --git a/src/js/SearchBox.js b/src/js/SearchBox.js index 3ecfe02..e9c2b23 100644 --- a/src/js/SearchBox.js +++ b/src/js/SearchBox.js @@ -1,293 +1,288 @@ -define(function () { +/** + * @constructor 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 + */ +function SearchBox (editor, container) { + var searchBox = this; - /** - * @constructor 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 - */ - function SearchBox (editor, container) { - var searchBox = this; + this.editor = editor; + this.timeout = undefined; + this.delay = 200; // ms + this.lastText = undefined; - this.editor = editor; - this.timeout = undefined; - this.delay = 200; // ms - this.lastText = undefined; + this.dom = {}; + this.dom.container = container; - this.dom = {}; - this.dom.container = container; + var table = document.createElement('table'); + this.dom.table = table; + table.className = '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 table = document.createElement('table'); - this.dom.table = table; - table.className = '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'); + tr.appendChild(td); + var results = document.createElement('div'); + this.dom.results = results; + results.className = 'results'; + td.appendChild(results); - var td = document.createElement('td'); - tr.appendChild(td); - var results = document.createElement('div'); - this.dom.results = results; - results.className = 'results'; - td.appendChild(results); + td = document.createElement('td'); + tr.appendChild(td); + var divInput = document.createElement('div'); + this.dom.input = divInput; + divInput.className = 'frame'; + divInput.title = 'Search fields and values'; + td.appendChild(divInput); - td = document.createElement('td'); - tr.appendChild(td); - var divInput = document.createElement('div'); - this.dom.input = divInput; - divInput.className = 'frame'; - divInput.title = 'Search fields and values'; - td.appendChild(divInput); + // table to contain the text input and search button + var tableInput = document.createElement('table'); + divInput.appendChild(tableInput); + var tbodySearch = document.createElement('tbody'); + tableInput.appendChild(tbodySearch); + tr = document.createElement('tr'); + tbodySearch.appendChild(tr); - // table to contain the text input and search button - var tableInput = document.createElement('table'); - 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 = 'refresh'; + td = document.createElement('td'); + td.appendChild(refreshSearch); + tr.appendChild(td); - var refreshSearch = document.createElement('button'); - refreshSearch.className = 'refresh'; - td = document.createElement('td'); - td.appendChild(refreshSearch); - tr.appendChild(td); + var search = document.createElement('input'); + this.dom.search = search; + search.oninput = function (event) { + searchBox._onDelayedSearch(event); + }; + search.onchange = function (event) { // For IE 9 + searchBox._onSearch(event); + }; + search.onkeydown = function (event) { + searchBox._onKeyDown(event); + }; + search.onkeyup = function (event) { + searchBox._onKeyUp(event); + }; + refreshSearch.onclick = function (event) { + search.select(); + }; - var search = document.createElement('input'); - this.dom.search = search; - search.oninput = function (event) { - searchBox._onDelayedSearch(event); - }; - search.onchange = function (event) { // For IE 9 - 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); - // 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 = 'Next result (Enter)'; + searchNext.className = 'next'; + searchNext.onclick = function () { + searchBox.next(); + }; + td = document.createElement('td'); + td.appendChild(searchNext); + tr.appendChild(td); - var searchNext = document.createElement('button'); - searchNext.title = 'Next result (Enter)'; - searchNext.className = 'next'; - searchNext.onclick = function () { - searchBox.next(); - }; - td = document.createElement('td'); - td.appendChild(searchNext); - tr.appendChild(td); + var searchPrevious = document.createElement('button'); + searchPrevious.title = 'Previous result (Shift+Enter)'; + searchPrevious.className = 'previous'; + searchPrevious.onclick = function () { + searchBox.previous(); + }; + td = document.createElement('td'); + td.appendChild(searchPrevious); + tr.appendChild(td); +} - var searchPrevious = document.createElement('button'); - searchPrevious.title = 'Previous result (Shift+Enter)'; - searchPrevious.className = 'previous'; - searchPrevious.onclick = function () { - searchBox.previous(); - }; - td = document.createElement('td'); - td.appendChild(searchPrevious); - tr.appendChild(td); +/** + * Go to the next search result + * @param {boolean} [focus] If true, focus will be set to the next result + * focus is false by default. + */ +SearchBox.prototype.next = function(focus) { + if (this.results != undefined) { + var index = (this.resultIndex != undefined) ? this.resultIndex + 1 : 0; + if (index > this.results.length - 1) { + index = 0; + } + this._setActiveResult(index, focus); } +}; - /** - * Go to the next search result - * @param {boolean} [focus] If true, focus will be set to the next result - * focus is false by default. - */ - SearchBox.prototype.next = function(focus) { - if (this.results != undefined) { - var index = (this.resultIndex != undefined) ? this.resultIndex + 1 : 0; - if (index > this.results.length - 1) { - index = 0; - } - this._setActiveResult(index, focus); +/** + * Go to the prevous search result + * @param {boolean} [focus] If true, focus will be set to the next result + * focus is false by default. + */ +SearchBox.prototype.previous = function(focus) { + 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, focus); + } +}; - /** - * Go to the prevous search result - * @param {boolean} [focus] If true, focus will be set to the next result - * focus is false by default. - */ - SearchBox.prototype.previous = function(focus) { - 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, focus); - } - }; - - /** - * Set new value for the current active result - * @param {Number} index - * @param {boolean} [focus] If true, focus will be set to the next result. - * focus is false by default. - * @private - */ - SearchBox.prototype._setActiveResult = function(index, focus) { - // 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; +/** + * Set new value for the current active result + * @param {Number} index + * @param {boolean} [focus] If true, focus will be set to the next result. + * focus is false by default. + * @private + */ +SearchBox.prototype._setActiveResult = function(index, focus) { + // 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 { - node.searchValueActive = true; + delete prevNode.searchValueActive; } - this.activeResult = this.results[this.resultIndex]; - node.updateDom(); + prevNode.updateDom(); + } - // TODO: not so nice that the focus is only set after the animation is finished - node.scrollTo(function () { - if (focus) { - node.focus(elem); - } - }); - }; + if (!this.results || !this.results[index]) { + // out of range, set to undefined + this.resultIndex = undefined; + this.activeResult = undefined; + return; + } - /** - * Cancel any running onDelayedSearch. - * @private - */ - SearchBox.prototype._clearDelay = function() { - if (this.timeout != undefined) { - clearTimeout(this.timeout); - delete this.timeout; + 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(); + + // TODO: not so nice that the focus is only set after the animation is finished + node.scrollTo(function () { + if (focus) { + node.focus(elem); } - }; + }); +}; - /** - * Start a timer to execute a search after a short delay. - * Used for reducing the number of searches while typing. - * @param {Event} event - * @private - */ - 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); - }; +/** + * Cancel any running onDelayedSearch. + * @private + */ +SearchBox.prototype._clearDelay = function() { + if (this.timeout != undefined) { + clearTimeout(this.timeout); + delete this.timeout; + } +}; - /** - * 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. - * @private - */ - SearchBox.prototype._onSearch = function (event, forceSearch) { - this._clearDelay(); +/** + * Start a timer to execute a search after a short delay. + * Used for reducing the number of searches while typing. + * @param {Event} event + * @private + */ +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); +}; - 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); +/** + * 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. + * @private + */ +SearchBox.prototype._onSearch = function (event, forceSearch) { + this._clearDelay(); - // display search results - if (text != undefined) { - var resultCount = this.results.length; - switch (resultCount) { - case 0: this.dom.results.innerHTML = 'no results'; break; - case 1: this.dom.results.innerHTML = '1 result'; break; - default: this.dom.results.innerHTML = resultCount + ' results'; break; - } - } - else { - this.dom.results.innerHTML = ''; + 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 = 'no results'; break; + case 1: this.dom.results.innerHTML = '1 result'; break; + default: this.dom.results.innerHTML = resultCount + ' results'; break; } } - }; - - /** - * Handle onKeyDown event in the input box - * @param {Event} event - * @private - */ - SearchBox.prototype._onKeyDown = function (event) { - var keynum = event.which; - if (keynum == 27) { // ESC - this.dom.search.value = ''; // clear search - this._onSearch(event); - event.preventDefault(); - event.stopPropagation(); + else { + this.dom.results.innerHTML = ''; } - 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(); - } - event.preventDefault(); - event.stopPropagation(); + } +}; + +/** + * Handle onKeyDown event in the input box + * @param {Event} event + * @private + */ +SearchBox.prototype._onKeyDown = function (event) { + var keynum = event.which; + if (keynum == 27) { // ESC + this.dom.search.value = ''; // clear search + this._onSearch(event); + event.preventDefault(); + event.stopPropagation(); + } + else if (keynum == 13) { // Enter + if (event.ctrlKey) { + // force to search again + this._onSearch(event, true); } - }; - - /** - * Handle onKeyUp event in the input box - * @param {Event} event - * @private - */ - SearchBox.prototype._onKeyUp = function (event) { - var keynum = event.keyCode; - if (keynum != 27 && keynum != 13) { // !show and !Enter - this._onDelayedSearch(event); // For IE 9 + else if (event.shiftKey) { + // move to the previous search result + this.previous(); } - }; - - return SearchBox; -}); + else { + // move to the next search result + this.next(); + } + event.preventDefault(); + event.stopPropagation(); + } +}; +/** + * Handle onKeyUp event in the input box + * @param {Event} event + * @private + */ +SearchBox.prototype._onKeyUp = function (event) { + var keynum = event.keyCode; + if (keynum != 27 && keynum != 13) { // !show and !Enter + this._onDelayedSearch(event); // For IE 9 + } +}; +module.exports = SearchBox; diff --git a/src/js/appendNodeFactory.js b/src/js/appendNodeFactory.js index 6937d4e..9469bb6 100644 --- a/src/js/appendNodeFactory.js +++ b/src/js/appendNodeFactory.js @@ -1,227 +1,226 @@ -define(['./ContextMenu', './util'], function (ContextMenu, util) { +var util = require('./util'); +var ContextMenu = require('./ContextMenu'); +/** + * A factory function to create an AppendNode, which depends on a Node + * @param {Node} Node + */ +function appendNodeFactory(Node) { /** - * A factory function to create an AppendNode, which depends on a Node - * @param {Node} Node + * @constructor AppendNode + * @extends Node + * @param {TreeEditor} 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 */ - function appendNodeFactory(Node) { - /** - * @constructor AppendNode - * @extends Node - * @param {TreeEditor} 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 - */ - function AppendNode (editor) { - /** @type {TreeEditor} */ - this.editor = editor; - this.dom = {}; - } - - AppendNode.prototype = new Node(); - - /** - * Return a table row with an append button. - * @return {Element} dom TR element - */ - AppendNode.prototype.getDom = function () { - // TODO: implement a new solution for the append node - var dom = this.dom; - - if (dom.tr) { - return dom.tr; - } - - this._updateEditability(); - - // a row for the append button - var trAppend = document.createElement('tr'); - trAppend.node = this; - dom.tr = trAppend; - - // TODO: consistent naming - - if (this.editable.field) { - // a cell for the dragarea column - dom.tdDrag = document.createElement('td'); - - // create context menu - var tdMenu = document.createElement('td'); - dom.tdMenu = tdMenu; - var menu = document.createElement('button'); - menu.className = 'contextmenu'; - menu.title = 'Click to open the actions menu (Ctrl+M)'; - dom.menu = menu; - tdMenu.appendChild(dom.menu); - } - - // a cell for the contents (showing text 'empty') - var tdAppend = document.createElement('td'); - var domText = document.createElement('div'); - domText.innerHTML = '(empty)'; - domText.className = 'readonly'; - tdAppend.appendChild(domText); - dom.td = tdAppend; - dom.text = domText; - - this.updateDom(); - - return trAppend; - }; - - /** - * Update the HTML dom of the Node - */ - AppendNode.prototype.updateDom = function () { - var dom = this.dom; - var tdAppend = dom.td; - if (tdAppend) { - tdAppend.style.paddingLeft = (this.getLevel() * 24 + 26) + 'px'; - // TODO: not so nice hard coded offset - } - - var domText = dom.text; - if (domText) { - domText.innerHTML = '(empty ' + this.parent.type + ')'; - } - - // attach or detach the contents of the append node: - // hide when the parent has childs, show when the parent has no childs - var trAppend = dom.tr; - if (!this.isVisible()) { - if (dom.tr.firstChild) { - if (dom.tdDrag) { - trAppend.removeChild(dom.tdDrag); - } - if (dom.tdMenu) { - trAppend.removeChild(dom.tdMenu); - } - trAppend.removeChild(tdAppend); - } - } - else { - if (!dom.tr.firstChild) { - if (dom.tdDrag) { - trAppend.appendChild(dom.tdDrag); - } - if (dom.tdMenu) { - trAppend.appendChild(dom.tdMenu); - } - trAppend.appendChild(tdAppend); - } - } - }; - - /** - * Check whether the AppendNode is currently visible. - * the AppendNode is visible when its parent has no childs (i.e. is empty). - * @return {boolean} isVisible - */ - AppendNode.prototype.isVisible = function () { - return (this.parent.childs.length == 0); - }; - - /** - * Show a contextmenu for this node - * @param {HTMLElement} anchor The element to attach the menu to. - * @param {function} [onClose] Callback method called when the context menu - * is being closed. - */ - AppendNode.prototype.showContextMenu = function (anchor, onClose) { - var node = this; - var titles = Node.TYPE_TITLES; - var items = [ - // create append button - { - 'text': 'Append', - 'title': 'Append a new field with type \'auto\' (Ctrl+Shift+Ins)', - 'submenuTitle': 'Select the type of the field to be appended', - 'className': 'insert', - 'click': function () { - node._onAppend('', '', 'auto'); - }, - 'submenu': [ - { - 'text': 'Auto', - 'className': 'type-auto', - 'title': titles.auto, - 'click': function () { - node._onAppend('', '', 'auto'); - } - }, - { - 'text': 'Array', - 'className': 'type-array', - 'title': titles.array, - 'click': function () { - node._onAppend('', []); - } - }, - { - 'text': 'Object', - 'className': 'type-object', - 'title': titles.object, - 'click': function () { - node._onAppend('', {}); - } - }, - { - 'text': 'String', - 'className': 'type-string', - 'title': titles.string, - 'click': function () { - node._onAppend('', '', 'string'); - } - } - ] - } - ]; - - var menu = new ContextMenu(items, {close: onClose}); - menu.show(anchor); - }; - - /** - * Handle an event. The event is catched centrally by the editor - * @param {Event} event - */ - AppendNode.prototype.onEvent = function (event) { - var type = event.type; - var target = event.target || event.srcElement; - var dom = this.dom; - - // highlight the append nodes parent - var menu = dom.menu; - if (target == menu) { - if (type == 'mouseover') { - this.editor.highlighter.highlight(this.parent); - } - else if (type == 'mouseout') { - this.editor.highlighter.unhighlight(); - } - } - - // context menu events - if (type == 'click' && target == dom.menu) { - var highlighter = this.editor.highlighter; - highlighter.highlight(this.parent); - highlighter.lock(); - util.addClassName(dom.menu, 'selected'); - this.showContextMenu(dom.menu, function () { - util.removeClassName(dom.menu, 'selected'); - highlighter.unlock(); - highlighter.unhighlight(); - }); - } - - if (type == 'keydown') { - this.onKeyDown(event); - } - }; - - return AppendNode; + function AppendNode (editor) { + /** @type {TreeEditor} */ + this.editor = editor; + this.dom = {}; } - // return the factory function - return appendNodeFactory; -}); + AppendNode.prototype = new Node(); + + /** + * Return a table row with an append button. + * @return {Element} dom TR element + */ + AppendNode.prototype.getDom = function () { + // TODO: implement a new solution for the append node + var dom = this.dom; + + if (dom.tr) { + return dom.tr; + } + + this._updateEditability(); + + // a row for the append button + var trAppend = document.createElement('tr'); + trAppend.node = this; + dom.tr = trAppend; + + // TODO: consistent naming + + if (this.editable.field) { + // a cell for the dragarea column + dom.tdDrag = document.createElement('td'); + + // create context menu + var tdMenu = document.createElement('td'); + dom.tdMenu = tdMenu; + var menu = document.createElement('button'); + menu.className = 'contextmenu'; + menu.title = 'Click to open the actions menu (Ctrl+M)'; + dom.menu = menu; + tdMenu.appendChild(dom.menu); + } + + // a cell for the contents (showing text 'empty') + var tdAppend = document.createElement('td'); + var domText = document.createElement('div'); + domText.innerHTML = '(empty)'; + domText.className = 'readonly'; + tdAppend.appendChild(domText); + dom.td = tdAppend; + dom.text = domText; + + this.updateDom(); + + return trAppend; + }; + + /** + * Update the HTML dom of the Node + */ + AppendNode.prototype.updateDom = function () { + var dom = this.dom; + var tdAppend = dom.td; + if (tdAppend) { + tdAppend.style.paddingLeft = (this.getLevel() * 24 + 26) + 'px'; + // TODO: not so nice hard coded offset + } + + var domText = dom.text; + if (domText) { + domText.innerHTML = '(empty ' + this.parent.type + ')'; + } + + // attach or detach the contents of the append node: + // hide when the parent has childs, show when the parent has no childs + var trAppend = dom.tr; + if (!this.isVisible()) { + if (dom.tr.firstChild) { + if (dom.tdDrag) { + trAppend.removeChild(dom.tdDrag); + } + if (dom.tdMenu) { + trAppend.removeChild(dom.tdMenu); + } + trAppend.removeChild(tdAppend); + } + } + else { + if (!dom.tr.firstChild) { + if (dom.tdDrag) { + trAppend.appendChild(dom.tdDrag); + } + if (dom.tdMenu) { + trAppend.appendChild(dom.tdMenu); + } + trAppend.appendChild(tdAppend); + } + } + }; + + /** + * Check whether the AppendNode is currently visible. + * the AppendNode is visible when its parent has no childs (i.e. is empty). + * @return {boolean} isVisible + */ + AppendNode.prototype.isVisible = function () { + return (this.parent.childs.length == 0); + }; + + /** + * Show a contextmenu for this node + * @param {HTMLElement} anchor The element to attach the menu to. + * @param {function} [onClose] Callback method called when the context menu + * is being closed. + */ + AppendNode.prototype.showContextMenu = function (anchor, onClose) { + var node = this; + var titles = Node.TYPE_TITLES; + var items = [ + // create append button + { + 'text': 'Append', + 'title': 'Append a new field with type \'auto\' (Ctrl+Shift+Ins)', + 'submenuTitle': 'Select the type of the field to be appended', + 'className': 'insert', + 'click': function () { + node._onAppend('', '', 'auto'); + }, + 'submenu': [ + { + 'text': 'Auto', + 'className': 'type-auto', + 'title': titles.auto, + 'click': function () { + node._onAppend('', '', 'auto'); + } + }, + { + 'text': 'Array', + 'className': 'type-array', + 'title': titles.array, + 'click': function () { + node._onAppend('', []); + } + }, + { + 'text': 'Object', + 'className': 'type-object', + 'title': titles.object, + 'click': function () { + node._onAppend('', {}); + } + }, + { + 'text': 'String', + 'className': 'type-string', + 'title': titles.string, + 'click': function () { + node._onAppend('', '', 'string'); + } + } + ] + } + ]; + + var menu = new ContextMenu(items, {close: onClose}); + menu.show(anchor); + }; + + /** + * Handle an event. The event is catched centrally by the editor + * @param {Event} event + */ + AppendNode.prototype.onEvent = function (event) { + var type = event.type; + var target = event.target || event.srcElement; + var dom = this.dom; + + // highlight the append nodes parent + var menu = dom.menu; + if (target == menu) { + if (type == 'mouseover') { + this.editor.highlighter.highlight(this.parent); + } + else if (type == 'mouseout') { + this.editor.highlighter.unhighlight(); + } + } + + // context menu events + if (type == 'click' && target == dom.menu) { + var highlighter = this.editor.highlighter; + highlighter.highlight(this.parent); + highlighter.lock(); + util.addClassName(dom.menu, 'selected'); + this.showContextMenu(dom.menu, function () { + util.removeClassName(dom.menu, 'selected'); + highlighter.unlock(); + highlighter.unhighlight(); + }); + } + + if (type == 'keydown') { + this.onKeyDown(event); + } + }; + + return AppendNode; +} + +module.exports = appendNodeFactory; diff --git a/src/js/modeswitcher.js b/src/js/modeswitcher.js index 250fb21..17ab9ef 100644 --- a/src/js/modeswitcher.js +++ b/src/js/modeswitcher.js @@ -1,103 +1,100 @@ -define(['./ContextMenu'], function (ContextMenu) { +var ContextMenu = require('./ContextMenu'); + +/** + * Create a select box to be used in the editor menu's, which allows to switch mode + * @param {Object} editor + * @param {String[]} modes Available modes: 'code', 'form', 'text', 'tree', 'view' + * @param {String} current Available modes: 'code', 'form', 'text', 'tree', 'view' + * @returns {HTMLElement} box + */ +function createModeSwitcher(editor, modes, current) { + // TODO: decouple mode switcher from editor /** - * Create a select box to be used in the editor menu's, which allows to switch mode - * @param {Object} editor - * @param {String[]} modes Available modes: 'code', 'form', 'text', 'tree', 'view' - * @param {String} current Available modes: 'code', 'form', 'text', 'tree', 'view' - * @returns {HTMLElement} box + * Switch the mode of the editor + * @param {String} mode */ - function createModeSwitcher(editor, modes, current) { - // TODO: decouple mode switcher from editor + function switchMode(mode) { + // switch mode + editor.setMode(mode); - /** - * Switch the mode of the editor - * @param {String} mode - */ - function switchMode(mode) { - // switch mode - editor.setMode(mode); - - // restore focus on mode box - var modeBox = editor.dom && editor.dom.modeBox; - if (modeBox) { - modeBox.focus(); - } + // restore focus on mode box + var modeBox = editor.dom && editor.dom.modeBox; + if (modeBox) { + modeBox.focus(); } - - // available modes - var availableModes = { - code: { - 'text': 'Code', - 'title': 'Switch to code highlighter', - 'click': function () { - switchMode('code') - } - }, - form: { - 'text': 'Form', - 'title': 'Switch to form editor', - 'click': function () { - switchMode('form'); - } - }, - text: { - 'text': 'Text', - 'title': 'Switch to plain text editor', - 'click': function () { - switchMode('text'); - } - }, - tree: { - 'text': 'Tree', - 'title': 'Switch to tree editor', - 'click': function () { - switchMode('tree'); - } - }, - view: { - 'text': 'View', - 'title': 'Switch to tree view', - 'click': function () { - switchMode('view'); - } - } - }; - - // list the selected modes - var items = []; - for (var i = 0; i < modes.length; i++) { - var mode = modes[i]; - var item = availableModes[mode]; - if (!item) { - throw new Error('Unknown mode "' + mode + '"'); - } - - item.className = 'type-modes' + ((current == mode) ? ' selected' : ''); - items.push(item); - } - - // retrieve the title of current mode - var currentMode = availableModes[current]; - if (!currentMode) { - throw new Error('Unknown mode "' + current + '"'); - } - var currentTitle = currentMode.text; - - // create the html element - var box = document.createElement('button'); - box.className = 'modes separator'; - box.innerHTML = currentTitle + ' ▾'; - box.title = 'Switch editor mode'; - box.onclick = function () { - var menu = new ContextMenu(items); - menu.show(box); - }; - - return box; } - return { - create: createModeSwitcher + // available modes + var availableModes = { + code: { + 'text': 'Code', + 'title': 'Switch to code highlighter', + 'click': function () { + switchMode('code') + } + }, + form: { + 'text': 'Form', + 'title': 'Switch to form editor', + 'click': function () { + switchMode('form'); + } + }, + text: { + 'text': 'Text', + 'title': 'Switch to plain text editor', + 'click': function () { + switchMode('text'); + } + }, + tree: { + 'text': 'Tree', + 'title': 'Switch to tree editor', + 'click': function () { + switchMode('tree'); + } + }, + view: { + 'text': 'View', + 'title': 'Switch to tree view', + 'click': function () { + switchMode('view'); + } + } + }; + + // list the selected modes + var items = []; + for (var i = 0; i < modes.length; i++) { + var mode = modes[i]; + var item = availableModes[mode]; + if (!item) { + throw new Error('Unknown mode "' + mode + '"'); + } + + item.className = 'type-modes' + ((current == mode) ? ' selected' : ''); + items.push(item); } -}); + + // retrieve the title of current mode + var currentMode = availableModes[current]; + if (!currentMode) { + throw new Error('Unknown mode "' + current + '"'); + } + var currentTitle = currentMode.text; + + // create the html element + var box = document.createElement('button'); + box.className = 'modes separator'; + box.innerHTML = currentTitle + ' ▾'; + box.title = 'Switch editor mode'; + box.onclick = function () { + var menu = new ContextMenu(items); + menu.show(box); + }; + + return box; +} + +exports.create = createModeSwitcher; diff --git a/src/js/module.js b/src/js/module.js deleted file mode 100644 index c524fb6..0000000 --- a/src/js/module.js +++ /dev/null @@ -1,54 +0,0 @@ - -// module exports -var jsoneditor = { - 'JSONEditor': JSONEditor, - 'util': util -}; - -/** - * load jsoneditor.css - */ -var loadCss = function () { - // find the script named 'jsoneditor.js' or 'jsoneditor.min.js' or - // 'jsoneditor.min.js', and use its path to find the css file to be - // loaded. - var scripts = document.getElementsByTagName('script'); - for (var s = 0; s < scripts.length; s++) { - var src = scripts[s].src; - if (/(^|\/)jsoneditor([-\.]min)?.js$/.test(src)) { - var jsFile = src.split('?')[0]; - var cssFile = jsFile.substring(0, jsFile.length - 2) + 'css'; - - // load css file - var link = document.createElement('link'); - link.type = 'text/css'; - link.rel = 'stylesheet'; - link.href = cssFile; - document.getElementsByTagName('head')[0].appendChild(link); - - break; - } - } -}; - -/** - * CommonJS module exports - */ -if (typeof(module) != 'undefined' && typeof(exports) != 'undefined') { - loadCss(); - module.exports = exports = jsoneditor; -} - -/** - * AMD module exports - */ -if (typeof(require) != 'undefined' && typeof(define) != 'undefined') { - loadCss(); - define(function () { - return jsoneditor; - }); -} -else { - // attach the module to the window, load as a regular javascript file - window['jsoneditor'] = jsoneditor; -} diff --git a/src/js/textmode.js b/src/js/textmode.js index 29c9c9a..c4b1c0f 100644 --- a/src/js/textmode.js +++ b/src/js/textmode.js @@ -1,335 +1,335 @@ -define(['./modeswitcher', './util'], function (modeswitcher, util) { +var modeswitcher = require('./modeswitcher'); +var util = require('./util'); - // create a mixin with the functions for text mode - var textmode = {}; +// create a mixin with the functions for text mode +var textmode = {}; - /** - * Create a text editor - * @param {Element} container - * @param {Object} [options] Object with options. available options: - * {String} mode Available values: - * "text" (default) - * or "code". - * {Number} indentation Number of indentation - * spaces. 2 by default. - * {function} change Callback method - * triggered on change - * @private - */ - textmode.create = function (container, options) { - // read options - options = options || {}; - this.options = options; - if (options.indentation) { - this.indentation = Number(options.indentation); - } - else { - this.indentation = 2; // number of spaces - } - this.mode = (options.mode == 'code') ? 'code' : 'text'; - if (this.mode == 'code') { - // verify whether Ace editor is available and supported - if (typeof ace === 'undefined') { - this.mode = 'text'; - util.log('WARNING: Cannot load code editor, Ace library not loaded. ' + - 'Falling back to plain text editor'); - } +/** + * Create a text editor + * @param {Element} container + * @param {Object} [options] Object with options. available options: + * {String} mode Available values: + * "text" (default) + * or "code". + * {Number} indentation Number of indentation + * spaces. 2 by default. + * {function} change Callback method + * triggered on change + * @private + */ +textmode.create = function (container, options) { + // read options + options = options || {}; + this.options = options; + if (options.indentation) { + this.indentation = Number(options.indentation); + } + else { + this.indentation = 2; // number of spaces + } + this.mode = (options.mode == 'code') ? 'code' : 'text'; + if (this.mode == 'code') { + // verify whether Ace editor is available and supported + if (typeof ace === 'undefined') { + this.mode = 'text'; + util.log('WARNING: Cannot load code editor, Ace library not loaded. ' + + 'Falling back to plain text editor'); } + } - var me = this; - this.container = container; - this.dom = {}; - this.editor = undefined; // ace code editor - this.textarea = undefined; // plain text editor (fallback when Ace is not available) + var me = this; + this.container = container; + this.dom = {}; + this.editor = undefined; // ace code editor + this.textarea = undefined; // plain text editor (fallback when Ace is not available) - this.width = container.clientWidth; - this.height = container.clientHeight; + this.width = container.clientWidth; + this.height = container.clientHeight; - this.frame = document.createElement('div'); - this.frame.className = 'jsoneditor'; - this.frame.onclick = function (event) { - // prevent default submit action when the editor is located inside a form - event.preventDefault(); - }; - this.frame.onkeydown = function (event) { - me._onKeyDown(event); - }; - - // create menu - this.menu = document.createElement('div'); - this.menu.className = 'menu'; - this.frame.appendChild(this.menu); - - // create format button - var buttonFormat = document.createElement('button'); - buttonFormat.className = 'format'; - buttonFormat.title = 'Format JSON data, with proper indentation and line feeds (Ctrl+\\)'; - this.menu.appendChild(buttonFormat); - buttonFormat.onclick = function () { - try { - me.format(); - } - catch (err) { - me._onError(err); - } - }; - - // create compact button - var buttonCompact = document.createElement('button'); - buttonCompact.className = 'compact'; - buttonCompact.title = 'Compact JSON data, remove all whitespaces (Ctrl+Shift+\\)'; - this.menu.appendChild(buttonCompact); - buttonCompact.onclick = function () { - try { - me.compact(); - } - catch (err) { - me._onError(err); - } - }; - - // create mode box - if (this.options && this.options.modes && this.options.modes.length) { - var modeBox = modeswitcher.create(this, this.options.modes, this.options.mode); - this.menu.appendChild(modeBox); - this.dom.modeBox = modeBox; - } - - this.content = document.createElement('div'); - this.content.className = 'outer'; - this.frame.appendChild(this.content); - - this.container.appendChild(this.frame); - - if (this.mode == 'code') { - this.editorDom = document.createElement('div'); - this.editorDom.style.height = '100%'; // TODO: move to css - this.editorDom.style.width = '100%'; // TODO: move to css - this.content.appendChild(this.editorDom); - - var editor = ace.edit(this.editorDom); - editor.setTheme('ace/theme/jsoneditor'); - editor.setShowPrintMargin(false); - editor.setFontSize(13); - editor.getSession().setMode('ace/mode/json'); - editor.getSession().setTabSize(this.indentation); - editor.getSession().setUseSoftTabs(true); - editor.getSession().setUseWrapMode(true); - this.editor = editor; - - var poweredBy = document.createElement('a'); - poweredBy.appendChild(document.createTextNode('powered by ace')); - poweredBy.href = 'http://ace.ajax.org'; - poweredBy.target = '_blank'; - poweredBy.className = 'poweredBy'; - poweredBy.onclick = function () { - // TODO: this anchor falls below the margin of the content, - // therefore the normal a.href does not work. We use a click event - // for now, but this should be fixed. - window.open(poweredBy.href, poweredBy.target); - }; - this.menu.appendChild(poweredBy); - - if (options.change) { - // register onchange event - editor.on('change', function () { - options.change(); - }); - } - } - else { - // load a plain text textarea - var textarea = document.createElement('textarea'); - textarea.className = 'text'; - textarea.spellcheck = false; - this.content.appendChild(textarea); - this.textarea = textarea; - - if (options.change) { - // register onchange event - if (this.textarea.oninput === null) { - this.textarea.oninput = function () { - options.change(); - } - } - else { - // oninput is undefined. For IE8- - this.textarea.onchange = function () { - options.change(); - } - } - } - } + this.frame = document.createElement('div'); + this.frame.className = 'jsoneditor'; + this.frame.onclick = function (event) { + // prevent default submit action when the editor is located inside a form + event.preventDefault(); + }; + this.frame.onkeydown = function (event) { + me._onKeyDown(event); }; - /** - * Event handler for keydown. Handles shortcut keys - * @param {Event} event - * @private - */ - textmode._onKeyDown = function (event) { - var keynum = event.which || event.keyCode; - var handled = false; - - if (keynum == 220 && event.ctrlKey) { - if (event.shiftKey) { // Ctrl+Shift+\ - this.compact(); - } - else { // Ctrl+\ - this.format(); - } - handled = true; - } - - if (handled) { - event.preventDefault(); - event.stopPropagation(); - } - }; - - /** - * Detach the editor from the DOM - * @private - */ - textmode._delete = function () { - if (this.frame && this.container && this.frame.parentNode == this.container) { - this.container.removeChild(this.frame); - } - }; - - /** - * Throw an error. If an error callback is configured in options.error, this - * callback will be invoked. Else, a regular error is thrown. - * @param {Error} err - * @private - */ - textmode._onError = function(err) { - // TODO: onError is deprecated since version 2.2.0. cleanup some day - if (typeof this.onError === 'function') { - util.log('WARNING: JSONEditor.onError is deprecated. ' + - 'Use options.error instead.'); - this.onError(err); - } - - if (this.options && typeof this.options.error === 'function') { - this.options.error(err); - } - else { - throw err; - } - }; - - /** - * Compact the code in the formatter - */ - textmode.compact = function () { - var json = this.get(); - var text = JSON.stringify(json); - this.setText(text); - }; - - /** - * Format the code in the formatter - */ - textmode.format = function () { - var json = this.get(); - var text = JSON.stringify(json, null, this.indentation); - this.setText(text); - }; - - /** - * Set focus to the formatter - */ - textmode.focus = function () { - if (this.textarea) { - this.textarea.focus(); - } - if (this.editor) { - this.editor.focus(); - } - }; - - /** - * Resize the formatter - */ - textmode.resize = function () { - if (this.editor) { - var force = false; - this.editor.resize(force); - } - }; - - /** - * Set json data in the formatter - * @param {Object} json - */ - textmode.set = function(json) { - this.setText(JSON.stringify(json, null, this.indentation)); - }; - - /** - * Get json data from the formatter - * @return {Object} json - */ - textmode.get = function() { - var text = this.getText(); - var json; + // create menu + this.menu = document.createElement('div'); + this.menu.className = 'menu'; + this.frame.appendChild(this.menu); + // create format button + var buttonFormat = document.createElement('button'); + buttonFormat.className = 'format'; + buttonFormat.title = 'Format JSON data, with proper indentation and line feeds (Ctrl+\\)'; + this.menu.appendChild(buttonFormat); + buttonFormat.onclick = function () { try { - json = util.parse(text); // this can throw an error + me.format(); } catch (err) { - // try to sanitize json, replace JavaScript notation with JSON notation - text = util.sanitize(text); - this.setText(text); - - // try to parse again - json = util.parse(text); // this can throw an error - } - - return json; - }; - - /** - * Get the text contents of the editor - * @return {String} jsonText - */ - textmode.getText = function() { - if (this.textarea) { - return this.textarea.value; - } - if (this.editor) { - return this.editor.getValue(); - } - return ''; - }; - - /** - * Set the text contents of the editor - * @param {String} jsonText - */ - textmode.setText = function(jsonText) { - if (this.textarea) { - this.textarea.value = jsonText; - } - if (this.editor) { - this.editor.setValue(jsonText, -1); + me._onError(err); } }; - // define modes - return [ - { - mode: 'text', - mixin: textmode, - data: 'text', - load: textmode.format - }, - { - mode: 'code', - mixin: textmode, - data: 'text', - load: textmode.format + // create compact button + var buttonCompact = document.createElement('button'); + buttonCompact.className = 'compact'; + buttonCompact.title = 'Compact JSON data, remove all whitespaces (Ctrl+Shift+\\)'; + this.menu.appendChild(buttonCompact); + buttonCompact.onclick = function () { + try { + me.compact(); } - ]; -}); + catch (err) { + me._onError(err); + } + }; + + // create mode box + if (this.options && this.options.modes && this.options.modes.length) { + var modeBox = modeswitcher.create(this, this.options.modes, this.options.mode); + this.menu.appendChild(modeBox); + this.dom.modeBox = modeBox; + } + + this.content = document.createElement('div'); + this.content.className = 'outer'; + this.frame.appendChild(this.content); + + this.container.appendChild(this.frame); + + if (this.mode == 'code') { + this.editorDom = document.createElement('div'); + this.editorDom.style.height = '100%'; // TODO: move to css + this.editorDom.style.width = '100%'; // TODO: move to css + this.content.appendChild(this.editorDom); + + var editor = ace.edit(this.editorDom); + editor.setTheme('ace/theme/jsoneditor'); + editor.setShowPrintMargin(false); + editor.setFontSize(13); + editor.getSession().setMode('ace/mode/json'); + editor.getSession().setTabSize(this.indentation); + editor.getSession().setUseSoftTabs(true); + editor.getSession().setUseWrapMode(true); + this.editor = editor; + + var poweredBy = document.createElement('a'); + poweredBy.appendChild(document.createTextNode('powered by ace')); + poweredBy.href = 'http://ace.ajax.org'; + poweredBy.target = '_blank'; + poweredBy.className = 'poweredBy'; + poweredBy.onclick = function () { + // TODO: this anchor falls below the margin of the content, + // therefore the normal a.href does not work. We use a click event + // for now, but this should be fixed. + window.open(poweredBy.href, poweredBy.target); + }; + this.menu.appendChild(poweredBy); + + if (options.change) { + // register onchange event + editor.on('change', function () { + options.change(); + }); + } + } + else { + // load a plain text textarea + var textarea = document.createElement('textarea'); + textarea.className = 'text'; + textarea.spellcheck = false; + this.content.appendChild(textarea); + this.textarea = textarea; + + if (options.change) { + // register onchange event + if (this.textarea.oninput === null) { + this.textarea.oninput = function () { + options.change(); + } + } + else { + // oninput is undefined. For IE8- + this.textarea.onchange = function () { + options.change(); + } + } + } + } +}; + +/** + * Event handler for keydown. Handles shortcut keys + * @param {Event} event + * @private + */ +textmode._onKeyDown = function (event) { + var keynum = event.which || event.keyCode; + var handled = false; + + if (keynum == 220 && event.ctrlKey) { + if (event.shiftKey) { // Ctrl+Shift+\ + this.compact(); + } + else { // Ctrl+\ + this.format(); + } + handled = true; + } + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } +}; + +/** + * Detach the editor from the DOM + * @private + */ +textmode._delete = function () { + if (this.frame && this.container && this.frame.parentNode == this.container) { + this.container.removeChild(this.frame); + } +}; + +/** + * Throw an error. If an error callback is configured in options.error, this + * callback will be invoked. Else, a regular error is thrown. + * @param {Error} err + * @private + */ +textmode._onError = function(err) { + // TODO: onError is deprecated since version 2.2.0. cleanup some day + if (typeof this.onError === 'function') { + util.log('WARNING: JSONEditor.onError is deprecated. ' + + 'Use options.error instead.'); + this.onError(err); + } + + if (this.options && typeof this.options.error === 'function') { + this.options.error(err); + } + else { + throw err; + } +}; + +/** + * Compact the code in the formatter + */ +textmode.compact = function () { + var json = this.get(); + var text = JSON.stringify(json); + this.setText(text); +}; + +/** + * Format the code in the formatter + */ +textmode.format = function () { + var json = this.get(); + var text = JSON.stringify(json, null, this.indentation); + this.setText(text); +}; + +/** + * Set focus to the formatter + */ +textmode.focus = function () { + if (this.textarea) { + this.textarea.focus(); + } + if (this.editor) { + this.editor.focus(); + } +}; + +/** + * Resize the formatter + */ +textmode.resize = function () { + if (this.editor) { + var force = false; + this.editor.resize(force); + } +}; + +/** + * Set json data in the formatter + * @param {Object} json + */ +textmode.set = function(json) { + this.setText(JSON.stringify(json, null, this.indentation)); +}; + +/** + * Get json data from the formatter + * @return {Object} json + */ +textmode.get = function() { + var text = this.getText(); + var json; + + try { + json = util.parse(text); // this can throw an error + } + catch (err) { + // try to sanitize json, replace JavaScript notation with JSON notation + text = util.sanitize(text); + this.setText(text); + + // try to parse again + json = util.parse(text); // this can throw an error + } + + return json; +}; + +/** + * Get the text contents of the editor + * @return {String} jsonText + */ +textmode.getText = function() { + if (this.textarea) { + return this.textarea.value; + } + if (this.editor) { + return this.editor.getValue(); + } + return ''; +}; + +/** + * Set the text contents of the editor + * @param {String} jsonText + */ +textmode.setText = function(jsonText) { + if (this.textarea) { + this.textarea.value = jsonText; + } + if (this.editor) { + this.editor.setValue(jsonText, -1); + } +}; + +// define modes +module.exports = [ + { + mode: 'text', + mixin: textmode, + data: 'text', + load: textmode.format + }, + { + mode: 'code', + mixin: textmode, + data: 'text', + load: textmode.format + } +]; diff --git a/src/js/treemode.js b/src/js/treemode.js index 1467f14..9b8fff8 100644 --- a/src/js/treemode.js +++ b/src/js/treemode.js @@ -1,714 +1,717 @@ -define(['./Highlighter', './History', './SearchBox', './Node', './modeswitcher', './util'], - function (Highlighter, History, SearchBox, Node, modeswitcher, util) { +var Highlighter = require('./Highlighter'); +var History = require('./History'); +var SearchBox = require('./SearchBox'); +var Node = require('./Node'); +var modeswitcher = require('./modeswitcher'); +var util = require('./util'); - // create a mixin with the functions for tree mode - var treemode = {}; - - /** - * Create a tree editor - * @param {Element} container Container element - * @param {Object} [options] Object with options. available options: - * {String} mode Editor mode. Available values: - * 'tree' (default), 'view', - * and 'form'. - * {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 - */ - treemode.create = function (container, options) { - if (!container) { - throw new Error('No container element provided.'); - } - this.container = container; - this.dom = {}; - this.highlighter = new Highlighter(); - this.selection = undefined; // will hold the last input selection +// create a mixin with the functions for tree mode +var treemode = {}; - this._setOptions(options); +/** + * Create a tree editor + * @param {Element} container Container element + * @param {Object} [options] Object with options. available options: + * {String} mode Editor mode. Available values: + * 'tree' (default), 'view', + * and 'form'. + * {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 + */ +treemode.create = function (container, options) { + if (!container) { + throw new Error('No container element provided.'); + } + this.container = container; + this.dom = {}; + this.highlighter = new Highlighter(); + this.selection = undefined; // will hold the last input selection - if (this.options.history && this.options.mode !== 'view') { - this.history = new History(this); - } + this._setOptions(options); - this._createFrame(); - this._createTable(); + if (this.options.history && this.options.mode !== 'view') { + this.history = new History(this); + } + + this._createFrame(); + this._createTable(); +}; + +/** + * Detach the editor from the DOM + * @private + */ +treemode._delete = function () { + if (this.frame && this.container && this.frame.parentNode == this.container) { + this.container.removeChild(this.frame); + } +}; + +/** + * Initialize and set default options + * @param {Object} [options] See description in constructor + * @private + */ +treemode._setOptions = function (options) { + this.options = { + search: true, + history: true, + mode: 'tree', + name: undefined // field name of root node }; - /** - * Detach the editor from the DOM - * @private - */ - treemode._delete = function () { - if (this.frame && this.container && this.frame.parentNode == this.container) { - this.container.removeChild(this.frame); - } - }; - - /** - * Initialize and set default options - * @param {Object} [options] See description in constructor - * @private - */ - treemode._setOptions = function (options) { - this.options = { - search: true, - history: true, - mode: 'tree', - 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]; - } + // copy all options + if (options) { + for (var prop in options) { + if (options.hasOwnProperty(prop)) { + this.options[prop] = options[prop]; } } - }; + } +}; - // node currently being edited - var focusNode = undefined; +// node currently being edited +var focusNode = undefined; - // dom having focus - var domFocus = null; +// dom having focus +var domFocus = null; - /** - * 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). - */ - treemode.set = function (json, name) { - // adjust field name for root node - if (name) { - // TODO: deprecated since version 2.2.0. Cleanup some day. - util.log('Warning: second parameter "name" is deprecated. ' + - 'Use setName(name) instead.'); - 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 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 - */ - treemode.get = function () { - // remove focus from currently edited node - if (focusNode) { - focusNode.blur(); - } - - if (this.node) { - return this.node.getValue(); - } - else { - return undefined; - } - }; - - /** - * Get the text contents of the editor - * @return {String} jsonText - */ - treemode.getText = function() { - return JSON.stringify(this.get()); - }; - - /** - * Set the text contents of the editor - * @param {String} jsonText - */ - treemode.setText = function(jsonText) { - this.set(util.parse(jsonText)); - }; - - /** - * Set a field name for the root node. - * @param {String | undefined} name - */ - treemode.setName = function (name) { +/** + * 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). + */ +treemode.set = function (json, name) { + // adjust field name for root node + if (name) { + // TODO: deprecated since version 2.2.0. Cleanup some day. + util.log('Warning: second parameter "name" is deprecated. ' + + 'Use setName(name) instead.'); 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 - */ - treemode.getName = function () { - return this.options.name; - }; - - /** - * Remove the root node from the editor - */ - treemode.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 {Node} node - * @private - */ - treemode._setRoot = function (node) { + // 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 - 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: - * - {Node} node, - * - {String} elem the dom element name where - * the result is found ('field' or - * 'value') - */ - treemode.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 - */ - treemode.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 - */ - treemode.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. - * @private - */ - treemode._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) { - util.log('Error in change callback: ', err); - } - } - }; - - /** - * Start autoscrolling when given mouse position is above the top of the - * editor contents, or below the bottom. - * @param {Number} mouseY Absolute mouse position in pixels - */ - treemode.startAutoScroll = function (mouseY) { - var me = this; - var content = this.content; - var top = util.getAbsoluteTop(content); - var height = content.clientHeight; - var bottom = top + height; - var margin = 24; - var interval = 50; // ms - - if ((mouseY < top + margin) && content.scrollTop > 0) { - this.autoScrollStep = ((top + margin) - mouseY) / 3; - } - else if (mouseY > bottom - margin && - height + content.scrollTop < content.scrollHeight) { - this.autoScrollStep = ((bottom - margin) - mouseY) / 3; - } - else { - this.autoScrollStep = undefined; - } - - if (this.autoScrollStep) { - if (!this.autoScrollTimer) { - this.autoScrollTimer = setInterval(function () { - if (me.autoScrollStep) { - content.scrollTop -= me.autoScrollStep; - } - else { - me.stopAutoScroll(); - } - }, interval); - } - } - else { - this.stopAutoScroll(); - } - }; - - /** - * Stop auto scrolling. Only applicable when scrolling - */ - treemode.stopAutoScroll = function () { - if (this.autoScrollTimer) { - clearTimeout(this.autoScrollTimer); - delete this.autoScrollTimer; - } - if (this.autoScrollStep) { - delete this.autoScrollStep; - } - }; - - - /** - * Set the focus to an element in the editor, set text selection, and - * set scroll position. - * @param {Object} selection An object containing fields: - * {Element | undefined} dom The dom element - * which has focus - * {Range | TextRange} range A text selection - * {Number} scrollTop Scroll position - */ - treemode.setSelection = function (selection) { - if (!selection) { - return; - } - - if ('scrollTop' in selection && this.content) { - // TODO: animated scroll - this.content.scrollTop = selection.scrollTop; - } - if (selection.range) { - util.setSelectionOffset(selection.range); - } - if (selection.dom) { - selection.dom.focus(); - } - }; - - /** - * Get the current focus - * @return {Object} selection An object containing fields: - * {Element | undefined} dom The dom element - * which has focus - * {Range | TextRange} range A text selection - * {Number} scrollTop Scroll position - */ - treemode.getSelection = function () { - return { - dom: domFocus, - scrollTop: this.content ? this.content.scrollTop : 0, - range: util.getSelectionOffset() + // replace the root node + var params = { + 'field': this.options.name, + 'value': json }; - }; + var node = new Node(this, params); + this._setRoot(node); - /** - * Adjust the scroll position such that given top position is shown at 1/4 - * of the window height. - * @param {Number} top - * @param {function(boolean)} [callback] Callback, executed when animation is - * finished. The callback returns true - * when animation is finished, or false - * when not. - */ - treemode.scrollTo = function (top, callback) { - var content = this.content; - if (content) { - var editor = this; - // cancel any running animation - if (editor.animateTimeout) { - clearTimeout(editor.animateTimeout); - delete editor.animateTimeout; + // 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 + */ +treemode.get = function () { + // remove focus from currently edited node + if (focusNode) { + focusNode.blur(); + } + + if (this.node) { + return this.node.getValue(); + } + else { + return undefined; + } +}; + +/** + * Get the text contents of the editor + * @return {String} jsonText + */ +treemode.getText = function() { + return JSON.stringify(this.get()); +}; + +/** + * Set the text contents of the editor + * @param {String} jsonText + */ +treemode.setText = function(jsonText) { + this.set(util.parse(jsonText)); +}; + +/** + * Set a field name for the root node. + * @param {String | undefined} name + */ +treemode.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 + */ +treemode.getName = function () { + return this.options.name; +}; + +/** + * Remove the root node from the editor + */ +treemode.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 {Node} node + * @private + */ +treemode._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: + * - {Node} node, + * - {String} elem the dom element name where + * the result is found ('field' or + * 'value') + */ +treemode.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 + */ +treemode.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 + */ +treemode.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. + * @private + */ +treemode._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) { + util.log('Error in change callback: ', err); + } + } +}; + +/** + * Start autoscrolling when given mouse position is above the top of the + * editor contents, or below the bottom. + * @param {Number} mouseY Absolute mouse position in pixels + */ +treemode.startAutoScroll = function (mouseY) { + var me = this; + var content = this.content; + var top = util.getAbsoluteTop(content); + var height = content.clientHeight; + var bottom = top + height; + var margin = 24; + var interval = 50; // ms + + if ((mouseY < top + margin) && content.scrollTop > 0) { + this.autoScrollStep = ((top + margin) - mouseY) / 3; + } + else if (mouseY > bottom - margin && + height + content.scrollTop < content.scrollHeight) { + this.autoScrollStep = ((bottom - margin) - mouseY) / 3; + } + else { + this.autoScrollStep = undefined; + } + + if (this.autoScrollStep) { + if (!this.autoScrollTimer) { + this.autoScrollTimer = setInterval(function () { + if (me.autoScrollStep) { + content.scrollTop -= me.autoScrollStep; + } + else { + me.stopAutoScroll(); + } + }, interval); + } + } + else { + this.stopAutoScroll(); + } +}; + +/** + * Stop auto scrolling. Only applicable when scrolling + */ +treemode.stopAutoScroll = function () { + if (this.autoScrollTimer) { + clearTimeout(this.autoScrollTimer); + delete this.autoScrollTimer; + } + if (this.autoScrollStep) { + delete this.autoScrollStep; + } +}; + + +/** + * Set the focus to an element in the editor, set text selection, and + * set scroll position. + * @param {Object} selection An object containing fields: + * {Element | undefined} dom The dom element + * which has focus + * {Range | TextRange} range A text selection + * {Number} scrollTop Scroll position + */ +treemode.setSelection = function (selection) { + if (!selection) { + return; + } + + if ('scrollTop' in selection && this.content) { + // TODO: animated scroll + this.content.scrollTop = selection.scrollTop; + } + if (selection.range) { + util.setSelectionOffset(selection.range); + } + if (selection.dom) { + selection.dom.focus(); + } +}; + +/** + * Get the current focus + * @return {Object} selection An object containing fields: + * {Element | undefined} dom The dom element + * which has focus + * {Range | TextRange} range A text selection + * {Number} scrollTop Scroll position + */ +treemode.getSelection = function () { + return { + dom: domFocus, + scrollTop: this.content ? this.content.scrollTop : 0, + range: util.getSelectionOffset() + }; +}; + +/** + * Adjust the scroll position such that given top position is shown at 1/4 + * of the window height. + * @param {Number} top + * @param {function(boolean)} [callback] Callback, executed when animation is + * finished. The callback returns true + * when animation is finished, or false + * when not. + */ +treemode.scrollTo = function (top, callback) { + var content = this.content; + if (content) { + var editor = this; + // cancel any running animation + if (editor.animateTimeout) { + clearTimeout(editor.animateTimeout); + delete editor.animateTimeout; + } + if (editor.animateCallback) { + editor.animateCallback(false); + delete editor.animateCallback; + } + + // 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.animateCallback = callback; + editor.animateTimeout = setTimeout(animate, 50); } - if (editor.animateCallback) { - editor.animateCallback(false); + else { + // finished + if (callback) { + callback(true); + } + content.scrollTop = finalScrollTop; + delete editor.animateTimeout; delete editor.animateCallback; } - - // 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.animateCallback = callback; - editor.animateTimeout = setTimeout(animate, 50); - } - else { - // finished - if (callback) { - callback(true); - } - content.scrollTop = finalScrollTop; - delete editor.animateTimeout; - delete editor.animateCallback; - } - }; - animate(); - } - else { - if (callback) { - callback(false); - } - } - }; - - /** - * Create main frame - * @private - */ - treemode._createFrame = function () { - // create the frame - this.frame = document.createElement('div'); - this.frame.className = 'jsoneditor'; - this.container.appendChild(this.frame); - - // create one global event listener to handle all events from all nodes - var editor = this; - function onEvent(event) { - editor._onEvent(event); - } - this.frame.onclick = function (event) { - var target = event.target;// || event.srcElement; - - onEvent(event); - - // prevent default submit action of buttons when editor is located - // inside a form - if (target.nodeName == 'BUTTON') { - event.preventDefault(); - } }; - this.frame.oninput = onEvent; - 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 - util.addEventListener(this.frame, 'focus', onEvent, true); - util.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 = 'menu'; - this.frame.appendChild(this.menu); - - // create expand all button - var expandAll = document.createElement('button'); - expandAll.className = 'expand-all'; - expandAll.title = 'Expand all fields'; - expandAll.onclick = function () { - editor.expandAll(); - }; - this.menu.appendChild(expandAll); - - // create expand all button - var collapseAll = document.createElement('button'); - collapseAll.title = 'Collapse all fields'; - collapseAll.className = 'collapse-all'; - collapseAll.onclick = function () { - editor.collapseAll(); - }; - this.menu.appendChild(collapseAll); - - // create undo/redo buttons - if (this.history) { - // create undo button - var undo = document.createElement('button'); - undo.className = 'undo separator'; - undo.title = 'Undo last action (Ctrl+Z)'; - undo.onclick = function () { - editor._onUndo(); - }; - this.menu.appendChild(undo); - this.dom.undo = undo; - - // create redo button - var redo = document.createElement('button'); - redo.className = 'redo'; - redo.title = 'Redo (Ctrl+Shift+Z)'; - redo.onclick = function () { - editor._onRedo(); - }; - 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(); + animate(); + } + else { + if (callback) { + callback(false); } + } +}; - // create mode box - if (this.options && this.options.modes && this.options.modes.length) { - var modeBox = modeswitcher.create(this, this.options.modes, this.options.mode); - this.menu.appendChild(modeBox); - this.dom.modeBox = modeBox; - } +/** + * Create main frame + * @private + */ +treemode._createFrame = function () { + // create the frame + this.frame = document.createElement('div'); + this.frame.className = 'jsoneditor'; + this.container.appendChild(this.frame); - // create search box - if (this.options.search) { - this.searchBox = new SearchBox(this, this.menu); - } - }; + // create one global event listener to handle all events from all nodes + var editor = this; + function onEvent(event) { + editor._onEvent(event); + } + this.frame.onclick = function (event) { + var target = event.target;// || event.srcElement; - /** - * Perform an undo action - * @private - */ - treemode._onUndo = function () { - if (this.history) { - // undo last action - this.history.undo(); + onEvent(event); - // trigger change callback - if (this.options.change) { - this.options.change(); - } - } - }; - - /** - * Perform a redo action - * @private - */ - treemode._onRedo = function () { - if (this.history) { - // redo last action - this.history.redo(); - - // trigger change callback - if (this.options.change) { - this.options.change(); - } - } - }; - - /** - * Event handler - * @param event - * @private - */ - treemode._onEvent = function (event) { - var target = event.target; - - if (event.type == 'keydown') { - this._onKeyDown(event); - } - - if (event.type == 'focus') { - domFocus = target; - } - - var node = Node.getNodeFromTarget(target); - if (node) { - node.onEvent(event); - } - }; - - /** - * Event handler for keydown. Handles shortcut keys - * @param {Event} event - * @private - */ - treemode._onKeyDown = function (event) { - var keynum = event.which || event.keyCode; - var ctrlKey = event.ctrlKey; - var shiftKey = event.shiftKey; - var handled = false; - - if (keynum == 9) { // Tab or Shift+Tab - setTimeout(function () { - // select all text when moving focus to an editable div - util.selectContentEditable(domFocus); - }, 0); - } - - if (this.searchBox) { - if (ctrlKey && keynum == 70) { // Ctrl+F - this.searchBox.dom.search.focus(); - this.searchBox.dom.search.select(); - handled = true; - } - else if (keynum == 114 || (ctrlKey && keynum == 71)) { // F3 or Ctrl+G - var focus = true; - if (!shiftKey) { - // select next search result (F3 or Ctrl+G) - this.searchBox.next(focus); - } - else { - // select previous search result (Shift+F3 or Ctrl+Shift+G) - this.searchBox.previous(focus); - } - - handled = true; - } - } - - if (this.history) { - if (ctrlKey && !shiftKey && keynum == 90) { // Ctrl+Z - // undo - this._onUndo(); - handled = true; - } - else if (ctrlKey && shiftKey && keynum == 90) { // Ctrl+Shift+Z - // redo - this._onRedo(); - handled = true; - } - } - - if (handled) { + // prevent default submit action of buttons when editor is located + // inside a form + if (target.nodeName == 'BUTTON') { event.preventDefault(); - event.stopPropagation(); } }; + this.frame.oninput = onEvent; + 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 + util.addEventListener(this.frame, 'focus', onEvent, true); + util.addEventListener(this.frame, 'blur', onEvent, true); + this.frame.onfocusin = onEvent; // for IE + this.frame.onfocusout = onEvent; // for IE - /** - * Create main table - * @private - */ - treemode._createTable = function () { - var contentOuter = document.createElement('div'); - contentOuter.className = 'outer'; - this.contentOuter = contentOuter; + // create menu + this.menu = document.createElement('div'); + this.menu.className = 'menu'; + this.frame.appendChild(this.menu); - this.content = document.createElement('div'); - this.content.className = 'tree'; - contentOuter.appendChild(this.content); + // create expand all button + var expandAll = document.createElement('button'); + expandAll.className = 'expand-all'; + expandAll.title = 'Expand all fields'; + expandAll.onclick = function () { + editor.expandAll(); + }; + this.menu.appendChild(expandAll); - this.table = document.createElement('table'); - this.table.className = 'tree'; - this.content.appendChild(this.table); + // create expand all button + var collapseAll = document.createElement('button'); + collapseAll.title = 'Collapse all fields'; + collapseAll.className = 'collapse-all'; + collapseAll.onclick = function () { + editor.collapseAll(); + }; + this.menu.appendChild(collapseAll); - // 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'); - if (this.options.mode === 'tree') { - col = document.createElement('col'); - col.width = "24px"; - this.colgroupContent.appendChild(col); + // create undo/redo buttons + if (this.history) { + // create undo button + var undo = document.createElement('button'); + undo.className = 'undo separator'; + undo.title = 'Undo last action (Ctrl+Z)'; + undo.onclick = function () { + editor._onUndo(); + }; + this.menu.appendChild(undo); + this.dom.undo = undo; + + // create redo button + var redo = document.createElement('button'); + redo.className = 'redo'; + redo.title = 'Redo (Ctrl+Shift+Z)'; + redo.onclick = function () { + editor._onRedo(); + }; + 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 mode box + if (this.options && this.options.modes && this.options.modes.length) { + var modeBox = modeswitcher.create(this, this.options.modes, this.options.mode); + this.menu.appendChild(modeBox); + this.dom.modeBox = modeBox; + } + + // create search box + if (this.options.search) { + this.searchBox = new SearchBox(this, this.menu); + } +}; + +/** + * Perform an undo action + * @private + */ +treemode._onUndo = function () { + if (this.history) { + // undo last action + this.history.undo(); + + // trigger change callback + if (this.options.change) { + this.options.change(); } + } +}; + +/** + * Perform a redo action + * @private + */ +treemode._onRedo = function () { + if (this.history) { + // redo last action + this.history.redo(); + + // trigger change callback + if (this.options.change) { + this.options.change(); + } + } +}; + +/** + * Event handler + * @param event + * @private + */ +treemode._onEvent = function (event) { + var target = event.target; + + if (event.type == 'keydown') { + this._onKeyDown(event); + } + + if (event.type == 'focus') { + domFocus = target; + } + + var node = Node.getNodeFromTarget(target); + if (node) { + node.onEvent(event); + } +}; + +/** + * Event handler for keydown. Handles shortcut keys + * @param {Event} event + * @private + */ +treemode._onKeyDown = function (event) { + var keynum = event.which || event.keyCode; + var ctrlKey = event.ctrlKey; + var shiftKey = event.shiftKey; + var handled = false; + + if (keynum == 9) { // Tab or Shift+Tab + setTimeout(function () { + // select all text when moving focus to an editable div + util.selectContentEditable(domFocus); + }, 0); + } + + if (this.searchBox) { + if (ctrlKey && keynum == 70) { // Ctrl+F + this.searchBox.dom.search.focus(); + this.searchBox.dom.search.select(); + handled = true; + } + else if (keynum == 114 || (ctrlKey && keynum == 71)) { // F3 or Ctrl+G + var focus = true; + if (!shiftKey) { + // select next search result (F3 or Ctrl+G) + this.searchBox.next(focus); + } + else { + // select previous search result (Shift+F3 or Ctrl+Shift+G) + this.searchBox.previous(focus); + } + + handled = true; + } + } + + if (this.history) { + if (ctrlKey && !shiftKey && keynum == 90) { // Ctrl+Z + // undo + this._onUndo(); + handled = true; + } + else if (ctrlKey && shiftKey && keynum == 90) { // Ctrl+Shift+Z + // redo + this._onRedo(); + handled = true; + } + } + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } +}; + +/** + * Create main table + * @private + */ +treemode._createTable = function () { + var contentOuter = document.createElement('div'); + contentOuter.className = 'outer'; + this.contentOuter = contentOuter; + + this.content = document.createElement('div'); + this.content.className = 'tree'; + contentOuter.appendChild(this.content); + + this.table = document.createElement('table'); + this.table.className = 'tree'; + this.content.appendChild(this.table); + + // 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'); + if (this.options.mode === 'tree') { col = document.createElement('col'); col.width = "24px"; this.colgroupContent.appendChild(col); - col = document.createElement('col'); - this.colgroupContent.appendChild(col); - this.table.appendChild(this.colgroupContent); + } + col = document.createElement('col'); + col.width = "24px"; + this.colgroupContent.appendChild(col); + col = document.createElement('col'); + this.colgroupContent.appendChild(col); + this.table.appendChild(this.colgroupContent); - this.tbody = document.createElement('tbody'); - this.table.appendChild(this.tbody); + this.tbody = document.createElement('tbody'); + this.table.appendChild(this.tbody); - this.frame.appendChild(contentOuter); - }; + this.frame.appendChild(contentOuter); +}; - // define modes - return [ - { - mode: 'tree', - mixin: treemode, - data: 'json' - }, - { - mode: 'view', - mixin: treemode, - data: 'json' - }, - { - mode: 'form', - mixin: treemode, - data: 'json' - } - ]; -}); +// define modes +module.exports = [ + { + mode: 'tree', + mixin: treemode, + data: 'json' + }, + { + mode: 'view', + mixin: treemode, + data: 'json' + }, + { + mode: 'form', + mixin: treemode, + data: 'json' + } +]; \ No newline at end of file diff --git a/src/js/util.js b/src/js/util.js index 740113c..6870ddf 100644 --- a/src/js/util.js +++ b/src/js/util.js @@ -1,543 +1,535 @@ -define(function () { +/** + * 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 + * @return {JSON} json + */ +exports.parse = function parse(jsonString) { + try { + return JSON.parse(jsonString); + } + catch (err) { + // try to throw a more detailed error message using validate + exports.validate(jsonString); - // create namespace - var util = {}; + // rethrow the original error + throw err; + } +}; - /** - * 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 - * @return {JSON} json - */ - util.parse = function parse(jsonString) { - try { - return JSON.parse(jsonString); +/** + * Sanitize a JSON-like string containing. For example changes JavaScript + * notation into JSON notation. + * This function for example changes a string like "{a: 2, 'b': {c: 'd'}" + * into '{"a": 2, "b": {"c": "d"}' + * @param {string} jsString + * @returns {string} json + */ +exports.sanitize = function (jsString) { + // escape all single and double quotes inside strings + var chars = []; + var inString = false; + var i = 0; + while(i < jsString.length) { + var c = jsString.charAt(i); + var isEscaped = jsString.charAt(i - 1) === '\\'; + + if ((c === '"' || c === '\'') && !isEscaped) { + if (c === inString) { + // end of string + inString = false; + } + else if (!inString) { + // start of string + inString = c; + } + else { + // add escape character + chars.push('\\'); + } } - catch (err) { - // try to throw a more detailed error message using validate - util.validate(jsonString); - // rethrow the original error - throw err; + chars.push(c); + i++; + } + var jsonString = chars.join(''); + + // replace unescaped single quotes with double quotes, + // and replace escaped single quotes with unescaped single quotes + // TODO: we could do this step immediately in the previous step + jsonString = jsonString.replace(/(.?)'/g, function ($0, $1) { + return ($1 == '\\') ? '\'' : $1 + '"'; + }); + + // enclose unquoted object keys with double quotes + jsonString = jsonString.replace(/([{,]\s*)([a-zA-Z_][a-zA-Z0-9_]*)(\s*:)/g, function ($0, $1, $2, $3) { + return $1 + '"' + $2 + '"' + $3; + }); + + return jsonString; +}; + +/** + * 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 + * @throws Error + */ +exports.validate = function validate(jsonString) { + if (typeof(jsonlint) != 'undefined') { + jsonlint.parse(jsonString); + } + else { + JSON.parse(jsonString); + } +}; + +/** + * Extend object a with the properties of object b + * @param {Object} a + * @param {Object} b + * @return {Object} a + */ +exports.extend = function extend(a, b) { + for (var prop in b) { + if (b.hasOwnProperty(prop)) { + a[prop] = b[prop]; } - }; + } + return a; +}; - /** - * Sanitize a JSON-like string containing. For example changes JavaScript - * notation into JSON notation. - * This function for example changes a string like "{a: 2, 'b': {c: 'd'}" - * into '{"a": 2, "b": {"c": "d"}' - * @param {string} jsString - * @returns {string} json - */ - util.sanitize = function (jsString) { - // escape all single and double quotes inside strings - var chars = []; - var inString = false; - var i = 0; - while(i < jsString.length) { - var c = jsString.charAt(i); - var isEscaped = jsString.charAt(i - 1) === '\\'; +/** + * Remove all properties from object a + * @param {Object} a + * @return {Object} a + */ +exports.clear = function clear (a) { + for (var prop in a) { + if (a.hasOwnProperty(prop)) { + delete a[prop]; + } + } + return a; +}; - if ((c === '"' || c === '\'') && !isEscaped) { - if (c === inString) { - // end of string - inString = false; - } - else if (!inString) { - // start of string - inString = c; - } - else { - // add escape character - chars.push('\\'); +/** + * Output text to the console, if console is available + * @param {...*} args + */ +exports.log = function log (args) { + if (typeof console !== 'undefined' && typeof console.log === 'function') { + console.log.apply(console, arguments); + } +}; + +/** + * Get the type of an object + * @param {*} object + * @return {String} type + */ +exports.type = function type (object) { + if (object === null) { + return 'null'; + } + if (object === undefined) { + return 'undefined'; + } + if ((object instanceof Number) || (typeof object === 'number')) { + return 'number'; + } + if ((object instanceof String) || (typeof object === 'string')) { + return 'string'; + } + if ((object instanceof Boolean) || (typeof object === 'boolean')) { + return 'boolean'; + } + if ((object instanceof RegExp) || (typeof object === 'regexp')) { + return 'regexp'; + } + if (exports.isArray(object)) { + return 'array'; + } + + return 'object'; +}; + +/** + * Test whether a text contains a url (matches when a string starts + * with 'http://*' or 'https://*' and has no whitespace characters) + * @param {String} text + */ +var isUrlRegex = /^https?:\/\/\S+$/; +exports.isUrl = function isUrl (text) { + return (typeof text == 'string' || text instanceof String) && + isUrlRegex.test(text); +}; + +/** + * Tes whether given object is an Array + * @param {*} obj + * @returns {boolean} returns true when obj is an array + */ +exports.isArray = function (obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; +}; + +/** + * 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. + */ +exports.getAbsoluteLeft = function getAbsoluteLeft(elem) { + var rect = elem.getBoundingClientRect(); + return rect.left + window.pageXOffset || document.scrollLeft || 0; +}; + +/** + * 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. + */ +exports.getAbsoluteTop = function getAbsoluteTop(elem) { + var rect = elem.getBoundingClientRect(); + return rect.top + window.pageYOffset || document.scrollTop || 0; +}; + +/** + * add a className to the given elements style + * @param {Element} elem + * @param {String} className + */ +exports.addClassName = function addClassName(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 + */ +exports.removeClassName = function removeClassName(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 + */ +exports.stripFormatting = function stripFormatting(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); } } - - chars.push(c); - i++; - } - var jsonString = chars.join(''); - - // replace unescaped single quotes with double quotes, - // and replace escaped single quotes with unescaped single quotes - // TODO: we could do this step immediately in the previous step - jsonString = jsonString.replace(/(.?)'/g, function ($0, $1) { - return ($1 == '\\') ? '\'' : $1 + '"'; - }); - - // enclose unquoted object keys with double quotes - jsonString = jsonString.replace(/([{,]\s*)([a-zA-Z_][a-zA-Z0-9_]*)(\s*:)/g, function ($0, $1, $2, $3) { - return $1 + '"' + $2 + '"' + $3; - }); - - return jsonString; - }; - - /** - * 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 - * @throws Error - */ - util.validate = function validate(jsonString) { - if (typeof(jsonlint) != 'undefined') { - jsonlint.parse(jsonString); - } - else { - JSON.parse(jsonString); - } - }; - - /** - * Extend object a with the properties of object b - * @param {Object} a - * @param {Object} b - * @return {Object} a - */ - util.extend = function extend(a, b) { - for (var prop in b) { - if (b.hasOwnProperty(prop)) { - a[prop] = b[prop]; - } - } - return a; - }; - - /** - * Remove all properties from object a - * @param {Object} a - * @return {Object} a - */ - util.clear = function clear (a) { - for (var prop in a) { - if (a.hasOwnProperty(prop)) { - delete a[prop]; - } - } - return a; - }; - - /** - * Output text to the console, if console is available - * @param {...*} args - */ - util.log = function log (args) { - if (typeof console !== 'undefined' && typeof console.log === 'function') { - console.log.apply(console, arguments); - } - }; - - /** - * Get the type of an object - * @param {*} object - * @return {String} type - */ - util.type = function type (object) { - if (object === null) { - return 'null'; - } - if (object === undefined) { - return 'undefined'; - } - if ((object instanceof Number) || (typeof object === 'number')) { - return 'number'; - } - if ((object instanceof String) || (typeof object === 'string')) { - return 'string'; - } - if ((object instanceof Boolean) || (typeof object === 'boolean')) { - return 'boolean'; - } - if ((object instanceof RegExp) || (typeof object === 'regexp')) { - return 'regexp'; - } - if (util.isArray(object)) { - return 'array'; } - return 'object'; - }; + // recursively strip childs + exports.stripFormatting(child); + } +}; - /** - * Test whether a text contains a url (matches when a string starts - * with 'http://*' or 'https://*' and has no whitespace characters) - * @param {String} text - */ - var isUrlRegex = /^https?:\/\/\S+$/; - util.isUrl = function isUrl (text) { - return (typeof text == 'string' || text instanceof String) && - isUrlRegex.test(text); - }; +/** + * 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 A content editable div + */ +exports.setEndOfContentEditable = function setEndOfContentEditable(contentEditableElement) { + var range, selection; + if(document.createRange) { + 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 + } +}; - /** - * Tes whether given object is an Array - * @param {*} obj - * @returns {boolean} returns true when obj is an array - */ - util.isArray = function (obj) { - return Object.prototype.toString.call(obj) === '[object Array]'; - }; +/** + * Select all text of a content editable div. + * http://stackoverflow.com/a/3806004/1262753 + * @param {Element} contentEditableElement A content editable div + */ +exports.selectContentEditable = function selectContentEditable(contentEditableElement) { + if (!contentEditableElement || contentEditableElement.nodeName != 'DIV') { + return; + } - /** - * 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. - */ - util.getAbsoluteLeft = function getAbsoluteLeft(elem) { - var rect = elem.getBoundingClientRect(); - return rect.left + window.pageXOffset || document.scrollLeft || 0; - }; + var sel, range; + if (window.getSelection && document.createRange) { + range = document.createRange(); + range.selectNodeContents(contentEditableElement); + sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + } +}; - /** - * 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. - */ - util.getAbsoluteTop = function getAbsoluteTop(elem) { - var rect = elem.getBoundingClientRect(); - return rect.top + window.pageYOffset || document.scrollTop || 0; - }; - - /** - * add a className to the given elements style - * @param {Element} elem - * @param {String} className - */ - util.addClassName = function addClassName(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(' '); +/** + * Get text selection + * http://stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore + * @return {Range | TextRange | null} range + */ +exports.getSelection = function getSelection() { + if (window.getSelection) { + var sel = window.getSelection(); + if (sel.getRangeAt && sel.rangeCount) { + return sel.getRangeAt(0); } - }; + } + return null; +}; - /** - * add a className to the given elements style - * @param {Element} elem - * @param {String} className - */ - util.removeClassName = function removeClassName(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 - */ - util.stripFormatting = function stripFormatting(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 - util.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 A content editable div - */ - util.setEndOfContentEditable = function setEndOfContentEditable(contentEditableElement) { - var range, selection; - if(document.createRange) { - 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 - } - }; - - /** - * Select all text of a content editable div. - * http://stackoverflow.com/a/3806004/1262753 - * @param {Element} contentEditableElement A content editable div - */ - util.selectContentEditable = function selectContentEditable(contentEditableElement) { - if (!contentEditableElement || contentEditableElement.nodeName != 'DIV') { - return; - } - - var sel, range; - if (window.getSelection && document.createRange) { - range = document.createRange(); - range.selectNodeContents(contentEditableElement); - sel = window.getSelection(); +/** + * Set text selection + * http://stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore + * @param {Range | TextRange | null} range + */ +exports.setSelection = function setSelection(range) { + if (range) { + if (window.getSelection) { + var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); } - }; + } +}; - /** - * Get text selection - * http://stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore - * @return {Range | TextRange | null} range - */ - util.getSelection = function getSelection() { - if (window.getSelection) { - var sel = window.getSelection(); - if (sel.getRangeAt && sel.rangeCount) { - return sel.getRangeAt(0); +/** + * Get selected text range + * @return {Object} params object containing parameters: + * {Number} startOffset + * {Number} endOffset + * {Element} container HTML element holding the + * selected text element + * Returns null if no text selection is found + */ +exports.getSelectionOffset = function getSelectionOffset() { + var range = exports.getSelection(); + + if (range && 'startOffset' in range && 'endOffset' in range && + range.startContainer && (range.startContainer == range.endContainer)) { + return { + startOffset: range.startOffset, + endOffset: range.endOffset, + container: range.startContainer.parentNode + }; + } + + return null; +}; + +/** + * Set selected text range in given element + * @param {Object} params An object containing: + * {Element} container + * {Number} startOffset + * {Number} endOffset + */ +exports.setSelectionOffset = function setSelectionOffset(params) { + if (document.createRange && window.getSelection) { + var selection = window.getSelection(); + if(selection) { + var range = document.createRange(); + // TODO: do not suppose that the first child of the container is a textnode, + // but recursively find the textnodes + range.setStart(params.container.firstChild, params.startOffset); + range.setEnd(params.container.firstChild, params.endOffset); + + exports.setSelection(range); + } + } +}; + +/** + * Get the inner text of an HTML element (for example a div element) + * @param {Element} element + * @param {Object} [buffer] + * @return {String} innerText + */ +exports.getInnerText = function getInnerText(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; } - } - return null; - }; + }; + } - /** - * Set text selection - * http://stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore - * @param {Range | TextRange | null} range - */ - util.setSelection = function setSelection(range) { - if (range) { - if (window.getSelection) { - var sel = window.getSelection(); - sel.removeAllRanges(); - sel.addRange(range); - } - } - }; + // text node + if (element.nodeValue) { + return buffer.flush() + element.nodeValue; + } - /** - * Get selected text range - * @return {Object} params object containing parameters: - * {Number} startOffset - * {Number} endOffset - * {Element} container HTML element holding the - * selected text element - * Returns null if no text selection is found - */ - util.getSelectionOffset = function getSelectionOffset() { - var range = util.getSelection(); + // divs or other HTML elements + if (element.hasChildNodes()) { + var childNodes = element.childNodes; + var innerText = ''; - if (range && 'startOffset' in range && 'endOffset' in range && - range.startContainer && (range.startContainer == range.endContainer)) { - return { - startOffset: range.startOffset, - endOffset: range.endOffset, - container: range.startContainer.parentNode - }; - } + for (var i = 0, iMax = childNodes.length; i < iMax; i++) { + var child = childNodes[i]; - return null; - }; - - /** - * Set selected text range in given element - * @param {Object} params An object containing: - * {Element} container - * {Number} startOffset - * {Number} endOffset - */ - util.setSelectionOffset = function setSelectionOffset(params) { - if (document.createRange && window.getSelection) { - var selection = window.getSelection(); - if(selection) { - var range = document.createRange(); - // TODO: do not suppose that the first child of the container is a textnode, - // but recursively find the textnodes - range.setStart(params.container.firstChild, params.startOffset); - range.setEnd(params.container.firstChild, params.endOffset); - - util.setSelection(range); - } - } - }; - - /** - * Get the inner text of an HTML element (for example a div element) - * @param {Element} element - * @param {Object} [buffer] - * @return {String} innerText - */ - util.getInnerText = function getInnerText(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 += util.getInnerText(child, buffer); - buffer.set('\n'); - } - else if (child.nodeName == 'BR') { - innerText += buffer.flush(); - buffer.set('\n'); - } - else { - innerText += util.getInnerText(child, buffer); + 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 += exports.getInnerText(child, buffer); + buffer.set('\n'); } - - return innerText; - } - else { - if (element.nodeName == 'P' && util.getInternetExplorerVersion() != -1) { - // On Internet Explorer, a

with hasChildNodes()==false is - // rendered with a new line. Note that a

with - // hasChildNodes()==true is rendered without a new line - // Other browsers always ensure there is a
inside the

, - // and if not, the

does not render a new line - return buffer.flush(); + else if (child.nodeName == 'BR') { + innerText += buffer.flush(); + buffer.set('\n'); + } + else { + innerText += exports.getInnerText(child, buffer); } } - // br or unknown - return ''; - }; + return innerText; + } + else { + if (element.nodeName == 'P' && exports.getInternetExplorerVersion() != -1) { + // On Internet Explorer, a

with hasChildNodes()==false is + // rendered with a new line. Note that a

with + // hasChildNodes()==true is rendered without a new line + // Other browsers always ensure there is a
inside the

, + // and if not, the

does not render a new line + return buffer.flush(); + } + } - /** - * 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 - */ - util.getInternetExplorerVersion = function getInternetExplorerVersion() { - if (_ieVersion == -1) { - 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 ); - } + // 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 + */ +exports.getInternetExplorerVersion = function getInternetExplorerVersion() { + if (_ieVersion == -1) { + 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 ); } - - _ieVersion = rv; } - return _ieVersion; - }; + _ieVersion = rv; + } - /** - * Test whether the current browser is Firefox - * @returns {boolean} isFirefox - */ - util.isFirefox = function isFirefox () { - return (navigator.userAgent.indexOf("Firefox") != -1); - }; + return _ieVersion; +}; - /** - * cached internet explorer version - * @type {Number} - * @private - */ - var _ieVersion = -1; +/** + * Test whether the current browser is Firefox + * @returns {boolean} isFirefox + */ +exports.isFirefox = function isFirefox () { + return (navigator.userAgent.indexOf("Firefox") != -1); +}; - /** - * 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] false by default - * @return {function} the created event listener - */ - util.addEventListener = function addEventListener(element, action, listener, useCapture) { - if (element.addEventListener) { - if (useCapture === undefined) - useCapture = false; +/** + * cached internet explorer version + * @type {Number} + * @private + */ +var _ieVersion = -1; - if (action === "mousewheel" && util.isFirefox()) { - action = "DOMMouseScroll"; // For Firefox - } +/** + * 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] false by default + * @return {function} the created event listener + */ +exports.addEventListener = function addEventListener(element, action, listener, useCapture) { + if (element.addEventListener) { + if (useCapture === undefined) + useCapture = false; - element.addEventListener(action, listener, useCapture); - return listener; - } else if (element.attachEvent) { - // Old IE browsers - var f = function () { - return listener.call(element, window.event); - }; - element.attachEvent("on" + action, f); - return f; + if (action === "mousewheel" && exports.isFirefox()) { + action = "DOMMouseScroll"; // For Firefox } - }; - /** - * 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] false by default - */ - util.removeEventListener = function removeEventListener(element, action, listener, useCapture) { - if (element.removeEventListener) { - if (useCapture === undefined) - useCapture = false; + element.addEventListener(action, listener, useCapture); + return listener; + } else if (element.attachEvent) { + // Old IE browsers + var f = function () { + return listener.call(element, window.event); + }; + element.attachEvent("on" + action, f); + return f; + } +}; - if (action === "mousewheel" && util.isFirefox()) { - action = "DOMMouseScroll"; // For Firefox - } +/** + * 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] false by default + */ +exports.removeEventListener = function removeEventListener(element, action, listener, useCapture) { + if (element.removeEventListener) { + if (useCapture === undefined) + useCapture = false; - element.removeEventListener(action, listener, useCapture); - } else if (element.detachEvent) { - // Old IE browsers - element.detachEvent("on" + action, listener); + if (action === "mousewheel" && exports.isFirefox()) { + action = "DOMMouseScroll"; // For Firefox } - }; - return util; -}); \ No newline at end of file + element.removeEventListener(action, listener, useCapture); + } else if (element.detachEvent) { + // Old IE browsers + element.detachEvent("on" + action, listener); + } +};