randomjson
Version:
Generate random json according to condition json
1,802 lines (1,606 loc) • 920 kB
JavaScript
/*!
* 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