jsoneditor4code
Version:
JSON Editor for UML Diagrams developed with Javascript Code Templates based on JSON Editor of Jeremy Dorn
1,701 lines (1,542 loc) • 533 kB
JavaScript
/* ---------------------------------------
Exported Module Variable: JSONEditor4Code
Package: jsoneditor4code
Version: 1.1.34 Date: 2021/01/03 12:40:57
Homepage: https://niebert.github.io/JSONEditor4Code
Author: Engelbert Niehaus
License: MIT
Date: 2021/01/03 12:40:57
Require Module with:
const JSONEditor4Code = require('jsoneditor4code');
JSHint: installation with 'npm install jshint -g'
------------------------------------------ */
/*jshint laxcomma: true, asi: true, maxerr: 150 */
/*global alert, confirm, console, prompt */
window = window || {
"document":{
"getElementById": function(id) {
console.error("getElementById() is not defined");
}
}
}
/**
* Extend object 'a' with the properties of object 'b'.
* If there's a conflict, content of object 'b' overwrites content of 'a'
*/
function cloneJSON(pJSON) {
var vJSON = {};
if (pJSON) {
vJSON = JSON.parse(JSON.stringify(pJSON));
} else {
console.log("ERROR: cloneJSON(pJSON) - pJSON undefined!");
};
return vJSON;
}
function concat_array( a, b ) {
var c = [];
for (var i = 0; i < a.length; i++) {
c.push(a[i])
}
for (var i = 0; i < b.length; i++) {
c.push(b[i])
};
return c;
}
function value_in_array( pValue, pArray ) {
var ret = -1;
if (pArray) {
for (var i = 0; i < pArray.length; i++) {
if (pValue == pArray[i]) {
ret = i;
}
};
} else {
console.log("value_in_array()-Call pArray undefined");
};
return ret;
}
function extendHash( a, b ) {
for( var i in b ) {
a[ i ] = b[ i ];
};
}
/**
* Check if element is a Hash
*/
function isHash(pObject) {
return pObject && (typeof(pObject) === "object");
}
/**
* Check if element is an Array
*/
function isArray(pObj) {
return isHash(pObj) && (pObj instanceof Array);
}
function makeMap(str){
var obj = {};
var items = str.split(",");
for ( var i = 0; i < items.length; i++ )
obj[ items[i] ] = true;
return obj;
}
function lengthHash(pHash) {
var vLength = 0;
if (isHash(pHash)) {
for (var key in pHash) {
if (pHash.hasOwnProperty(key)) {
vLength++;
};
};
};
return vLength;
}
function getDeleteBoolean4Hash(pHash) {
var vDelHash = {};
var vArrID_OLD = getArray4HashID(pHash);
// init the Delete Hash
for (var i = 0; i < vArrID_OLD.length; i++) {
vDelHash[vArrID_OLD[i]] = true;
};
return vDelHash;
}
function updateHash4NewIDs(pHash,pArrID_NEW,pDefaultValue) {
var vDelHash = getDeleteBoolean4Hash(pHash);
var vDefaultValue = pDefaultValue || "";
var vArrID_OLD = getArray4HashID(pHash);
var vID = "";
// mark IDs that should be kept in hash
for (var i = 0; i < pArrID_NEW.length; i++) {
vID = pArrID_NEW[i];
if (pHash.hasOwnProperty(vID)) {
// do not delete the ID in Hash
vDelHash[vID] = false;
} else {
// init default value for new keys/IDs
if (vDefaultValue != "") {
// append the ID to the default value;
pHash[vID] = vDefaultValue+ " '"+vID+"'";
} else {
// init new value with an empty string
pHash[vID] = "";
};
};
};
// delete all keys with vDelHash[vID] = true
for (var i = 0; i < vArrID_OLD.length; i++) {
vID = vArrID_OLD[i];
if (vDelHash[vID] === true) {
delete pHash[vID];
};
};
}
function updateHashSourceDestination(pSource,pDest) {
}
function renameHashKey(pHash,old_key,new_key) {
var vErrorMSG = "";
if (isHash(pHash)) {
if (pHash.hasOwnProperty(new_key)) {
vErrorMSG = "ERROR: Rename Hash Key - New Key already exists";
} else {
pHash[ new_key ] = pHash[ old_key ];
delete pHash[ old_key ];
console.log("Rename hash form '"+old_key+"' to '"+new_key+"'");
}
};
return vErrorMSG;
}
function firstKey4Hash(pHash) {
var vLength = 0;
var vKey = "";
if (isHash(pHash)) {
for (var key in pHash) {
if (pHash.hasOwnProperty(key)) {
vLength++;
if (vLength == 1) {
vKey = key;
break;
};
};
};
};
return vKey;
}
function createHash4Array(pArr,pHash) {
// general call createHash4Array(pArr)
// with the Parameter pHash the Map of pHash will be extended
// existing key value pairs in pHash will be overwritten
var vHash = pHash || {};
if (isArray(pArr)) {
for (var i = 0; i < pArr.length; i++) {
vHash[pArr[i]] = pArr[i];
};
};
return vHash;
}
function createArray4HashID(pHash) {
var vArr = [];
for (var iID in pHash) {
if (pHash.hasOwnProperty(iID)) {
vArr.push(iID);
};
};
return vArr;
}
function isValidJSON(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
};
return true;
}
function existsPathJSON(pJSON,pPath) {
var vDefinedPath = definedPathJSON(pJSON,pPath);
return (vDefinedPath == pPath)
};
function getLastID4PathJSON(pJSON,pPath) {
var vPathArr = pPath.split(".");
var vID = vPathArr.pop() || "";
if (vID != "") {
if (vID == vID.replace(/[^0-9]/g,"")) {
vID = parseInt(vID);
}
};
console.log("getLastID4PathJSON(pJSON,'"+pPath+"')='"+vID+"' Type='"+typeof(vID)+"'");
return vID;
};
function set4PathJSON(pJSON,pPath,pValue) {
var x = getObject4PathJSON(pJSON,pPath);
var vID = getLastID4PathJSON(pJSON,pPath);
x[vID] = pValue;
}
function get4PathJSON(pJSON,pPath) {
var x = getObject4PathJSON(pJSON,pPath);
var vID = getLastID4PathJSON(pJSON,pPath);
return x[vID];
}
function getJSON4Path(pPath) {
var vPathArr = pPath.split(".");
var vJSON;
eval("vJSON="+vPathArr[0]);
if (!vJSON) {
alert("getJSON4Path('"+pPath+"') root element of path undefined")
} else {
vPathArr.shift();
pPath = vPathArr.join(".");
return getObject4PathJSON(pJSON,pPath)
};
}
function getObject4PathJSON(pJSON,pPath) {
var vPathArr = pPath.split(".");
var vID = "";
var x = pJSON;
//var x;
//eval("x="+vPathArr[0]);
for (var i = 0; i < (vPathArr.length-1); i++) {
vID = (vPathArr[i]).replace(/[^0-9]/g,"");
if (vID == vPathArr[i]) {
vID = parseInt(vPathArr[i]);
} else {
vID = vPathArr[i];
};
x = x[ID];
};
return x;
}
function definedPathJSON(pJSON,pPath) {
// pPath="myhash.myarr.7.9.myhash2.myhash3"
var vPathArr = pPath.split(".");
var vExists = true;
// vPathArr = ["myhash","myarr","7","9","myhash2","myhash3"];
var x = pJSON;
var vSep = ""
var vID = "";
var vUndefPath = "";
var vDefinedPath = "";
var k = 0;
for (var i = 0; i < vPathArr.length; i++) {
if (vExists) {
vID = (vPathArr[i]).replace(/[^0-9]/g,"");
if ((vID != "") && (vID == vPathArr[i])) {
//--- Array -------
// vPathArr[i] is a number e.g. vPathArr[i]="7" - treat as array index parseInt
k = parseInt(vPathArr[i]);
if ((isArray(x)) && (k < x.length)) {
if (k>=0) {
vDefinedPath+= vSep + vPathArr[i];
vSep = ".";
//console.log("PathOK: "+vPathOK);
} else {
vExists = false;
vUndefPath += vPathArr[i];
}
} else if ((isHash(x)) && (x.hasOwnProperty(vPathArr[i]))) {
//--- Hash with Number as ID -------
vDefinedPath += vSep + vPathArr[i];
vSep = ".";
} else {
vExists = false;
vUndefPath += vPathArr[i];
}
} else if ((isHash(x)) && (x.hasOwnProperty(vPathArr[i]))) {
//--- Hash -------
vDefinedPath += vSep + vPathArr[i];
vSep = ".";
} else {
vExists = false;
vUndefPath += vPathArr[i];
};
} else {
vUndefPath += vSep + vPathArr[i];
vSep = ".";
}
}; //end for
return vDefinedPath;
}
/* Blob.js
* A Blob implementation.
* 2014-07-24
*
* By Eli Grey, http://eligrey.com
* By Devin Samarin, https://github.com/dsamarin
* License: MIT
* See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md
*/
/*global self, unescape */
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
plusplus: true */
/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
(function (view) {
"use strict";
view.URL = view.URL || view.webkitURL;
if (view.Blob && view.URL) {
try {
new Blob;
return;
} catch (e) {}
}
// Internally we use a BlobBuilder implementation to base Blob off of
// in order to support older browsers that only have BlobBuilder
var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) {
var
get_class = function(object) {
return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
}
, FakeBlobBuilder = function BlobBuilder() {
this.data = [];
}
, FakeBlob = function Blob(data, type, encoding) {
this.data = data;
this.size = data.length;
this.type = type;
this.encoding = encoding;
}
, FBB_proto = FakeBlobBuilder.prototype
, FB_proto = FakeBlob.prototype
, FileReaderSync = view.FileReaderSync
, FileException = function(type) {
this.code = this[this.name = type];
}
, file_ex_codes = (
"NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
+ "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
).split(" ")
, file_ex_code = file_ex_codes.length
, real_URL = view.URL || view.webkitURL || view
, real_create_object_URL = real_URL.createObjectURL
, real_revoke_object_URL = real_URL.revokeObjectURL
, URL = real_URL
, btoa = view.btoa
, atob = view.atob
, ArrayBuffer = view.ArrayBuffer
, Uint8Array = view.Uint8Array
, origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/
;
FakeBlob.fake = FB_proto.fake = true;
while (file_ex_code--) {
FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
}
// Polyfill URL
if (!real_URL.createObjectURL) {
URL = view.URL = function(uri) {
var
uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a")
, uri_origin
;
uri_info.href = uri;
if (!("origin" in uri_info)) {
if (uri_info.protocol.toLowerCase() === "data:") {
uri_info.origin = null;
} else {
uri_origin = uri.match(origin);
uri_info.origin = uri_origin && uri_origin[1];
}
}
return uri_info;
};
}
URL.createObjectURL = function(blob) {
var
type = blob.type
, data_URI_header
;
if (type === null) {
type = "application/octet-stream";
}
if (blob instanceof FakeBlob) {
data_URI_header = "data:" + type;
if (blob.encoding === "base64") {
return data_URI_header + ";base64," + blob.data;
} else if (blob.encoding === "URI") {
return data_URI_header + "," + decodeURIComponent(blob.data);
} if (btoa) {
return data_URI_header + ";base64," + btoa(blob.data);
} else {
return data_URI_header + "," + encodeURIComponent(blob.data);
}
} else if (real_create_object_URL) {
return real_create_object_URL.call(real_URL, blob);
}
};
URL.revokeObjectURL = function(object_URL) {
if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
real_revoke_object_URL.call(real_URL, object_URL);
}
};
FBB_proto.append = function(data/*, endings*/) {
var bb = this.data;
// decode data to a binary string
if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
var
str = ""
, buf = new Uint8Array(data)
, i = 0
, buf_len = buf.length
;
for (; i < buf_len; i++) {
str += String.fromCharCode(buf[i]);
}
bb.push(str);
} else if (get_class(data) === "Blob" || get_class(data) === "File") {
if (FileReaderSync) {
var fr = new FileReaderSync;
bb.push(fr.readAsBinaryString(data));
} else {
// async FileReader won't work as BlobBuilder is sync
throw new FileException("NOT_READABLE_ERR");
}
} else if (data instanceof FakeBlob) {
if (data.encoding === "base64" && atob) {
bb.push(atob(data.data));
} else if (data.encoding === "URI") {
bb.push(decodeURIComponent(data.data));
} else if (data.encoding === "raw") {
bb.push(data.data);
}
} else {
if (typeof data !== "string") {
data += ""; // convert unsupported types to strings
}
// decode UTF-16 to binary string
bb.push(unescape(encodeURIComponent(data)));
}
};
FBB_proto.getBlob = function(type) {
if (!arguments.length) {
type = null;
}
return new FakeBlob(this.data.join(""), type, "raw");
};
FBB_proto.toString = function() {
return "[object BlobBuilder]";
};
FB_proto.slice = function(start, end, type) {
var args = arguments.length;
if (args < 3) {
type = null;
}
return new FakeBlob(
this.data.slice(start, args > 1 ? end : this.data.length)
, type
, this.encoding
);
};
FB_proto.toString = function() {
return "[object Blob]";
};
FB_proto.close = function() {
this.size = 0;
delete this.data;
};
return FakeBlobBuilder;
}(view));
view.Blob = function(blobParts, options) {
var type = options ? (options.type || "") : "";
var builder = new BlobBuilder();
if (blobParts) {
for (var i = 0, len = blobParts.length; i < len; i++) {
if (Uint8Array && blobParts[i] instanceof Uint8Array) {
builder.append(blobParts[i].buffer);
}
else {
builder.append(blobParts[i]);
}
}
}
var blob = builder.getBlob(type);
if (!blob.slice && blob.webkitSlice) {
blob.slice = blob.webkitSlice;
}
return blob;
};
var getPrototypeOf = Object.getPrototypeOf || function(object) {
return object.__proto__;
};
view.Blob.prototype = getPrototypeOf(new view.Blob());
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
function deleteClass() {
vJSONEditor.delete_ask();
//editor.setValue(vDataJSON["UML_DEFAULT"]);
}
function update_editor(pJSON) {
var vJSON = pJSON || vJSONEditor.getValue();
$('#display_filename').html(class2filename(vJSON.data.classname,".json"));
vEditNode = null;
if (vJSONEditor && vJSONEditor.aEditor) {
vEditNode = vJSONEditor.aEditor.getEditor('root.data');
};
if (vEditNode) {
if (vJSON.data.hasOwnProperty("reposinfo")) {
vJSON.data.reposinfo.modified = getDateTime();
};
vEditNode.setValue(vJSON.data);
} else {
console.log("Update 'root.data' undefined");
};
vJSONEditor.setValue(vJSON);
update_editor_post(vJSON);
}
function update_editor_post(pJSON) {
console.log("CALL: update_editor_post(pJSON) jsoneditor4code.js");
}
function saver4JSON(pFile) {
//var vFile = pFile || vFileBase+".json";
vJSONEditor.saveJSON();
//alert("File: '"+vFile+"' saved!");
};
function exporter4Schema(pFilename) {
// Get the value from the editor
/*
console.log("BEFORE editor.schema:\n"+JSON.stringify(vDataJSON["class_schema"],null,4));
var vContent = vDataJSON["class_schema"];
var vFile = pFilename || "uml_schema.json";
console.log("JSON Schema output '"+vFile+"':\n"+vContent);
saveFile2HDD(vFile,vContent);
*/
vJSONEditor.saveSchema();
}
function getClassName(pJSON) {
var vClassName = "my_class";
if (pJSON) {
if (pJSON.data) {
if (pJSON.data.classname) {
vClassName = pJSON.data.classname;
} else {
console.log("ERROR: getClassName(pJSON) pJSON.data.classname undefined");
}
} else {
console.log("ERROR: getClassName(pJSON) pJSON.data undefined");
}
} else {
console.log("ERROR: getClassName(pJSON) pJSON undefined");
}
return vClassName;
}
function exporter4JSON(pFile) {
// Get the value from the editor
var vJSON = vJSONEditor.getValue();
var vFile = class2filename(getClassName(vJSON),".json");
// set modified date in reposinfo.modified
updateModified(vJSON);
var vContent = JSON.stringify(vJSON,null,4);
saveFile2HDD(vFile,vContent);
console.log("JSON output '"+vFile+"':\n"+vContent);
};
function updateModified(pJSON) {
if (pJSON) {
if (pJSON.reposinfo) {
pJSON.reposinfo.modified = getDateTime();
console.log("reposinfo.modified updated: '"+pJSON.reposinfo.modified+"'");
}
};
};
function class2filename(pClassName,pExt) {
var vExt = pExt || "";
var vFilename = pClassName || "Undefined Class";
vFilename = vFilename.toLowerCase();
vFilename = vFilename.replace(/[^a-z0-9]/g,"_");
vFilename = vFilename.replace(/_[_]+/g,"_");
return vFilename+vExt;
}
function loader4JSON(pFileID4DOM) {
var fileToLoad = document.getElementById(pFileID4DOM).files[0]; //for input type=file
if (fileToLoad) {
console.log("loader4JSON() - File '"+fileToLoad.name+"' exists.");
$('#display_filename').html(fileToLoad.name); // this.value.replace(/.*[\/\\]/, '')
var fileReader = new FileReader();
// set the onload handler
fileReader.onload = function(fileLoadedEvent){
var vTextFromFileLoaded = fileLoadedEvent.target.result;
//document.getElementById("inputTextToSave").value = textFromFileLoaded;
//alert("textFromFileLoaded="+textFromFileLoaded);
try {
vJSONEditor.setValue(JSON.parse(vTextFromFileLoaded));
alert("File JSON '"+fileToLoad.name+"' loaded successfully!");
validate_errors();
} catch(e) {
vJSONEditor.setValue([]); // Init with an empty class
alert(e); // error in the above string (in this case, yes)!
};
};
//onload handler set now start loading the file
fileReader.readAsText(fileToLoad, "UTF-8");
} else {
alert("File is missing");
};
saveLS(fileToLoad.name);
};
function getDate() {
var vNow = new Date();
var vMonth = vNow.getMonth()+1;
return vNow.getDate()+"."+vMonth+"."+vNow.getFullYear();
}
function outTime(pNr) {
var vOut = pNr;
if (pNr == 0) {
vOut = "00"
} if (pNr<10) {
vOut = "0"+pNr;
};
return vOut
}
function getDateTime() {
var vNow = new Date();
var vSep = "/"; // set separator for date
var vOut = vNow.getFullYear() + vSep +outTime(vNow.getMonth()+1) + vSep + outTime(vNow.getDate());
vOut += " "; // Separator between Date and Time
vSep = ":"; // set separator for time
vOut += vNow.getHours() + vSep + outTime(vNow.getMinutes()) + vSep + outTime(vNow.getSeconds());
return vOut;
}
function getTimeIndex() {
var d = new Date();
return d.getTime();
};
function saveFile2HDD(pFilename,pContent) {
var file = new Blob([pContent], {type: "text/plain;charset=utf-8"});
saveAs(file,pFilename);
}
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,a=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},i=/constructor/i.test(e.HTMLElement)||e.safari,f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},s="application/octet-stream",d=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,d)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{o.call(e,n||e)}catch(a){u(a)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,d){if(!d){t=p(t)}var v=this,w=t.type,m=w===s,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&i)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var n=e.open(t,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var o=e.open(y,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;r.download=u;a(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||e.name||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||e.name||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define("FileSaver.js",function(){return saveAs})}
/*! JSON Editor v0.7.28 - JSON Schema -> HTML Editor
* By Jeremy Dorn - https://github.com/jdorn/json-editor/
* Released under the MIT license
*
* Date: 2016-08-07
*/
/**
* See README.md for requirements and usage info
*/
(function() {
/*jshint loopfunc: true */
/* Simple JavaScript Inheritance
* By John Resig http://ejohn.org/
* MIT Licensed.
*/
// Inspired by base2 and Prototype
var Class;
(function(){
var initializing = false, fnTest = /xyz/.test(function(){window.postMessage("xyz");}) ? /\b_super\b/ : /.*/;
// The base Class implementation (does nothing)
Class = function(){};
// Create a new Class that inherits from this class
Class.extend = function extend(prop) {
var _super = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn){
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) :
prop[name];
}
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if ( !initializing && this.init )
this.init.apply(this, arguments);
}
// Populate our constructed prototype object
Class.prototype = prototype;
// Enforce the constructor to be what we expect
Class.prototype.constructor = Class;
// And make this class extendable
Class.extend = extend;
return Class;
};
return Class;
})();
// CustomEvent constructor polyfill
// From MDN
(function () {
function CustomEvent ( event, params ) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent( 'CustomEvent' );
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
return evt;
}
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
})();
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
// MIT license
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] ||
window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
// Array.isArray polyfill
// From MDN
(function() {
if(!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
}());/**
* Taken from jQuery 2.1.3
*
* @param obj
* @returns {boolean}
*/
var $isplainobject = function( obj ) {
// Not plain objects:
// - Any object or value whose internal [[Class]] property is not "[object Object]"
// - DOM nodes
// - window
if (typeof obj !== "object" || obj.nodeType || (obj !== null && obj === obj.window)) {
return false;
}
if (obj.constructor && !Object.prototype.hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) {
return false;
}
// If the function hasn't returned already, we're confident that
// |obj| is a plain object, created by {} or constructed with new Object
return true;
};
var $extend = function(destination) {
var source, i,property;
for(i=1; i<arguments.length; i++) {
source = arguments[i];
for (property in source) {
if(!source.hasOwnProperty(property)) continue;
if(source[property] && $isplainobject(source[property])) {
if(!destination.hasOwnProperty(property)) destination[property] = {};
$extend(destination[property], source[property]);
}
else {
destination[property] = source[property];
}
}
}
return destination;
};
var $each = function(obj,callback) {
if(!obj || typeof obj !== "object") return;
var i;
if(Array.isArray(obj) || (typeof obj.length === 'number' && obj.length > 0 && (obj.length - 1) in obj)) {
for(i=0; i<obj.length; i++) {
if(callback(i,obj[i])===false) return;
}
}
else {
if (Object.keys) {
var keys = Object.keys(obj);
for(i=0; i<keys.length; i++) {
if(callback(keys[i],obj[keys[i]])===false) return;
}
}
else {
for(i in obj) {
if(!obj.hasOwnProperty(i)) continue;
if(callback(i,obj[i])===false) return;
}
}
}
};
var $trigger = function(el,event) {
var e = document.createEvent('HTMLEvents');
e.initEvent(event, true, true);
el.dispatchEvent(e);
};
var $triggerc = function(el,event) {
var e = new CustomEvent(event,{
bubbles: true,
cancelable: true
});
el.dispatchEvent(e);
};
var JSONEditor = function(element,options) {
if (!(element instanceof Element)) {
throw new Error('element should be an instance of Element');
}
options = $extend({},JSONEditor.defaults.options,options||{});
this.element = element;
this.options = options;
this.init();
};
JSONEditor.prototype = {
// necessary since we remove the ctor property by doing a literal assignment. Without this
// the $isplainobject function will think that this is a plain object.
constructor: JSONEditor,
init: function() {
var self = this;
this.ready = false;
var theme_class = JSONEditor.defaults.themes[this.options.theme || JSONEditor.defaults.theme];
if(!theme_class) throw "Unknown theme " + (this.options.theme || JSONEditor.defaults.theme);
this.schema = this.options.schema;
this.theme = new theme_class();
this.template = this.options.template;
this.refs = this.options.refs || {};
this.uuid = 0;
this.__data = {};
var icon_class = JSONEditor.defaults.iconlibs[this.options.iconlib || JSONEditor.defaults.iconlib];
if(icon_class) this.iconlib = new icon_class();
this.root_container = this.theme.getContainer();
this.element.appendChild(this.root_container);
this.translate = this.options.translate || JSONEditor.defaults.translate;
// Fetch all external refs via ajax
this._loadExternalRefs(this.schema, function() {
self._getDefinitions(self.schema);
// Validator options
var validator_options = {};
if(self.options.custom_validators) {
validator_options.custom_validators = self.options.custom_validators;
}
self.validator = new JSONEditor.Validator(self,null,validator_options);
// Create the root editor
var editor_class = self.getEditorClass(self.schema);
self.root = self.createEditor(editor_class, {
jsoneditor: self,
schema: self.schema,
required: true,
container: self.root_container
});
self.root.preBuild();
self.root.build();
self.root.postBuild();
// Starting data
if(self.options.startval) self.root.setValue(self.options.startval);
self.validation_results = self.validator.validate(self.root.getValue());
self.root.showValidationErrors(self.validation_results);
self.ready = true;
// Fire ready event asynchronously
window.requestAnimationFrame(function() {
if(!self.ready) return;
self.validation_results = self.validator.validate(self.root.getValue());
self.root.showValidationErrors(self.validation_results);
self.trigger('ready');
self.trigger('change');
});
});
},
getValue: function() {
if(!this.ready) throw "JSON Editor not ready yet. Listen for 'ready' event before getting the value";
return this.root.getValue();
},
setValue: function(value) {
if(!this.ready) throw "JSON Editor not ready yet. Listen for 'ready' event before setting the value";
this.root.setValue(value);
return this;
},
validate: function(value) {
if(!this.ready) throw "JSON Editor not ready yet. Listen for 'ready' event before validating";
// Custom value
if(arguments.length === 1) {
return this.validator.validate(value);
}
// Current value (use cached result)
else {
return this.validation_results;
}
},
destroy: function() {
if(this.destroyed) return;
if(!this.ready) return;
this.schema = null;
this.options = null;
this.root.destroy();
this.root = null;
this.root_container = null;
this.validator = null;
this.validation_results = null;
this.theme = null;
this.iconlib = null;
this.template = null;
this.__data = null;
this.ready = false;
this.element.innerHTML = '';
this.destroyed = true;
},
on: function(event, callback) {
this.callbacks = this.callbacks || {};
this.callbacks[event] = this.callbacks[event] || [];
this.callbacks[event].push(callback);
return this;
},
off: function(event, callback) {
// Specific callback
if(event && callback) {
this.callbacks = this.callbacks || {};
this.callbacks[event] = this.callbacks[event] || [];
var newcallbacks = [];
for(var i=0; i<this.callbacks[event].length; i++) {
if(this.callbacks[event][i]===callback) continue;
newcallbacks.push(this.callbacks[event][i]);
}
this.callbacks[event] = newcallbacks;
}
// All callbacks for a specific event
else if(event) {
this.callbacks = this.callbacks || {};
this.callbacks[event] = [];
}
// All callbacks for all events
else {
this.callbacks = {};
}
return this;
},
trigger: function(event) {
if(this.callbacks && this.callbacks[event] && this.callbacks[event].length) {
for(var i=0; i<this.callbacks[event].length; i++) {
this.callbacks[event][i]();
}
}
return this;
},
setOption: function(option, value) {
if(option === "show_errors") {
this.options.show_errors = value;
this.onChange();
}
// Only the `show_errors` option is supported for now
else {
throw "Option "+option+" must be set during instantiation and cannot be changed later";
}
return this;
},
getEditorClass: function(schema) {
var classname;
schema = this.expandSchema(schema);
$each(JSONEditor.defaults.resolvers,function(i,resolver) {
var tmp = resolver(schema);
if(tmp) {
if(JSONEditor.defaults.editors[tmp]) {
classname = tmp;
return false;
}
}
});
if(!classname) throw "Unknown editor for schema "+JSON.stringify(schema);
if(!JSONEditor.defaults.editors[classname]) throw "Unknown editor "+classname;
return JSONEditor.defaults.editors[classname];
},
createEditor: function(editor_class, options) {
options = $extend({},editor_class.options||{},options);
return new editor_class(options);
},
onChange: function() {
if(!this.ready) return;
if(this.firing_change) return;
this.firing_change = true;
var self = this;
window.requestAnimationFrame(function() {
self.firing_change = false;
if(!self.ready) return;
// Validate and cache results
self.validation_results = self.validator.validate(self.root.getValue());
if(self.options.show_errors !== "never") {
self.root.showValidationErrors(self.validation_results);
}
else {
self.root.showValidationErrors([]);
}
// Fire change event
self.trigger('change');
});
return this;
},
compileTemplate: function(template, name) {
name = name || JSONEditor.defaults.template;
var engine;
// Specifying a preset engine
if(typeof name === 'string') {
if(!JSONEditor.defaults.templates[name]) throw "Unknown template engine "+name;
engine = JSONEditor.defaults.templates[name]();
if(!engine) throw "Template engine "+name+" missing required library.";
}
// Specifying a custom engine
else {
engine = name;
}
if(!engine) throw "No template engine set";
if(!engine.compile) throw "Invalid template engine set";
return engine.compile(template);
},
_data: function(el,key,value) {
// Setting data
if(arguments.length === 3) {
var uuid;
if(el.hasAttribute('data-jsoneditor-'+key)) {
uuid = el.getAttribute('data-jsoneditor-'+key);
}
else {
uuid = this.uuid++;
el.setAttribute('data-jsoneditor-'+key,uuid);
}
this.__data[uuid] = value;
}
// Getting data
else {
// No data stored
if(!el.hasAttribute('data-jsoneditor-'+key)) return null;
return this.__data[el.getAttribute('data-jsoneditor-'+key)];
}
},
registerEditor: function(editor) {
this.editors = this.editors || {};
this.editors[editor.path] = editor;
return this;
},
unregisterEditor: function(editor) {
this.editors = this.editors || {};
this.editors[editor.path] = null;
return this;
},
getEditor: function(path) {
if(!this.editors) return;
return this.editors[path];
},
watch: function(path,callback) {
this.watchlist = this.watchlist || {};
this.watchlist[path] = this.watchlist[path] || [];
this.watchlist[path].push(callback);
return this;
},
unwatch: function(path,callback) {
if(!this.watchlist || !this.watchlist[path]) return this;
// If removing all callbacks for a path
if(!callback) {
this.watchlist[path] = null;
return this;
}
var newlist = [];
for(var i=0; i<this.watchlist[path].length; i++) {
if(this.watchlist[path][i] === callback) continue;
else newlist.push(this.watchlist[path][i]);
}
this.watchlist[path] = newlist.length? newlist : null;
return this;
},
notifyWatchers: function(path) {
if(!this.watchlist || !this.watchlist[path]) return this;
for(var i=0; i<this.watchlist[path].length; i++) {
this.watchlist[path][i]();
}
},
isEnabled: function() {
return !this.root || this.root.isEnabled();
},
enable: function() {
this.root.enable();
},
disable: function() {
this.root.disable();
},
_getDefinitions: function(schema,path) {
path = path || '#/definitions/';
if(schema.definitions) {
for(var i in schema.definitions) {
if(!schema.definitions.hasOwnProperty(i)) continue;
this.refs[path+i] = schema.definitions[i];
if(schema.definitions[i].definitions) {
this._getDefinitions(schema.definitions[i],path+i+'/definitions/');
}
}
}
},
_getExternalRefs: function(schema) {
var refs = {};
var merge_refs = function(newrefs) {
for(var i in newrefs) {
if(newrefs.hasOwnProperty(i)) {
refs[i] = true;
}
}
};
if(schema.$ref && typeof schema.$ref !== "object" && schema.$ref.substr(0,1) !== "#" && !this.refs[schema.$ref]) {
refs[schema.$ref] = true;
}
for(var i in schema) {
if(!schema.hasOwnProperty(i)) continue;
if(schema[i] && typeof schema[i] === "object" && Array.isArray(schema[i])) {
for(var j=0; j<schema[i].length; j++) {
if(typeof schema[i][j]==="object") {
merge_refs(this._getExternalRefs(schema[i][j]));
}
}
}
else if(schema[i] && typeof schema[i] === "object") {
merge_refs(this._getExternalRefs(schema[i]));
}
}
return refs;
},
_loadExternalRefs: function(schema, callback) {
var self = this;
var refs = this._getExternalRefs(schema);
var done = 0, waiting = 0, callback_fired = false;
$each(refs,function(url) {
if(self.refs[url]) return;
if(!self.options.ajax) throw "Must set ajax option to true to load external ref "+url;
self.refs[url] = 'loading';
waiting++;
var r = new XMLHttpRequest();
r.open("GET", url, true);
r.onreadystatechange = function () {
if (r.readyState != 4) return;
// Request succeeded
if(r.status === 200) {
var response;
try {
response = JSON.parse(r.responseText);
}
catch(e) {
window.console.log(e);
throw "Failed to parse external ref "+url;
}
if(!response || typeof response !== "object") throw "External ref does not contain a valid schema - "+url;
self.refs[url] = response;
self._loadExternalRefs(response,function() {
done++;
if(done >= waiting && !callback_fired) {
callback_fired = true;
callback();
}
});
}
// Request failed
else {
window.console.log(r);
throw "Failed to fetch ref via ajax- "+url;
}
};
r.send();
});
if(!waiting) {
callback();
}
},
expandRefs: function(schema) {
schema = $extend({},schema);
while (schema.$ref) {
var ref = schema.$ref;
delete schema.$ref;
if(!this.refs[ref]) ref = decodeURIComponent(ref);
schema = this.extendSchemas(schema,this.refs[ref]);
}
return schema;
},
expandSchema: function(schema) {
var self = this;
var extended = $extend({},schema);
var i;
// Version 3 `type`
if(typeof schema.type === 'object') {
// Array of types
if(Array.isArray(schema.type)) {
$each(schema.type, function(key,value) {
// Schema
if(typeof value === 'object') {
schema.type[key] = self.expandSchema(value);
}
});
}
// Schema
else {
schema.type = self.expandSchema(schema.type);
}
}
// Version 3 `disallow`
if(typeof schema.disallow === 'object') {
// Array of types
if(Array.isArray(schema.disallow)) {
$each(schema.disallow, function(key,value) {
// Schema
if(typeof value === 'object') {
schema.disallow[key] = self.expandSchema(value);
}
});
}
// Schema
else {
schema.disallow = self.expandSchema(schema.disallow);
}
}
// Version 4 `anyOf`
if(schema.anyOf) {
$each(schema.anyOf, function(key,value) {
schema.anyOf[key] = self.expandSchema(value);
});
}
// Version 4 `dependencies` (schema dependencies)
if(schema.dependencies) {
$each(schema.dependencies,function(key,value) {
if(typeof value === "object" && !(Array.isArray(value))) {
schema.dependencies[key] = self.expandSchema(value);
}
});
}
// Version 4 `not`
if(schema.not) {
schema.not = this.expandSchema(schema.not);
}
// allOf schemas should be merged into the parent
if(schema.allOf) {
for(i=0; i<schema.allOf.length; i++) {
extended = this.extendSchemas(extended,this.expandSchema(schema.allOf[i]));
}
delete extended.allOf;
}
// extends schemas should be merged into parent
if(schema["extends"]) {
// If extends is a schema
if(!(Array.isArray(schema["extends"]))) {
extended = this.extendSchemas(extended,this.expandSchema(schema["extends"]));
}
// If extends is an array of schemas
else {
for(i=0; i<schema["extends"].length; i++) {
extended = this.extendSchemas(extended,this.expandSchema(schema["extends"][i]));
}
}
delete extended["extends"];
}
// parent should be merged into oneOf schemas
if(schema.oneOf) {
var tmp = $extend({},extended);
delete tmp.oneOf;
for(i=0; i<schema.oneOf.length; i++) {
extended.oneOf[i] = this.extendSchemas(this.expandSchema(schema.oneOf[i]),tmp);
}
}
return this.expandRefs(extended);
},
extendSchemas: function(obj1, obj2) {
obj1 = $extend({},obj1);
obj2 = $extend({},obj2);
var self = this;
var extended = {};
$each(obj1, function(prop,val) {
// If this key is also defined in obj2, merge them
if(typeof obj2[prop] !== "undefined") {
// Required and defaultProperties arrays should be unioned together
if((prop === 'required'||prop === 'defaultProperties') && typeof val === "object" && Array.isArray(val)) {
// Union arrays and unique
extended[prop] = val.concat(obj2[prop]).reduce(function(p, c) {
if (p.indexOf(c) < 0) p.push(c);
return p;
}, []);
}
// Type should be intersected and is either an array or string
else if(prop === 'type' && (typeof val === "string" || Array.isArray(val))) {
// Make sure we're dealing with arrays
if(typeof val === "string") val = [val];
if(typeof obj2.type === "string") obj2.type = [obj2.type];
// If type is only defined in the first schema, keep it
if(!obj2.type || !obj2.type.length) {
extended.type = val;
}
// If type is defined in both schemas, do an intersect
else {
extended.type = val.filter(function(n) {
return obj2.type.indexOf(n) !== -1;
});
}
// If there's only 1 type and it's a primitive, use a string instead of array
if(extended.type.length === 1 && typeof extended.type[0] === "string") {
extended.type = extended.type[0];
}
// Remove the type property if it's empty
else if(extended.type.length === 0) {
delete extended.type;
}
}
// All other arrays should be intersected (enum, etc.)
else if(typeof val === "object" && Array.isArray(val)){
extended[prop] = val.filter(function(n) {
return obj2[prop].indexOf(n) !== -1;
});
}
// Objects should be recursively merged
else if(typeof val === "object" && val !== null) {
extended[prop] = self.extendSchemas(val,obj2[prop]);
}
// Otherwise, use the first value
else {
extended[prop] = val;
}
}
// Otherwise, just use the one in obj1
else {
extended[prop] = val;
}
});
// Properties in obj2 that aren't in obj1
$each(obj2, function(prop,val) {
if(typeof obj1[prop] === "undefined") {
extended[prop] = val;
}
});
return extended;
}
};
JSONEditor.defaults = {
themes: {},
templates: {},
iconlibs: {},
editors: {},
languages: {},
resolvers: [],
custom_validators: []
};
JSONEditor.Validator = Class.extend({
init: function(jsoneditor,schema,options) {
this.jsoneditor = jsoneditor;
this.schema = schema || this.jsoneditor.schema;
this.options = options || {};
this.translate = this.jsoneditor.translate || JSONEditor.defaults.translate;
},
validate: function(value) {
return this._validateSchema(this.schema, value);
},
_validateSchema: function(schema,value,path) {
var self = this;
var errors = [];
var valid, i, j;
var stringified = JSON.stringify(value);
path = path || 'root';
// Work on a copy of the schema
schema = $extend({},this.jsoneditor.expandRefs(schema));
/*
* Type Agnostic Validation
*/
// Version 3 `required`
if(schema.required && schema.required === true) {
if(typeof value === "undefined") {
errors.push({
path: path,
property: 'required',
message: this.translate("error_notset")
});
// Can't do any more validation at this point
return errors;
}
}
// Value not defined
else if(typeof value === "undefined") {
// If required_by_default is set, all fields are required
if(this.jsoneditor.options.required_by_default) {
errors.push({
path: path,
property: 'required',
message: this.translate("error_notset")
});
}
// Not required, no further validation needed
else {
return errors;
}
}
// `enum`
if(schema["enum"]) {
valid = false;
for(i=0; i<schema["enum"].length; i++) {
if(stringified === JSON.stringify(schema["enum"][i])) valid = true;
}
if(!valid) {
errors.push({
path: path,
property: 'enum',
message: this.translate("error_enum")
});
}
}
// `extends` (version 3)
if(schema["extends"]) {
for(i=0; i<schema["extends"].length; i++) {
errors = errors.concat(this._validateSchema(schema["extends"][i],value,path));
}
}
// `allOf`
if(schema.allOf) {
for(i=0; i<schema.allOf.length; i++) {
errors = errors.concat(this._validateSchema(schema.allOf[i],value,path));
}
}
// `anyOf`
if(schema.anyOf) {
valid = false;
for(i=0; i<schema.anyOf.length; i++) {
if(!this._validateSchema(schema.anyOf[i],value,path).length) {
valid = true;
break;
}
}
if(!valid) {
errors.push({
path: path,
property: 'anyOf',
message: this.translate('error_anyOf')
});
}
}
// `oneOf`
if(schema.oneOf) {
valid = 0;
var oneof_errors = [];
for(i=0; i<schema.oneOf.length; i++) {
// Set the error paths to be path.oneOf[i].rest.of.path
var tmp = this._validateSchema(schema.oneOf[i],value,path);
if(!tmp.length) {
valid++;
}
for(j=0; j<tmp.length; j++) {
tmp[j].path = path+'.oneOf['+i+']'+tmp[j].path.substr(path.length);
}
oneof_errors = oneof_errors.concat(tmp);
}
if(valid !== 1) {
errors.push({
path: path,
property: 'oneOf',
message: this.translate('error_oneOf', [valid])
});
errors = errors.concat(oneof_errors);
}
}
// `not`
if(schema.not) {
if(!this._validateSchema(schema.not,value,path).length) {
errors.push({
path: path,
property: 'not',
message: this.translate('error_not')
});
}
}
// `type` (both Version 3 and Version 4 support)
if(schema.type) {
// Union type
if(Array.isArray(schema.type)) {
valid = false;
for(i=0;i<schema.type.length;i++) {
if(this._checkType(schema.type[i], value)) {
valid = true;
break;
}
}
if(!valid) {
errors.push({
path: path,
property: 'type',
message: this.translate('error_type_union')
});
}
}
// Simple type
else {
if(!this._checkType(schema.type, value)) {
errors.push({
path: path,
property: 'type',
message: this.translate('error_type', [schema.type])
});
}
}
}
// `disallow` (version 3)
if(schema.disallow) {
// Union type
if(Array.isArray(schema.disallow)) {
valid = true;
for(i=0;i<schema.disallow.length;i++) {
if(this._checkType(schema.disallow[i], value)) {
valid = false;
break;
}
}
if(!valid) {
errors.push({
path: path,
property: 'disallow',
message: this.translate('error_disallow_union')
});
}
}
// Simple type
else {
if(this._checkType(schema.disallow, value)) {
errors.push({
path: path,
property: 'disallow',
message: this.translate('error_disallow', [schema.disallow])
});
}
}
}