http://git-wip-us.apache.org/repos/asf/stratos/blob/c6a485f9/components/org.apache.stratos.manager.console/console/themes/theme0/js/JSONEditor-0.7.12/jsoneditor-0.7.12.js ---------------------------------------------------------------------- diff --git a/components/org.apache.stratos.manager.console/console/themes/theme0/js/JSONEditor-0.7.12/jsoneditor-0.7.12.js b/components/org.apache.stratos.manager.console/console/themes/theme0/js/JSONEditor-0.7.12/jsoneditor-0.7.12.js new file mode 100644 index 0000000..f216d6c --- /dev/null +++ b/components/org.apache.stratos.manager.console/console/themes/theme0/js/JSONEditor-0.7.12/jsoneditor-0.7.12.js @@ -0,0 +1,7001 @@ +/*! JSON Editor v0.7.12 - JSON Schema -> HTML Editor + * By Jeremy Dorn - https://github.com/jdorn/json-editor/ + * Released under the MIT license + * + * Date: 2014-10-05 + */ + +/** + * 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(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 = arguments.callee; + + 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]'; + }; + } + }()); + var $isplainobject = function( obj ) { + // Not own constructor property must be Object + if ( obj.constructor && + !obj.hasOwnProperty('constructor') && + !obj.constructor.prototype.hasOwnProperty('isPrototypeOf')) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || obj.hasOwnProperty(key); + }; + + 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) return; + var i; + if(typeof obj.length !== 'undefined') { + for(i=0; i<obj.length; i++) { + if(callback(i,obj[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) { + options = $extend({},JSONEditor.defaults.options,options||{}); + this.element = element; + this.options = options; + this.init(); + }; + JSONEditor.prototype = { + 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); + self.validator = new JSONEditor.Validator(self); + + // 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() { + 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; + + // 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 && 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; + 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 arrays should be unioned together + if(prop === 'required' && typeof val === "object" && Array.isArray(val)) { + // Union arrays and unique + extended.required = 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]; + + + 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]; + } + } + // 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) { + this.jsoneditor = jsoneditor; + this.schema = schema || this.jsoneditor.schema; + this.options = {}; + this.translate = this.jsoneditor.translate || JSONEditor.defaults.translate; + }, + validate: function(value) { + return this._validateSchema(this.schema, value); + }, + _validateSchema: function(schema,value,path) { + 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]) + }); + } + } + } + + /* + * Type Specific Validation + */ + + // Number Specific Validation + if(typeof value === "number") { + // `multipleOf` and `divisibleBy` + if(schema.multipleOf || schema.divisibleBy) { + valid = value / (schema.multipleOf || schema.divisibleBy); + if(valid !== Math.floor(valid)) { + errors.push({ + path: path, + property: schema.multipleOf? 'multipleOf' : 'divisibleBy', + message: this.translate('error_multipleOf', [schema.multipleOf || schema.divisibleBy]) + }); + } + } + + // `maximum` + if(schema.hasOwnProperty('maximum')) { + if(schema.exclusiveMaximum && value >= schema.maximum) { + errors.push({ + path: path, + property: 'maximum', + message: this.translate('error_maximum_excl', [schema.maximum]) + }); + } + else if(!schema.exclusiveMaximum && value > schema.maximum) { + errors.push({ + path: path, + property: 'maximum', + message: this.translate('error_maximum_incl', [schema.maximum]) + }); + } + } + + // `minimum` + if(schema.hasOwnProperty('minimum')) { + if(schema.exclusiveMinimum && value <= schema.minimum) { + errors.push({ + path: path, + property: 'minimum', + message: this.translate('error_minimum_excl', [schema.minimum]) + }); + } + else if(!schema.exclusiveMinimum && value < schema.minimum) { + errors.push({ + path: path, + property: 'minimum', + message: this.translate('error_minimum_incl', [schema.minimum]) + }); + } + } + } + // String specific validation + else if(typeof value === "string") { + // `maxLength` + if(schema.maxLength) { + if((value+"").length > schema.maxLength) { + errors.push({ + path: path, + property: 'maxLength', + message: this.translate('error_maxLength', [schema.maxLength]) + }); + } + } + + // `minLength` + if(schema.minLength) { + if((value+"").length < schema.minLength) { + errors.push({ + path: path, + property: 'minLength', + message: this.translate((schema.minLength===1?'error_notempty':'error_minLength'), [schema.minLength]) + }); + } + } + + // `pattern` + if(schema.pattern) { + if(!(new RegExp(schema.pattern)).test(value)) { + errors.push({ + path: path, + property: 'pattern', + message: this.translate('error_pattern') + }); + } + } + } + // Array specific validation + else if(typeof value === "object" && value !== null && Array.isArray(value)) { + // `items` and `additionalItems` + if(schema.items) { + // `items` is an array + if(Array.isArray(schema.items)) { + for(i=0; i<value.length; i++) { + // If this item has a specific schema tied to it + // Validate against it + if(schema.items[i]) { + errors = errors.concat(this._validateSchema(schema.items[i],value[i],path+'.'+i)); + } + // If all additional items are allowed + else if(schema.additionalItems === true) { + break; + } + // If additional items is a schema + // TODO: Incompatibility between version 3 and 4 of the spec + else if(schema.additionalItems) { + errors = errors.concat(this._validateSchema(schema.additionalItems,value[i],path+'.'+i)); + } + // If no additional items are allowed + else if(schema.additionalItems === false) { + errors.push({ + path: path, + property: 'additionalItems', + message: this.translate('error_additionalItems') + }); + break; + } + // Default for `additionalItems` is an empty schema + else { + break; + } + } + } + // `items` is a schema + else { + // Each item in the array must validate against the schema + for(i=0; i<value.length; i++) { + errors = errors.concat(this._validateSchema(schema.items,value[i],path+'.'+i)); + } + } + } + + // `maxItems` + if(schema.maxItems) { + if(value.length > schema.maxItems) { + errors.push({ + path: path, + property: 'maxItems', + message: this.translate('error_maxItems', [schema.maxItems]) + }); + } + } + + // `minItems` + if(schema.minItems) { + if(value.length < schema.minItems) { + errors.push({ + path: path, + property: 'minItems', + message: this.translate('error_minItems', [schema.minItems]) + }); + } + } + + // `uniqueItems` + if(schema.uniqueItems) { + var seen = {}; + for(i=0; i<value.length; i++) { + valid = JSON.stringify(value[i]); + if(seen[valid]) { + errors.push({ + path: path, + property: 'uniqueItems', + message: this.translate('error_uniqueItems') + }); + break; + } + seen[valid] = true; + } + } + } + // Object specific validation + else if(typeof value === "object" && value !== null) { + // `maxProperties` + if(schema.maxProperties) { + valid = 0; + for(i in value) { + if(!value.hasOwnProperty(i)) continue; + valid++; + } + if(valid > schema.maxProperties) { + errors.push({ + path: path, + property: 'maxProperties', + message: this.translate('error_maxProperties', [schema.maxProperties]) + }); + } + } + + // `minProperties` + if(schema.minProperties) { + valid = 0; + for(i in value) { + if(!value.hasOwnProperty(i)) continue; + valid++; + } + if(valid < schema.minProperties) { + errors.push({ + path: path, + property: 'minProperties', + message: this.translate('error_minProperties', [schema.minProperties]) + }); + } + } + + // Version 4 `required` + if(schema.required && Array.isArray(schema.required)) { + for(i=0; i<schema.required.length; i++) { + if(typeof value[schema.required[i]] === "undefined") { + errors.push({ + path: path, + property: 'required', + message: this.translate('error_required', [schema.required[i]]) + }); + } + } + } + + // `properties` + var validated_properties = {}; + if(schema.properties) { + for(i in schema.properties) { + if(!schema.properties.hasOwnProperty(i)) continue; + validated_properties[i] = true; + errors = errors.concat(this._validateSchema(schema.properties[i],value[i],path+'.'+i)); + } + } + + // `patternProperties` + if(schema.patternProperties) { + for(i in schema.patternProperties) { + if(!schema.patternProperties.hasOwnProperty(i)) continue; + + var regex = new RegExp(i); + + // Check which properties match + for(j in value) { + if(!value.hasOwnProperty(j)) continue; + if(regex.test(j)) { + validated_properties[j] = true; + errors = errors.concat(this._validateSchema(schema.patternProperties[i],value[j],path+'.'+j)); + } + } + } + } + + // The no_additional_properties option currently doesn't work with extended schemas that use oneOf or anyOf + if(typeof schema.additionalProperties === "undefined" && this.jsoneditor.options.no_additional_properties && !schema.oneOf && !schema.anyOf) { + schema.additionalProperties = false; + } + + // `additionalProperties` + if(typeof schema.additionalProperties !== "undefined") { + for(i in value) { + if(!value.hasOwnProperty(i)) continue; + if(!validated_properties[i]) { + // No extra properties allowed + if(!schema.additionalProperties) { + errors.push({ + path: path, + property: 'additionalProperties', + message: this.translate('error_additional_properties', [i]) + }); + break; + } + // Allowed + else if(schema.additionalProperties === true) { + break; + } + // Must match schema + // TODO: incompatibility between version 3 and 4 of the spec + else { + errors = errors.concat(this._validateSchema(schema.additionalProperties,value[i],path+'.'+i)); + } + } + } + } + + // `dependencies` + if(schema.dependencies) { + for(i in schema.dependencies) { + if(!schema.dependencies.hasOwnProperty(i)) continue; + + // Doesn't need to meet the dependency + if(typeof value[i] === "undefined") continue; + + // Property dependency + if(Array.isArray(schema.dependencies[i])) { + for(j=0; j<schema.dependencies[i].length; j++) { + if(typeof value[schema.dependencies[i][j]] === "undefined") { + errors.push({ + path: path, + property: 'dependencies', + message: this.translate('error_dependency', [schema.dependencies[i][j]]) + }); + } + } + } + // Schema dependency + else { + errors = errors.concat(this._validateSchema(schema.dependencies[i],value,path)); + } + } + } + } + + // Custom type validation + $each(JSONEditor.defaults.custom_validators,function(i,validator) { + errors = errors.concat(validator(schema,value,path)); + }); + + return errors; + }, + _checkType: function(type, value) { + // Simple types + if(typeof type === "string") { + if(type==="string") return typeof value === "string"; + else if(type==="number") return typeof value === "number"; + else if(type==="integer") return typeof value === "number" && value === Math.floor(value); + else if(type==="boolean") return typeof value === "boolean"; + else if(type==="array") return Array.isArray(value); + else if(type === "object") return value !== null && !(Array.isArray(value)) && typeof value === "object"; + else if(type === "null") return value === null; + else return true; + } + // Schema + else { + return !this._validateSchema(type,value).length; + } + } + }); + + /** + * All editors should extend from this class + */ + JSONEditor.AbstractEditor = Class.extend({ + onChildEditorChange: function(editor) { + this.onChange(true); + }, + notify: function() { + this.jsoneditor.notifyWatchers(this.path); + }, + change: function() { + if(this.parent) this.parent.onChildEditorChange(this); + else this.jsoneditor.onChange(); + }, + onChange: function(bubble) { + this.notify(); + if(this.watch_listener) this.watch_listener(); + if(bubble) this.change(); + }, + register: function() { + this.jsoneditor.registerEditor(this); + this.onChange(); + }, + unregister: function() { + if(!this.jsoneditor) return; + this.jsoneditor.unregisterEditor(this); + }, + getNumColumns: function() { + return 12; + }, + init: function(options) { + this.jsoneditor = options.jsoneditor; + + this.theme = this.jsoneditor.theme; + this.template_engine = this.jsoneditor.template; + this.iconlib = this.jsoneditor.iconlib; + + this.original_schema = options.schema; + this.schema = this.jsoneditor.expandSchema(this.original_schema); + + this.options = $extend({}, (this.options || {}), (options.schema.options || {}), options); + + if(!options.path && !this.schema.id) this.schema.id = 'root'; + this.path = options.path || 'root'; + this.formname = options.formname || this.path.replace(/\.([^.]+)/g,'[$1]'); + if(this.jsoneditor.options.form_name_root) this.formname = this.formname.replace(/^root\[/,this.jsoneditor.options.form_name_root+'['); + this.key = this.path.split('.').pop(); + this.parent = options.parent; + + this.link_watchers = []; + + if(options.container) this.setContainer(options.container); + }, + setContainer: function(container) { + this.container = container; + if(this.schema.id) this.container.setAttribute('data-schemaid',this.schema.id); + if(this.schema.type && typeof this.schema.type === "string") this.container.setAttribute('data-schematype',this.schema.type); + this.container.setAttribute('data-schemapath',this.path); + }, + + preBuild: function() { + + }, + build: function() { + + }, + postBuild: function() { + this.setupWatchListeners(); + this.addLinks(); + this.setValue(this.getDefault(), true); + this.updateHeaderText(); + this.register(); + this.onWatchedFieldChange(); + }, + + setupWatchListeners: function() { + var self = this; + + // Watched fields + this.watched = {}; + if(this.schema.vars) this.schema.watch = this.schema.vars; + this.watched_values = {}; + this.watch_listener = function() { + if(self.refreshWatchedFieldValues()) { + self.onWatchedFieldChange(); + } + }; + + this.register(); + if(this.schema.hasOwnProperty('watch')) { + var path,path_parts,first,root,adjusted_path; + + for(var name in this.schema.watch) { + if(!this.schema.watch.hasOwnProperty(name)) continue; + path = this.schema.watch[name]; + + if(Array.isArray(path)) { + path_parts = [path[0]].concat(path[1].split('.')); + } + else { + path_parts = path.split('.'); + if(!self.theme.closest(self.container,'[data-schemaid="'+path_parts[0]+'"]')) path_parts.unshift('#'); + } + first = path_parts.shift(); + + if(first === '#') first = self.jsoneditor.schema.id || 'root'; + + // Find the root node for this template variable + root = self.theme.closest(self.container,'[data-schemaid="'+first+'"]'); + if(!root) throw "Could not find ancestor node with id "+first; + + // Keep track of the root node and path for use when rendering the template + adjusted_path = root.getAttribute('data-schemapath') + '.' + path_parts.join('.'); + + self.jsoneditor.watch(adjusted_path,self.watch_listener); + + self.watched[name] = adjusted_path; + } + } + + // Dynamic header + if(this.schema.headerTemplate) { + this.header_template = this.jsoneditor.compileTemplate(this.schema.headerTemplate, this.template_engine); + } + }, + + addLinks: function() { + // Add links + if(!this.no_link_holder) { + this.link_holder = this.theme.getLinksHolder(); + this.container.appendChild(this.link_holder); + if(this.schema.links) { + for(var i=0; i<this.schema.links.length; i++) { + this.addLink(this.getLink(this.schema.links[i])); + } + } + } + }, + + + getButton: function(text, icon, title) { + var btnClass = 'json-editor-btn-'+icon; + if(!this.iconlib) icon = null; + else icon = this.iconlib.getIcon(icon); + + if(!icon && title) { + text = title; + title = null; + } + + var btn = this.theme.getButton(text, icon, title); + btn.className += ' ' + btnClass + ' '; + return btn; + }, + setButtonText: function(button, text, icon, title) { + if(!this.iconlib) icon = null; + else icon = this.iconlib.getIcon(icon); + + if(!icon && title) { + text = title; + title = null; + } + + return this.theme.setButtonText(button, text, icon, title); + }, + addLink: function(link) { + if(this.link_holder) this.link_holder.appendChild(link); + }, + getLink: function(data) { + var holder, link; + + // Get mime type of the link + var mime = data.mediaType || 'application/javascript'; + var type = mime.split('/')[0]; + + // Template to generate the link href + var href = this.jsoneditor.compileTemplate(data.href,this.template_engine); + + // Image links + if(type === 'image') { + holder = this.theme.getBlockLinkHolder(); + link = document.createElement('a'); + link.setAttribute('target','_blank'); + var image = document.createElement('img'); + + this.theme.createImageLink(holder,link,image); + + // When a watched field changes, update the url + this.link_watchers.push(function(vars) { + var url = href(vars); + link.setAttribute('href',url); + link.setAttribute('title',data.rel || url); + image.setAttribute('src',url); + }); + } + // Audio/Video links + else if(['audio','video'].indexOf(type) >=0) { + holder = this.theme.getBlockLinkHolder(); + + link = this.theme.getBlockLink(); + link.setAttribute('target','_blank'); + + var media = document.createElement(type); + media.setAttribute('controls','controls'); + + this.theme.createMediaLink(holder,link,media); + + // When a watched field changes, update the url + this.link_watchers.push(function(vars) { + var url = href(vars); + link.setAttribute('href',url); + link.textContent = data.rel || url; + media.setAttribute('src',url); + }); + } + // Text links + else { + holder = this.theme.getBlockLink(); + holder.setAttribute('target','_blank'); + holder.textContent = data.rel; + + // When a watched field changes, update the url + this.link_watchers.push(function(vars) { + var url = href(vars); + holder.setAttribute('href',url); + holder.textContent = data.rel || url; + }); + } + + return holder; + }, + refreshWatchedFieldValues: function() { + if(!this.watched_values) return; + var watched = {}; + var changed = false; + var self = this; + + if(this.watched) { + var val,editor; + for(var name in this.watched) { + if(!this.watched.hasOwnProperty(name)) continue; + editor = self.jsoneditor.getEditor(this.watched[name]); + val = editor? editor.getValue() : null; + if(self.watched_values[name] !== val) changed = true; + watched[name] = val; + } + } + + watched.self = this.getValue(); + if(this.watched_values.self !== watched.self) changed = true; + + this.watched_values = watched; + + return changed; + }, + getWatchedFieldValues: function() { + return this.watched_values; + }, + updateHeaderText: function() { + if(this.header) { + this.header.textContent = this.getHeaderText(); + } + }, + getHeaderText: function(title_only) { + if(this.header_text) return this.header_text; + else if(title_only) return this.schema.title; + else return this.getTitle(); + }, + onWatchedFieldChange: function() { + var vars; + if(this.header_template) { + vars = $extend(this.getWatchedFieldValues(),{ + key: this.key, + i: this.key, + i0: (this.key*1), + i1: (this.key*1+1), + title: this.getTitle() + }); + var header_text = this.header_template(vars); + + if(header_text !== this.header_text) { + this.header_text = header_text; + this.updateHeaderText(); + this.notify(); + //this.fireChangeHeaderEvent(); + } + } + if(this.link_watchers.length) { + vars = this.getWatchedFieldValues(); + for(var i=0; i<this.link_watchers.length; i++) { + this.link_watchers[i](vars); + } + } + }, + setValue: function(value) { + this.value = value; + }, + getValue: function() { + return this.value; + }, + refreshValue: function() { + + }, + getChildEditors: function() { + return false; + }, + destroy: function() { + var self = this; + this.unregister(this); + $each(this.watched,function(name,adjusted_path) { + self.jsoneditor.unwatch(adjusted_path,self.watch_listener); + }); + this.watched = null; + this.watched_values = null; + this.watch_listener = null; + this.header_text = null; + this.header_template = null; + this.value = null; + if(this.container && this.container.parentNode) this.container.parentNode.removeChild(this.container); + this.container = null; + this.jsoneditor = null; + this.schema = null; + this.path = null; + this.key = null; + this.parent = null; + }, + getDefault: function() { + if(this.schema.default) return this.schema.default; + if(this.schema.enum) return this.schema.enum[0]; + + var type = this.schema.type || this.schema.oneOf; + if(type && Array.isArray(type)) type = type[0]; + if(type && typeof type === "object") type = type.type; + if(type && Array.isArray(type)) type = type[0]; + + if(typeof type === "string") { + if(type === "number") return 0.0; + if(type === "boolean") return false; + if(type === "integer") return 0; + if(type === "string") return ""; + if(type === "object") return {}; + if(type === "array") return []; + } + + return null; + }, + getTitle: function() { + return this.schema.title || this.key; + }, + enable: function() { + this.disabled = false; + }, + disable: function() { + this.disabled = true; + }, + isEnabled: function() { + return !this.disabled; + }, + getDisplayText: function(arr) { + var disp = []; + var used = {}; + + // Determine how many times each attribute name is used. + // This helps us pick the most distinct display text for the schemas. + $each(arr,function(i,el) { + if(el.title) { + used[el.title] = used[el.title] || 0; + used[el.title]++; + } + if(el.description) { + used[el.description] = used[el.description] || 0; + used[el.description]++; + } + if(el.format) { + used[el.format] = used[el.format] || 0; + used[el.format]++; + } + if(el.type) { + used[el.type] = used[el.type] || 0; + used[el.type]++; + } + }); + + // Determine display text for each element of the array + $each(arr,function(i,el) { + var name; + + // If it's a simple string + if(typeof el === "string") name = el; + // Object + else if(el.title && used[el.title]<=1) name = el.title; + else if(el.format && used[el.format]<=1) name = el.format; + else if(el.type && used[el.type]<=1) name = el.type; + else if(el.description && used[el.description]<=1) name = el.descripton; + else if(el.title) name = el.title; + else if(el.format) name = el.format; + else if(el.type) name = el.type; + else if(el.description) name = el.description; + else if(JSON.stringify(el).length < 50) name = JSON.stringify(el); + else name = "type"; + + disp.push(name); + }); + + // Replace identical display text with "text 1", "text 2", etc. + var inc = {}; + $each(disp,function(i,name) { + inc[name] = inc[name] || 0; + inc[name]++; + + if(used[name] > 1) disp[i] = name + " " + inc[name]; + }); + + return disp; + }, + getOption: function(key) { + try { + throw "getOption is deprecated"; + } + catch(e) { + window.console.error(e); + } + + return this.options[key]; + }, + showValidationErrors: function(errors) { + + } + }); + + JSONEditor.defaults.editors.null = JSONEditor.AbstractEditor.extend({ + getValue: function() { + return null; + }, + setValue: function() { + this.onChange(); + }, + getNumColumns: function() { + return 2; + } + }); + + JSONEditor.defaults.editors.string = JSONEditor.AbstractEditor.extend({ + register: function() { + this._super(); + if(!this.input) return; + this.input.setAttribute('name',this.formname); + }, + unregister: function() { + this._super(); + if(!this.input) return; + this.input.removeAttribute('name'); + }, + setValue: function(value,initial,from_template) { + var self = this; + + if(this.template && !from_template) { + return; + } + + if(value === null) value = ""; + else if(typeof value === "object") value = JSON.stringify(value); + else if(typeof value !== "string") value = ""+value; + + if(value === this.serialized) return; + + // Sanitize value before setting it + var sanitized = this.sanitize(value); + + if(this.input.value === sanitized) { + return; + } + + this.input.value = sanitized; + + // If using SCEditor, update the WYSIWYG + if(this.sceditor_instance) { + this.sceditor_instance.val(sanitized); + } + else if(this.epiceditor) { + this.epiceditor.importFile(null,sanitized); + } + else if(this.ace_editor) { + this.ace_editor.setValue(sanitized); + } + + var changed = from_template || this.getValue() !== value; + + this.refreshValue(); + + if(initial) this.is_dirty = false; + else if(this.jsoneditor.options.show_errors === "change") this.is_dirty = true; + + // Bubble this setValue to parents if the value changed + this.onChange(changed); + }, + getNumColumns: function() { + var min = Math.ceil(Math.max(this.getTitle().length,this.schema.maxLength||0,this.schema.minLength||0)/5); + var num; + + if(this.input_type === 'textarea') num = 6; + else if(['text','email'].indexOf(this.input_type) >= 0) num = 4; + else num = 2; + + return Math.min(12,Math.max(min,num)); + }, + build: function() { + var self = this, i; + if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle()); + if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description); + + this.format = this.schema.format; + if(!this.format && this.schema.media && this.schema.media.type) { + this.format = this.schema.media.type.replace(/(^(application|text)\/(x-)?(script\.)?)|(-source$)/g,''); + } + if(!this.format && this.options.default_format) { + this.format = this.options.default_format; + } + if(this.options.format) { + this.format = this.options.format; + } + + // Specific format + if(this.format) { + // Text Area + if(this.format === 'textarea') { + this.input_type = 'textarea'; + this.input = this.theme.getTextareaInput(); + } + // Range Input + else if(this.format === 'range') { + this.input_type = 'range'; + var min = this.schema.minimum || 0; + var max = this.schema.maximum || Math.max(100,min+1); + var step = 1; + if(this.schema.multipleOf) { + if(min%this.schema.multipleOf) min = Math.ceil(min/this.schema.multipleOf)*this.schema.multipleOf; + if(max%this.schema.multipleOf) max = Math.floor(max/this.schema.multipleOf)*this.schema.multipleOf; + step = this.schema.multipleOf; + } + + this.input = this.theme.getRangeInput(min,max,step); + } + // Source Code + else if([ + 'actionscript', + 'batchfile', + 'bbcode', + 'c', + 'c++', + 'cpp', + 'coffee', + 'csharp', + 'css', + 'dart', + 'django', + 'ejs', + 'erlang', + 'golang', + 'handlebars', + 'haskell', + 'haxe', + 'html', + 'ini', + 'jade', + 'java', + 'javascript', + 'json', + 'less', + 'lisp', + 'lua', + 'makefile', + 'markdown', + 'matlab', + 'mysql', + 'objectivec', + 'pascal', + 'perl', + 'pgsql', + 'php', + 'python', + 'r', + 'ruby', + 'sass', + 'scala', + 'scss', + 'smarty', + 'sql', + 'stylus', + 'svg', + 'twig', + 'vbscript', + 'xml', + 'yaml' + ].indexOf(this.format) >= 0 + ) { + this.input_type = this.format; + this.source_code = true; + + this.input = this.theme.getTextareaInput(); + } + // HTML5 Input type + else { + this.input_type = this.format; + this.input = this.theme.getFormInputField(this.input_type); + } + } + // Normal text input + else { + this.input_type = 'text'; + this.input = this.theme.getFormInputField(this.input_type); + } + + // minLength, maxLength, and pattern + if(typeof this.schema.maxLength !== "undefined") this.input.setAttribute('maxlength',this.schema.maxLength); + if(typeof this.schema.pattern !== "undefined") this.input.setAttribute('pattern',this.schema.pattern); + else if(typeof this.schema.minLength !== "undefined") this.input.setAttribute('pattern','.{'+this.schema.minLength+',}'); + + if(this.options.compact) this.container.setAttribute('class',this.container.getAttribute('class')+' compact'); + + if(this.schema.readOnly || this.schema.readonly || this.schema.template) { + this.always_disabled = true; + this.input.disabled = true; + } + + this.input + .addEventListener('change',function(e) { + e.preventDefault(); + e.stopPropagation(); + + // Don't allow changing if this field is a template + if(self.schema.template) { + this.value = self.value; + return; + } + + var val = this.value; + + // sanitize value + var sanitized = self.sanitize(val); + if(val !== sanitized) { + this.value = sanitized; + } + + self.is_dirty = true; + + self.refreshValue(); + self.onChange(true); + }); + + if(this.format) this.input.setAttribute('data-schemaformat',this.format); + + this.control = this.theme.getFormControl(this.label, this.input, this.description); + this.container.appendChild(this.control); + + // Any special formatting that needs to happen after the input is added to the dom + window.requestAnimationFrame(function() { + // Skip in case the input is only a temporary editor, + // otherwise, in the case of an ace_editor creation, + // it will generate an error trying to append it to the missing parentNode + if(self.input.parentNode) self.afterInputReady(); + }); + + // Compile and store the template + if(this.schema.template) { + this.template = this.jsoneditor.compileTemplate(this.schema.template, this.template_engine); + this.refreshValue(); + } + else { + this.refreshValue(); + } + }, + enable: function() { + if(!this.always_disabled) { + this.input.disabled = false; + // TODO: WYSIWYG and Markdown editors + } + this._super(); + }, + disable: function() { + this.input.disabled = true; + // TODO: WYSIWYG and Markdown editors + this._super(); + }, + afterInputReady: function() { + var self = this, options; + + // Code editor + if(this.source_code) { + // WYSIWYG html and bbcode editor + if(this.options.wysiwyg && + ['html','bbcode'].indexOf(this.input_type) >= 0 && + window.jQuery && window.jQuery.fn && window.jQuery.fn.sceditor + ) { + options = $extend({},{ + plugins: self.input_type==='html'? 'xhtml' : 'bbcode', + emoticonsEnabled: false, + width: '100%', + height: 300 + },JSONEditor.plugins.sceditor,self.options.sceditor_options||{}); + + window.jQuery(self.input).sceditor(options); + + self.sceditor_instance = window.jQuery(self.input).sceditor('instance'); + + self.sceditor_instance.blur(function() { + // Get editor's value + var val = window.jQuery("<div>"+self.sceditor_instance.val()+"</div>"); + // Remove sceditor spans/divs + window.jQuery('#sceditor-start-marker,#sceditor-end-marker,.sceditor-nlf',val).remove(); + // Set the value and update + self.input.value = val.html(); + self.value = self.input.value; + self.is_dirty = true; + self.onChange(true); + }); + } + // EpicEditor for markdown (if it's loaded) + else if (this.input_type === 'markdown' && window.EpicEditor) { + this.epiceditor_container = document.createElement('div'); + this.input.parentNode.insertBefore(this.epiceditor_container,this.input); + this.input.style.display = 'none'; + + options = $extend({},JSONEditor.plugins.epiceditor,{ + container: this.epiceditor_container, + clientSideStorage: false + }); + + this.epiceditor = new window.EpicEditor(options).load(); + + this.epiceditor.importFile(null,this.getValue()); + + this.epiceditor.on('update',function() { + var val = self.epiceditor.exportFile(); + self.input.value = val; + self.value = val; + self.is_dirty = true; + self.onChange(true); + }); + } + // ACE editor for everything else + else if(window.ace) { + var mode = this.input_type; + // aliases for c/cpp + if(mode === 'cpp' || mode === 'c++' || mode === 'c') { + mode = 'c_cpp'; + } + + this.ace_container = document.createElement('div'); + this.ace_container.style.width = '100%'; + this.ace_container.style.position = 'relative'; + this.ace_container.style.height = '400px'; + this.input.parentNode.insertBefore(this.ace_container,this.input); + this.input.style.display = 'none'; + this.ace_editor = window.ace.edit(this.ace_container); + + this.ace_editor.setValue(this.getValue()); + + // The theme + if(JSONEditor.plugins.ace.theme) this.ace_editor.setTheme('ace/theme/'+JSONEditor.plugins.ace.theme); + // The mode + mode = window.ace.require("ace/mode/"+mode); + if(mode) this.ace_editor.getSession().setMode(new mode.Mode()); + + // Listen for changes + this.ace_editor.on('change',function() { + var val = self.ace_editor.getValue(); + self.input.value = val; + self.refreshValue(); + self.is_dirty = true; + self.onChange(true); + }); + } + } + + self.theme.afterInputReady(self.input); + }, + refreshValue: function() { + this.value = this.input.value; + if(typeof this.value !== "string") this.value = ''; + this.serialized = this.value; + }, + destroy: function() { + // If using SCEditor, destroy the editor instance + if(this.sceditor_instance) { + this.sceditor_instance.destroy(); + } + else if(this.epiceditor) { + this.epiceditor.unload(); + } + else if(this.ace_editor) { + this.ace_editor.destroy(); + } + + + this.template = null; + if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); + if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label); + if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description); + + this._super(); + }, + /** + * This is overridden in derivative editors + */ + sanitize: function(value) { + return value; + }, + /** + * Re-calculates the value if needed + */ + onWatchedFieldChange: function() { + var self = this, vars, j; + + // If this editor needs to be rendered by a macro template + if(this.template) { + vars = this.getWatchedFieldValues(); + this.setValue(this.template(vars),false,true); + } + + this._super(); + }, + showValidationErrors: function(errors) { + var self = this; + + if(this.jsoneditor.options.show_errors === "always") {} + else if(!this.is_dirty && this.previous_error_setting===this.jsoneditor.options.show_errors) return; + + this.previous_error_setting = this.jsoneditor.options.show_errors; + + var messages = []; + $each(errors,function(i,error) { + if(error.path === self.path) { + messages.push(error.message); + } + }); + + if(messages.length) { + this.theme.addInputError(this.input, messages.join('. ')+'.'); + } + else { + this.theme.removeInputError(this.input); + } + } + }); + + JSONEditor.defaults.editors.number = JSONEditor.defaults.editors.string.extend({ + sanitize: function(value) { + return (value+"").replace(/[^0-9\.\-eE]/g,''); + }, + getNumColumns: function() { + return 2; + }, + getValue: function() { + return this.value*1; + } + }); + + JSONEditor.defaults.editors.integer = JSONEditor.defaults.editors.number.extend({ + sanitize: function(value) { + value = value + ""; + return value.replace(/[^0-9\-]/g,''); + }, + getNumColumns: function() { + return 2; + } + }); + + JSONEditor.defaults.editors.object = JSONEditor.AbstractEditor.extend({ + getDefault: function() { + return $extend({},this.schema.default || {}); + }, + getChildEditors: function() { + return this.editors; + }, + register: function() { + this._super(); + if(this.editors) { + for(var i in this.editors) { + if(!this.editors.hasOwnProperty(i)) continue; + this.editors[i].register(); + } + } + }, + unregister: function() { + this._super(); + if(this.editors) { + for(var i in this.editors) { + if(!this.editors.hasOwnProperty(i)) continue; + this.editors[i].unregister(); + } + } + }, + getNumColumns: function() { + return Math.max(Math.min(12,this.maxwidth),3); + }, + enable: function() { + if(this.editjson_button) this.editjson_button.disabled = false; + if(this.addproperty_button) this.addproperty_button.disabled = false; + + this._super(); + if(this.editors) { + for(var i in this.editors) { + if(!this.editors.hasOwnProperty(i)) continue; + this.editors[i].enable(); + } + } + }, + disable: function() { + if(this.editjson_button) this.editjson_button.disabled = true; + if(this.addproperty_button) this.addproperty_button.disabled = true; + this.hideEditJSON(); + + this._super(); + if(this.editors) { + for(var i in this.editors) { + if(!this.editors.hasOwnProperty(i)) continue; + this.editors[i].disable(); + } + } + }, + layoutEditors: function() { + var self = this, i, j; + + if(!this.row_container) return; + + // Sort editors by propertyOrder + this.property_order = Object.keys(this.editors); + this.property_order = this.property_order.sort(function(a,b) { + var ordera = self.editors[a].schema.propertyOrder; + var orderb = self.editors[b].schema.propertyOrder; + if(typeof ordera !== "number") ordera = 1000; + if(typeof orderb !== "number") orderb = 1000; + + return ordera - orderb; + }); + + var container; + + if(this.format === 'grid') { + var rows = []; + $each(this.property_order, function(j,key) { + var editor = self.editors[key]; + if(editor.property_removed) return; + var found = false; + var width = editor.options.hidden? 0 : editor.getNumColumns(); + var height = editor.options.hidden? 0 : editor.container.offsetHeight; + // See if the editor will fit in any of the existing rows first + for(var i=0; i<rows.length; i++) { + // If the editor will fit in the row horizontally + if(rows[i].width + width <= 12) { + // If the editor is close to the other elements in height + // i.e. Don't put a really tall editor in an otherwise short row or vice versa + if(!height || (rows[i].minh*0.5 < height && rows[i].maxh*2 > height)) { + found = i; + } + } + } + + // If there isn't a spot in any of the existing rows, start a new row + if(found === false) { + rows.push({ + width: 0, + minh: 999999, + maxh: 0, + editors: [] + }); + found = rows.length-1; + } + + rows[found].editors.push({ + key: key, + //edito
<TRUNCATED>
