http://git-wip-us.apache.org/repos/asf/ambari/blob/fb86fb3b/ambari-web/api-docs/lib/jsoneditor.js ---------------------------------------------------------------------- diff --git a/ambari-web/api-docs/lib/jsoneditor.js b/ambari-web/api-docs/lib/jsoneditor.js new file mode 100644 index 0000000..ba48b06 --- /dev/null +++ b/ambari-web/api-docs/lib/jsoneditor.js @@ -0,0 +1,7287 @@ +/*! JSON Editor v0.7.22 - JSON Schema -> HTML Editor + * By Jeremy Dorn - https://github.com/jdorn/json-editor/ + * Released under the MIT license + * + * Date: 2015-08-12 + */ + +/** + * 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]'; + }; + } +}()); +/** + * 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); + 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() { + 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 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 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]) + }); + } + } + } + + /* + * 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]) + }); + } + } + // [TODO] find why defaultProperties is not functioning. + // 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.call(self,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 = 'btn-xs btn-info 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) { + // If the header has children, only update the text node's value + if(this.header.children.length) { + for(var i=0; i<this.header.childNodes.length; i++) { + if(this.header.childNodes[i].nodeType===3) { + this.header.childNodes[i].nodeValue = this.getHeaderText(); + break; + } + } + } + // Otherwise, just update the entire node + else { + 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; + }, + isRequired: function() { + if(typeof this.schema.required === "boolean") return this.schema.required; + else if(this.parent && this.parent.schema && Array.isArray(this.parent.schema.required)) return this.parent.schema.required.indexOf(this.key) > -1; + else if(this.jsoneditor.options.required_by_default) return true; + else return false; + }, + 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 || typeof value === 'undefined') 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; + + if(this.adjust_height) this.adjust_height(this.input); + + // 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.className += ' compact'; + } + else { + if(this.options.input_width) this.input.style.width = this.options.input_width; + } + + 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.options.input_height) this.input.style.height = this.options.input_height; + if(this.options.expand_height) { + this.adjust_height = function(el) { + if(!el) return; + var i, ch=el.offsetHeight; + // Input too short + if(el.offsetHeight < el.scrollHeight) { + i=0; + while(el.offsetHeight < el.scrollHeight+3) { + if(i>100) break; + i++; + ch++; + el.style.height = ch+'px'; + } + } + else { + i=0; + while(el.offsetHeight >= el.scrollHeight+3) { + if(i>100) break; + i++; + ch--; + el.style.height = ch+'px'; + } + el.style.height = (ch+1)+'px'; + } + }; + + this.input.addEventListener('keyup',function(e) { + self.adjust_height(this); + }); + this.input.addEventListener('change',function(e) { + self.adjust_height(this); + }); + this.adjust_height(); + } + + 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(); + if(self.adjust_height) self.adjust_height(self.input); + }); + + // 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.options.grid_columns || 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, + //editor: editor, + width: width, + height: height + }); + rows[found].width += width; + rows[found].minh = Math.min(rows[found].minh,height); + rows[found].maxh = Math.max(rows[found].maxh,height); + }); + + // Make almost full rows width 12 + // Do this by increasing all editors' sizes proprotionately + // Any left over space goes to the biggest editor + // Don't touch rows with a width of 6 or less + for(i=0; i<rows.length; i++) { + if(rows[i].width < 12) { + var biggest = false; + var new_width = 0; + for(j=0; j<rows[i].editors.length; j++) { + if(biggest === false) biggest = j; + else if(rows[i].editors[j].width > rows[i].editors[biggest].width) biggest = j; + rows[i].editors[j].width *= 12/rows[i].width; + rows[i].editors[j].width = Math.floor(rows[i].editors[j].width); + new_width += rows[i].editors[j].width; + } + if(new_width < 12) rows[i].editors[biggest].width += 12-new_width; + rows[i].width = 12; + } + } + + // layout hasn't changed + if(this.layout === JSON.stringify(rows)) return false; + this.layout = JSON.stringify(rows); + + // Layout the form + container = document.createElement('div'); + for(i=0; i<rows.length; i++) { + var row = this.theme.getGridRow(); + container.appendChild(row); + for(j=0; j<rows[i].editors.length; j++) { + var key = rows[i].editors[j].key; + var editor = this.editors[key]; + + if(editor.options.hidden) editor.container.style.display = 'none'; + else this.theme.setGridColumnSize(editor.container,rows[i].editors[j].width); + row.appendChild(editor.container); + } + } + } + // Normal layout + else { + container = document.createElement('div'); + $each(this.property_order, function(i,key) { + var editor = self.editors[key]; + if(editor.property_removed) return; + var row = self.theme.getGridRow(); + container.appendChild(row); + + if(editor.options.hidden) editor.container.style.display = 'none'; + else self.theme.setGridColumnSize(editor.container,12); + row.appendChild(editor.container); + }); + } + this.row_container.innerHTML = ''; + this.row_container.appendChild(container); + }, + getPropertySchema: function(key) { + // Schema declared directly in properties + var schema = this.schema.properties[key] || {}; + schema = $extend({},schema); + var matched = this.schema.properties[key]? true : false; + + // Any matching patternProperties should be merged in + if(this.schema.patternProperties) { + for(var i in this.schema.patternProperties) { + if(!this.schema.patternProperties.hasOwnProperty(i)) continue; + var regex = new RegExp(i); + if(regex.test(key)) { + schema.allOf = schema.allOf || []; + schema.allOf.push(this.schema.patternProperties[i]); + matched = true; + } + } + } + + // Hasn't matched other rules, use additionalProperties schema + if(!matched && this.schema.additionalProperties && typeof this.schema.additionalProperties === "object") { + schema = $extend({},this.schema.additionalProperties); + } + + return schema; + }, + preBuild: function() { + this._super(); + + this.editors = {}; + this.cached_editors = {}; + var self = this; + + this.format = this.options.layout || this.options.object_layout || this.schema.format || this.jsoneditor.options.object_layout || 'normal'; + + this.schema.properties = this.schema.properties || {}; + + this.minwidth = 0; + this.maxwidth = 0; + + // If the object should be rendered as a table row + if(this.options.table_row) { + $each(this.schema.properties, function(key,schema) { + var editor = self.jsoneditor.getEditorClass(schema); + self.editors[key] = self.jsoneditor.createEditor(editor,{ + jsoneditor: self.jsoneditor, + schema: schema, + path: self.path+'.'+key, + parent: self, + compact: true, + required: true + }); + self.editors[key].preBuild(); + + var width = self.editors[key].options.hidden? 0 : (self.editors[key].options.grid_columns || self.editors[key].getNumColumns()); + + self.minwidth += width; + self.maxwidth += width; + }); + this.no_link_holder = true; + } + // If the object should be rendered as a table + else if(this.options.table) { + // TODO: table display format + throw "Not supported yet"; + } + // If the object should be rendered as a div + else { + this.defaultProperties = this.schema.defaultProperties || Object.keys(this.schema.properties); + + // Increase the grid width to account for padding + self.maxwidth += 1; + + $each(this.defaultProperties, function(i,key) { + self.addObjectProperty(key, true); + + if(self.editors[key]) { + self.minwidth = Math.max(self.minwidth,(self.editors[key].options.grid_columns || self.editors[key].getNumColumns())); + self.maxwidth += (self.editors[key].options.grid_columns || self.editors[key].getNumColumns()); + } + }); + } + + // 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; + }); + }, + build: function() { + var self = this; + + // If the object should be rendered as a table row + if(this.options.table_row) { + this.editor_holder = this.container; + $each(this.editors, function(key,editor) { + var holder = self.theme.getTableCell(); + self.editor_holder.appendChild(holder); + + editor.setContainer(holder); + editor.build(); + editor.postBuild(); + + if(self.editors[key].options.hidden) { + holder.style.display = 'none'; + } + if(self.editors[key].options.input_width) { + holder.style.width = self.editors[key].options.input_width; + } + }); + } + // If the object should be rendered as a table + else if(this.options.table) { + // TODO: table display format + throw "Not supported yet"; + } + // If the object should be rendered as a div + else { + this.header = document.createElement('span'); + this.header.textContent = this.getTitle(); + this.title = this.theme.getHeader(this.header); + this.container.appendChild(this.title); + this.container.style.position = 'relative'; + + // Edit JSON modal + this.editjson_holder = this.theme.getModal(); + this.editjson_textarea = this.theme.getTextareaInput(); + this.editjson_textarea.style.height = '170px'; + this.editjson_textarea.style.width = '300px'; + this.editjson_textarea.style.display = 'block'; + this.editjson_save = this.getButton('Save','save','Save'); + this.editjson_save.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + self.saveJSON(); + }); + this.editjson_cancel = this.getButton('Cancel','cancel','Cancel'); + this.editjson_cancel.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + self.hideEditJSON(); + }); + this.editjson_holder.appendChild(this.editjson_textarea); + this.editjson_holder.appendChild(this.editjson_save); + this.editjson_holder.appendChild(this.editjson_cancel); + + // Manage Properties modal + this.addproperty_holder = this.theme.getModal(); + this.addproperty_list = document.createElement('div'); + this.addproperty_list.style.width = '295px'; + this.addproperty_list.style.maxHeight = '160px'; + this.addproperty_list.style.padding = '5px 0'; + this.addproperty_list.style.overflowY = 'auto'; + this.addproperty_list.style.overflowX = 'hidden'; + this.addproperty_list.style.paddingLeft = '5px'; + this.addproperty_list.setAttribute('class', 'property-selector'); + this.addproperty_add = this.getButton('add','add','add'); + this.addproperty_input = this.theme.getFormInputField('text'); + this.addproperty_input.setAttribute('placeholder','Property name...'); + this.addproperty_input.style.width = '220px'; + this.addproperty_input.style.marginBottom = '0'; + this.addproperty_input.style.display = 'inline-block'; + this.addproperty_add.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + if(self.addproperty_input.value) { + if(self.editors[self.addproperty_input.value]) { + window.alert('there is already a property with that name'); + return; + } + + self.addObjectProperty(self.addproperty_input.value); + if(self.editors[self.addproperty_input.value]) { + self.editors[self.addproperty_input.value].disable(); + } + self.onChange(true); + } + }); + this.addproperty_holder.appendChild(this.addproperty_list); + this.addproperty_holder.appendChild(this.addproperty_input); + this.addproperty_holder.appendChild(this.addproperty_add); + var spacer = document.createElement('div'); + spacer.style.clear = 'both'; + this.addproperty_holder.appendChild(spacer); + + + // Description + if(this.schema.description) { + this.description = this.theme.getDescription(this.schema.description); + this.container.appendChild(this.description); + } + + // Validation error placeholder area + this.error_holder = document.createElement('div'); + this.container.appendChild(this.error_holder); + + // Container for child editor area + this.editor_holder = this.theme.getIndentedPanel(); + this.editor_holder.style.paddingBottom = '0'; + this.container.appendChild(this.editor_holder); + + // Container for rows of child editors + this.row_container = this.theme.getGridContainer(); + this.editor_holder.appendChild(this.row_container); + + $each(this.editors, function(key,editor) { + var holder = self.theme.getGridColumn(); + self.row_container.appendChild(holder); + + editor.setContainer(holder); + editor.build(); + editor.postBuild(); + }); + + // Control buttons + this.title_controls = this.theme.getHeaderButtonHolder(); + this.editjson_controls = this.theme.getHeaderButtonHolder(); + this.addproperty_controls = this.theme.getHeaderButtonHolder(); + this.title.appendChild(this.title_controls); + this.title.appendChild(this.editjson_controls); + this.title.appendChild(this.addproperty_controls); + + // Show/Hide button + this.collapsed = false; + this.toggle_button = this.getButton('','collapse','Collapse'); + this.title_controls.appendChild(this.toggle_button); + this.toggle_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + if(self.collapsed) { + self.editor_holder.style.display = ''; + self.collapsed = false; + self.setButtonText(self.toggle_button,'','collapse','Collapse'); + } + else { + self.editor_holder.style.display = 'none'; + self.collapsed = true; + self.setButtonText(self.toggle_button,'','expand','Expand'); + } + }); + + // If it should start collapsed + if(this.options.collapsed) { + $trigger(this.toggle_button,'click'); + } + + // Collapse button disabled + if(this.schema.options && typeof this.schema.options.disable_collapse !== "undefined") { + if(this.schema.options.disable_collapse) this.toggle_button.style.display = 'none'; + } + else if(this.jsoneditor.options.disable_collapse) { + this.toggle_button.style.display = 'none'; + } + + // Edit JSON Button + this.editjson_button = this.getButton('JSON','edit','Edit JSON'); + this.editjson_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + self.toggleEditJSON(); + }); + this.editjson_controls.appendChild(this.editjson_button); + this.editjson_controls.appendChild(this.editjson_holder); + + // Edit JSON Buttton disabled + if(this.schema.options && typeof this.schema.options.disable_edit_json !== "undefined") { + if(this.schema.options.disable_edit_json) this.editjson_button.style.display = 'none'; + } + else if(this.jsoneditor.options.disable_edit_json) { + this.editjson_button.style.display = 'none'; + } + + // Object Properties Button + this.addproperty_button = this.getButton('Properties','edit','Object Properties'); + this.addproperty_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + self.toggleAddProperty(); + }); + this.addproperty_controls.appendChild(this.addproperty_button); + this.addproperty_controls.appendChild(this.addproperty_holder); + this.refreshAddProperties(); + } + + // Fix table cell ordering + if(this.options.table_row) { + this.editor_holder = this.container; + $each(this.property_order,function(i,key) { + self.editor_holder.appendChild(self.editors[key].container); + }); + } + // Layout object editors in grid if needed + else { + // Initial layout + this.layoutEditors(); + // Do it again now that we know the approximate heights of elements + this.layoutEditors(); + } + }, + showEditJSON: function() { + if(!this.editjson_holder) return; + this.hideAddProperty(); + + // Position the form directly beneath the button + // TODO: edge detection + this.editjson_holder.style.left = this.editjson_button.offsetLeft+"px"; + this.editjson_holder.style.top = this.editjson_button.offsetTop + this.editjson_button.offsetHeight+"px"; + + // Start the textarea with the current value + this.editjson_textarea.value = JSON.stringify(this.getValue(),null,2); + + // Disable the rest of the form while editing JSON + this.disable(); + + this.editjson_holder.style.display = ''; + this.editjson_button.disabled = false; + this.editing_json = true; + }, + hideEditJSON: function() { + if(!this.editjson_holder) return; + if(!this.editing_json) return; + + this.editjson_holder.style.display = 'none'; + this.enable(); + this.editing_json = false; + }, + saveJSON: function() { + if(!this.editjson_holder) return; + + try { + var json = JSON.parse(this.editjson_textarea.value); + this.setValue(json); + this.hideEditJSON(); + } + catch(e) { + window.alert('invalid JSON'); + throw e; + } + }, + toggleEditJSON: function() { + if(this.editing_json) this.hideEditJSON(); + else this.showEditJSON(); + }, + insertPropertyControlUsingPropertyOrder: function (property, control, container) { + var propertyOrder; + if (this.schema.properties[property]) + propertyOrder = this.schema.properties[property].propertyOrder; + if (typeof propertyOrder !== "number") propertyOrder = 1000; + control.propertyOrder = propertyOrder; + + for (var i = 0; i < container.childNodes.length; i++) { + var child = container.childNodes[i]; + if (control.propertyOrder < child.propertyOrder) { + this.addproper
<TRUNCATED>