/*! JSON Editor v0.7.24 - JSON Schema -> HTML Editor * By Jeremy Dorn - https://github.com/jdorn/json-editor/ * Released under the MIT license * * Date: 2016-02-11 */ /** * 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); // Validator options var validator_options = {}; if(self.options.custom_validators) { validator_options.custom_validators = self.options.custom_validators; } self.validator = new JSONEditor.Validator(self,null,validator_options); // Create the root editor var editor_class = self.getEditorClass(self.schema); self.root = self.createEditor(editor_class, { jsoneditor: self, schema: self.schema, required: true, container: self.root_container }); self.root.preBuild(); self.root.build(); self.root.postBuild(); // Starting data if(self.options.startval) self.root.setValue(self.options.startval); self.validation_results = self.validator.validate(self.root.getValue()); self.root.showValidationErrors(self.validation_results); self.ready = true; // Fire ready event asynchronously window.requestAnimationFrame(function() { if(!self.ready) return; self.validation_results = self.validator.validate(self.root.getValue()); self.root.showValidationErrors(self.validation_results); self.trigger('ready'); self.trigger('change'); }); }); }, getValue: function() { if(!this.ready) throw "JSON Editor not ready yet. Listen for 'ready' event before getting the value"; return this.root.getValue(); }, setValue: function(value) { if(!this.ready) throw "JSON Editor not ready yet. Listen for 'ready' event before setting the value"; this.root.setValue(value); return this; }, validate: function(value) { if(!this.ready) throw "JSON Editor not ready yet. Listen for 'ready' event before validating"; // Custom value if(arguments.length === 1) { return this.validator.validate(value); } // Current value (use cached result) else { return this.validation_results; } }, destroy: function() { if(this.destroyed) return; if(!this.ready) return; this.schema = null; this.options = null; this.root.destroy(); this.root = null; this.root_container = null; this.validator = null; this.validation_results = null; this.theme = null; this.iconlib = null; this.template = null; this.__data = null; this.ready = false; this.element.innerHTML = ''; this.destroyed = true; }, on: function(event, callback) { this.callbacks = this.callbacks || {}; this.callbacks[event] = this.callbacks[event] || []; this.callbacks[event].push(callback); return this; }, off: function(event, callback) { // Specific callback if(event && callback) { this.callbacks = this.callbacks || {}; this.callbacks[event] = this.callbacks[event] || []; var newcallbacks = []; for(var i=0; i<this.callbacks[event].length; i++) { if(this.callbacks[event][i]===callback) continue; newcallbacks.push(this.callbacks[event][i]); } this.callbacks[event] = newcallbacks; } // All callbacks for a specific event else if(event) { this.callbacks = this.callbacks || {}; this.callbacks[event] = []; } // All callbacks for all events else { this.callbacks = {}; } return this; }, trigger: function(event) { if(this.callbacks && this.callbacks[event] && this.callbacks[event].length) { for(var i=0; i<this.callbacks[event].length; i++) { this.callbacks[event][i](); } } return this; }, setOption: function(option, value) { if(option === "show_errors") { this.options.show_errors = value; this.onChange(); } // Only the `show_errors` option is supported for now else { throw "Option "+option+" must be set during instantiation and cannot be changed later"; } return this; }, getEditorClass: function(schema) { var classname; schema = this.expandSchema(schema); $each(JSONEditor.defaults.resolvers,function(i,resolver) { var tmp = resolver(schema); if(tmp) { if(JSONEditor.defaults.editors[tmp]) { classname = tmp; return false; } } }); if(!classname) throw "Unknown editor for schema "+JSON.stringify(schema); if(!JSONEditor.defaults.editors[classname]) throw "Unknown editor "+classname; return JSONEditor.defaults.editors[classname]; }, createEditor: function(editor_class, options) { options = $extend({},editor_class.options||{},options); return new editor_class(options); }, onChange: function() { if(!this.ready) return; if(this.firing_change) return; this.firing_change = true; var self = this; window.requestAnimationFrame(function() { self.firing_change = false; if(!self.ready) return; // Validate and cache results self.validation_results = self.validator.validate(self.root.getValue()); if(self.options.show_errors !== "never") { self.root.showValidationErrors(self.validation_results); } else { self.root.showValidationErrors([]); } // Fire change event self.trigger('change'); }); return this; }, compileTemplate: function(template, name) { name = name || JSONEditor.defaults.template; var engine; // Specifying a preset engine if(typeof name === 'string') { if(!JSONEditor.defaults.templates[name]) throw "Unknown template engine "+name; engine = JSONEditor.defaults.templates[name](); if(!engine) throw "Template engine "+name+" missing required library."; } // Specifying a custom engine else { engine = name; } if(!engine) throw "No template engine set"; if(!engine.compile) throw "Invalid template engine set"; return engine.compile(template); }, _data: function(el,key,value) { // Setting data if(arguments.length === 3) { var uuid; if(el.hasAttribute('data-jsoneditor-'+key)) { uuid = el.getAttribute('data-jsoneditor-'+key); } else { uuid = this.uuid++; el.setAttribute('data-jsoneditor-'+key,uuid); } this.__data[uuid] = value; } // Getting data else { // No data stored if(!el.hasAttribute('data-jsoneditor-'+key)) return null; return this.__data[el.getAttribute('data-jsoneditor-'+key)]; } }, registerEditor: function(editor) { this.editors = this.editors || {}; this.editors[editor.path] = editor; return this; }, unregisterEditor: function(editor) { this.editors = this.editors || {}; this.editors[editor.path] = null; return this; }, getEditor: function(path) { if(!this.editors) return; return this.editors[path]; }, watch: function(path,callback) { this.watchlist = this.watchlist || {}; this.watchlist[path] = this.watchlist[path] || []; this.watchlist[path].push(callback); return this; }, unwatch: function(path,callback) { if(!this.watchlist || !this.watchlist[path]) return this; // If removing all callbacks for a path if(!callback) { this.watchlist[path] = null; return this; } var newlist = []; for(var i=0; i<this.watchlist[path].length; i++) { if(this.watchlist[path][i] === callback) continue; else newlist.push(this.watchlist[path][i]); } this.watchlist[path] = newlist.length? newlist : null; return this; }, notifyWatchers: function(path) { if(!this.watchlist || !this.watchlist[path]) return this; for(var i=0; i<this.watchlist[path].length; i++) { this.watchlist[path][i](); } }, isEnabled: function() { return !this.root || this.root.isEnabled(); }, enable: function() { this.root.enable(); }, disable: function() { this.root.disable(); }, _getDefinitions: function(schema,path) { path = path || '#/definitions/'; if(schema.definitions) { for(var i in schema.definitions) { if(!schema.definitions.hasOwnProperty(i)) continue; this.refs[path+i] = schema.definitions[i]; if(schema.definitions[i].definitions) { this._getDefinitions(schema.definitions[i],path+i+'/definitions/'); } } } }, _getExternalRefs: function(schema) { var refs = {}; var merge_refs = function(newrefs) { for(var i in newrefs) { if(newrefs.hasOwnProperty(i)) { refs[i] = true; } } }; if(schema.$ref && typeof schema.$ref !== "object" && schema.$ref.substr(0,1) !== "#" && !this.refs[schema.$ref]) { refs[schema.$ref] = true; } for(var i in schema) { if(!schema.hasOwnProperty(i)) continue; if(schema[i] && typeof schema[i] === "object" && Array.isArray(schema[i])) { for(var j=0; j<schema[i].length; j++) { if(typeof schema[i][j]==="object") { merge_refs(this._getExternalRefs(schema[i][j])); } } } else if(schema[i] && typeof schema[i] === "object") { merge_refs(this._getExternalRefs(schema[i])); } } return refs; }, _loadExternalRefs: function(schema, callback) { var self = this; var refs = this._getExternalRefs(schema); var done = 0, waiting = 0, callback_fired = false; $each(refs,function(url) { if(self.refs[url]) return; if(!self.options.ajax) throw "Must set ajax option to true to load external ref "+url; self.refs[url] = 'loading'; waiting++; var r = new XMLHttpRequest(); r.open("GET", url, true); r.onreadystatechange = function () { if (r.readyState != 4) return; // Request succeeded if(r.status === 200) { var response; try { response = JSON.parse(r.responseText); } catch(e) { window.console.log(e); throw "Failed to parse external ref "+url; } if(!response || typeof response !== "object") throw "External ref does not contain a valid schema - "+url; self.refs[url] = response; self._loadExternalRefs(response,function() { done++; if(done >= waiting && !callback_fired) { callback_fired = true; callback(); } }); } // Request failed else { window.console.log(r); throw "Failed to fetch ref via ajax- "+url; } }; r.send(); }); if(!waiting) { callback(); } }, expandRefs: function(schema) { schema = $extend({},schema); while (schema.$ref) { var ref = schema.$ref; delete schema.$ref; if(!this.refs[ref]) ref = decodeURIComponent(ref); schema = this.extendSchemas(schema,this.refs[ref]); } return schema; }, expandSchema: function(schema) { var self = this; var extended = $extend({},schema); var i; // Version 3 `type` if(typeof schema.type === 'object') { // Array of types if(Array.isArray(schema.type)) { $each(schema.type, function(key,value) { // Schema if(typeof value === 'object') { schema.type[key] = self.expandSchema(value); } }); } // Schema else { schema.type = self.expandSchema(schema.type); } } // Version 3 `disallow` if(typeof schema.disallow === 'object') { // Array of types if(Array.isArray(schema.disallow)) { $each(schema.disallow, function(key,value) { // Schema if(typeof value === 'object') { schema.disallow[key] = self.expandSchema(value); } }); } // Schema else { schema.disallow = self.expandSchema(schema.disallow); } } // Version 4 `anyOf` if(schema.anyOf) { $each(schema.anyOf, function(key,value) { schema.anyOf[key] = self.expandSchema(value); }); } // Version 4 `dependencies` (schema dependencies) if(schema.dependencies) { $each(schema.dependencies,function(key,value) { if(typeof value === "object" && !(Array.isArray(value))) { schema.dependencies[key] = self.expandSchema(value); } }); } // Version 4 `not` if(schema.not) { schema.not = this.expandSchema(schema.not); } // allOf schemas should be merged into the parent if(schema.allOf) { for(i=0; i<schema.allOf.length; i++) { extended = this.extendSchemas(extended,this.expandSchema(schema.allOf[i])); } delete extended.allOf; } // extends schemas should be merged into parent if(schema["extends"]) { // If extends is a schema if(!(Array.isArray(schema["extends"]))) { extended = this.extendSchemas(extended,this.expandSchema(schema["extends"])); } // If extends is an array of schemas else { for(i=0; i<schema["extends"].length; i++) { extended = this.extendSchemas(extended,this.expandSchema(schema["extends"][i])); } } delete extended["extends"]; } // parent should be merged into oneOf schemas if(schema.oneOf) { var tmp = $extend({},extended); delete tmp.oneOf; for(i=0; i<schema.oneOf.length; i++) { extended.oneOf[i] = this.extendSchemas(this.expandSchema(schema.oneOf[i]),tmp); } } return this.expandRefs(extended); }, extendSchemas: function(obj1, obj2) { obj1 = $extend({},obj1); obj2 = $extend({},obj2); var self = this; var extended = {}; $each(obj1, function(prop,val) { // If this key is also defined in obj2, merge them if(typeof obj2[prop] !== "undefined") { // Required 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,options) { this.jsoneditor = jsoneditor; this.schema = schema || this.jsoneditor.schema; this.options = options || {}; this.translate = this.jsoneditor.translate || JSONEditor.defaults.translate; }, validate: function(value) { return this._validateSchema(this.schema, value); }, _validateSchema: function(schema,value,path) { var self = this; var errors = []; var valid, i, j; var stringified = JSON.stringify(value); path = path || 'root'; // Work on a copy of the schema schema = $extend({},this.jsoneditor.expandRefs(schema)); /* * Type Agnostic Validation */ // Version 3 `required` if(schema.required && schema.required === true) { if(typeof value === "undefined") { errors.push({ path: path, property: 'required', message: this.translate("error_notset") }); // Can't do any more validation at this point return errors; } } // Value not defined else if(typeof value === "undefined") { // If required_by_default is set, all fields are required if(this.jsoneditor.options.required_by_default) { errors.push({ path: path, property: 'required', message: this.translate("error_notset") }); } // Not required, no further validation needed else { return errors; } } // `enum` if(schema["enum"]) { valid = false; for(i=0; i<schema["enum"].length; i++) { if(stringified === JSON.stringify(schema["enum"][i])) valid = true; } if(!valid) { errors.push({ path: path, property: 'enum', message: this.translate("error_enum") }); } } // `extends` (version 3) if(schema["extends"]) { for(i=0; i<schema["extends"].length; i++) { errors = errors.concat(this._validateSchema(schema["extends"][i],value,path)); } } // `allOf` if(schema.allOf) { for(i=0; i<schema.allOf.length; i++) { errors = errors.concat(this._validateSchema(schema.allOf[i],value,path)); } } // `anyOf` if(schema.anyOf) { valid = false; for(i=0; i<schema.anyOf.length; i++) { if(!this._validateSchema(schema.anyOf[i],value,path).length) { valid = true; break; } } if(!valid) { errors.push({ path: path, property: 'anyOf', message: this.translate('error_anyOf') }); } } // `oneOf` if(schema.oneOf) { valid = 0; var oneof_errors = []; for(i=0; i<schema.oneOf.length; i++) { // Set the error paths to be path.oneOf[i].rest.of.path var tmp = this._validateSchema(schema.oneOf[i],value,path); if(!tmp.length) { valid++; } for(j=0; j<tmp.length; j++) { tmp[j].path = path+'.oneOf['+i+']'+tmp[j].path.substr(path.length); } oneof_errors = oneof_errors.concat(tmp); } if(valid !== 1) { errors.push({ path: path, property: 'oneOf', message: this.translate('error_oneOf', [valid]) }); errors = errors.concat(oneof_errors); } } // `not` if(schema.not) { if(!this._validateSchema(schema.not,value,path).length) { errors.push({ path: path, property: 'not', message: this.translate('error_not') }); } } // `type` (both Version 3 and Version 4 support) if(schema.type) { // Union type if(Array.isArray(schema.type)) { valid = false; for(i=0;i<schema.type.length;i++) { if(this._checkType(schema.type[i], value)) { valid = true; break; } } if(!valid) { errors.push({ path: path, property: 'type', message: this.translate('error_type_union') }); } } // Simple type else { if(!this._checkType(schema.type, value)) { errors.push({ path: path, property: 'type', message: this.translate('error_type', [schema.type]) }); } } } // `disallow` (version 3) if(schema.disallow) { // Union type if(Array.isArray(schema.disallow)) { valid = true; for(i=0;i<schema.disallow.length;i++) { if(this._checkType(schema.disallow[i], value)) { valid = false; break; } } if(!valid) { errors.push({ path: path, property: 'disallow', message: this.translate('error_disallow_union') }); } } // Simple type else { if(this._checkType(schema.disallow, value)) { errors.push({ path: path, property: 'disallow', message: this.translate('error_disallow', [schema.disallow]) }); } } } /* * Type Specific Validation */ // Number Specific Validation if(typeof value === "number") { // `multipleOf` and `divisibleBy` if(schema.multipleOf || schema.divisibleBy) { var divisor = schema.multipleOf || schema.divisibleBy; // Vanilla JS, prone to floating point rounding errors (e.g. 1.14 / .01 == 113.99999) valid = (value/divisor === Math.floor(value/divisor)); // Use math.js is available if(window.math) { valid = window.math.mod(window.math.bignumber(value), window.math.bignumber(divisor)).equals(0); } // Use decimal.js is available else if(window.Decimal) { valid = (new window.Decimal(value)).mod(new window.Decimal(divisor)).equals(0); } if(!valid) { errors.push({ path: path, property: schema.multipleOf? 'multipleOf' : 'divisibleBy', message: this.translate('error_multipleOf', [divisor]) }); } } // `maximum` if(schema.hasOwnProperty('maximum')) { // Vanilla JS, prone to floating point rounding errors (e.g. .999999999999999 == 1) valid = schema.exclusiveMaximum? (value < schema.maximum) : (value <= schema.maximum); // Use math.js is available if(window.math) { valid = window.math[schema.exclusiveMaximum?'smaller':'smallerEq']( window.math.bignumber(value), window.math.bignumber(schema.maximum) ); } // Use Decimal.js if available else if(window.Decimal) { valid = (new window.Decimal(value))[schema.exclusiveMaximum?'lt':'lte'](new window.Decimal(schema.maximum)); } if(!valid) { errors.push({ path: path, property: 'maximum', message: this.translate( (schema.exclusiveMaximum?'error_maximum_excl':'error_maximum_incl'), [schema.maximum] ) }); } } // `minimum` if(schema.hasOwnProperty('minimum')) { // Vanilla JS, prone to floating point rounding errors (e.g. .999999999999999 == 1) valid = schema.exclusiveMinimum? (value > schema.minimum) : (value >= schema.minimum); // Use math.js is available if(window.math) { valid = window.math[schema.exclusiveMinimum?'larger':'largerEq']( window.math.bignumber(value), window.math.bignumber(schema.minimum) ); } // Use Decimal.js if available else if(window.Decimal) { valid = (new window.Decimal(value))[schema.exclusiveMinimum?'gt':'gte'](new window.Decimal(schema.minimum)); } if(!valid) { errors.push({ path: path, property: 'minimum', message: this.translate( (schema.exclusiveMinimum?'error_minimum_excl':'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', [schema.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 (global) $each(JSONEditor.defaults.custom_validators,function(i,validator) { errors = errors.concat(validator.call(self,schema,value,path)); }); // Custom type validation (instance specific) if(this.options.custom_validators) { $each(this.options.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.translate = this.jsoneditor.translate || JSONEditor.defaults.translate; 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) { // 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.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.addproperty_list.insertBefore(control, child); control = null; break; } } if (control) { this.addproperty_list.appendChild(control); } }, addPropertyCheckbox: function(key) { var self = this; var checkbox, label, labelText, control; checkbox = self.theme.getCheckbox(); checkbox.style.width = 'auto'; if (this.schema.properties[key] && this.schema.properties[key].title) labelText = this.schema.properties[key].title; else labelText = key; label = self.theme.getCheckboxLabel(labelText); control = self.theme.getFormControl(label,checkbox); control.style.paddingBottom = control.style.marginBottom = control.style.paddingTop = control.style.marginTop = 0; control.style.height = 'auto'; //control.style.overflowY = 'hidden'; this.insertPropertyControlUsingPropertyOrder(key, control, this.addproperty_list); checkbox.checked = key in this.editors; checkbox.addEventListener('change',function() { if(checkbox.checked) { self.addObjectProperty(key); } else { self.removeObjectProperty(key); } self.onChange(true); }); self.addproperty_checkboxes[key] = checkbox; return checkbox; }, showAddProperty: function() { if(!this.addproperty_holder) return; this.hideEditJSON(); // Position the form directly beneath the button // TODO: edge detection this.addproperty_holder.style.left = this.addproperty_button.offsetLeft+"px"; this.addproperty_holder.style.top = this.addproperty_button.offsetTop + this.addproperty_button.offsetHeight+"px"; // Disable the rest of the form while editing JSON this.disable(); this.adding_property = true; this.addproperty_button.disabled = false; this.addproperty_holder.style.display = ''; this.refreshAddProperties(); }, hideAddProperty: function() { if(!this.addproperty_holder) return; if(!this.adding_property) return; this.addproperty_holder.style.display = 'none'; this.enable(); this.adding_property = false; }, toggleAddProperty: function() { if(this.adding_property) this.hideAddProperty(); else this.showAddProperty(); }, removeObjectProperty: function(property) { if(this.editors[property]) { this.editors[property].unregister(); delete this.editors[property]; this.refreshValue(); this.layoutEditors(); } }, addObjectProperty: function(name, prebuild_only) { var self = this; // Property is already added if(this.editors[name]) return; // Property was added before and is cached if(this.cached_editors[name]) { this.editors[name] = this.cached_editors[name]; if(prebuild_only) return; this.editors[name].register(); } // New property else { if(!this.canHaveAdditionalProperties() && (!this.schema.properties || !this.schema.properties[name])) { return; } var schema = self.getPropertySchema(name); // Add the property var editor = self.jsoneditor.getEditorClass(schema); self.editors[name] = self.jsoneditor.createEditor(editor,{ jsoneditor: self.jsoneditor, schema: schema, path: self.path+'.'+name, parent: self }); self.editors[name].preBuild(); if(!prebuild_only) { var holder = self.theme.getChildEditorHolder(); self.editor_holder.appendChild(holder); self.editors[name].setContainer(holder); self.editors[name].build(); self.editors[name].postBuild(); } self.cached_editors[name] = self.editors[name]; } // If we're only prebuilding the editors, don't refresh values if(!prebuild_only) { self.refreshValue(); self.layoutEditors(); } }, onChildEditorChange: function(editor) { this.refreshValue(); this._super(editor); }, canHaveAdditionalProperties: function() { if (typeof this.schema.additionalProperties === "boolean") { return this.schema.additionalProperties; } return !this.jsoneditor.options.no_additional_properties; }, destroy: function() { $each(this.cached_editors, function(i,el) { el.destroy(); }); if(this.editor_holder) this.editor_holder.innerHTML = ''; if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); if(this.error_holder && this.error_holder.parentNode) this.error_holder.parentNode.removeChild(this.error_holder); this.editors = null; this.cached_editors = null; if(this.editor_holder && this.editor_holder.parentNode) this.editor_holder.parentNode.removeChild(this.editor_holder); this.editor_holder = null; this._super(); }, getValue: function() { var result = this._super(); if(this.jsoneditor.options.remove_empty_properties || this.options.remove_empty_properties) { for(var i in result) { if(result.hasOwnProperty(i)) { if(!result[i]) delete result[i]; } } } return result; }, refreshValue: function() { this.value = {}; var self = this; for(var i in this.editors) { if(!this.editors.hasOwnProperty(i)) continue; this.value[i] = this.editors[i].getValue(); } if(this.adding_property) this.refreshAddProperties(); }, refreshAddProperties: function() { if(this.options.disable_properties || (this.options.disable_properties !== false && this.jsoneditor.options.disable_properties)) { this.addproperty_controls.style.display = 'none'; return; } var can_add = false, can_remove = false, num_props = 0, i, show_modal = false; // Get number of editors for(i in this.editors) { if(!this.editors.hasOwnProperty(i)) continue; num_props++; } // Determine if we can add back removed properties can_add = this.canHaveAdditionalProperties() && !(typeof this.schema.maxProperties !== "undefined" && num_props >= this.schema.maxProperties); if(this.addproperty_checkboxes) { this.addproperty_list.innerHTML = ''; } this.addproperty_checkboxes = {}; // Check for which editors can't be removed or added back for(i in this.cached_editors) { if(!this.cached_editors.hasOwnProperty(i)) continue; this.addPropertyCheckbox(i); if(this.isRequired(this.cached_editors[i]) && i in this.editors) { this.addproperty_checkboxes[i].disabled = true; } if(typeof this.schema.minProperties !== "undefined" && num_props <= this.schema.minProperties) { this.addproperty_checkboxes[i].disabled = this.addproperty_checkboxes[i].checked; if(!this.addproperty_checkboxes[i].checked) show_modal = true; } else if(!(i in this.editors)) { if(!can_add && !this.schema.properties.hasOwnProperty(i)) { this.addproperty_checkboxes[i].disabled = true; } else { this.addproperty_checkboxes[i].disabled = false; show_modal = true; } } else { show_modal = true; can_remove = true; } } if(this.canHaveAdditionalProperties()) { show_modal = true; } // Additional addproperty checkboxes not tied to a current editor for(i in this.schema.properties) { if(!this.schema.properties.hasOwnProperty(i)) continue; if(this.cached_editors[i]) continue; show_modal = true; this.addPropertyCheckbox(i); } // If no editors can be added or removed, hide the modal button if(!show_modal) { this.hideAddProperty(); this.addproperty_controls.style.display = 'none'; } // If additional properties are disabled else if(!this.canHaveAdditionalProperties()) { this.addproperty_add.style.display = 'none'; this.addproperty_input.style.display = 'none'; } // If no new properties can be added else if(!can_add) { this.addproperty_add.disabled = true; } // If new properties can be added else { this.addproperty_add.disabled = false; } }, isRequired: function(editor) { if(typeof editor.schema.required === "boolean") return editor.schema.required; else if(Array.isArray(this.schema.required)) return this.schema.required.indexOf(editor.key) > -1; else if(this.jsoneditor.options.required_by_default) return true; else return false; }, setValue: function(value, initial) { var self = this; value = value || {}; if(typeof value !== "object" || Array.isArray(value)) value = {}; // First, set the values for all of the defined properties $each(this.cached_editors, function(i,editor) { // Value explicitly set if(typeof value[i] !== "undefined") { self.addObjectProperty(i); editor.setValue(value[i],initial); } // Otherwise, remove value unless this is the initial set or it's required else if(!initial && !self.isRequired(editor)) { self.removeObjectProperty(i); } // Otherwise, set the value to the default else { editor.setValue(editor.getDefault(),initial); } }); $each(value, function(i,val) { if(!self.cached_editors[i]) { self.addObjectProperty(i); if(self.editors[i]) self.editors[i].setValue(val,initial); } }); this.refreshValue(); this.layoutEditors(); this.onChange(); }, showValidationErrors: function(errors) { var self = this; // Get all the errors that pertain to this editor var my_errors = []; var other_errors = []; $each(errors, function(i,error) { if(error.path === self.path) { my_errors.push(error); } else { other_errors.push(error); } }); // Show errors for this editor if(this.error_holder) { if(my_errors.length) { var message = []; this.error_holder.innerHTML = ''; this.error_holder.style.display = ''; $each(my_errors, function(i,error) { self.error_holder.appendChild(self.theme.getErrorMessage(error.message)); }); } // Hide error area else { this.error_holder.style.display = 'none'; } } // Show error for the table row if this is inside a table if(this.options.table_row) { if(my_errors.length) { this.theme.addTableRowError(this.container); } else { this.theme.removeTableRowError(this.container); } } // Show errors for child editors $each(this.editors, function(i,editor) { editor.showValidationErrors(other_errors); }); } }); JSONEditor.defaults.editors.array = JSONEditor.AbstractEditor.extend({ getDefault: function() { return this.schema["default"] || []; }, register: function() { this._super(); if(this.rows) { for(var i=0; i<this.rows.length; i++) { this.rows[i].register(); } } }, unregister: function() { this._super(); if(this.rows) { for(var i=0; i<this.rows.length; i++) { this.rows[i].unregister(); } } }, getNumColumns: function() { var info = this.getItemInfo(0); // Tabs require extra horizontal space if(this.tabs_holder) { return Math.max(Math.min(12,info.width+2),4); } else { return info.width; } }, enable: function() { if(this.add_row_button) this.add_row_button.disabled = false; if(this.remove_all_rows_button) this.remove_all_rows_button.disabled = false; if(this.delete_last_row_button) this.delete_last_row_button.disabled = false; if(this.rows) { for(var i=0; i<this.rows.length; i++) { this.rows[i].enable(); if(this.rows[i].moveup_button) this.rows[i].moveup_button.disabled = false; if(this.rows[i].movedown_button) this.rows[i].movedown_button.disabled = false; if(this.rows[i].delete_button) this.rows[i].delete_button.disabled = false; } } this._super(); }, disable: function() { if(this.add_row_button) this.add_row_button.disabled = true; if(this.remove_all_rows_button) this.remove_all_rows_button.disabled = true; if(this.delete_last_row_button) this.delete_last_row_button.disabled = true; if(this.rows) { for(var i=0; i<this.rows.length; i++) { this.rows[i].disable(); if(this.rows[i].moveup_button) this.rows[i].moveup_button.disabled = true; if(this.rows[i].movedown_button) this.rows[i].movedown_button.disabled = true; if(this.rows[i].delete_button) this.rows[i].delete_button.disabled = true; } } this._super(); }, preBuild: function() { this._super(); this.rows = []; this.row_cache = []; this.hide_delete_buttons = this.options.disable_array_delete || this.jsoneditor.options.disable_array_delete; this.hide_move_buttons = this.options.disable_array_reorder || this.jsoneditor.options.disable_array_reorder; this.hide_add_button = this.options.disable_array_add || this.jsoneditor.options.disable_array_add; }, build: function() { var self = this; if(!this.options.compact) { this.header = document.createElement('span'); this.header.textContent = this.getTitle(); this.title = this.theme.getHeader(this.header); this.container.appendChild(this.title); this.title_controls = this.theme.getHeaderButtonHolder(); this.title.appendChild(this.title_controls); if(this.schema.description) { this.description = this.theme.getDescription(this.schema.description); this.container.appendChild(this.description); } this.error_holder = document.createElement('div'); this.container.appendChild(this.error_holder); if(this.schema.format === 'tabs') { this.controls = this.theme.getHeaderButtonHolder(); this.title.appendChild(this.controls); this.tabs_holder = this.theme.getTabHolder(); this.container.appendChild(this.tabs_holder); this.row_holder = this.theme.getTabContentHolder(this.tabs_holder); this.active_tab = null; } else { this.panel = this.theme.getIndentedPanel(); this.container.appendChild(this.panel); this.row_holder = document.createElement('div'); this.panel.appendChild(this.row_holder); this.controls = this.theme.getButtonHolder(); this.panel.appendChild(this.controls); } } else { this.panel = this.theme.getIndentedPanel(); this.container.appendChild(this.panel); this.controls = this.theme.getButtonHolder(); this.panel.appendChild(this.controls); this.row_holder = document.createElement('div'); this.panel.appendChild(this.row_holder); } // Add controls this.addControls(); }, onChildEditorChange: function(editor) { this.refreshValue(); this.refreshTabs(true); this._super(editor); }, getItemTitle: function() { if(!this.item_title) { if(this.schema.items && !Array.isArray(this.schema.items)) { var tmp = this.jsoneditor.expandRefs(this.schema.items); this.item_title = tmp.title || 'item'; } else { this.item_title = 'item'; } } return this.item_title; }, getItemSchema: function(i) { if(Array.isArray(this.schema.items)) { if(i >= this.schema.items.length) { if(this.schema.additionalItems===true) { return {}; } else if(this.schema.additionalItems) { return $extend({},this.schema.additionalItems); } } else { return $extend({},this.schema.items[i]); } } else if(this.schema.items) { return $extend({},this.schema.items); } else { return {}; } }, getItemInfo: function(i) { var schema = this.getItemSchema(i); // Check if it's cached this.item_info = this.item_info || {}; var stringified = JSON.stringify(schema); if(typeof this.item_info[stringified] !== "undefined") return this.item_info[stringified]; // Get the schema for this item schema = this.jsoneditor.expandRefs(schema); this.item_info[stringified] = { title: schema.title || "item", 'default': schema["default"], width: 12, child_editors: schema.properties || schema.items }; return this.item_info[stringified]; }, getElementEditor: function(i) { var item_info = this.getItemInfo(i); var schema = this.getItemSchema(i); schema = this.jsoneditor.expandRefs(schema); schema.title = item_info.title+' '+(i+1); var editor = this.jsoneditor.getEditorClass(schema); var holder; if(this.tabs_holder) { holder = this.theme.getTabContent(); } else if(item_info.child_editors) { holder = this.theme.getChildEditorHolder(); } else { holder = this.theme.getIndentedPanel(); } this.row_holder.appendChild(holder); var ret = this.jsoneditor.createEditor(editor,{ jsoneditor: this.jsoneditor, schema: schema, container: holder, path: this.path+'.'+i, parent: this, required: true }); ret.preBuild(); ret.build(); ret.postBuild(); if(!ret.title_controls) { ret.array_controls = this.theme.getButtonHolder(); holder.appendChild(ret.array_controls); } return ret; }, destroy: function() { this.empty(true); if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description); if(this.row_holder && this.row_holder.parentNode) this.row_holder.parentNode.removeChild(this.row_holder); if(this.controls && this.controls.parentNode) this.controls.parentNode.removeChild(this.controls); if(this.panel && this.panel.parentNode) this.panel.parentNode.removeChild(this.panel); this.rows = this.row_cache = this.title = this.description = this.row_holder = this.panel = this.controls = null; this._super(); }, empty: function(hard) { if(!this.rows) return; var self = this; $each(this.rows,function(i,row) { if(hard) { if(row.tab && row.tab.parentNode) row.tab.parentNode.removeChild(row.tab); self.destroyRow(row,true); self.row_cache[i] = null; } self.rows[i] = null; }); self.rows = []; if(hard) self.row_cache = []; }, destroyRow: function(row,hard) { var holder = row.container; if(hard) { row.destroy(); if(holder.parentNode) holder.parentNode.removeChild(holder); if(row.tab && row.tab.parentNode) row.tab.parentNode.removeChild(row.tab); } else { if(row.tab) row.tab.style.display = 'none'; holder.style.display = 'none'; row.unregister(); } }, getMax: function() { if((Array.isArray(this.schema.items)) && this.schema.additionalItems === false) { return Math.min(this.schema.items.length,this.schema.maxItems || Infinity); } else { return this.schema.maxItems || Infinity; } }, refreshTabs: function(refresh_headers) { var self = this; $each(this.rows, function(i,row) { if(!row.tab) return; if(refresh_headers) { row.tab_text.textContent = row.getHeaderText(); } else { if(row.tab === self.active_tab) { self.theme.markTabActive(row.tab); row.container.style.display = ''; } else { self.theme.markTabInactive(row.tab); row.container.style.display = 'none'; } } }); }, setValue: function(value, initial) { // Update the array's value, adding/removing rows when necessary value = value || []; if(!(Array.isArray(value))) value = [value]; var serialized = JSON.stringify(value); if(serialized === this.serialized) return; // Make sure value has between minItems and maxItems items in it if(this.schema.minItems) { while(value.length < this.schema.minItems) { value.push(this.getItemInfo(value.length)["default"]); } } if(this.getMax() && value.length > this.getMax()) { value = value.slice(0,this.getMax()); } var self = this; $each(value,function(i,val) { if(self.rows[i]) { // TODO: don't set the row's value if it hasn't changed self.rows[i].setValue(val,initial); } else if(self.row_cache[i]) { self.rows[i] = self.row_cache[i]; self.rows[i].setValue(val,initial); self.rows[i].container.style.display = ''; if(self.rows[i].tab) self.rows[i].tab.style.display = ''; self.rows[i].register(); } else { self.addRow(val,initial); } }); for(var j=value.length; j<self.rows.length; j++) { self.destroyRow(self.rows[j]); self.rows[j] = null; } self.rows = self.rows.slice(0,value.length); // Set the active tab var new_active_tab = null; $each(self.rows, function(i,row) { if(row.tab === self.active_tab) { new_active_tab = row.tab; return false; } }); if(!new_active_tab && self.rows.length) new_active_tab = self.rows[0].tab; self.active_tab = new_active_tab; self.refreshValue(initial); self.refreshTabs(true); self.refreshTabs(); self.onChange(); // TODO: sortable }, refreshValue: function(force) { var self = this; var oldi = this.value? this.value.length : 0; this.value = []; $each(this.rows,function(i,editor) { // Get the value for this editor self.value[i] = editor.getValue(); }); if(oldi !== this.value.length || force) { // If we currently have minItems items in the array var minItems = this.schema.minItems && this.schema.minItems >= this.rows.length; $each(this.rows,function(i,editor) { // Hide the move down button for the last row if(editor.movedown_button) { if(i === self.rows.length - 1) { editor.movedown_button.style.display = 'none'; } else { editor.movedown_button.style.display = ''; } } // Hide the delete button if we have minItems items if(editor.delete_button) { if(minItems) { editor.delete_button.style.display = 'none'; } else { editor.delete_button.style.display = ''; } } // Get the value for this editor self.value[i] = editor.getValue(); }); var controls_needed = false; if(!this.value.length) { this.delete_last_row_button.style.display = 'none'; this.remove_all_rows_button.style.display = 'none'; } else if(this.value.length === 1) { this.remove_all_rows_button.style.display = 'none'; // If there are minItems items in the array, hide the delete button beneath the rows if(minItems || this.hide_delete_buttons) { this.delete_last_row_button.style.display = 'none'; } else { this.delete_last_row_button.style.display = ''; controls_needed = true; } } else { // If there are minItems items in the array, hide the delete button beneath the rows if(minItems || this.hide_delete_buttons) { this.delete_last_row_button.style.display = 'none'; this.remove_all_rows_button.style.display = 'none'; } else { this.delete_last_row_button.style.display = ''; this.remove_all_rows_button.style.display = ''; controls_needed = true; } } // If there are maxItems in the array, hide the add button beneath the rows if((this.getMax() && this.getMax() <= this.rows.length) || this.hide_add_button){ this.add_row_button.style.display = 'none'; } else { this.add_row_button.style.display = ''; controls_needed = true; } if(!this.collapsed && controls_needed) { this.controls.style.display = 'inline-block'; } else { this.controls.style.display = 'none'; } } }, addRow: function(value, initial) { var self = this; var i = this.rows.length; self.rows[i] = this.getElementEditor(i); self.row_cache[i] = self.rows[i]; if(self.tabs_holder) { self.rows[i].tab_text = document.createElement('span'); self.rows[i].tab_text.textContent = self.rows[i].getHeaderText(); self.rows[i].tab = self.theme.getTab(self.rows[i].tab_text); self.rows[i].tab.addEventListener('click', function(e) { self.active_tab = self.rows[i].tab; self.refreshTabs(); e.preventDefault(); e.stopPropagation(); }); self.theme.addTab(self.tabs_holder, self.rows[i].tab); } var controls_holder = self.rows[i].title_controls || self.rows[i].array_controls; // Buttons to delete row, move row up, and move row down if(!self.hide_delete_buttons) { self.rows[i].delete_button = this.getButton(self.getItemTitle(),'delete',this.translate('button_delete_row_title',[self.getItemTitle()])); self.rows[i].delete_button.className += ' delete'; self.rows[i].delete_button.setAttribute('data-i',i); self.rows[i].delete_button.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); var i = this.getAttribute('data-i')*1; var value = self.getValue(); var newval = []; var new_active_tab = null; $each(value,function(j,row) { if(j===i) { // If the one we're deleting is the active tab if(self.rows[j].tab === self.active_tab) { // Make the next tab active if there is one // Note: the next tab is going to be the current tab after deletion if(self.rows[j+1]) new_active_tab = self.rows[j].tab; // Otherwise, make the previous tab active if there is one else if(j) new_active_tab = self.rows[j-1].tab; } return; // If this is the one we're deleting } newval.push(row); }); self.setValue(newval); if(new_active_tab) { self.active_tab = new_active_tab; self.refreshTabs(); } self.onChange(true); }); if(controls_holder) { controls_holder.appendChild(self.rows[i].delete_button); } } if(i && !self.hide_move_buttons) { self.rows[i].moveup_button = this.getButton('','moveup',this.translate('button_move_up_title')); self.rows[i].moveup_button.className += ' moveup'; self.rows[i].moveup_button.setAttribute('data-i',i); self.rows[i].moveup_button.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); var i = this.getAttribute('data-i')*1; if(i<=0) return; var rows = self.getValue(); var tmp = rows[i-1]; rows[i-1] = rows[i]; rows[i] = tmp; self.setValue(rows); self.active_tab = self.rows[i-1].tab; self.refreshTabs(); self.onChange(true); }); if(controls_holder) { controls_holder.appendChild(self.rows[i].moveup_button); } } if(!self.hide_move_buttons) { self.rows[i].movedown_button = this.getButton('','movedown',this.translate('button_move_down_title')); self.rows[i].movedown_button.className += ' movedown'; self.rows[i].movedown_button.setAttribute('data-i',i); self.rows[i].movedown_button.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); var i = this.getAttribute('data-i')*1; var rows = self.getValue(); if(i>=rows.length-1) return; var tmp = rows[i+1]; rows[i+1] = rows[i]; rows[i] = tmp; self.setValue(rows); self.active_tab = self.rows[i+1].tab; self.refreshTabs(); self.onChange(true); }); if(controls_holder) { controls_holder.appendChild(self.rows[i].movedown_button); } } if(value) self.rows[i].setValue(value, initial); self.refreshTabs(); }, addControls: function() { var self = this; this.collapsed = false; this.toggle_button = this.getButton('','collapse',this.translate('button_collapse')); this.title_controls.appendChild(this.toggle_button); var row_holder_display = self.row_holder.style.display; var controls_display = self.controls.style.display; this.toggle_button.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); if(self.collapsed) { self.collapsed = false; if(self.panel) self.panel.style.display = ''; self.row_holder.style.display = row_holder_display; if(self.tabs_holder) self.tabs_holder.style.display = ''; self.controls.style.display = controls_display; self.setButtonText(this,'','collapse','Collapse'); } else { self.collapsed = true; self.row_holder.style.display = 'none'; if(self.tabs_holder) self.tabs_holder.style.display = 'none'; self.controls.style.display = 'none'; if(self.panel) self.panel.style.display = 'none'; self.setButtonText(this,'','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'; } // Add "new row" and "delete last" buttons below editor this.add_row_button = this.getButton(this.getItemTitle(),'add',this.translate('button_add_row_title',[this.getItemTitle()])); this.add_row_button.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); var i = self.rows.length; if(self.row_cache[i]) { self.rows[i] = self.row_cache[i]; self.rows[i].setValue(self.rows[i].getDefault()); self.rows[i].container.style.display = ''; if(self.rows[i].tab) self.rows[i].tab.style.display = ''; self.rows[i].register(); } else { self.addRow(); } self.active_tab = self.rows[i].tab; self.refreshTabs(); self.refreshValue(); self.onChange(true); }); self.controls.appendChild(this.add_row_button); this.delete_last_row_button = this.getButton(this.translate('button_delete_last',[this.getItemTitle()]),'delete',this.translate('button_delete_last_title',[this.getItemTitle()])); this.delete_last_row_button.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); var rows = self.getValue(); var new_active_tab = null; if(self.rows.length > 1 && self.rows[self.rows.length-1].tab === self.active_tab) new_active_tab = self.rows[self.rows.length-2].tab; rows.pop(); self.setValue(rows); if(new_active_tab) { self.active_tab = new_active_tab; self.refreshTabs(); } self.onChange(true); }); self.controls.appendChild(this.delete_last_row_button); this.remove_all_rows_button = this.getButton(this.translate('button_delete_all'),'delete',this.translate('button_delete_all_title')); this.remove_all_rows_button.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); self.setValue([]); self.onChange(true); }); self.controls.appendChild(this.remove_all_rows_button); if(self.tabs) { this.add_row_button.style.width = '100%'; this.add_row_button.style.textAlign = 'left'; this.add_row_button.style.marginBottom = '3px'; this.delete_last_row_button.style.width = '100%'; this.delete_last_row_button.style.textAlign = 'left'; this.delete_last_row_button.style.marginBottom = '3px'; this.remove_all_rows_button.style.width = '100%'; this.remove_all_rows_button.style.textAlign = 'left'; this.remove_all_rows_button.style.marginBottom = '3px'; } }, showValidationErrors: function(errors) { var self = this; // Get all the errors that pertain to this editor var my_errors = []; var other_errors = []; $each(errors, function(i,error) { if(error.path === self.path) { my_errors.push(error); } else { other_errors.push(error); } }); // Show errors for this editor if(this.error_holder) { if(my_errors.length) { var message = []; this.error_holder.innerHTML = ''; this.error_holder.style.display = ''; $each(my_errors, function(i,error) { self.error_holder.appendChild(self.theme.getErrorMessage(error.message)); }); } // Hide error area else { this.error_holder.style.display = 'none'; } } // Show errors for child editors $each(this.rows, function(i,row) { row.showValidationErrors(other_errors); }); } }); JSONEditor.defaults.editors.table = JSONEditor.defaults.editors.array.extend({ register: function() { this._super(); if(this.rows) { for(var i=0; i<this.rows.length; i++) { this.rows[i].register(); } } }, unregister: function() { this._super(); if(this.rows) { for(var i=0; i<this.rows.length; i++) { this.rows[i].unregister(); } } }, getNumColumns: function() { return Math.max(Math.min(12,this.width),3); }, preBuild: function() { var item_schema = this.jsoneditor.expandRefs(this.schema.items || {}); this.item_title = item_schema.title || 'row'; this.item_default = item_schema["default"] || null; this.item_has_child_editors = item_schema.properties || item_schema.items; this.width = 12; this._super(); }, build: function() { var self = this; this.table = this.theme.getTable(); this.container.appendChild(this.table); this.thead = this.theme.getTableHead(); this.table.appendChild(this.thead); this.header_row = this.theme.getTableRow(); this.thead.appendChild(this.header_row); this.row_holder = this.theme.getTableBody(); this.table.appendChild(this.row_holder); // Determine the default value of array element var tmp = this.getElementEditor(0,true); this.item_default = tmp.getDefault(); this.width = tmp.getNumColumns() + 2; if(!this.options.compact) { this.title = this.theme.getHeader(this.getTitle()); this.container.appendChild(this.title); this.title_controls = this.theme.getHeaderButtonHolder(); this.title.appendChild(this.title_controls); if(this.schema.description) { this.description = this.theme.getDescription(this.schema.description); this.container.appendChild(this.description); } this.panel = this.theme.getIndentedPanel(); this.container.appendChild(this.panel); this.error_holder = document.createElement('div'); this.panel.appendChild(this.error_holder); } else { this.panel = document.createElement('div'); this.container.appendChild(this.panel); } this.panel.appendChild(this.table); this.controls = this.theme.getButtonHolder(); this.panel.appendChild(this.controls); if(this.item_has_child_editors) { var ce = tmp.getChildEditors(); var order = tmp.property_order || Object.keys(ce); for(var i=0; i<order.length; i++) { var th = self.theme.getTableHeaderCell(ce[order[i]].getTitle()); if(ce[order[i]].options.hidden) th.style.display = 'none'; self.header_row.appendChild(th); } } else { self.header_row.appendChild(self.theme.getTableHeaderCell(this.item_title)); } tmp.destroy(); this.row_holder.innerHTML = ''; // Row Controls column this.controls_header_cell = self.theme.getTableHeaderCell(" "); self.header_row.appendChild(this.controls_header_cell); // Add controls this.addControls(); }, onChildEditorChange: function(editor) { this.refreshValue(); this._super(); }, getItemDefault: function() { return $extend({},{"default":this.item_default})["default"]; }, getItemTitle: function() { return this.item_title; }, getElementEditor: function(i,ignore) { var schema_copy = $extend({},this.schema.items); var editor = this.jsoneditor.getEditorClass(schema_copy, this.jsoneditor); var row = this.row_holder.appendChild(this.theme.getTableRow()); var holder = row; if(!this.item_has_child_editors) { holder = this.theme.getTableCell(); row.appendChild(holder); } var ret = this.jsoneditor.createEditor(editor,{ jsoneditor: this.jsoneditor, schema: schema_copy, container: holder, path: this.path+'.'+i, parent: this, compact: true, table_row: true }); ret.preBuild(); if(!ignore) { ret.build(); ret.postBuild(); ret.controls_cell = row.appendChild(this.theme.getTableCell()); ret.row = row; ret.table_controls = this.theme.getButtonHolder(); ret.controls_cell.appendChild(ret.table_controls); ret.table_controls.style.margin = 0; ret.table_controls.style.padding = 0; } return ret; }, destroy: function() { this.innerHTML = ''; if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description); if(this.row_holder && this.row_holder.parentNode) this.row_holder.parentNode.removeChild(this.row_holder); if(this.table && this.table.parentNode) this.table.parentNode.removeChild(this.table); if(this.panel && this.panel.parentNode) this.panel.parentNode.removeChild(this.panel); this.rows = this.title = this.description = this.row_holder = this.table = this.panel = null; this._super(); }, setValue: function(value, initial) { // Update the array's value, adding/removing rows when necessary value = value || []; // Make sure value has between minItems and maxItems items in it if(this.schema.minItems) { while(value.length < this.schema.minItems) { value.push(this.getItemDefault()); } } if(this.schema.maxItems && value.length > this.schema.maxItems) { value = value.slice(0,this.schema.maxItems); } var serialized = JSON.stringify(value); if(serialized === this.serialized) return; var numrows_changed = false; var self = this; $each(value,function(i,val) { if(self.rows[i]) { // TODO: don't set the row's value if it hasn't changed self.rows[i].setValue(val); } else { self.addRow(val); numrows_changed = true; } }); for(var j=value.length; j<self.rows.length; j++) { var holder = self.rows[j].container; if(!self.item_has_child_editors) { self.rows[j].row.parentNode.removeChild(self.rows[j].row); } self.rows[j].destroy(); if(holder.parentNode) holder.parentNode.removeChild(holder); self.rows[j] = null; numrows_changed = true; } self.rows = self.rows.slice(0,value.length); self.refreshValue(); if(numrows_changed || initial) self.refreshRowButtons(); self.onChange(); // TODO: sortable }, refreshRowButtons: function() { var self = this; // If we currently have minItems items in the array var minItems = this.schema.minItems && this.schema.minItems >= this.rows.length; var need_row_buttons = false; $each(this.rows,function(i,editor) { // Hide the move down button for the last row if(editor.movedown_button) { if(i === self.rows.length - 1) { editor.movedown_button.style.display = 'none'; } else { need_row_buttons = true; editor.movedown_button.style.display = ''; } } // Hide the delete button if we have minItems items if(editor.delete_button) { if(minItems) { editor.delete_button.style.display = 'none'; } else { need_row_buttons = true; editor.delete_button.style.display = ''; } } if(editor.moveup_button) { need_row_buttons = true; } }); // Show/hide controls column in table $each(this.rows,function(i,editor) { if(need_row_buttons) { editor.controls_cell.style.display = ''; } else { editor.controls_cell.style.display = 'none'; } }); if(need_row_buttons) { this.controls_header_cell.style.display = ''; } else { this.controls_header_cell.style.display = 'none'; } var controls_needed = false; if(!this.value.length) { this.delete_last_row_button.style.display = 'none'; this.remove_all_rows_button.style.display = 'none'; this.table.style.display = 'none'; } else if(this.value.length === 1 || this.hide_delete_buttons) { this.table.style.display = ''; this.remove_all_rows_button.style.display = 'none'; // If there are minItems items in the array, hide the delete button beneath the rows if(minItems || this.hide_delete_buttons) { this.delete_last_row_button.style.display = 'none'; } else { this.delete_last_row_button.style.display = ''; controls_needed = true; } } else { this.table.style.display = ''; // If there are minItems items in the array, hide the delete button beneath the rows if(minItems || this.hide_delete_buttons) { this.delete_last_row_button.style.display = 'none'; this.remove_all_rows_button.style.display = 'none'; } else { this.delete_last_row_button.style.display = ''; this.remove_all_rows_button.style.display = ''; controls_needed = true; } } // If there are maxItems in the array, hide the add button beneath the rows if((this.schema.maxItems && this.schema.maxItems <= this.rows.length) || this.hide_add_button) { this.add_row_button.style.display = 'none'; } else { this.add_row_button.style.display = ''; controls_needed = true; } if(!controls_needed) { this.controls.style.display = 'none'; } else { this.controls.style.display = ''; } }, refreshValue: function() { var self = this; this.value = []; $each(this.rows,function(i,editor) { // Get the value for this editor self.value[i] = editor.getValue(); }); this.serialized = JSON.stringify(this.value); }, addRow: function(value) { var self = this; var i = this.rows.length; self.rows[i] = this.getElementEditor(i); var controls_holder = self.rows[i].table_controls; // Buttons to delete row, move row up, and move row down if(!this.hide_delete_buttons) { self.rows[i].delete_button = this.getButton('','delete',this.translate('button_delete_row_title_short')); self.rows[i].delete_button.className += ' delete'; self.rows[i].delete_button.setAttribute('data-i',i); self.rows[i].delete_button.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); var i = this.getAttribute('data-i')*1; var value = self.getValue(); var newval = []; $each(value,function(j,row) { if(j===i) return; // If this is the one we're deleting newval.push(row); }); self.setValue(newval); self.onChange(true); }); controls_holder.appendChild(self.rows[i].delete_button); } if(i && !this.hide_move_buttons) { self.rows[i].moveup_button = this.getButton('','moveup',this.translate('button_move_up_title')); self.rows[i].moveup_button.className += ' moveup'; self.rows[i].moveup_button.setAttribute('data-i',i); self.rows[i].moveup_button.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); var i = this.getAttribute('data-i')*1; if(i<=0) return; var rows = self.getValue(); var tmp = rows[i-1]; rows[i-1] = rows[i]; rows[i] = tmp; self.setValue(rows); self.onChange(true); }); controls_holder.appendChild(self.rows[i].moveup_button); } if(!this.hide_move_buttons) { self.rows[i].movedown_button = this.getButton('','movedown',this.translate('button_move_down_title')); self.rows[i].movedown_button.className += ' movedown'; self.rows[i].movedown_button.setAttribute('data-i',i); self.rows[i].movedown_button.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); var i = this.getAttribute('data-i')*1; var rows = self.getValue(); if(i>=rows.length-1) return; var tmp = rows[i+1]; rows[i+1] = rows[i]; rows[i] = tmp; self.setValue(rows); self.onChange(true); }); controls_holder.appendChild(self.rows[i].movedown_button); } if(value) self.rows[i].setValue(value); }, addControls: function() { var self = this; this.collapsed = false; this.toggle_button = this.getButton('','collapse',this.translate('button_collapse')); if(this.title_controls) { this.title_controls.appendChild(this.toggle_button); this.toggle_button.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); if(self.collapsed) { self.collapsed = false; self.panel.style.display = ''; self.setButtonText(this,'','collapse','Collapse'); } else { self.collapsed = true; self.panel.style.display = 'none'; self.setButtonText(this,'','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'; } } // Add "new row" and "delete last" buttons below editor this.add_row_button = this.getButton(this.getItemTitle(),'add',this.translate('button_add_row_title',[this.getItemTitle()])); this.add_row_button.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); self.addRow(); self.refreshValue(); self.refreshRowButtons(); self.onChange(true); }); self.controls.appendChild(this.add_row_button); this.delete_last_row_button = this.getButton(this.translate('button_delete_last',[this.getItemTitle()]),'delete',this.translate('button_delete_last_title',[this.getItemTitle()])); this.delete_last_row_button.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); var rows = self.getValue(); rows.pop(); self.setValue(rows); self.onChange(true); }); self.controls.appendChild(this.delete_last_row_button); this.remove_all_rows_button = this.getButton(this.translate('button_delete_all'),'delete',this.translate('button_delete_all_title')); this.remove_all_rows_button.addEventListener('click',function(e) { e.preventDefault(); e.stopPropagation(); self.setValue([]); self.onChange(true); }); self.controls.appendChild(this.remove_all_rows_button); } }); // Multiple Editor (for when `type` is an array) JSONEditor.defaults.editors.multiple = JSONEditor.AbstractEditor.extend({ register: function() { if(this.editors) { for(var i=0; i<this.editors.length; i++) { if(!this.editors[i]) continue; this.editors[i].unregister(); } if(this.editors[this.type]) this.editors[this.type].register(); } this._super(); }, unregister: function() { this._super(); if(this.editors) { for(var i=0; i<this.editors.length; i++) { if(!this.editors[i]) continue; this.editors[i].unregister(); } } }, getNumColumns: function() { if(!this.editors[this.type]) return 4; return Math.max(this.editors[this.type].getNumColumns(),4); }, enable: function() { if(this.editors) { for(var i=0; i<this.editors.length; i++) { if(!this.editors[i]) continue; this.editors[i].enable(); } } this.switcher.disabled = false; this._super(); }, disable: function() { if(this.editors) { for(var i=0; i<this.editors.length; i++) { if(!this.editors[i]) continue; this.editors[i].disable(); } } this.switcher.disabled = true; this._super(); }, switchEditor: function(i) { var self = this; if(!this.editors[i]) { this.buildChildEditor(i); } self.type = i; self.register(); var current_value = self.getValue(); $each(self.editors,function(type,editor) { if(!editor) return; if(self.type === type) { if(self.keep_values) editor.setValue(current_value,true); editor.container.style.display = ''; } else editor.container.style.display = 'none'; }); self.refreshValue(); self.refreshHeaderText(); }, buildChildEditor: function(i) { var self = this; var type = this.types[i]; var holder = self.theme.getChildEditorHolder(); self.editor_holder.appendChild(holder); var schema; if(typeof type === "string") { schema = $extend({},self.schema); schema.type = type; } else { schema = $extend({},self.schema,type); schema = self.jsoneditor.expandRefs(schema); // If we need to merge `required` arrays if(type.required && Array.isArray(type.required) && self.schema.required && Array.isArray(self.schema.required)) { schema.required = self.schema.required.concat(type.required); } } var editor = self.jsoneditor.getEditorClass(schema); self.editors[i] = self.jsoneditor.createEditor(editor,{ jsoneditor: self.jsoneditor, schema: schema, container: holder, path: self.path, parent: self, required: true }); self.editors[i].preBuild(); self.editors[i].build(); self.editors[i].postBuild(); if(self.editors[i].header) self.editors[i].header.style.display = 'none'; self.editors[i].option = self.switcher_options[i]; holder.addEventListener('change_header_text',function() { self.refreshHeaderText(); }); if(i !== self.type) holder.style.display = 'none'; }, preBuild: function() { var self = this; this.types = []; this.type = 0; this.editors = []; this.validators = []; this.keep_values = true; if(typeof this.jsoneditor.options.keep_oneof_values !== "undefined") this.keep_values = this.jsoneditor.options.keep_oneof_values; if(typeof this.options.keep_oneof_values !== "undefined") this.keep_values = this.options.keep_oneof_values; if(this.schema.oneOf) { this.oneOf = true; this.types = this.schema.oneOf; $each(this.types,function(i,oneof) { //self.types[i] = self.jsoneditor.expandSchema(oneof); }); delete this.schema.oneOf; } else { if(!this.schema.type || this.schema.type === "any") { this.types = ['string','number','integer','boolean','object','array','null']; // If any of these primitive types are disallowed if(this.schema.disallow) { var disallow = this.schema.disallow; if(typeof disallow !== 'object' || !(Array.isArray(disallow))) { disallow = [disallow]; } var allowed_types = []; $each(this.types,function(i,type) { if(disallow.indexOf(type) === -1) allowed_types.push(type); }); this.types = allowed_types; } } else if(Array.isArray(this.schema.type)) { this.types = this.schema.type; } else { this.types = [this.schema.type]; } delete this.schema.type; } this.display_text = this.getDisplayText(this.types); }, build: function() { var self = this; var container = this.container; this.header = this.label = this.theme.getFormInputLabel(this.getTitle()); this.container.appendChild(this.header); this.switcher = this.theme.getSwitcher(this.display_text); container.appendChild(this.switcher); this.switcher.addEventListener('change',function(e) { e.preventDefault(); e.stopPropagation(); self.switchEditor(self.display_text.indexOf(this.value)); self.onChange(true); }); this.editor_holder = document.createElement('div'); container.appendChild(this.editor_holder); var validator_options = {}; if(self.jsoneditor.options.custom_validators) { validator_options.custom_validators = self.jsoneditor.options.custom_validators; } this.switcher_options = this.theme.getSwitcherOptions(this.switcher); $each(this.types,function(i,type) { self.editors[i] = false; var schema; if(typeof type === "string") { schema = $extend({},self.schema); schema.type = type; } else { schema = $extend({},self.schema,type); // If we need to merge `required` arrays if(type.required && Array.isArray(type.required) && self.schema.required && Array.isArray(self.schema.required)) { schema.required = self.schema.required.concat(type.required); } } self.validators[i] = new JSONEditor.Validator(self.jsoneditor,schema,validator_options); }); this.switchEditor(0); }, onChildEditorChange: function(editor) { if(this.editors[this.type]) { this.refreshValue(); this.refreshHeaderText(); } this._super(); }, refreshHeaderText: function() { var display_text = this.getDisplayText(this.types); $each(this.switcher_options, function(i,option) { option.textContent = display_text[i]; }); }, refreshValue: function() { this.value = this.editors[this.type].getValue(); }, setValue: function(val,initial) { // Determine type by getting the first one that validates var self = this; $each(this.validators, function(i,validator) { if(!validator.validate(val).length) { self.type = i; self.switcher.value = self.display_text[i]; return false; } }); this.switchEditor(this.type); this.editors[this.type].setValue(val,initial); this.refreshValue(); self.onChange(); }, destroy: function() { $each(this.editors, function(type,editor) { if(editor) editor.destroy(); }); if(this.editor_holder && this.editor_holder.parentNode) this.editor_holder.parentNode.removeChild(this.editor_holder); if(this.switcher && this.switcher.parentNode) this.switcher.parentNode.removeChild(this.switcher); this._super(); }, showValidationErrors: function(errors) { var self = this; // oneOf error paths need to remove the oneOf[i] part before passing to child editors if(this.oneOf) { $each(this.editors,function(i,editor) { if(!editor) return; var check = self.path+'.oneOf['+i+']'; var new_errors = []; $each(errors, function(j,error) { if(error.path.substr(0,check.length)===check) { var new_error = $extend({},error); new_error.path = self.path+new_error.path.substr(check.length); new_errors.push(new_error); } }); editor.showValidationErrors(new_errors); }); } else { $each(this.editors,function(type,editor) { if(!editor) return; editor.showValidationErrors(errors); }); } } }); // Enum Editor (used for objects and arrays with enumerated values) JSONEditor.defaults.editors["enum"] = JSONEditor.AbstractEditor.extend({ getNumColumns: function() { return 4; }, build: function() { var container = this.container; this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle()); this.container.appendChild(this.title); this.options.enum_titles = this.options.enum_titles || []; this["enum"] = this.schema["enum"]; this.selected = 0; this.select_options = []; this.html_values = []; var self = this; for(var i=0; i<this["enum"].length; i++) { this.select_options[i] = this.options.enum_titles[i] || "Value "+(i+1); this.html_values[i] = this.getHTML(this["enum"][i]); } // Switcher this.switcher = this.theme.getSwitcher(this.select_options); this.container.appendChild(this.switcher); // Display area this.display_area = this.theme.getIndentedPanel(); this.container.appendChild(this.display_area); if(this.options.hide_display) this.display_area.style.display = "none"; this.switcher.addEventListener('change',function() { self.selected = self.select_options.indexOf(this.value); self.value = self["enum"][self.selected]; self.refreshValue(); self.onChange(true); }); this.value = this["enum"][0]; this.refreshValue(); if(this["enum"].length === 1) this.switcher.style.display = 'none'; }, refreshValue: function() { var self = this; self.selected = -1; var stringified = JSON.stringify(this.value); $each(this["enum"], function(i, el) { if(stringified === JSON.stringify(el)) { self.selected = i; return false; } }); if(self.selected<0) { self.setValue(self["enum"][0]); return; } this.switcher.value = this.select_options[this.selected]; this.display_area.innerHTML = this.html_values[this.selected]; }, enable: function() { if(!this.always_disabled) this.switcher.disabled = false; this._super(); }, disable: function() { this.switcher.disabled = true; this._super(); }, getHTML: function(el) { var self = this; if(el === null) { return '<em>null</em>'; } // Array or Object else if(typeof el === "object") { // TODO: use theme var ret = ''; $each(el,function(i,child) { var html = self.getHTML(child); // Add the keys to object children if(!(Array.isArray(el))) { // TODO: use theme html = '<div><em>'+i+'</em>: '+html+'</div>'; } // TODO: use theme ret += '<li>'+html+'</li>'; }); if(Array.isArray(el)) ret = '<ol>'+ret+'</ol>'; else ret = "<ul style='margin-top:0;margin-bottom:0;padding-top:0;padding-bottom:0;'>"+ret+'</ul>'; return ret; } // Boolean else if(typeof el === "boolean") { return el? 'true' : 'false'; } // String else if(typeof el === "string") { return el.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); } // Number else { return el; } }, setValue: function(val) { if(this.value !== val) { this.value = val; this.refreshValue(); this.onChange(); } }, destroy: function() { if(this.display_area && this.display_area.parentNode) this.display_area.parentNode.removeChild(this.display_area); if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); if(this.switcher && this.switcher.parentNode) this.switcher.parentNode.removeChild(this.switcher); this._super(); } }); JSONEditor.defaults.editors.select = JSONEditor.AbstractEditor.extend({ setValue: function(value,initial) { value = this.typecast(value||''); // Sanitize value before setting it var sanitized = value; if(this.enum_values.indexOf(sanitized) < 0) { sanitized = this.enum_values[0]; } if(this.value === sanitized) { return; } this.input.value = this.enum_options[this.enum_values.indexOf(sanitized)]; if(this.select2) this.select2.select2('val',this.input.value); this.value = sanitized; this.onChange(); }, 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'); }, getNumColumns: function() { if(!this.enum_options) return 3; var longest_text = this.getTitle().length; for(var i=0; i<this.enum_options.length; i++) { longest_text = Math.max(longest_text,this.enum_options[i].length+4); } return Math.min(12,Math.max(longest_text/7,2)); }, typecast: function(value) { if(this.schema.type === "boolean") { return !!value; } else if(this.schema.type === "number") { return 1*value; } else if(this.schema.type === "integer") { return Math.floor(value*1); } else { return ""+value; } }, getValue: function() { return this.value; }, preBuild: function() { var self = this; this.input_type = 'select'; this.enum_options = []; this.enum_values = []; this.enum_display = []; // Enum options enumerated if(this.schema["enum"]) { var display = this.schema.options && this.schema.options.enum_titles || []; $each(this.schema["enum"],function(i,option) { self.enum_options[i] = ""+option; self.enum_display[i] = ""+(display[i] || option); self.enum_values[i] = self.typecast(option); }); if(!this.isRequired()){ self.enum_display.unshift(' '); self.enum_options.unshift('undefined'); self.enum_values.unshift(undefined); } } // Boolean else if(this.schema.type === "boolean") { self.enum_display = this.schema.options && this.schema.options.enum_titles || ['true','false']; self.enum_options = ['1','']; self.enum_values = [true,false]; if(!this.isRequired()){ self.enum_display.unshift(' '); self.enum_options.unshift('undefined'); self.enum_values.unshift(undefined); } } // Dynamic Enum else if(this.schema.enumSource) { this.enumSource = []; this.enum_display = []; this.enum_options = []; this.enum_values = []; // Shortcut declaration for using a single array if(!(Array.isArray(this.schema.enumSource))) { if(this.schema.enumValue) { this.enumSource = [ { source: this.schema.enumSource, value: this.schema.enumValue } ]; } else { this.enumSource = [ { source: this.schema.enumSource } ]; } } else { for(i=0; i<this.schema.enumSource.length; i++) { // Shorthand for watched variable if(typeof this.schema.enumSource[i] === "string") { this.enumSource[i] = { source: this.schema.enumSource[i] }; } // Make a copy of the schema else if(!(Array.isArray(this.schema.enumSource[i]))) { this.enumSource[i] = $extend({},this.schema.enumSource[i]); } else { this.enumSource[i] = this.schema.enumSource[i]; } } } // Now, enumSource is an array of sources // Walk through this array and fix up the values for(i=0; i<this.enumSource.length; i++) { if(this.enumSource[i].value) { this.enumSource[i].value = this.jsoneditor.compileTemplate(this.enumSource[i].value, this.template_engine); } if(this.enumSource[i].title) { this.enumSource[i].title = this.jsoneditor.compileTemplate(this.enumSource[i].title, this.template_engine); } if(this.enumSource[i].filter) { this.enumSource[i].filter = this.jsoneditor.compileTemplate(this.enumSource[i].filter, this.template_engine); } } } // Other, not supported else { throw "'select' editor requires the enum property to be set."; } }, build: function() { var self = this; 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); if(this.options.compact) this.container.className += ' compact'; this.input = this.theme.getSelectInput(this.enum_options); this.theme.setSelectOptions(this.input,this.enum_options,this.enum_display); if(this.schema.readOnly || this.schema.readonly) { this.always_disabled = true; this.input.disabled = true; } this.input.addEventListener('change',function(e) { e.preventDefault(); e.stopPropagation(); self.onInputChange(); }); this.control = this.theme.getFormControl(this.label, this.input, this.description); this.container.appendChild(this.control); this.value = this.enum_values[0]; }, onInputChange: function() { var val = this.input.value; var new_val; // Invalid option, use first option instead if(this.enum_options.indexOf(val) === -1) { new_val = this.enum_values[0]; } else { new_val = this.enum_values[this.enum_options.indexOf(val)]; } // If valid hasn't changed if(new_val === this.value) return; // Store new value and propogate change event this.value = new_val; this.onChange(true); }, setupSelect2: function() { // If the Select2 library is loaded use it when we have lots of items if(window.jQuery && window.jQuery.fn && window.jQuery.fn.select2 && (this.enum_options.length > 2 || (this.enum_options.length && this.enumSource))) { var options = $extend({},JSONEditor.plugins.select2); if(this.schema.options && this.schema.options.select2_options) options = $extend(options,this.schema.options.select2_options); this.select2 = window.jQuery(this.input).select2(options); var self = this; this.select2.on('select2-blur',function() { self.input.value = self.select2.select2('val'); self.onInputChange(); }); this.select2.on('change',function() { self.input.value = self.select2.select2('val'); self.onInputChange(); }); } else { this.select2 = null; } }, postBuild: function() { this._super(); this.theme.afterInputReady(this.input); this.setupSelect2(); }, onWatchedFieldChange: function() { var self = this, vars, j; // If this editor uses a dynamic select box if(this.enumSource) { vars = this.getWatchedFieldValues(); var select_options = []; var select_titles = []; for(var i=0; i<this.enumSource.length; i++) { // Constant values if(Array.isArray(this.enumSource[i])) { select_options = select_options.concat(this.enumSource[i]); select_titles = select_titles.concat(this.enumSource[i]); } else { var items = []; // Static list of items if(Array.isArray(this.enumSource[i].source)) { items = this.enumSource[i].source; // A watched field } else { items = vars[this.enumSource[i].source]; } if(items) { // Only use a predefined part of the array if(this.enumSource[i].slice) { items = Array.prototype.slice.apply(items,this.enumSource[i].slice); } // Filter the items if(this.enumSource[i].filter) { var new_items = []; for(j=0; j<items.length; j++) { if(this.enumSource[i].filter({i:j,item:items[j],watched:vars})) new_items.push(items[j]); } items = new_items; } var item_titles = []; var item_values = []; for(j=0; j<items.length; j++) { var item = items[j]; // Rendered value if(this.enumSource[i].value) { item_values[j] = this.enumSource[i].value({ i: j, item: item }); } // Use value directly else { item_values[j] = items[j]; } // Rendered title if(this.enumSource[i].title) { item_titles[j] = this.enumSource[i].title({ i: j, item: item }); } // Use value as the title also else { item_titles[j] = item_values[j]; } } // TODO: sort select_options = select_options.concat(item_values); select_titles = select_titles.concat(item_titles); } } } var prev_value = this.value; this.theme.setSelectOptions(this.input, select_options, select_titles); this.enum_options = select_options; this.enum_display = select_titles; this.enum_values = select_options; if(this.select2) { this.select2.select2('destroy'); } // If the previous value is still in the new select options, stick with it if(select_options.indexOf(prev_value) !== -1) { this.input.value = prev_value; this.value = prev_value; } // Otherwise, set the value to the first select option else { this.input.value = select_options[0]; this.value = select_options[0] || ""; if(this.parent) this.parent.onChildEditorChange(this); else this.jsoneditor.onChange(); this.jsoneditor.notifyWatchers(this.path); } this.setupSelect2(); } this._super(); }, enable: function() { if(!this.always_disabled) { this.input.disabled = false; if(this.select2) this.select2.select2("enable",true); } this._super(); }, disable: function() { this.input.disabled = true; if(this.select2) this.select2.select2("enable",false); this._super(); }, destroy: function() { if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label); if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description); if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); if(this.select2) { this.select2.select2('destroy'); this.select2 = null; } this._super(); } }); JSONEditor.defaults.editors.selectize = JSONEditor.AbstractEditor.extend({ setValue: function(value,initial) { value = this.typecast(value||''); // Sanitize value before setting it var sanitized = value; if(this.enum_values.indexOf(sanitized) < 0) { sanitized = this.enum_values[0]; } if(this.value === sanitized) { return; } this.input.value = this.enum_options[this.enum_values.indexOf(sanitized)]; if(this.selectize) { this.selectize[0].selectize.addItem(sanitized); } this.value = sanitized; this.onChange(); }, 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'); }, getNumColumns: function() { if(!this.enum_options) return 3; var longest_text = this.getTitle().length; for(var i=0; i<this.enum_options.length; i++) { longest_text = Math.max(longest_text,this.enum_options[i].length+4); } return Math.min(12,Math.max(longest_text/7,2)); }, typecast: function(value) { if(this.schema.type === "boolean") { return !!value; } else if(this.schema.type === "number") { return 1*value; } else if(this.schema.type === "integer") { return Math.floor(value*1); } else { return ""+value; } }, getValue: function() { return this.value; }, preBuild: function() { var self = this; this.input_type = 'select'; this.enum_options = []; this.enum_values = []; this.enum_display = []; // Enum options enumerated if(this.schema.enum) { var display = this.schema.options && this.schema.options.enum_titles || []; $each(this.schema.enum,function(i,option) { self.enum_options[i] = ""+option; self.enum_display[i] = ""+(display[i] || option); self.enum_values[i] = self.typecast(option); }); } // Boolean else if(this.schema.type === "boolean") { self.enum_display = this.schema.options && this.schema.options.enum_titles || ['true','false']; self.enum_options = ['1','0']; self.enum_values = [true,false]; } // Dynamic Enum else if(this.schema.enumSource) { this.enumSource = []; this.enum_display = []; this.enum_options = []; this.enum_values = []; // Shortcut declaration for using a single array if(!(Array.isArray(this.schema.enumSource))) { if(this.schema.enumValue) { this.enumSource = [ { source: this.schema.enumSource, value: this.schema.enumValue } ]; } else { this.enumSource = [ { source: this.schema.enumSource } ]; } } else { for(i=0; i<this.schema.enumSource.length; i++) { // Shorthand for watched variable if(typeof this.schema.enumSource[i] === "string") { this.enumSource[i] = { source: this.schema.enumSource[i] }; } // Make a copy of the schema else if(!(Array.isArray(this.schema.enumSource[i]))) { this.enumSource[i] = $extend({},this.schema.enumSource[i]); } else { this.enumSource[i] = this.schema.enumSource[i]; } } } // Now, enumSource is an array of sources // Walk through this array and fix up the values for(i=0; i<this.enumSource.length; i++) { if(this.enumSource[i].value) { this.enumSource[i].value = this.jsoneditor.compileTemplate(this.enumSource[i].value, this.template_engine); } if(this.enumSource[i].title) { this.enumSource[i].title = this.jsoneditor.compileTemplate(this.enumSource[i].title, this.template_engine); } if(this.enumSource[i].filter) { this.enumSource[i].filter = this.jsoneditor.compileTemplate(this.enumSource[i].filter, this.template_engine); } } } // Other, not supported else { throw "'select' editor requires the enum property to be set."; } }, build: function() { var self = this; 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); if(this.options.compact) this.container.className += ' compact'; this.input = this.theme.getSelectInput(this.enum_options); this.theme.setSelectOptions(this.input,this.enum_options,this.enum_display); if(this.schema.readOnly || this.schema.readonly) { this.always_disabled = true; this.input.disabled = true; } this.input.addEventListener('change',function(e) { e.preventDefault(); e.stopPropagation(); self.onInputChange(); }); this.control = this.theme.getFormControl(this.label, this.input, this.description); this.container.appendChild(this.control); this.value = this.enum_values[0]; }, onInputChange: function() { var val = this.input.value; var sanitized = val; if(this.enum_options.indexOf(val) === -1) { sanitized = this.enum_options[0]; } this.value = this.enum_values[this.enum_options.indexOf(val)]; this.onChange(true); }, setupSelectize: function() { // If the Selectize library is loaded use it when we have lots of items var self = this; if(window.jQuery && window.jQuery.fn && window.jQuery.fn.selectize && (this.enum_options.length >= 2 || (this.enum_options.length && this.enumSource))) { var options = $extend({},JSONEditor.plugins.selectize); if(this.schema.options && this.schema.options.selectize_options) options = $extend(options,this.schema.options.selectize_options); this.selectize = window.jQuery(this.input).selectize($extend(options, { create: true, onChange : function() { self.onInputChange(); } })); } else { this.selectize = null; } }, postBuild: function() { this._super(); this.theme.afterInputReady(this.input); this.setupSelectize(); }, onWatchedFieldChange: function() { var self = this, vars, j; // If this editor uses a dynamic select box if(this.enumSource) { vars = this.getWatchedFieldValues(); var select_options = []; var select_titles = []; for(var i=0; i<this.enumSource.length; i++) { // Constant values if(Array.isArray(this.enumSource[i])) { select_options = select_options.concat(this.enumSource[i]); select_titles = select_titles.concat(this.enumSource[i]); } // A watched field else if(vars[this.enumSource[i].source]) { var items = vars[this.enumSource[i].source]; // Only use a predefined part of the array if(this.enumSource[i].slice) { items = Array.prototype.slice.apply(items,this.enumSource[i].slice); } // Filter the items if(this.enumSource[i].filter) { var new_items = []; for(j=0; j<items.length; j++) { if(this.enumSource[i].filter({i:j,item:items[j]})) new_items.push(items[j]); } items = new_items; } var item_titles = []; var item_values = []; for(j=0; j<items.length; j++) { var item = items[j]; // Rendered value if(this.enumSource[i].value) { item_values[j] = this.enumSource[i].value({ i: j, item: item }); } // Use value directly else { item_values[j] = items[j]; } // Rendered title if(this.enumSource[i].title) { item_titles[j] = this.enumSource[i].title({ i: j, item: item }); } // Use value as the title also else { item_titles[j] = item_values[j]; } } // TODO: sort select_options = select_options.concat(item_values); select_titles = select_titles.concat(item_titles); } } var prev_value = this.value; this.theme.setSelectOptions(this.input, select_options, select_titles); this.enum_options = select_options; this.enum_display = select_titles; this.enum_values = select_options; // If the previous value is still in the new select options, stick with it if(select_options.indexOf(prev_value) !== -1) { this.input.value = prev_value; this.value = prev_value; } // Otherwise, set the value to the first select option else { this.input.value = select_options[0]; this.value = select_options[0] || ""; if(this.parent) this.parent.onChildEditorChange(this); else this.jsoneditor.onChange(); this.jsoneditor.notifyWatchers(this.path); } if(this.selectize) { // Update the Selectize options this.updateSelectizeOptions(select_options); } else { this.setupSelectize(); } this._super(); } }, updateSelectizeOptions: function(select_options) { var selectized = this.selectize[0].selectize, self = this; selectized.off(); selectized.clearOptions(); for(var n in select_options) { selectized.addOption({value:select_options[n],text:select_options[n]}); } selectized.addItem(this.value); selectized.on('change',function() { self.onInputChange(); }); }, enable: function() { if(!this.always_disabled) { this.input.disabled = false; if(this.selectize) { this.selectize[0].selectize.unlock(); } } this._super(); }, disable: function() { this.input.disabled = true; if(this.selectize) { this.selectize[0].selectize.lock(); } this._super(); }, destroy: function() { if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label); if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description); if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); if(this.selectize) { this.selectize[0].selectize.destroy(); this.selectize = null; } this._super(); } }); JSONEditor.defaults.editors.multiselect = JSONEditor.AbstractEditor.extend({ preBuild: function() { this._super(); this.select_options = {}; this.select_values = {}; var items_schema = this.jsoneditor.expandRefs(this.schema.items || {}); var e = items_schema["enum"] || []; this.option_keys = []; for(i=0; i<e.length; i++) { // If the sanitized value is different from the enum value, don't include it if(this.sanitize(e[i]) !== e[i]) continue; this.option_keys.push(e[i]+""); this.select_values[e[i]+""] = e[i]; } }, 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); if((!this.schema.format && this.option_keys.length < 8) || this.schema.format === "checkbox") { this.input_type = 'checkboxes'; this.inputs = {}; this.controls = {}; for(i=0; i<this.option_keys.length; i++) { this.inputs[this.option_keys[i]] = this.theme.getCheckbox(); this.select_options[this.option_keys[i]] = this.inputs[this.option_keys[i]]; var label = this.theme.getCheckboxLabel(this.option_keys[i]); this.controls[this.option_keys[i]] = this.theme.getFormControl(label, this.inputs[this.option_keys[i]]); } this.control = this.theme.getMultiCheckboxHolder(this.controls,this.label,this.description); } else { this.input_type = 'select'; this.input = this.theme.getSelectInput(this.option_keys); this.input.multiple = true; this.input.size = Math.min(10,this.option_keys.length); for(i=0; i<this.option_keys.length; i++) { this.select_options[this.option_keys[i]] = this.input.children[i]; } if(this.schema.readOnly || this.schema.readonly) { this.always_disabled = true; this.input.disabled = true; } this.control = this.theme.getFormControl(this.label, this.input, this.description); } this.container.appendChild(this.control); this.control.addEventListener('change',function(e) { e.preventDefault(); e.stopPropagation(); var new_value = []; for(i = 0; i<self.option_keys.length; i++) { if(self.select_options[self.option_keys[i]].selected || self.select_options[self.option_keys[i]].checked) new_value.push(self.select_values[self.option_keys[i]]); } self.updateValue(new_value); self.onChange(true); }); }, setValue: function(value, initial) { var i; value = value || []; if(typeof value !== "object") value = [value]; else if(!(Array.isArray(value))) value = []; // Make sure we are dealing with an array of strings so we can check for strict equality for(i=0; i<value.length; i++) { if(typeof value[i] !== "string") value[i] += ""; } // Update selected status of options for(i in this.select_options) { if(!this.select_options.hasOwnProperty(i)) continue; this.select_options[i][this.input_type === "select"? "selected" : "checked"] = (value.indexOf(i) !== -1); } this.updateValue(value); this.onChange(); }, setupSelect2: function() { if(window.jQuery && window.jQuery.fn && window.jQuery.fn.select2) { var options = window.jQuery.extend({},JSONEditor.plugins.select2); if(this.schema.options && this.schema.options.select2_options) options = $extend(options,this.schema.options.select2_options); this.select2 = window.jQuery(this.input).select2(options); var self = this; this.select2.on('select2-blur',function() { var val =self.select2.select2('val'); self.value = val; self.onChange(true); }); } else { this.select2 = null; } }, onInputChange: function() { this.value = this.input.value; this.onChange(true); }, postBuild: function() { this._super(); this.setupSelect2(); }, 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'); }, getNumColumns: function() { var longest_text = this.getTitle().length; for(var i in this.select_values) { if(!this.select_values.hasOwnProperty(i)) continue; longest_text = Math.max(longest_text,(this.select_values[i]+"").length+4); } return Math.min(12,Math.max(longest_text/7,2)); }, updateValue: function(value) { var changed = false; var new_value = []; for(var i=0; i<value.length; i++) { if(!this.select_options[value[i]+""]) { changed = true; continue; } var sanitized = this.sanitize(this.select_values[value[i]]); new_value.push(sanitized); if(sanitized !== value[i]) changed = true; } this.value = new_value; if(this.select2) this.select2.select2('val',this.value); return changed; }, sanitize: function(value) { if(this.schema.items.type === "number") { return 1*value; } else if(this.schema.items.type === "integer") { return Math.floor(value*1); } else { return ""+value; } }, enable: function() { if(!this.always_disabled) { if(this.input) { this.input.disabled = false; } else if(this.inputs) { for(var i in this.inputs) { if(!this.inputs.hasOwnProperty(i)) continue; this.inputs[i].disabled = false; } } if(this.select2) this.select2.select2("enable",true); } this._super(); }, disable: function() { if(this.input) { this.input.disabled = true; } else if(this.inputs) { for(var i in this.inputs) { if(!this.inputs.hasOwnProperty(i)) continue; this.inputs[i].disabled = true; } } if(this.select2) this.select2.select2("enable",false); this._super(); }, destroy: function() { if(this.select2) { this.select2.select2('destroy'); this.select2 = null; } this._super(); } }); JSONEditor.defaults.editors.base64 = JSONEditor.AbstractEditor.extend({ getNumColumns: function() { return 4; }, build: function() { var self = this; this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle()); // Input that holds the base64 string this.input = this.theme.getFormInputField('hidden'); this.container.appendChild(this.input); // Don't show uploader if this is readonly if(!this.schema.readOnly && !this.schema.readonly) { if(!window.FileReader) throw "FileReader required for base64 editor"; // File uploader this.uploader = this.theme.getFormInputField('file'); this.uploader.addEventListener('change',function(e) { e.preventDefault(); e.stopPropagation(); if(this.files && this.files.length) { var fr = new FileReader(); fr.onload = function(evt) { self.value = evt.target.result; self.refreshPreview(); self.onChange(true); fr = null; }; fr.readAsDataURL(this.files[0]); } }); } this.preview = this.theme.getFormInputDescription(this.schema.description); this.container.appendChild(this.preview); this.control = this.theme.getFormControl(this.label, this.uploader||this.input, this.preview); this.container.appendChild(this.control); }, refreshPreview: function() { if(this.last_preview === this.value) return; this.last_preview = this.value; this.preview.innerHTML = ''; if(!this.value) return; var mime = this.value.match(/^data:([^;,]+)[;,]/); if(mime) mime = mime[1]; if(!mime) { this.preview.innerHTML = '<em>Invalid data URI</em>'; } else { this.preview.innerHTML = '<strong>Type:</strong> '+mime+', <strong>Size:</strong> '+Math.floor((this.value.length-this.value.split(',')[0].length-1)/1.33333)+' bytes'; if(mime.substr(0,5)==="image") { this.preview.innerHTML += '<br>'; var img = document.createElement('img'); img.style.maxWidth = '100%'; img.style.maxHeight = '100px'; img.src = this.value; this.preview.appendChild(img); } } }, enable: function() { if(this.uploader) this.uploader.disabled = false; this._super(); }, disable: function() { if(this.uploader) this.uploader.disabled = true; this._super(); }, setValue: function(val) { if(this.value !== val) { this.value = val; this.input.value = this.value; this.refreshPreview(); this.onChange(); } }, destroy: function() { if(this.preview && this.preview.parentNode) this.preview.parentNode.removeChild(this.preview); if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); if(this.uploader && this.uploader.parentNode) this.uploader.parentNode.removeChild(this.uploader); this._super(); } }); JSONEditor.defaults.editors.upload = JSONEditor.AbstractEditor.extend({ getNumColumns: function() { return 4; }, build: function() { var self = this; this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle()); // Input that holds the base64 string this.input = this.theme.getFormInputField('hidden'); this.container.appendChild(this.input); // Don't show uploader if this is readonly if(!this.schema.readOnly && !this.schema.readonly) { if(!this.jsoneditor.options.upload) throw "Upload handler required for upload editor"; // File uploader this.uploader = this.theme.getFormInputField('file'); this.uploader.addEventListener('change',function(e) { e.preventDefault(); e.stopPropagation(); if(this.files && this.files.length) { var fr = new FileReader(); fr.onload = function(evt) { self.preview_value = evt.target.result; self.refreshPreview(); self.onChange(true); fr = null; }; fr.readAsDataURL(this.files[0]); } }); } var description = this.schema.description; if (!description) description = ''; this.preview = this.theme.getFormInputDescription(description); this.container.appendChild(this.preview); this.control = this.theme.getFormControl(this.label, this.uploader||this.input, this.preview); this.container.appendChild(this.control); }, refreshPreview: function() { if(this.last_preview === this.preview_value) return; this.last_preview = this.preview_value; this.preview.innerHTML = ''; if(!this.preview_value) return; var self = this; var mime = this.preview_value.match(/^data:([^;,]+)[;,]/); if(mime) mime = mime[1]; if(!mime) mime = 'unknown'; var file = this.uploader.files[0]; this.preview.innerHTML = '<strong>Type:</strong> '+mime+', <strong>Size:</strong> '+file.size+' bytes'; if(mime.substr(0,5)==="image") { this.preview.innerHTML += '<br>'; var img = document.createElement('img'); img.style.maxWidth = '100%'; img.style.maxHeight = '100px'; img.src = this.preview_value; this.preview.appendChild(img); } this.preview.innerHTML += '<br>'; var uploadButton = this.getButton('Upload', 'upload', 'Upload'); this.preview.appendChild(uploadButton); uploadButton.addEventListener('click',function(event) { event.preventDefault(); uploadButton.setAttribute("disabled", "disabled"); self.theme.removeInputError(self.uploader); if (self.theme.getProgressBar) { self.progressBar = self.theme.getProgressBar(); self.preview.appendChild(self.progressBar); } self.jsoneditor.options.upload(self.path, file, { success: function(url) { self.setValue(url); if(self.parent) self.parent.onChildEditorChange(self); else self.jsoneditor.onChange(); if (self.progressBar) self.preview.removeChild(self.progressBar); uploadButton.removeAttribute("disabled"); }, failure: function(error) { self.theme.addInputError(self.uploader, error); if (self.progressBar) self.preview.removeChild(self.progressBar); uploadButton.removeAttribute("disabled"); }, updateProgress: function(progress) { if (self.progressBar) { if (progress) self.theme.updateProgressBar(self.progressBar, progress); else self.theme.updateProgressBarUnknown(self.progressBar); } } }); }); }, enable: function() { if(this.uploader) this.uploader.disabled = false; this._super(); }, disable: function() { if(this.uploader) this.uploader.disabled = true; this._super(); }, setValue: function(val) { if(this.value !== val) { this.value = val; this.input.value = this.value; this.onChange(); } }, destroy: function() { if(this.preview && this.preview.parentNode) this.preview.parentNode.removeChild(this.preview); if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); if(this.uploader && this.uploader.parentNode) this.uploader.parentNode.removeChild(this.uploader); this._super(); } }); JSONEditor.defaults.editors.checkbox = JSONEditor.AbstractEditor.extend({ setValue: function(value,initial) { this.value = !!value; this.input.checked = this.value; this.onChange(); }, 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'); }, getNumColumns: function() { return Math.min(12,Math.max(this.getTitle().length/7,2)); }, build: function() { var self = this; if(!this.options.compact) { this.label = this.header = this.theme.getCheckboxLabel(this.getTitle()); } if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description); if(this.options.compact) this.container.className += ' compact'; this.input = this.theme.getCheckbox(); this.control = this.theme.getFormControl(this.label, this.input, this.description); if(this.schema.readOnly || this.schema.readonly) { this.always_disabled = true; this.input.disabled = true; } this.input.addEventListener('change',function(e) { e.preventDefault(); e.stopPropagation(); self.value = this.checked; self.onChange(true); }); this.container.appendChild(this.control); }, enable: function() { if(!this.always_disabled) { this.input.disabled = false; } this._super(); }, disable: function() { this.input.disabled = true; this._super(); }, destroy: function() { if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label); if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description); if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); this._super(); } }); JSONEditor.defaults.editors.arraySelectize = JSONEditor.AbstractEditor.extend({ build: function() { this.title = this.theme.getFormInputLabel(this.getTitle()); this.title_controls = this.theme.getHeaderButtonHolder(); this.title.appendChild(this.title_controls); this.error_holder = document.createElement('div'); if(this.schema.description) { this.description = this.theme.getDescription(this.schema.description); } this.input = document.createElement('select'); this.input.setAttribute('multiple', 'multiple'); var group = this.theme.getFormControl(this.title, this.input, this.description); this.container.appendChild(group); this.container.appendChild(this.error_holder); window.jQuery(this.input).selectize({ delimiter: false, createOnBlur: true, create: true }); }, postBuild: function() { var self = this; this.input.selectize.on('change', function(event) { self.refreshValue(); self.onChange(true); }); }, destroy: function() { this.empty(true); if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description); if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); this._super(); }, empty: function(hard) {}, setValue: function(value, initial) { var self = this; // Update the array's value, adding/removing rows when necessary value = value || []; if(!(Array.isArray(value))) value = [value]; this.input.selectize.clearOptions(); this.input.selectize.clear(true); value.forEach(function(item) { self.input.selectize.addOption({text: item, value: item}); }); this.input.selectize.setValue(value); this.refreshValue(initial); }, refreshValue: function(force) { this.value = this.input.selectize.getValue(); }, showValidationErrors: function(errors) { var self = this; // Get all the errors that pertain to this editor var my_errors = []; var other_errors = []; $each(errors, function(i,error) { if(error.path === self.path) { my_errors.push(error); } else { other_errors.push(error); } }); // Show errors for this editor if(this.error_holder) { if(my_errors.length) { var message = []; this.error_holder.innerHTML = ''; this.error_holder.style.display = ''; $each(my_errors, function(i,error) { self.error_holder.appendChild(self.theme.getErrorMessage(error.message)); }); } // Hide error area else { this.error_holder.style.display = 'none'; } } } }); var matchKey = (function () { var elem = document.documentElement; if (elem.matches) return 'matches'; else if (elem.webkitMatchesSelector) return 'webkitMatchesSelector'; else if (elem.mozMatchesSelector) return 'mozMatchesSelector'; else if (elem.msMatchesSelector) return 'msMatchesSelector'; else if (elem.oMatchesSelector) return 'oMatchesSelector'; })(); JSONEditor.AbstractTheme = Class.extend({ getContainer: function() { return document.createElement('div'); }, getFloatRightLinkHolder: function() { var el = document.createElement('div'); el.style = el.style || {}; el.style.cssFloat = 'right'; el.style.marginLeft = '10px'; return el; }, getModal: function() { var el = document.createElement('div'); el.style.backgroundColor = 'white'; el.style.border = '1px solid black'; el.style.boxShadow = '3px 3px black'; el.style.position = 'absolute'; el.style.zIndex = '10'; el.style.display = 'none'; return el; }, getGridContainer: function() { var el = document.createElement('div'); return el; }, getGridRow: function() { var el = document.createElement('div'); el.className = 'row'; return el; }, getGridColumn: function() { var el = document.createElement('div'); return el; }, setGridColumnSize: function(el,size) { }, getLink: function(text) { var el = document.createElement('a'); el.setAttribute('href','#'); el.appendChild(document.createTextNode(text)); return el; }, disableHeader: function(header) { header.style.color = '#ccc'; }, disableLabel: function(label) { label.style.color = '#ccc'; }, enableHeader: function(header) { header.style.color = ''; }, enableLabel: function(label) { label.style.color = ''; }, getFormInputLabel: function(text) { var el = document.createElement('label'); el.appendChild(document.createTextNode(text)); return el; }, getCheckboxLabel: function(text) { var el = this.getFormInputLabel(text); el.style.fontWeight = 'normal'; return el; }, getHeader: function(text) { var el = document.createElement('h3'); if(typeof text === "string") { el.textContent = text; } else { el.appendChild(text); } return el; }, getCheckbox: function() { var el = this.getFormInputField('checkbox'); el.style.display = 'inline-block'; el.style.width = 'auto'; return el; }, getMultiCheckboxHolder: function(controls,label,description) { var el = document.createElement('div'); if(label) { label.style.display = 'block'; el.appendChild(label); } for(var i in controls) { if(!controls.hasOwnProperty(i)) continue; controls[i].style.display = 'inline-block'; controls[i].style.marginRight = '20px'; el.appendChild(controls[i]); } if(description) el.appendChild(description); return el; }, getSelectInput: function(options) { var select = document.createElement('select'); if(options) this.setSelectOptions(select, options); return select; }, getSwitcher: function(options) { var switcher = this.getSelectInput(options); switcher.style.backgroundColor = 'transparent'; switcher.style.display = 'inline-block'; switcher.style.fontStyle = 'italic'; switcher.style.fontWeight = 'normal'; switcher.style.height = 'auto'; switcher.style.marginBottom = 0; switcher.style.marginLeft = '5px'; switcher.style.padding = '0 0 0 3px'; switcher.style.width = 'auto'; return switcher; }, getSwitcherOptions: function(switcher) { return switcher.getElementsByTagName('option'); }, setSwitcherOptions: function(switcher, options, titles) { this.setSelectOptions(switcher, options, titles); }, setSelectOptions: function(select, options, titles) { titles = titles || []; select.innerHTML = ''; for(var i=0; i<options.length; i++) { var option = document.createElement('option'); option.setAttribute('value',options[i]); option.textContent = titles[i] || options[i]; select.appendChild(option); } }, getTextareaInput: function() { var el = document.createElement('textarea'); el.style = el.style || {}; el.style.width = '100%'; el.style.height = '300px'; el.style.boxSizing = 'border-box'; return el; }, getRangeInput: function(min,max,step) { var el = this.getFormInputField('range'); el.setAttribute('min',min); el.setAttribute('max',max); el.setAttribute('step',step); return el; }, getFormInputField: function(type) { var el = document.createElement('input'); el.setAttribute('type',type); return el; }, afterInputReady: function(input) { }, getFormControl: function(label, input, description) { var el = document.createElement('div'); el.className = 'form-control'; if(label) el.appendChild(label); if(input.type === 'checkbox') { label.insertBefore(input,label.firstChild); } else { el.appendChild(input); } if(description) el.appendChild(description); return el; }, getIndentedPanel: function() { var el = document.createElement('div'); el.style = el.style || {}; el.style.paddingLeft = '10px'; el.style.marginLeft = '10px'; el.style.borderLeft = '1px solid #ccc'; return el; }, getChildEditorHolder: function() { return document.createElement('div'); }, getDescription: function(text) { var el = document.createElement('p'); el.innerHTML = text; return el; }, getCheckboxDescription: function(text) { return this.getDescription(text); }, getFormInputDescription: function(text) { return this.getDescription(text); }, getHeaderButtonHolder: function() { return this.getButtonHolder(); }, getButtonHolder: function() { return document.createElement('div'); }, getButton: function(text, icon, title) { var el = document.createElement('button'); el.type = 'button'; this.setButtonText(el,text,icon,title); return el; }, setButtonText: function(button, text, icon, title) { button.innerHTML = ''; if(icon) { button.appendChild(icon); button.innerHTML += ' '; } button.appendChild(document.createTextNode(text)); if(title) button.setAttribute('title',title); }, getTable: function() { return document.createElement('table'); }, getTableRow: function() { return document.createElement('tr'); }, getTableHead: function() { return document.createElement('thead'); }, getTableBody: function() { return document.createElement('tbody'); }, getTableHeaderCell: function(text) { var el = document.createElement('th'); el.textContent = text; return el; }, getTableCell: function() { var el = document.createElement('td'); return el; }, getErrorMessage: function(text) { var el = document.createElement('p'); el.style = el.style || {}; el.style.color = 'red'; el.appendChild(document.createTextNode(text)); return el; }, addInputError: function(input, text) { }, removeInputError: function(input) { }, addTableRowError: function(row) { }, removeTableRowError: function(row) { }, getTabHolder: function() { var el = document.createElement('div'); el.innerHTML = "<div style='float: left; width: 130px;' class='tabs'></div><div class='content' style='margin-left: 130px;'></div><div style='clear:both;'></div>"; return el; }, applyStyles: function(el,styles) { el.style = el.style || {}; for(var i in styles) { if(!styles.hasOwnProperty(i)) continue; el.style[i] = styles[i]; } }, closest: function(elem, selector) { while (elem && elem !== document) { if (matchKey) { if (elem[matchKey](selector)) { return elem; } else { elem = elem.parentNode; } } else { return false; } } return false; }, getTab: function(span) { var el = document.createElement('div'); el.appendChild(span); el.style = el.style || {}; this.applyStyles(el,{ border: '1px solid #ccc', borderWidth: '1px 0 1px 1px', textAlign: 'center', lineHeight: '30px', borderRadius: '5px', borderBottomRightRadius: 0, borderTopRightRadius: 0, fontWeight: 'bold', cursor: 'pointer' }); return el; }, getTabContentHolder: function(tab_holder) { return tab_holder.children[1]; }, getTabContent: function() { return this.getIndentedPanel(); }, markTabActive: function(tab) { this.applyStyles(tab,{ opacity: 1, background: 'white' }); }, markTabInactive: function(tab) { this.applyStyles(tab,{ opacity:0.5, background: '' }); }, addTab: function(holder, tab) { holder.children[0].appendChild(tab); }, getBlockLink: function() { var link = document.createElement('a'); link.style.display = 'block'; return link; }, getBlockLinkHolder: function() { var el = document.createElement('div'); return el; }, getLinksHolder: function() { var el = document.createElement('div'); return el; }, createMediaLink: function(holder,link,media) { holder.appendChild(link); media.style.width='100%'; holder.appendChild(media); }, createImageLink: function(holder,link,image) { holder.appendChild(link); link.appendChild(image); } }); JSONEditor.defaults.themes.bootstrap2 = JSONEditor.AbstractTheme.extend({ getRangeInput: function(min, max, step) { // TODO: use bootstrap slider return this._super(min, max, step); }, getGridContainer: function() { var el = document.createElement('div'); el.className = 'container-fluid'; return el; }, getGridRow: function() { var el = document.createElement('div'); el.className = 'row-fluid'; return el; }, getFormInputLabel: function(text) { var el = this._super(text); el.style.display = 'inline-block'; el.style.fontWeight = 'bold'; return el; }, setGridColumnSize: function(el,size) { el.className = 'span'+size; }, getSelectInput: function(options) { var input = this._super(options); input.style.width = 'auto'; input.style.maxWidth = '98%'; return input; }, getFormInputField: function(type) { var el = this._super(type); el.style.width = '98%'; return el; }, afterInputReady: function(input) { if(input.controlgroup) return; input.controlgroup = this.closest(input,'.control-group'); input.controls = this.closest(input,'.controls'); if(this.closest(input,'.compact')) { input.controlgroup.className = input.controlgroup.className.replace(/control-group/g,'').replace(/[ ]{2,}/g,' '); input.controls.className = input.controlgroup.className.replace(/controls/g,'').replace(/[ ]{2,}/g,' '); input.style.marginBottom = 0; } // TODO: use bootstrap slider }, getIndentedPanel: function() { var el = document.createElement('div'); el.className = 'well well-small'; el.style.paddingBottom = 0; return el; }, getFormInputDescription: function(text) { var el = document.createElement('p'); el.className = 'help-inline'; el.textContent = text; return el; }, getFormControl: function(label, input, description) { var ret = document.createElement('div'); ret.className = 'control-group'; var controls = document.createElement('div'); controls.className = 'controls'; if(label && input.getAttribute('type') === 'checkbox') { ret.appendChild(controls); label.className += ' checkbox'; label.appendChild(input); controls.appendChild(label); controls.style.height = '30px'; } else { if(label) { label.className += ' control-label'; ret.appendChild(label); } controls.appendChild(input); ret.appendChild(controls); } if(description) controls.appendChild(description); return ret; }, getHeaderButtonHolder: function() { var el = this.getButtonHolder(); el.style.marginLeft = '10px'; return el; }, getButtonHolder: function() { var el = document.createElement('div'); el.className = 'btn-group'; return el; }, getButton: function(text, icon, title) { var el = this._super(text, icon, title); el.className += ' btn btn-default'; return el; }, getTable: function() { var el = document.createElement('table'); el.className = 'table table-bordered'; el.style.width = 'auto'; el.style.maxWidth = 'none'; return el; }, addInputError: function(input,text) { if(!input.controlgroup || !input.controls) return; input.controlgroup.className += ' error'; if(!input.errmsg) { input.errmsg = document.createElement('p'); input.errmsg.className = 'help-block errormsg'; input.controls.appendChild(input.errmsg); } else { input.errmsg.style.display = ''; } input.errmsg.textContent = text; }, removeInputError: function(input) { if(!input.errmsg) return; input.errmsg.style.display = 'none'; input.controlgroup.className = input.controlgroup.className.replace(/\s?error/g,''); }, getTabHolder: function() { var el = document.createElement('div'); el.className = 'tabbable tabs-left'; el.innerHTML = "<ul class='nav nav-tabs span2' style='margin-right: 0;'></ul><div class='tab-content span10' style='overflow:visible;'></div>"; return el; }, getTab: function(text) { var el = document.createElement('li'); var a = document.createElement('a'); a.setAttribute('href','#'); a.appendChild(text); el.appendChild(a); return el; }, getTabContentHolder: function(tab_holder) { return tab_holder.children[1]; }, getTabContent: function() { var el = document.createElement('div'); el.className = 'tab-pane active'; return el; }, markTabActive: function(tab) { tab.className += ' active'; }, markTabInactive: function(tab) { tab.className = tab.className.replace(/\s?active/g,''); }, addTab: function(holder, tab) { holder.children[0].appendChild(tab); }, getProgressBar: function() { var container = document.createElement('div'); container.className = 'progress'; var bar = document.createElement('div'); bar.className = 'bar'; bar.style.width = '0%'; container.appendChild(bar); return container; }, updateProgressBar: function(progressBar, progress) { if (!progressBar) return; progressBar.firstChild.style.width = progress + "%"; }, updateProgressBarUnknown: function(progressBar) { if (!progressBar) return; progressBar.className = 'progress progress-striped active'; progressBar.firstChild.style.width = '100%'; } }); JSONEditor.defaults.themes.bootstrap3 = JSONEditor.AbstractTheme.extend({ getSelectInput: function(options) { var el = this._super(options); el.className += 'form-control'; //el.style.width = 'auto'; return el; }, setGridColumnSize: function(el,size) { el.className = 'col-md-'+size; }, afterInputReady: function(input) { if(input.controlgroup) return; input.controlgroup = this.closest(input,'.form-group'); if(this.closest(input,'.compact')) { input.controlgroup.style.marginBottom = 0; } // TODO: use bootstrap slider }, getTextareaInput: function() { var el = document.createElement('textarea'); el.className = 'form-control'; return el; }, getRangeInput: function(min, max, step) { // TODO: use better slider return this._super(min, max, step); }, getFormInputField: function(type) { var el = this._super(type); if(type !== 'checkbox') { el.className += 'form-control'; } return el; }, getFormControl: function(label, input, description) { var group = document.createElement('div'); if(label && input.type === 'checkbox') { group.className += ' checkbox'; label.appendChild(input); label.style.fontSize = '14px'; group.style.marginTop = '0'; group.appendChild(label); input.style.position = 'relative'; input.style.cssFloat = 'left'; } else { group.className += ' form-group'; if(label) { label.className += ' control-label'; group.appendChild(label); } group.appendChild(input); } if(description) group.appendChild(description); return group; }, getIndentedPanel: function() { var el = document.createElement('div'); el.className = 'well well-sm'; el.style.paddingBottom = 0; return el; }, getFormInputDescription: function(text) { var el = document.createElement('p'); el.className = 'help-block'; el.innerHTML = text; return el; }, getHeaderButtonHolder: function() { var el = this.getButtonHolder(); el.style.marginLeft = '10px'; return el; }, getButtonHolder: function() { var el = document.createElement('div'); el.className = 'btn-group'; return el; }, getButton: function(text, icon, title) { var el = this._super(text, icon, title); el.className += 'btn btn-default'; return el; }, getTable: function() { var el = document.createElement('table'); el.className = 'table table-bordered'; el.style.width = 'auto'; el.style.maxWidth = 'none'; return el; }, addInputError: function(input,text) { if(!input.controlgroup) return; input.controlgroup.className += ' has-error'; if(!input.errmsg) { input.errmsg = document.createElement('p'); input.errmsg.className = 'help-block errormsg'; input.controlgroup.appendChild(input.errmsg); } else { input.errmsg.style.display = ''; } input.errmsg.textContent = text; }, removeInputError: function(input) { if(!input.errmsg) return; input.errmsg.style.display = 'none'; input.controlgroup.className = input.controlgroup.className.replace(/\s?has-error/g,''); }, getTabHolder: function() { var el = document.createElement('div'); el.innerHTML = "<div class='tabs list-group col-md-2'></div><div class='col-md-10'></div>"; el.className = 'rows'; return el; }, getTab: function(text) { var el = document.createElement('a'); el.className = 'list-group-item'; el.setAttribute('href','#'); el.appendChild(text); return el; }, markTabActive: function(tab) { tab.className += ' active'; }, markTabInactive: function(tab) { tab.className = tab.className.replace(/\s?active/g,''); }, getProgressBar: function() { var min = 0, max = 100, start = 0; var container = document.createElement('div'); container.className = 'progress'; var bar = document.createElement('div'); bar.className = 'progress-bar'; bar.setAttribute('role', 'progressbar'); bar.setAttribute('aria-valuenow', start); bar.setAttribute('aria-valuemin', min); bar.setAttribute('aria-valuenax', max); bar.innerHTML = start + "%"; container.appendChild(bar); return container; }, updateProgressBar: function(progressBar, progress) { if (!progressBar) return; var bar = progressBar.firstChild; var percentage = progress + "%"; bar.setAttribute('aria-valuenow', progress); bar.style.width = percentage; bar.innerHTML = percentage; }, updateProgressBarUnknown: function(progressBar) { if (!progressBar) return; var bar = progressBar.firstChild; progressBar.className = 'progress progress-striped active'; bar.removeAttribute('aria-valuenow'); bar.style.width = '100%'; bar.innerHTML = ''; } }); // Base Foundation theme JSONEditor.defaults.themes.foundation = JSONEditor.AbstractTheme.extend({ getChildEditorHolder: function() { var el = document.createElement('div'); el.style.marginBottom = '15px'; return el; }, getSelectInput: function(options) { var el = this._super(options); el.style.minWidth = 'none'; el.style.padding = '5px'; el.style.marginTop = '3px'; return el; }, getSwitcher: function(options) { var el = this._super(options); el.style.paddingRight = '8px'; return el; }, afterInputReady: function(input) { if(this.closest(input,'.compact')) { input.style.marginBottom = 0; } input.group = this.closest(input,'.form-control'); }, getFormInputLabel: function(text) { var el = this._super(text); el.style.display = 'inline-block'; return el; }, getFormInputField: function(type) { var el = this._super(type); el.style.width = '100%'; el.style.marginBottom = type==='checkbox'? '0' : '12px'; return el; }, getFormInputDescription: function(text) { var el = document.createElement('p'); el.textContent = text; el.style.marginTop = '-10px'; el.style.fontStyle = 'italic'; return el; }, getIndentedPanel: function() { var el = document.createElement('div'); el.className = 'panel'; el.style.paddingBottom = 0; return el; }, getHeaderButtonHolder: function() { var el = this.getButtonHolder(); el.style.display = 'inline-block'; el.style.marginLeft = '10px'; el.style.verticalAlign = 'middle'; return el; }, getButtonHolder: function() { var el = document.createElement('div'); el.className = 'button-group'; return el; }, getButton: function(text, icon, title) { var el = this._super(text, icon, title); el.className += ' small button'; return el; }, addInputError: function(input,text) { if(!input.group) return; input.group.className += ' error'; if(!input.errmsg) { input.insertAdjacentHTML('afterend','<small class="error"></small>'); input.errmsg = input.parentNode.getElementsByClassName('error')[0]; } else { input.errmsg.style.display = ''; } input.errmsg.textContent = text; }, removeInputError: function(input) { if(!input.errmsg) return; input.group.className = input.group.className.replace(/ error/g,''); input.errmsg.style.display = 'none'; }, getProgressBar: function() { var progressBar = document.createElement('div'); progressBar.className = 'progress'; var meter = document.createElement('span'); meter.className = 'meter'; meter.style.width = '0%'; progressBar.appendChild(meter); return progressBar; }, updateProgressBar: function(progressBar, progress) { if (!progressBar) return; progressBar.firstChild.style.width = progress + '%'; }, updateProgressBarUnknown: function(progressBar) { if (!progressBar) return; progressBar.firstChild.style.width = '100%'; } }); // Foundation 3 Specific Theme JSONEditor.defaults.themes.foundation3 = JSONEditor.defaults.themes.foundation.extend({ getHeaderButtonHolder: function() { var el = this._super(); el.style.fontSize = '.6em'; return el; }, getFormInputLabel: function(text) { var el = this._super(text); el.style.fontWeight = 'bold'; return el; }, getTabHolder: function() { var el = document.createElement('div'); el.className = 'row'; el.innerHTML = "<dl class='tabs vertical two columns'></dl><div class='tabs-content ten columns'></div>"; return el; }, setGridColumnSize: function(el,size) { var sizes = ['zero','one','two','three','four','five','six','seven','eight','nine','ten','eleven','twelve']; el.className = 'columns '+sizes[size]; }, getTab: function(text) { var el = document.createElement('dd'); var a = document.createElement('a'); a.setAttribute('href','#'); a.appendChild(text); el.appendChild(a); return el; }, getTabContentHolder: function(tab_holder) { return tab_holder.children[1]; }, getTabContent: function() { var el = document.createElement('div'); el.className = 'content active'; el.style.paddingLeft = '5px'; return el; }, markTabActive: function(tab) { tab.className += ' active'; }, markTabInactive: function(tab) { tab.className = tab.className.replace(/\s*active/g,''); }, addTab: function(holder, tab) { holder.children[0].appendChild(tab); } }); // Foundation 4 Specific Theme JSONEditor.defaults.themes.foundation4 = JSONEditor.defaults.themes.foundation.extend({ getHeaderButtonHolder: function() { var el = this._super(); el.style.fontSize = '.6em'; return el; }, setGridColumnSize: function(el,size) { el.className = 'columns large-'+size; }, getFormInputDescription: function(text) { var el = this._super(text); el.style.fontSize = '.8rem'; return el; }, getFormInputLabel: function(text) { var el = this._super(text); el.style.fontWeight = 'bold'; return el; } }); // Foundation 5 Specific Theme JSONEditor.defaults.themes.foundation5 = JSONEditor.defaults.themes.foundation.extend({ getFormInputDescription: function(text) { var el = this._super(text); el.style.fontSize = '.8rem'; return el; }, setGridColumnSize: function(el,size) { el.className = 'columns medium-'+size; }, getButton: function(text, icon, title) { var el = this._super(text,icon,title); el.className = el.className.replace(/\s*small/g,'') + ' tiny'; return el; }, getTabHolder: function() { var el = document.createElement('div'); el.innerHTML = "<dl class='tabs vertical'></dl><div class='tabs-content vertical'></div>"; return el; }, getTab: function(text) { var el = document.createElement('dd'); var a = document.createElement('a'); a.setAttribute('href','#'); a.appendChild(text); el.appendChild(a); return el; }, getTabContentHolder: function(tab_holder) { return tab_holder.children[1]; }, getTabContent: function() { var el = document.createElement('div'); el.className = 'content active'; el.style.paddingLeft = '5px'; return el; }, markTabActive: function(tab) { tab.className += ' active'; }, markTabInactive: function(tab) { tab.className = tab.className.replace(/\s*active/g,''); }, addTab: function(holder, tab) { holder.children[0].appendChild(tab); } }); JSONEditor.defaults.themes.html = JSONEditor.AbstractTheme.extend({ getFormInputLabel: function(text) { var el = this._super(text); el.style.display = 'block'; el.style.marginBottom = '3px'; el.style.fontWeight = 'bold'; return el; }, getFormInputDescription: function(text) { var el = this._super(text); el.style.fontSize = '.8em'; el.style.margin = 0; el.style.display = 'inline-block'; el.style.fontStyle = 'italic'; return el; }, getIndentedPanel: function() { var el = this._super(); el.style.border = '1px solid #ddd'; el.style.padding = '5px'; el.style.margin = '5px'; el.style.borderRadius = '3px'; return el; }, getChildEditorHolder: function() { var el = this._super(); el.style.marginBottom = '8px'; return el; }, getHeaderButtonHolder: function() { var el = this.getButtonHolder(); el.style.display = 'inline-block'; el.style.marginLeft = '10px'; el.style.fontSize = '.8em'; el.style.verticalAlign = 'middle'; return el; }, getTable: function() { var el = this._super(); el.style.borderBottom = '1px solid #ccc'; el.style.marginBottom = '5px'; return el; }, addInputError: function(input, text) { input.style.borderColor = 'red'; if(!input.errmsg) { var group = this.closest(input,'.form-control'); input.errmsg = document.createElement('div'); input.errmsg.setAttribute('class','errmsg'); input.errmsg.style = input.errmsg.style || {}; input.errmsg.style.color = 'red'; group.appendChild(input.errmsg); } else { input.errmsg.style.display = 'block'; } input.errmsg.innerHTML = ''; input.errmsg.appendChild(document.createTextNode(text)); }, removeInputError: function(input) { input.style.borderColor = ''; if(input.errmsg) input.errmsg.style.display = 'none'; }, getProgressBar: function() { var max = 100, start = 0; var progressBar = document.createElement('progress'); progressBar.setAttribute('max', max); progressBar.setAttribute('value', start); return progressBar; }, updateProgressBar: function(progressBar, progress) { if (!progressBar) return; progressBar.setAttribute('value', progress); }, updateProgressBarUnknown: function(progressBar) { if (!progressBar) return; progressBar.removeAttribute('value'); } }); JSONEditor.defaults.themes.jqueryui = JSONEditor.AbstractTheme.extend({ getTable: function() { var el = this._super(); el.setAttribute('cellpadding',5); el.setAttribute('cellspacing',0); return el; }, getTableHeaderCell: function(text) { var el = this._super(text); el.className = 'ui-state-active'; el.style.fontWeight = 'bold'; return el; }, getTableCell: function() { var el = this._super(); el.className = 'ui-widget-content'; return el; }, getHeaderButtonHolder: function() { var el = this.getButtonHolder(); el.style.marginLeft = '10px'; el.style.fontSize = '.6em'; el.style.display = 'inline-block'; return el; }, getFormInputDescription: function(text) { var el = this.getDescription(text); el.style.marginLeft = '10px'; el.style.display = 'inline-block'; return el; }, getFormControl: function(label, input, description) { var el = this._super(label,input,description); if(input.type === 'checkbox') { el.style.lineHeight = '25px'; el.style.padding = '3px 0'; } else { el.style.padding = '4px 0 8px 0'; } return el; }, getDescription: function(text) { var el = document.createElement('span'); el.style.fontSize = '.8em'; el.style.fontStyle = 'italic'; el.textContent = text; return el; }, getButtonHolder: function() { var el = document.createElement('div'); el.className = 'ui-buttonset'; el.style.fontSize = '.7em'; return el; }, getFormInputLabel: function(text) { var el = document.createElement('label'); el.style.fontWeight = 'bold'; el.style.display = 'block'; el.textContent = text; return el; }, getButton: function(text, icon, title) { var button = document.createElement("button"); button.className = 'ui-button ui-widget ui-state-default ui-corner-all'; // Icon only if(icon && !text) { button.className += ' ui-button-icon-only'; icon.className += ' ui-button-icon-primary ui-icon-primary'; button.appendChild(icon); } // Icon and Text else if(icon) { button.className += ' ui-button-text-icon-primary'; icon.className += ' ui-button-icon-primary ui-icon-primary'; button.appendChild(icon); } // Text only else { button.className += ' ui-button-text-only'; } var el = document.createElement('span'); el.className = 'ui-button-text'; el.textContent = text||title||"."; button.appendChild(el); button.setAttribute('title',title); return button; }, setButtonText: function(button,text, icon, title) { button.innerHTML = ''; button.className = 'ui-button ui-widget ui-state-default ui-corner-all'; // Icon only if(icon && !text) { button.className += ' ui-button-icon-only'; icon.className += ' ui-button-icon-primary ui-icon-primary'; button.appendChild(icon); } // Icon and Text else if(icon) { button.className += ' ui-button-text-icon-primary'; icon.className += ' ui-button-icon-primary ui-icon-primary'; button.appendChild(icon); } // Text only else { button.className += ' ui-button-text-only'; } var el = document.createElement('span'); el.className = 'ui-button-text'; el.textContent = text||title||"."; button.appendChild(el); button.setAttribute('title',title); }, getIndentedPanel: function() { var el = document.createElement('div'); el.className = 'ui-widget-content ui-corner-all'; el.style.padding = '1em 1.4em'; el.style.marginBottom = '20px'; return el; }, afterInputReady: function(input) { if(input.controls) return; input.controls = this.closest(input,'.form-control'); }, addInputError: function(input,text) { if(!input.controls) return; if(!input.errmsg) { input.errmsg = document.createElement('div'); input.errmsg.className = 'ui-state-error'; input.controls.appendChild(input.errmsg); } else { input.errmsg.style.display = ''; } input.errmsg.textContent = text; }, removeInputError: function(input) { if(!input.errmsg) return; input.errmsg.style.display = 'none'; }, markTabActive: function(tab) { tab.className = tab.className.replace(/\s*ui-widget-header/g,'')+' ui-state-active'; }, markTabInactive: function(tab) { tab.className = tab.className.replace(/\s*ui-state-active/g,'')+' ui-widget-header'; } }); JSONEditor.AbstractIconLib = Class.extend({ mapping: { collapse: '', expand: '', "delete": '', edit: '', add: '', cancel: '', save: '', moveup: '', movedown: '' }, icon_prefix: '', getIconClass: function(key) { if(this.mapping[key]) return this.icon_prefix+this.mapping[key]; else return null; }, getIcon: function(key) { var iconclass = this.getIconClass(key); if(!iconclass) return null; var i = document.createElement('i'); i.className = iconclass; return i; } }); JSONEditor.defaults.iconlibs.bootstrap2 = JSONEditor.AbstractIconLib.extend({ mapping: { collapse: 'chevron-down', expand: 'chevron-up', "delete": 'trash', edit: 'pencil', add: 'plus', cancel: 'ban-circle', save: 'ok', moveup: 'arrow-up', movedown: 'arrow-down' }, icon_prefix: 'icon-' }); JSONEditor.defaults.iconlibs.bootstrap3 = JSONEditor.AbstractIconLib.extend({ mapping: { collapse: 'chevron-down', expand: 'chevron-right', "delete": 'remove', edit: 'pencil', add: 'plus', cancel: 'floppy-remove', save: 'floppy-saved', moveup: 'arrow-up', movedown: 'arrow-down' }, icon_prefix: 'glyphicon glyphicon-' }); JSONEditor.defaults.iconlibs.fontawesome3 = JSONEditor.AbstractIconLib.extend({ mapping: { collapse: 'chevron-down', expand: 'chevron-right', "delete": 'remove', edit: 'pencil', add: 'plus', cancel: 'ban-circle', save: 'save', moveup: 'arrow-up', movedown: 'arrow-down' }, icon_prefix: 'icon-' }); JSONEditor.defaults.iconlibs.fontawesome4 = JSONEditor.AbstractIconLib.extend({ mapping: { collapse: 'caret-square-o-down', expand: 'caret-square-o-right', "delete": 'times', edit: 'pencil', add: 'plus', cancel: 'ban', save: 'save', moveup: 'arrow-up', movedown: 'arrow-down' }, icon_prefix: 'fa fa-' }); JSONEditor.defaults.iconlibs.foundation2 = JSONEditor.AbstractIconLib.extend({ mapping: { collapse: 'minus', expand: 'plus', "delete": 'remove', edit: 'edit', add: 'add-doc', cancel: 'error', save: 'checkmark', moveup: 'up-arrow', movedown: 'down-arrow' }, icon_prefix: 'foundicon-' }); JSONEditor.defaults.iconlibs.foundation3 = JSONEditor.AbstractIconLib.extend({ mapping: { collapse: 'minus', expand: 'plus', "delete": 'x', edit: 'pencil', add: 'page-add', cancel: 'x-circle', save: 'save', moveup: 'arrow-up', movedown: 'arrow-down' }, icon_prefix: 'fi-' }); JSONEditor.defaults.iconlibs.jqueryui = JSONEditor.AbstractIconLib.extend({ mapping: { collapse: 'triangle-1-s', expand: 'triangle-1-e', "delete": 'trash', edit: 'pencil', add: 'plusthick', cancel: 'closethick', save: 'disk', moveup: 'arrowthick-1-n', movedown: 'arrowthick-1-s' }, icon_prefix: 'ui-icon ui-icon-' }); JSONEditor.defaults.templates["default"] = function() { return { compile: function(template) { var matches = template.match(/{{\s*([a-zA-Z0-9\-_ \.]+)\s*}}/g); var l = matches && matches.length; // Shortcut if the template contains no variables if(!l) return function() { return template; }; // Pre-compute the search/replace functions // This drastically speeds up template execution var replacements = []; var get_replacement = function(i) { var p = matches[i].replace(/[{}]+/g,'').trim().split('.'); var n = p.length; var func; if(n > 1) { var cur; func = function(vars) { cur = vars; for(i=0; i<n; i++) { cur = cur[p[i]]; if(!cur) break; } return cur; }; } else { p = p[0]; func = function(vars) { return vars[p]; }; } replacements.push({ s: matches[i], r: func }); }; for(var i=0; i<l; i++) { get_replacement(i); } // The compiled function return function(vars) { var ret = template+""; var r; for(i=0; i<l; i++) { r = replacements[i]; ret = ret.replace(r.s, r.r(vars)); } return ret; }; } }; }; JSONEditor.defaults.templates.ejs = function() { if(!window.EJS) return false; return { compile: function(template) { var compiled = new window.EJS({ text: template }); return function(context) { return compiled.render(context); }; } }; }; JSONEditor.defaults.templates.handlebars = function() { return window.Handlebars; }; JSONEditor.defaults.templates.hogan = function() { if(!window.Hogan) return false; return { compile: function(template) { var compiled = window.Hogan.compile(template); return function(context) { return compiled.render(context); }; } }; }; JSONEditor.defaults.templates.markup = function() { if(!window.Mark || !window.Mark.up) return false; return { compile: function(template) { return function(context) { return window.Mark.up(template,context); }; } }; }; JSONEditor.defaults.templates.mustache = function() { if(!window.Mustache) return false; return { compile: function(template) { return function(view) { return window.Mustache.render(template, view); }; } }; }; JSONEditor.defaults.templates.swig = function() { return window.swig; }; JSONEditor.defaults.templates.underscore = function() { if(!window._) return false; return { compile: function(template) { return function(context) { return window._.template(template, context); }; } }; }; // Set the default theme JSONEditor.defaults.theme = 'html'; // Set the default template engine JSONEditor.defaults.template = 'default'; // Default options when initializing JSON Editor JSONEditor.defaults.options = {}; // String translate function JSONEditor.defaults.translate = function(key, variables) { var lang = JSONEditor.defaults.languages[JSONEditor.defaults.language]; if(!lang) throw "Unknown language "+JSONEditor.defaults.language; var string = lang[key] || JSONEditor.defaults.languages[JSONEditor.defaults.default_language][key]; if(typeof string === "undefined") throw "Unknown translate string "+key; if(variables) { for(var i=0; i<variables.length; i++) { string = string.replace(new RegExp('\\{\\{'+i+'}}','g'),variables[i]); } } return string; }; // Translation strings and default languages JSONEditor.defaults.default_language = 'en'; JSONEditor.defaults.language = JSONEditor.defaults.default_language; JSONEditor.defaults.languages.en = { /** * When a property is not set */ error_notset: "Property must be set", /** * When a string must not be empty */ error_notempty: "Value required", /** * When a value is not one of the enumerated values */ error_enum: "Value must be one of the enumerated values", /** * When a value doesn't validate any schema of a 'anyOf' combination */ error_anyOf: "Value must validate against at least one of the provided schemas", /** * When a value doesn't validate * @variables This key takes one variable: The number of schemas the value does not validate */ error_oneOf: 'Value must validate against exactly one of the provided schemas. It currently validates against {{0}} of the schemas.', /** * When a value does not validate a 'not' schema */ error_not: "Value must not validate against the provided schema", /** * When a value does not match any of the provided types */ error_type_union: "Value must be one of the provided types", /** * When a value does not match the given type * @variables This key takes one variable: The type the value should be of */ error_type: "Value must be of type {{0}}", /** * When the value validates one of the disallowed types */ error_disallow_union: "Value must not be one of the provided disallowed types", /** * When the value validates a disallowed type * @variables This key takes one variable: The type the value should not be of */ error_disallow: "Value must not be of type {{0}}", /** * When a value is not a multiple of or divisible by a given number * @variables This key takes one variable: The number mentioned above */ error_multipleOf: "Value must be a multiple of {{0}}", /** * When a value is greater than it's supposed to be (exclusive) * @variables This key takes one variable: The maximum */ error_maximum_excl: "Value must be less than {{0}}", /** * When a value is greater than it's supposed to be (inclusive * @variables This key takes one variable: The maximum */ error_maximum_incl: "Value must be at most {{0}}", /** * When a value is lesser than it's supposed to be (exclusive) * @variables This key takes one variable: The minimum */ error_minimum_excl: "Value must be greater than {{0}}", /** * When a value is lesser than it's supposed to be (inclusive) * @variables This key takes one variable: The minimum */ error_minimum_incl: "Value must be at least {{0}}", /** * When a value have too many characters * @variables This key takes one variable: The maximum character count */ error_maxLength: "Value must be at most {{0}} characters long", /** * When a value does not have enough characters * @variables This key takes one variable: The minimum character count */ error_minLength: "Value must be at least {{0}} characters long", /** * When a value does not match a given pattern */ error_pattern: "Value must match the pattern {{0}}", /** * When an array has additional items whereas it is not supposed to */ error_additionalItems: "No additional items allowed in this array", /** * When there are to many items in an array * @variables This key takes one variable: The maximum item count */ error_maxItems: "Value must have at most {{0}} items", /** * When there are not enough items in an array * @variables This key takes one variable: The minimum item count */ error_minItems: "Value must have at least {{0}} items", /** * When an array is supposed to have unique items but has duplicates */ error_uniqueItems: "Array must have unique items", /** * When there are too many properties in an object * @variables This key takes one variable: The maximum property count */ error_maxProperties: "Object must have at most {{0}} properties", /** * When there are not enough properties in an object * @variables This key takes one variable: The minimum property count */ error_minProperties: "Object must have at least {{0}} properties", /** * When a required property is not defined * @variables This key takes one variable: The name of the missing property */ error_required: "Object is missing the required property '{{0}}'", /** * When there is an additional property is set whereas there should be none * @variables This key takes one variable: The name of the additional property */ error_additional_properties: "No additional properties allowed, but property {{0}} is set", /** * When a dependency is not resolved * @variables This key takes one variable: The name of the missing property for the dependency */ error_dependency: "Must have property {{0}}", /** * Text on Delete All buttons */ button_delete_all: "All", /** * Title on Delete All buttons */ button_delete_all_title: "Delete All", /** * Text on Delete Last buttons * @variable This key takes one variable: The title of object to delete */ button_delete_last: "Last {{0}}", /** * Title on Delete Last buttons * @variable This key takes one variable: The title of object to delete */ button_delete_last_title: "Delete Last {{0}}", /** * Title on Add Row buttons * @variable This key takes one variable: The title of object to add */ button_add_row_title: "Add {{0}}", /** * Title on Move Down buttons */ button_move_down_title: "Move down", /** * Title on Move Up buttons */ button_move_up_title: "Move up", /** * Title on Delete Row buttons * @variable This key takes one variable: The title of object to delete */ button_delete_row_title: "Delete {{0}}", /** * Title on Delete Row buttons, short version (no parameter with the object title) */ button_delete_row_title_short: "Delete", /** * Title on Collapse buttons */ button_collapse: "Collapse" }; // Miscellaneous Plugin Settings JSONEditor.plugins = { ace: { theme: '' }, epiceditor: { }, sceditor: { }, select2: { }, selectize: { } }; // Default per-editor options for(var i in JSONEditor.defaults.editors) { if(!JSONEditor.defaults.editors.hasOwnProperty(i)) continue; JSONEditor.defaults.editors[i].options = JSONEditor.defaults.editors.options || {}; } // Set the default resolvers // Use "multiple" as a fall back for everything JSONEditor.defaults.resolvers.unshift(function(schema) { if(typeof schema.type !== "string") return "multiple"; }); // If the type is not set but properties are defined, we can infer the type is actually object JSONEditor.defaults.resolvers.unshift(function(schema) { // If the schema is a simple type if(!schema.type && schema.properties ) return "object"; }); // If the type is set and it's a basic type, use the primitive editor JSONEditor.defaults.resolvers.unshift(function(schema) { // If the schema is a simple type if(typeof schema.type === "string") return schema.type; }); // Boolean editors JSONEditor.defaults.resolvers.unshift(function(schema) { if(schema.type === 'boolean') { // If explicitly set to 'checkbox', use that if(schema.format === "checkbox" || (schema.options && schema.options.checkbox)) { return "checkbox"; } // Otherwise, default to select menu return (JSONEditor.plugins.selectize.enable) ? 'selectize' : 'select'; } }); // Use the multiple editor for schemas where the `type` is set to "any" JSONEditor.defaults.resolvers.unshift(function(schema) { // If the schema can be of any type if(schema.type === "any") return "multiple"; }); // Editor for base64 encoded files JSONEditor.defaults.resolvers.unshift(function(schema) { // If the schema can be of any type if(schema.type === "string" && schema.media && schema.media.binaryEncoding==="base64") { return "base64"; } }); // Editor for uploading files JSONEditor.defaults.resolvers.unshift(function(schema) { if(schema.type === "string" && schema.format === "url" && schema.options && schema.options.upload === true) { if(window.FileReader) return "upload"; } }); // Use the table editor for arrays with the format set to `table` JSONEditor.defaults.resolvers.unshift(function(schema) { // Type `array` with format set to `table` if(schema.type == "array" && schema.format == "table") { return "table"; } }); // Use the `select` editor for dynamic enumSource enums JSONEditor.defaults.resolvers.unshift(function(schema) { if(schema.enumSource) return (JSONEditor.plugins.selectize.enable) ? 'selectize' : 'select'; }); // Use the `enum` or `select` editors for schemas with enumerated properties JSONEditor.defaults.resolvers.unshift(function(schema) { if(schema["enum"]) { if(schema.type === "array" || schema.type === "object") { return "enum"; } else if(schema.type === "number" || schema.type === "integer" || schema.type === "string") { return (JSONEditor.plugins.selectize.enable) ? 'selectize' : 'select'; } } }); // Specialized editors for arrays of strings JSONEditor.defaults.resolvers.unshift(function(schema) { if(schema.type === "array" && schema.items && !(Array.isArray(schema.items)) && schema.uniqueItems && ['string','number','integer'].indexOf(schema.items.type) >= 0) { // For enumerated strings, number, or integers if(schema.items.enum) { return 'multiselect'; } // For non-enumerated strings (tag editor) else if(JSONEditor.plugins.selectize.enable && schema.items.type === "string") { return 'arraySelectize'; } } }); // Use the multiple editor for schemas with `oneOf` set JSONEditor.defaults.resolvers.unshift(function(schema) { // If this schema uses `oneOf` if(schema.oneOf) return "multiple"; }); /** * This is a small wrapper for using JSON Editor like a typical jQuery plugin. */ (function() { if(window.jQuery || window.Zepto) { var $ = window.jQuery || window.Zepto; $.jsoneditor = JSONEditor.defaults; $.fn.jsoneditor = function(options) { var self = this; var editor = this.data('jsoneditor'); if(options === 'value') { if(!editor) throw "Must initialize jsoneditor before getting/setting the value"; // Set value if(arguments.length > 1) { editor.setValue(arguments[1]); } // Get value else { return editor.getValue(); } } else if(options === 'validate') { if(!editor) throw "Must initialize jsoneditor before validating"; // Validate a specific value if(arguments.length > 1) { return editor.validate(arguments[1]); } // Validate current value else { return editor.validate(); } } else if(options === 'destroy') { if(editor) { editor.destroy(); this.data('jsoneditor',null); } } else { // Destroy first if(editor) { editor.destroy(); } // Create editor editor = new JSONEditor(this.get(0),options); this.data('jsoneditor',editor); // Setup event listeners editor.on('change',function() { self.trigger('change'); }); editor.on('ready',function() { self.trigger('ready'); }); } return this; }; } })(); window.JSONEditor = JSONEditor; })(); //# sourceMappingURL=jsoneditor.js.map