| /*! JSON Editor v0.7.23 - JSON Schema -> HTML Editor | |
| * By Jeremy Dorn - https://github.com/jdorn/json-editor/ | |
| * Released under the MIT license | |
| * | |
| * Date: 2015-09-27 | |
| */ | |
| /** | |
| * 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) { | |
| valid = value / (schema.multipleOf || schema.divisibleBy); | |
| if (valid !== Math.floor(valid)) { | |
| errors.push({ | |
| path: path, | |
| property: schema.multipleOf ? 'multipleOf' : 'divisibleBy', | |
| message: this.translate('error_multipleOf', [schema.multipleOf || schema.divisibleBy]) | |
| }); | |
| } | |
| } | |
| // `maximum` | |
| if (schema.hasOwnProperty('maximum')) { | |
| if (schema.exclusiveMaximum && value >= schema.maximum) { | |
| errors.push({ | |
| path: path, | |
| property: 'maximum', | |
| message: this.translate('error_maximum_excl', [schema.maximum]) | |
| }); | |
| } else if (!schema.exclusiveMaximum && value > schema.maximum) { | |
| errors.push({ | |
| path: path, | |
| property: 'maximum', | |
| message: this.translate('error_maximum_incl', [schema.maximum]) | |
| }); | |
| } | |
| } | |
| // `minimum` | |
| if (schema.hasOwnProperty('minimum')) { | |
| if (schema.exclusiveMinimum && value <= schema.minimum) { | |
| errors.push({ | |
| path: path, | |
| property: 'minimum', | |
| message: this.translate('error_minimum_excl', [schema.minimum]) | |
| }); | |
| } else if (!schema.exclusiveMinimum && value < schema.minimum) { | |
| errors.push({ | |
| path: path, | |
| property: 'minimum', | |
| message: this.translate('error_minimum_incl', [schema.minimum]) | |
| }); | |
| } | |
| } | |
| } | |
| // String specific validation | |
| else if (typeof value === "string") { | |
| // `maxLength` | |
| if (schema.maxLength) { | |
| if ((value + "").length > schema.maxLength) { | |
| errors.push({ | |
| path: path, | |
| property: 'maxLength', | |
| message: this.translate('error_maxLength', [schema.maxLength]) | |
| }); | |
| } | |
| } | |
| // `minLength` | |
| if (schema.minLength) { | |
| if ((value + "").length < schema.minLength) { | |
| errors.push({ | |
| path: path, | |
| property: 'minLength', | |
| message: this.translate((schema.minLength === 1 ? 'error_notempty' : 'error_minLength'), [schema.minLength]) | |
| }); | |
| } | |
| } | |
| // `pattern` | |
| if (schema.pattern) { | |
| if (!(new RegExp(schema.pattern)).test(value)) { | |
| errors.push({ | |
| path: path, | |
| property: 'pattern', | |
| message: this.translate('error_pattern') | |
| }); | |
| } | |
| } | |
| } | |
| // Array specific validation | |
| else if (typeof value === "object" && value !== null && Array.isArray(value)) { | |
| // `items` and `additionalItems` | |
| if (schema.items) { | |
| // `items` is an array | |
| if (Array.isArray(schema.items)) { | |
| for (i = 0; i < value.length; i++) { | |
| // If this item has a specific schema tied to it | |
| // Validate against it | |
| if (schema.items[i]) { | |
| errors = errors.concat(this._validateSchema(schema.items[i], value[i], path + '.' + i)); | |
| } | |
| // If all additional items are allowed | |
| else if (schema.additionalItems === true) { | |
| break; | |
| } | |
| // If additional items is a schema | |
| // TODO: Incompatibility between version 3 and 4 of the spec | |
| else if (schema.additionalItems) { | |
| errors = errors.concat(this._validateSchema(schema.additionalItems, value[i], path + '.' + i)); | |
| } | |
| // If no additional items are allowed | |
| else if (schema.additionalItems === false) { | |
| errors.push({ | |
| path: path, | |
| property: 'additionalItems', | |
| message: this.translate('error_additionalItems') | |
| }); | |
| break; | |
| } | |
| // Default for `additionalItems` is an empty schema | |
| else { | |
| break; | |
| } | |
| } | |
| } | |
| // `items` is a schema | |
| else { | |
| // Each item in the array must validate against the schema | |
| for (i = 0; i < value.length; i++) { | |
| errors = errors.concat(this._validateSchema(schema.items, value[i], path + '.' + i)); | |
| } | |
| } | |
| } | |
| // `maxItems` | |
| if (schema.maxItems) { | |
| if (value.length > schema.maxItems) { | |
| errors.push({ | |
| path: path, | |
| property: 'maxItems', | |
| message: this.translate('error_maxItems', [schema.maxItems]) | |
| }); | |
| } | |
| } | |
| // `minItems` | |
| if (schema.minItems) { | |
| if (value.length < schema.minItems) { | |
| errors.push({ | |
| path: path, | |
| property: 'minItems', | |
| message: this.translate('error_minItems', [schema.minItems]) | |
| }); | |
| } | |
| } | |
| // `uniqueItems` | |
| if (schema.uniqueItems) { | |
| var seen = {}; | |
| for (i = 0; i < value.length; i++) { | |
| valid = JSON.stringify(value[i]); | |
| if (seen[valid]) { | |
| errors.push({ | |
| path: path, | |
| property: 'uniqueItems', | |
| message: this.translate('error_uniqueItems') | |
| }); | |
| break; | |
| } | |
| seen[valid] = true; | |
| } | |
| } | |
| } | |
| // Object specific validation | |
| else if (typeof value === "object" && value !== null) { | |
| // `maxProperties` | |
| if (schema.maxProperties) { | |
| valid = 0; | |
| for (i in value) { | |
| if (!value.hasOwnProperty(i)) continue; | |
| valid++; | |
| } | |
| if (valid > schema.maxProperties) { | |
| errors.push({ | |
| path: path, | |
| property: 'maxProperties', | |
| message: this.translate('error_maxProperties', [schema.maxProperties]) | |
| }); | |
| } | |
| } | |
| // `minProperties` | |
| if (schema.minProperties) { | |
| valid = 0; | |
| for (i in value) { | |
| if (!value.hasOwnProperty(i)) continue; | |
| valid++; | |
| } | |
| if (valid < schema.minProperties) { | |
| errors.push({ | |
| path: path, | |
| property: 'minProperties', | |
| message: this.translate('error_minProperties', [schema.minProperties]) | |
| }); | |
| } | |
| } | |
| // Version 4 `required` | |
| if (schema.required && Array.isArray(schema.required)) { | |
| for (i = 0; i < schema.required.length; i++) { | |
| if (typeof value[schema.required[i]] === "undefined") { | |
| errors.push({ | |
| path: path, | |
| property: 'required', | |
| message: this.translate('error_required', [schema.required[i]]) | |
| }); | |
| } | |
| } | |
| } | |
| // `properties` | |
| var validated_properties = {}; | |
| if (schema.properties) { | |
| for (i in schema.properties) { | |
| if (!schema.properties.hasOwnProperty(i)) continue; | |
| validated_properties[i] = true; | |
| errors = errors.concat(this._validateSchema(schema.properties[i], value[i], path + '.' + i)); | |
| } | |
| } | |
| // `patternProperties` | |
| if (schema.patternProperties) { | |
| for (i in schema.patternProperties) { | |
| if (!schema.patternProperties.hasOwnProperty(i)) continue; | |
| var regex = new RegExp(i); | |
| // Check which properties match | |
| for (j in value) { | |
| if (!value.hasOwnProperty(j)) continue; | |
| if (regex.test(j)) { | |
| validated_properties[j] = true; | |
| errors = errors.concat(this._validateSchema(schema.patternProperties[i], value[j], path + '.' + j)); | |
| } | |
| } | |
| } | |
| } | |
| // The no_additional_properties option currently doesn't work with extended schemas that use oneOf or anyOf | |
| if (typeof schema.additionalProperties === "undefined" && this.jsoneditor.options.no_additional_properties && !schema.oneOf && !schema.anyOf) { | |
| schema.additionalProperties = false; | |
| } | |
| // `additionalProperties` | |
| if (typeof schema.additionalProperties !== "undefined") { | |
| for (i in value) { | |
| if (!value.hasOwnProperty(i)) continue; | |
| if (!validated_properties[i]) { | |
| // No extra properties allowed | |
| if (!schema.additionalProperties) { | |
| errors.push({ | |
| path: path, | |
| property: 'additionalProperties', | |
| message: this.translate('error_additional_properties', [i]) | |
| }); | |
| break; | |
| } | |
| // Allowed | |
| else if (schema.additionalProperties === true) { | |
| break; | |
| } | |
| // Must match schema | |
| // TODO: incompatibility between version 3 and 4 of the spec | |
| else { | |
| errors = errors.concat(this._validateSchema(schema.additionalProperties, value[i], path + '.' + i)); | |
| } | |
| } | |
| } | |
| } | |
| // `dependencies` | |
| if (schema.dependencies) { | |
| for (i in schema.dependencies) { | |
| if (!schema.dependencies.hasOwnProperty(i)) continue; | |
| // Doesn't need to meet the dependency | |
| if (typeof value[i] === "undefined") continue; | |
| // Property dependency | |
| if (Array.isArray(schema.dependencies[i])) { | |
| for (j = 0; j < schema.dependencies[i].length; j++) { | |
| if (typeof value[schema.dependencies[i][j]] === "undefined") { | |
| errors.push({ | |
| path: path, | |
| property: 'dependencies', | |
| message: this.translate('error_dependency', [schema.dependencies[i][j]]) | |
| }); | |
| } | |
| } | |
| } | |
| // Schema dependency | |
| else { | |
| errors = errors.concat(this._validateSchema(schema.dependencies[i], value, path)); | |
| } | |
| } | |
| } | |
| } | |
| // Custom type validation (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.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); | |
| }, false); | |
| 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); | |
| }, false); | |
| this.input.addEventListener('change', function(e) { | |
| self.adjust_height(this); | |
| }, false); | |
| 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(); | |
| }, false); | |
| this.editjson_cancel = this.getButton('Cancel', 'cancel', 'Cancel'); | |
| this.editjson_cancel.addEventListener('click', function(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| self.hideEditJSON(); | |
| }, false); | |
| 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); | |
| } | |
| }, false); | |
| 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'); | |
| } | |
| }, false); | |
| // 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(); | |
| }, false); | |
| 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(); | |
| }, false); | |
| 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); | |
| }, false); | |
| 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(); | |
| }, false); | |
| 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', 'Delete ' + 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); | |
| }, false); | |
| 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', 'Move up'); | |
| 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); | |
| }, false); | |
| if (controls_holder) { | |
| controls_holder.appendChild(self.rows[i].moveup_button); | |
| } | |
| } | |
| if (!self.hide_move_buttons) { | |
| self.rows[i].movedown_button = this.getButton('', 'movedown', 'Move down'); | |
| 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); | |
| }, false); | |
| 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', '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'); | |
| } | |
| }, false); | |
| // 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', 'Add ' + 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); | |
| }, false); | |
| self.controls.appendChild(this.add_row_button); | |
| this.delete_last_row_button = this.getButton('Last ' + this.getItemTitle(), 'delete', 'Delete Last ' + 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); | |
| }, false); | |
| self.controls.appendChild(this.delete_last_row_button); | |
| this.remove_all_rows_button = this.getButton('All', 'delete', 'Delete All'); | |
| this.remove_all_rows_button.addEventListener('click', function(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| self.setValue([]); | |
| self.onChange(true); | |
| }, false); | |
| 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', 'Delete'); | |
| 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); | |
| }, false); | |
| controls_holder.appendChild(self.rows[i].delete_button); | |
| } | |
| if (i && !this.hide_move_buttons) { | |
| self.rows[i].moveup_button = this.getButton('', 'moveup', 'Move up'); | |
| 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); | |
| }, false); | |
| controls_holder.appendChild(self.rows[i].moveup_button); | |
| } | |
| if (!this.hide_move_buttons) { | |
| self.rows[i].movedown_button = this.getButton('', 'movedown', 'Move down'); | |
| 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); | |
| }, false); | |
| 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', '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'); | |
| } | |
| }, false); | |
| // 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', 'Add ' + this.getItemTitle()); | |
| this.add_row_button.addEventListener('click', function(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| self.addRow(); | |
| self.refreshValue(); | |
| self.refreshRowButtons(); | |
| self.onChange(true); | |
| }, false); | |
| self.controls.appendChild(this.add_row_button); | |
| this.delete_last_row_button = this.getButton('Last ' + this.getItemTitle(), 'delete', 'Delete Last ' + 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); | |
| }, false); | |
| self.controls.appendChild(this.delete_last_row_button); | |
| this.remove_all_rows_button = this.getButton('All', 'delete', 'Delete All'); | |
| this.remove_all_rows_button.addEventListener('click', function(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| self.setValue([]); | |
| self.onChange(true); | |
| }, false); | |
| 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(); | |
| }, false); | |
| 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); | |
| }, false); | |
| 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); | |
| }, false); | |
| 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(); | |
| }, false); | |
| 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(); | |
| }, false); | |
| 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); | |
| }, false); | |
| }, | |
| 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]); | |
| } | |
| }, false); | |
| } | |
| 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]); | |
| } | |
| }, false); | |
| } | |
| 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); | |
| } | |
| } | |
| }); | |
| }, false); | |
| }, | |
| 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); | |
| }, false); | |
| 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.setAttribute('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.setAttribute('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.setAttribute('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.setAttribute('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.setAttribute('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.setAttribute('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.cssText = input.errmsg.style.cssText || {}; | |
| 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 provided pattern", | |
| /** | |
| * 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}}" | |
| }; | |
| // 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 |