UNPKG

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
/* --------------------------------------- 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]) }); } } }