UNPKG

randomjson

Version:

Generate random json according to condition json

1,802 lines (1,606 loc) 920 kB
/*! * jsoneditor.js * * @brief * JSONEditor is a web-based tool to view, edit, and format JSON. * It shows data a clear, editable treeview. * * Supported browsers: Chrome, Firefox, Safari, Opera, Internet Explorer 8+ * * @license * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * Copyright (c) 2011-2015 Jos de Jong, http://jsoneditoronline.org * * @author Jos de Jong, <wjosdejong@gmail.com> * @version 4.2.1 * @date 2015-06-13 */ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define(factory); else if(typeof exports === 'object') exports["JSONEditor"] = factory(); else root["JSONEditor"] = factory(); })(this, function() { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) /******/ return installedModules[moduleId].exports; /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ exports: {}, /******/ id: moduleId, /******/ loaded: false /******/ }; /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ // Flag the module as loaded /******/ module.loaded = true; /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ // Load entry module and return exports /******/ return __webpack_require__(0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ function(module, exports, __webpack_require__) { 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); } } /** * Configuration for all registered modes. Example: * { * tree: { * mixin: TreeEditor, * data: 'json' * }, * text: { * mixin: TextEditor, * data: 'text' * } * } * * @type { Object.<String, {mixin: Object, data: String} > } */ JSONEditor.modes = {}; /** * 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 || {}; var mode = this.options.mode || 'tree'; this.setMode(mode); }; /** * Detach the editor from the DOM * @private */ JSONEditor.prototype._delete = function () {}; /** * Set JSON object in editor * @param {Object | undefined} json JSON data */ JSONEditor.prototype.set = function (json) { this.json = json; }; /** * Get JSON from the editor * @returns {Object} json */ JSONEditor.prototype.get = function () { return this.json; }; /** * Set string containing JSON for the editor * @param {String | undefined} jsonText */ JSONEditor.prototype.setText = function (jsonText) { this.json = util.parse(jsonText); }; /** * Get stringified JSON contents from the editor * @returns {String} jsonText */ JSONEditor.prototype.getText = function () { return JSON.stringify(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; }; /** * Get the field name for the root node. * @return {String | undefined} name */ JSONEditor.prototype.getName = function () { return this.options && this.options.name; }; /** * 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; 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 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) {} } } 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); } 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'); } // 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; } }; // register tree and text modes JSONEditor.registerMode(treemode); JSONEditor.registerMode(textmode); module.exports = JSONEditor; /***/ }, /* 1 */ /***/ function(module, exports, __webpack_require__) { 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 this._setOptions(options); 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 }; // 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; // 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) { 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; }; /** * Set focus to the editor. Focus will be set to: * - the first editable field or value, or else * - to the expand button of the root node, or else * - to the context menu button of the root node, or else * - to the first button in the top menu */ treemode.focus = function () { var input = this.content.querySelector('[contenteditable=true]'); if (input) { input.focus(); } else if (this.node.dom.expand) { this.node.dom.expand.focus(); } else if (this.node.dom.menu) { this.node.dom.menu.focus(); } else { // focus to the first button in the menu input = this.frame.querySelector('button'); if (input) { input.focus(); } } }; /** * 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); } 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(); } // 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'); 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.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 ace; try { ace = __webpack_require__(9); } catch (err) { // failed to load ace, no problem, we will fall back to plain text } var modeswitcher = __webpack_require__(8); var util = __webpack_require__(3); // 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 * {Object} ace A custom instance of * Ace editor. * @private */ textmode.create = function (container, options) { // read options options = options || {}; this.options = options; // indentation if (options.indentation) { this.indentation = Number(options.indentation); } else { this.indentation = 2; // number of spaces } // grab ace from options if provided var _ace = options.ace ? options.ace : ace; // determine mode 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'); } } // determine theme this.theme = options.theme || 'ace/theme/jsoneditor'; 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.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(this.theme); 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); // 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 jsonlint = __webpack_require__(12); /** * 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); // rethrow the original error throw err; } }; /** * 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 i = 0; //If JSON starts with a function (characters/digits/"_-"), remove this function. //This is useful for "stripping" JSONP objects to become JSON //For example: /* some comment */ function_12321321 ( [{"a":"b"}] ); => [{"a":"b"}] var match = jsString.match(/^\s*(\/\*(.|[\r\n])*?\*\/)?\s*[\da-zA-Z_$]+\s*\(([\s\S]*)\)\s*;?\s*$/); if (match) { jsString = match[3]; } // helper functions to get the current/prev/next character function curr () { return jsString.charAt(i); } function next() { return jsString.charAt(i + 1); } function prev() { return jsString.charAt(i - 1); } // test whether the last non-whitespace character was a brace-open '{' function prevIsBrace() { var ii = i - 1; while (ii >= 0) { var cc = jsString.charAt(ii); if (cc === '{') { return true; } else if (cc === ' ' || cc === '\n' || cc === '\r') { // whitespace ii--; } else { return false; } } return false; } // skip a block comment '/* ... */' function skipComment () { i += 2; while (i < jsString.length && (curr() !== '*' || next() !== '/')) { i++; } i += 2; } // parse single or double quoted string function parseString(quote) { chars.push('"'); i++; var c = curr(); while (i < jsString.length && c !== quote) { if (c === '"' && prev() !== '\\') { // unescaped double quote, escape it chars.push('\\'); } // handle escape character if (c === '\\') { i++; c = curr(); // remove the escape character when followed by a single quote ', not needed if (c !== '\'') { chars.push('\\'); } } chars.push(c); i++; c = curr(); } if (c === quote) { chars.push('"'); i++; } } // parse an unquoted key function parseKey() { var specialValues = ['null', 'true', 'false']; var key = ''; var c = curr(); var regexp = /[a-zA-Z_$\d]/; // letter, number, underscore, dollar character while (regexp.test(c)) { key += c; i++; c = curr(); } if (specialValues.indexOf(key) === -1) { chars.push('"' + key + '"'); } else { chars.push(key); } } while(i < jsString.length) { var c = curr(); if (c === '/' && next() === '*') { skipComment(); } else if (c === '\'' || c === '"') { parseString(c); } else if (/[a-zA-Z_$]/.test(c) && prevIsBrace()) { // an unquoted object key (like a in '{a:2}') parseKey(); } else { chars.push(c); i++; } } return chars.join(''); }; /** * 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; }; /** * 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; }; /** * 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); } } } // recursively strip childs exports.stripFormatting(child); } }; /** * Set focus to the end of an editable div * code from Nico Burns * http://stackoverflow.com/users/140293/nico-burns * http://s