3 * @description JSON Schema Based Editor
5 * This repo is no longer maintained (see also https://github.com/jdorn/json-editor/issues/800)
6 * Development is continued at https://github.com/json-editor/json-editor
7 * For details please visit https://github.com/json-editor/json-editor/issues/5
8 * @version 1.1.0-beta.2
10 * @see https://github.com/jdorn/json-editor/
11 * @see https://github.com/json-editor/json-editor
13 * @example see README.md and docs/ for requirements, examples and usage info
18 /*jshint loopfunc: true */
19 /* Simple JavaScript Inheritance
20 * By John Resig http://ejohn.org/
23 // Inspired by base2 and Prototype
26 var initializing = false, fnTest = /xyz/.test(function(){window.postMessage("xyz");}) ? /\b_super\b/ : /.*/;
28 // The base Class implementation (does nothing)
31 // Create a new Class that inherits from this class
32 Class.extend = function extend(prop) {
33 var _super = this.prototype;
35 // Instantiate a base class (but only create the instance,
36 // don't run the init constructor)
38 var prototype = new this();
41 // Copy the properties over onto the new prototype
42 for (var name in prop) {
43 // Check if we're overwriting an existing function
44 prototype[name] = typeof prop[name] == "function" &&
45 typeof _super[name] == "function" && fnTest.test(prop[name]) ?
48 var tmp = this._super;
50 // Add a new ._super() method that is the same method
51 // but on the super-class
52 this._super = _super[name];
54 // The method only need to be bound temporarily, so we
55 // remove it when we're done executing
56 var ret = fn.apply(this, arguments);
61 })(name, prop[name]) :
65 // The dummy class constructor
67 // All construction is actually done in the init method
68 if ( !initializing && this.init )
69 this.init.apply(this, arguments);
72 // Populate our constructed prototype object
73 Class.prototype = prototype;
75 // Enforce the constructor to be what we expect
76 Class.prototype.constructor = Class;
78 // And make this class extendable
79 Class.extend = extend;
87 // CustomEvent constructor polyfill
90 function CustomEvent ( event, params ) {
91 params = params || { bubbles: false, cancelable: false, detail: undefined };
92 var evt = document.createEvent( 'CustomEvent' );
93 evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
97 CustomEvent.prototype = window.Event.prototype;
99 window.CustomEvent = CustomEvent;
102 // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
106 var vendors = ['ms', 'moz', 'webkit', 'o'];
107 for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
108 window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
109 window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] ||
110 window[vendors[x]+'CancelRequestAnimationFrame'];
113 if (!window.requestAnimationFrame)
114 window.requestAnimationFrame = function(callback, element) {
115 var currTime = new Date().getTime();
116 var timeToCall = Math.max(0, 16 - (currTime - lastTime));
117 var id = window.setTimeout(function() { callback(currTime + timeToCall); },
119 lastTime = currTime + timeToCall;
123 if (!window.cancelAnimationFrame)
124 window.cancelAnimationFrame = function(id) {
129 // Array.isArray polyfill
133 Array.isArray = function(arg) {
134 return Object.prototype.toString.call(arg) === '[object Array]';
139 * Taken from jQuery 2.1.3
144 var $isplainobject = function( obj ) {
145 // Not plain objects:
146 // - Any object or value whose internal [[Class]] property is not "[object Object]"
149 if (typeof obj !== "object" || obj.nodeType || (obj !== null && obj === obj.window)) {
153 if (obj.constructor && !Object.prototype.hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) {
157 // If the function hasn't returned already, we're confident that
158 // |obj| is a plain object, created by {} or constructed with new Object
162 var $extend = function(destination) {
163 var source, i,property;
164 for(i=1; i<arguments.length; i++) {
165 source = arguments[i];
166 for (property in source) {
167 if(!source.hasOwnProperty(property)) continue;
168 if(source[property] && $isplainobject(source[property])) {
169 if(!destination.hasOwnProperty(property)) destination[property] = {};
170 $extend(destination[property], source[property]);
173 destination[property] = source[property];
180 var $each = function(obj,callback) {
181 if(!obj || typeof obj !== "object") return;
183 if(Array.isArray(obj) || (typeof obj.length === 'number' && obj.length > 0 && (obj.length - 1) in obj)) {
184 for(i=0; i<obj.length; i++) {
185 if(callback(i,obj[i])===false) return;
190 var keys = Object.keys(obj);
191 for(i=0; i<keys.length; i++) {
192 if(callback(keys[i],obj[keys[i]])===false) return;
197 if(!obj.hasOwnProperty(i)) continue;
198 if(callback(i,obj[i])===false) return;
204 var $trigger = function(el,event) {
205 var e = document.createEvent('HTMLEvents');
206 e.initEvent(event, true, true);
209 var $triggerc = function(el,event) {
210 var e = new CustomEvent(event,{
218 var JSONEditor = function(element,options) {
219 if (!(element instanceof Element)) {
220 throw new Error('element should be an instance of Element');
222 options = $extend({},JSONEditor.defaults.options,options||{});
223 this.element = element;
224 this.options = options;
227 JSONEditor.prototype = {
228 // necessary since we remove the ctor property by doing a literal assignment. Without this
229 // the $isplainobject function will think that this is a plain object.
230 constructor: JSONEditor,
235 this.copyClipboard = null;
237 var theme_class = JSONEditor.defaults.themes[this.options.theme || JSONEditor.defaults.theme];
238 if(!theme_class) throw "Unknown theme " + (this.options.theme || JSONEditor.defaults.theme);
240 this.schema = this.options.schema;
241 this.theme = new theme_class();
242 this.template = this.options.template;
243 this.refs = this.options.refs || {};
247 var icon_class = JSONEditor.defaults.iconlibs[this.options.iconlib || JSONEditor.defaults.iconlib];
248 if(icon_class) this.iconlib = new icon_class();
250 this.root_container = this.theme.getContainer();
251 this.element.appendChild(this.root_container);
253 this.translate = this.options.translate || JSONEditor.defaults.translate;
255 // Fetch all external refs via ajax
256 this._loadExternalRefs(this.schema, function() {
257 self._getDefinitions(self.schema);
260 var validator_options = {};
261 if(self.options.custom_validators) {
262 validator_options.custom_validators = self.options.custom_validators;
264 self.validator = new JSONEditor.Validator(self,null,validator_options);
266 // Create the root editor
267 var schema = self.expandRefs(self.schema);
268 var editor_class = self.getEditorClass(schema);
269 self.root = self.createEditor(editor_class, {
273 container: self.root_container
276 self.root.preBuild();
278 self.root.postBuild();
281 if(self.options.hasOwnProperty('startval')) self.root.setValue(self.options.startval, true);
283 self.validation_results = self.validator.validate(self.root.getValue());
284 self.root.showValidationErrors(self.validation_results);
287 // Fire ready event asynchronously
288 window.requestAnimationFrame(function() {
289 if(!self.ready) return;
290 self.validation_results = self.validator.validate(self.root.getValue());
291 self.root.showValidationErrors(self.validation_results);
292 self.trigger('ready');
293 self.trigger('change');
297 getValue: function() {
298 if(!this.ready) throw "JSON Editor not ready yet. Listen for 'ready' event before getting the value";
300 return this.root.getValue();
302 setValue: function(value) {
303 if(!this.ready) throw "JSON Editor not ready yet. Listen for 'ready' event before setting the value";
305 this.root.setValue(value);
308 validate: function(value) {
309 if(!this.ready) throw "JSON Editor not ready yet. Listen for 'ready' event before validating";
312 if(arguments.length === 1) {
313 return this.validator.validate(value);
315 // Current value (use cached result)
317 return this.validation_results;
320 destroy: function() {
321 if(this.destroyed) return;
322 if(!this.ready) return;
328 this.root_container = null;
329 this.validator = null;
330 this.validation_results = null;
333 this.template = null;
336 this.element.innerHTML = '';
338 this.destroyed = true;
340 on: function(event, callback) {
341 this.callbacks = this.callbacks || {};
342 this.callbacks[event] = this.callbacks[event] || [];
343 this.callbacks[event].push(callback);
347 off: function(event, callback) {
349 if(event && callback) {
350 this.callbacks = this.callbacks || {};
351 this.callbacks[event] = this.callbacks[event] || [];
352 var newcallbacks = [];
353 for(var i=0; i<this.callbacks[event].length; i++) {
354 if(this.callbacks[event][i]===callback) continue;
355 newcallbacks.push(this.callbacks[event][i]);
357 this.callbacks[event] = newcallbacks;
359 // All callbacks for a specific event
361 this.callbacks = this.callbacks || {};
362 this.callbacks[event] = [];
364 // All callbacks for all events
371 trigger: function(event) {
372 if(this.callbacks && this.callbacks[event] && this.callbacks[event].length) {
373 for(var i=0; i<this.callbacks[event].length; i++) {
374 this.callbacks[event][i].apply(this, []);
380 setOption: function(option, value) {
381 if(option === "show_errors") {
382 this.options.show_errors = value;
385 // Only the `show_errors` option is supported for now
387 throw "Option "+option+" must be set during instantiation and cannot be changed later";
392 getEditorClass: function(schema) {
395 schema = this.expandSchema(schema);
397 $each(JSONEditor.defaults.resolvers,function(i,resolver) {
398 var tmp = resolver(schema);
400 if(JSONEditor.defaults.editors[tmp]) {
407 if(!classname) throw "Unknown editor for schema "+JSON.stringify(schema);
408 if(!JSONEditor.defaults.editors[classname]) throw "Unknown editor "+classname;
410 return JSONEditor.defaults.editors[classname];
412 createEditor: function(editor_class, options) {
413 options = $extend({},editor_class.options||{},options);
414 return new editor_class(options);
416 onChange: function() {
417 if(!this.ready) return;
419 if(this.firing_change) return;
420 this.firing_change = true;
424 window.requestAnimationFrame(function() {
425 self.firing_change = false;
426 if(!self.ready) return;
428 // Validate and cache results
429 self.validation_results = self.validator.validate(self.root.getValue());
431 if(self.options.show_errors !== "never") {
432 self.root.showValidationErrors(self.validation_results);
435 self.root.showValidationErrors([]);
439 self.trigger('change');
444 compileTemplate: function(template, name) {
445 name = name || JSONEditor.defaults.template;
449 // Specifying a preset engine
450 if(typeof name === 'string') {
451 if(!JSONEditor.defaults.templates[name]) throw "Unknown template engine "+name;
452 engine = JSONEditor.defaults.templates[name]();
454 if(!engine) throw "Template engine "+name+" missing required library.";
456 // Specifying a custom engine
461 if(!engine) throw "No template engine set";
462 if(!engine.compile) throw "Invalid template engine set";
464 return engine.compile(template);
466 _data: function(el,key,value) {
468 if(arguments.length === 3) {
470 if(el.hasAttribute('data-jsoneditor-'+key)) {
471 uuid = el.getAttribute('data-jsoneditor-'+key);
475 el.setAttribute('data-jsoneditor-'+key,uuid);
478 this.__data[uuid] = value;
483 if(!el.hasAttribute('data-jsoneditor-'+key)) return null;
485 return this.__data[el.getAttribute('data-jsoneditor-'+key)];
488 registerEditor: function(editor) {
489 this.editors = this.editors || {};
490 this.editors[editor.path] = editor;
493 unregisterEditor: function(editor) {
494 this.editors = this.editors || {};
495 this.editors[editor.path] = null;
498 getEditor: function(path) {
499 if(!this.editors) return;
500 return this.editors[path];
502 watch: function(path,callback) {
503 this.watchlist = this.watchlist || {};
504 this.watchlist[path] = this.watchlist[path] || [];
505 this.watchlist[path].push(callback);
509 unwatch: function(path,callback) {
510 if(!this.watchlist || !this.watchlist[path]) return this;
511 // If removing all callbacks for a path
513 this.watchlist[path] = null;
518 for(var i=0; i<this.watchlist[path].length; i++) {
519 if(this.watchlist[path][i] === callback) continue;
520 else newlist.push(this.watchlist[path][i]);
522 this.watchlist[path] = newlist.length? newlist : null;
525 notifyWatchers: function(path) {
526 if(!this.watchlist || !this.watchlist[path]) return this;
527 for(var i=0; i<this.watchlist[path].length; i++) {
528 this.watchlist[path][i]();
531 isEnabled: function() {
532 return !this.root || this.root.isEnabled();
537 disable: function() {
540 _getDefinitions: function(schema,path) {
541 path = path || '#/definitions/';
542 if(schema.definitions) {
543 for(var i in schema.definitions) {
544 if(!schema.definitions.hasOwnProperty(i)) continue;
545 this.refs[path+i] = schema.definitions[i];
546 if(schema.definitions[i].definitions) {
547 this._getDefinitions(schema.definitions[i],path+i+'/definitions/');
552 _getExternalRefs: function(schema) {
554 var merge_refs = function(newrefs) {
555 for(var i in newrefs) {
556 if(newrefs.hasOwnProperty(i)) {
562 if(schema.$ref && typeof schema.$ref !== "object" && schema.$ref.substr(0,1) !== "#" && !this.refs[schema.$ref]) {
563 refs[schema.$ref] = true;
566 for(var i in schema) {
567 if(!schema.hasOwnProperty(i)) continue;
568 if(schema[i] && typeof schema[i] === "object" && Array.isArray(schema[i])) {
569 for(var j=0; j<schema[i].length; j++) {
570 if(schema[i][j] && typeof schema[i][j]==="object") {
571 merge_refs(this._getExternalRefs(schema[i][j]));
575 else if(schema[i] && typeof schema[i] === "object") {
576 merge_refs(this._getExternalRefs(schema[i]));
582 _loadExternalRefs: function(schema, callback) {
584 var refs = this._getExternalRefs(schema);
586 var done = 0, waiting = 0, callback_fired = false;
588 $each(refs,function(url) {
589 if(self.refs[url]) return;
590 if(!self.options.ajax) throw "Must set ajax option to true to load external ref "+url;
591 self.refs[url] = 'loading';
595 if( self.options.ajaxBase && self.options.ajaxBase!=url.substr(0,self.options.ajaxBase.length) && "http"!=url.substr(0,4)) fetchUrl=self.options.ajaxBase+url;
597 var r = new XMLHttpRequest();
598 r.open("GET", fetchUrl, true);
599 if(self.options.ajaxCredentials) r.withCredentials=self.options.ajaxCredentials;
600 r.onreadystatechange = function () {
601 if (r.readyState != 4) return;
603 if(r.status === 200) {
606 response = JSON.parse(r.responseText);
609 window.console.log(e);
610 throw "Failed to parse external ref "+fetchUrl;
612 if(!response || typeof response !== "object") throw "External ref does not contain a valid schema - "+fetchUrl;
614 self.refs[url] = response;
615 self._loadExternalRefs(response,function() {
617 if(done >= waiting && !callback_fired) {
618 callback_fired = true;
625 window.console.log(r);
626 throw "Failed to fetch ref via ajax- "+url;
636 expandRefs: function(schema) {
637 schema = $extend({},schema);
639 while (schema.$ref) {
640 var ref = schema.$ref;
643 if(!this.refs[ref]) ref = decodeURIComponent(ref);
645 schema = this.extendSchemas(schema,this.refs[ref]);
649 expandSchema: function(schema) {
651 var extended = $extend({},schema);
655 if(typeof schema.type === 'object') {
657 if(Array.isArray(schema.type)) {
658 $each(schema.type, function(key,value) {
660 if(typeof value === 'object') {
661 schema.type[key] = self.expandSchema(value);
667 schema.type = self.expandSchema(schema.type);
670 // Version 3 `disallow`
671 if(typeof schema.disallow === 'object') {
673 if(Array.isArray(schema.disallow)) {
674 $each(schema.disallow, function(key,value) {
676 if(typeof value === 'object') {
677 schema.disallow[key] = self.expandSchema(value);
683 schema.disallow = self.expandSchema(schema.disallow);
688 $each(schema.anyOf, function(key,value) {
689 schema.anyOf[key] = self.expandSchema(value);
692 // Version 4 `dependencies` (schema dependencies)
693 if(schema.dependencies) {
694 $each(schema.dependencies,function(key,value) {
695 if(typeof value === "object" && !(Array.isArray(value))) {
696 schema.dependencies[key] = self.expandSchema(value);
702 schema.not = this.expandSchema(schema.not);
705 // allOf schemas should be merged into the parent
707 for(i=0; i<schema.allOf.length; i++) {
708 extended = this.extendSchemas(extended,this.expandSchema(schema.allOf[i]));
710 delete extended.allOf;
712 // extends schemas should be merged into parent
713 if(schema["extends"]) {
714 // If extends is a schema
715 if(!(Array.isArray(schema["extends"]))) {
716 extended = this.extendSchemas(extended,this.expandSchema(schema["extends"]));
718 // If extends is an array of schemas
720 for(i=0; i<schema["extends"].length; i++) {
721 extended = this.extendSchemas(extended,this.expandSchema(schema["extends"][i]));
724 delete extended["extends"];
726 // parent should be merged into oneOf schemas
728 var tmp = $extend({},extended);
730 for(i=0; i<schema.oneOf.length; i++) {
731 extended.oneOf[i] = this.extendSchemas(this.expandSchema(schema.oneOf[i]),tmp);
735 return this.expandRefs(extended);
737 extendSchemas: function(obj1, obj2) {
738 obj1 = $extend({},obj1);
739 obj2 = $extend({},obj2);
743 $each(obj1, function(prop,val) {
744 // If this key is also defined in obj2, merge them
745 if(typeof obj2[prop] !== "undefined") {
746 // Required and defaultProperties arrays should be unioned together
747 if((prop === 'required'||prop === 'defaultProperties') && typeof val === "object" && Array.isArray(val)) {
748 // Union arrays and unique
749 extended[prop] = val.concat(obj2[prop]).reduce(function(p, c) {
750 if (p.indexOf(c) < 0) p.push(c);
754 // Type should be intersected and is either an array or string
755 else if(prop === 'type' && (typeof val === "string" || Array.isArray(val))) {
756 // Make sure we're dealing with arrays
757 if(typeof val === "string") val = [val];
758 if(typeof obj2.type === "string") obj2.type = [obj2.type];
760 // If type is only defined in the first schema, keep it
761 if(!obj2.type || !obj2.type.length) {
764 // If type is defined in both schemas, do an intersect
766 extended.type = val.filter(function(n) {
767 return obj2.type.indexOf(n) !== -1;
771 // If there's only 1 type and it's a primitive, use a string instead of array
772 if(extended.type.length === 1 && typeof extended.type[0] === "string") {
773 extended.type = extended.type[0];
775 // Remove the type property if it's empty
776 else if(extended.type.length === 0) {
777 delete extended.type;
780 // All other arrays should be intersected (enum, etc.)
781 else if(typeof val === "object" && Array.isArray(val)){
782 extended[prop] = val.filter(function(n) {
783 return obj2[prop].indexOf(n) !== -1;
786 // Objects should be recursively merged
787 else if(typeof val === "object" && val !== null) {
788 extended[prop] = self.extendSchemas(val,obj2[prop]);
790 // Otherwise, use the first value
792 extended[prop] = val;
795 // Otherwise, just use the one in obj1
797 extended[prop] = val;
800 // Properties in obj2 that aren't in obj1
801 $each(obj2, function(prop,val) {
802 if(typeof obj1[prop] === "undefined") {
803 extended[prop] = val;
809 setCopyClipboardContents: function(value) {
810 this.copyClipboard = value;
812 getCopyClipboardContents: function() {
813 return this.copyClipboard;
817 JSONEditor.defaults = {
824 custom_validators: []
827 JSONEditor.Validator = Class.extend({
828 init: function(jsoneditor,schema,options) {
829 this.jsoneditor = jsoneditor;
830 this.schema = schema || this.jsoneditor.schema;
831 this.options = options || {};
832 this.translate = this.jsoneditor.translate || JSONEditor.defaults.translate;
834 validate: function(value) {
835 return this._validateSchema(this.schema, value);
837 _validateSchema: function(schema,value,path) {
841 var stringified = JSON.stringify(value);
843 path = path || 'root';
845 // Work on a copy of the schema
846 schema = $extend({},this.jsoneditor.expandRefs(schema));
849 * Type Agnostic Validation
852 // Version 3 `required` and `required_by_default`
853 if(typeof value === "undefined" || value === null) {
854 if((typeof schema.required !== "undefined" && schema.required === true) || (typeof schema.required === "undefined" && this.jsoneditor.options.required_by_default === true)) {
857 property: 'required',
858 message: this.translate("error_notset", [schema.title ? schema.title : path.split('-').pop().trim()])
868 for(i=0; i<schema["enum"].length; i++) {
869 if(stringified === JSON.stringify(schema["enum"][i])) valid = true;
875 message: this.translate("error_enum", [schema.title ? schema.title : path.split('-').pop().trim()])
880 // `extends` (version 3)
881 if(schema["extends"]) {
882 for(i=0; i<schema["extends"].length; i++) {
883 errors = errors.concat(this._validateSchema(schema["extends"][i],value,path));
889 for(i=0; i<schema.allOf.length; i++) {
890 errors = errors.concat(this._validateSchema(schema.allOf[i],value,path));
897 for(i=0; i<schema.anyOf.length; i++) {
898 if(!this._validateSchema(schema.anyOf[i],value,path).length) {
907 message: this.translate('error_anyOf')
915 var oneof_errors = [];
916 for(i=0; i<schema.oneOf.length; i++) {
917 // Set the error paths to be path.oneOf[i].rest.of.path
918 var tmp = this._validateSchema(schema.oneOf[i],value,path);
923 for(j=0; j<tmp.length; j++) {
924 tmp[j].path = path+'.oneOf['+i+']'+tmp[j].path.substr(path.length);
926 oneof_errors = oneof_errors.concat(tmp);
933 message: this.translate('error_oneOf', [valid])
935 errors = errors.concat(oneof_errors);
941 if(!this._validateSchema(schema.not,value,path).length) {
945 message: this.translate('error_not')
950 // `type` (both Version 3 and Version 4 support)
953 if(Array.isArray(schema.type)) {
955 for(i=0;i<schema.type.length;i++) {
956 if(this._checkType(schema.type[i], value)) {
965 message: this.translate('error_type_union')
971 if(!this._checkType(schema.type, value)) {
975 message: this.translate('error_type', [schema.type])
982 // `disallow` (version 3)
983 if(schema.disallow) {
985 if(Array.isArray(schema.disallow)) {
987 for(i=0;i<schema.disallow.length;i++) {
988 if(this._checkType(schema.disallow[i], value)) {
996 property: 'disallow',
997 message: this.translate('error_disallow_union')
1003 if(this._checkType(schema.disallow, value)) {
1006 property: 'disallow',
1007 message: this.translate('error_disallow', [schema.disallow])
1014 * Type Specific Validation
1017 // Number Specific Validation
1018 if(typeof value === "number") {
1019 // `multipleOf` and `divisibleBy`
1020 if(schema.multipleOf || schema.divisibleBy) {
1021 var divisor = schema.multipleOf || schema.divisibleBy;
1022 // Vanilla JS, prone to floating point rounding errors (e.g. 1.14 / .01 == 113.99999)
1023 valid = (value/divisor === Math.floor(value/divisor));
1025 // Use math.js is available
1027 valid = window.math.mod(window.math.bignumber(value), window.math.bignumber(divisor)).equals(0);
1029 // Use decimal.js is available
1030 else if(window.Decimal) {
1031 valid = (new window.Decimal(value)).mod(new window.Decimal(divisor)).equals(0);
1037 property: schema.multipleOf? 'multipleOf' : 'divisibleBy',
1038 message: this.translate('error_multipleOf', [divisor])
1044 if(schema.hasOwnProperty('maximum')) {
1045 // Vanilla JS, prone to floating point rounding errors (e.g. .999999999999999 == 1)
1046 valid = schema.exclusiveMaximum? (value < schema.maximum) : (value <= schema.maximum);
1048 // Use math.js is available
1050 valid = window.math[schema.exclusiveMaximum?'smaller':'smallerEq'](
1051 window.math.bignumber(value),
1052 window.math.bignumber(schema.maximum)
1055 // Use Decimal.js if available
1056 else if(window.Decimal) {
1057 valid = (new window.Decimal(value))[schema.exclusiveMaximum?'lt':'lte'](new window.Decimal(schema.maximum));
1063 property: 'maximum',
1064 message: this.translate(
1065 (schema.exclusiveMaximum?'error_maximum_excl':'error_maximum_incl'),
1066 [schema.title ? schema.title : path.split('-').pop().trim(), schema.maximum]
1073 if(schema.hasOwnProperty('minimum')) {
1074 // Vanilla JS, prone to floating point rounding errors (e.g. .999999999999999 == 1)
1075 valid = schema.exclusiveMinimum? (value > schema.minimum) : (value >= schema.minimum);
1077 // Use math.js is available
1079 valid = window.math[schema.exclusiveMinimum?'larger':'largerEq'](
1080 window.math.bignumber(value),
1081 window.math.bignumber(schema.minimum)
1084 // Use Decimal.js if available
1085 else if(window.Decimal) {
1086 valid = (new window.Decimal(value))[schema.exclusiveMinimum?'gt':'gte'](new window.Decimal(schema.minimum));
1092 property: 'minimum',
1093 message: this.translate(
1094 (schema.exclusiveMinimum?'error_minimum_excl':'error_minimum_incl'),
1095 [schema.title ? schema.title : path.split('-').pop().trim(), schema.minimum]
1101 // String specific validation
1102 else if(typeof value === "string") {
1104 if(schema.maxLength) {
1105 if((value+"").length > schema.maxLength) {
1108 property: 'maxLength',
1109 message: this.translate('error_maxLength',
1110 [schema.title ? schema.title : path.split('-').pop().trim(), schema.maxLength])
1115 // `minLength` -- Commented because we are validating required field.
1116 if(schema.minLength) {
1117 if((value+"").length < schema.minLength) {
1120 property: 'minLength',
1121 message: this.translate((schema.minLength===1?'error_notempty':'error_minLength'),
1122 [schema.title ? schema.title : path.split('-').pop().trim(), schema.minLength])
1128 if(schema.pattern) {
1129 if(!(new RegExp(schema.pattern)).test(value)) {
1132 property: 'pattern',
1133 message: this.translate('error_pattern',
1134 [schema.title ? schema.title : path.split('-').pop().trim(), schema.pattern])
1139 // Array specific validation
1140 else if(typeof value === "object" && value !== null && Array.isArray(value)) {
1141 // `items` and `additionalItems`
1143 // `items` is an array
1144 if(Array.isArray(schema.items)) {
1145 for(i=0; i<value.length; i++) {
1146 // If this item has a specific schema tied to it
1147 // Validate against it
1148 if(schema.items[i]) {
1149 errors = errors.concat(this._validateSchema(schema.items[i],value[i],path+'.'+i));
1151 // If all additional items are allowed
1152 else if(schema.additionalItems === true) {
1155 // If additional items is a schema
1156 // TODO: Incompatibility between version 3 and 4 of the spec
1157 else if(schema.additionalItems) {
1158 errors = errors.concat(this._validateSchema(schema.additionalItems,value[i],path+'.'+i));
1160 // If no additional items are allowed
1161 else if(schema.additionalItems === false) {
1164 property: 'additionalItems',
1165 message: this.translate('error_additionalItems')
1169 // Default for `additionalItems` is an empty schema
1175 // `items` is a schema
1177 // Each item in the array must validate against the schema
1178 for(i=0; i<value.length; i++) {
1179 errors = errors.concat(this._validateSchema(schema.items,value[i],path+'.'+i));
1185 if(schema.maxItems) {
1186 if(value.length > schema.maxItems) {
1189 property: 'maxItems',
1190 message: this.translate('error_maxItems', [schema.maxItems])
1196 if(schema.minItems) {
1197 if(value.length < schema.minItems) {
1200 property: 'minItems',
1201 message: this.translate('error_minItems', [schema.minItems])
1207 if(schema.uniqueItems) {
1209 for(i=0; i<value.length; i++) {
1210 valid = JSON.stringify(value[i]);
1214 property: 'uniqueItems',
1215 message: this.translate('error_uniqueItems',
1216 [schema.title ? schema.title : path.split('-').pop().trim()])
1224 // Object specific validation
1225 else if(typeof value === "object" && value !== null) {
1227 if(schema.maxProperties) {
1230 if(!value.hasOwnProperty(i)) continue;
1233 if(valid > schema.maxProperties) {
1236 property: 'maxProperties',
1237 message: this.translate('error_maxProperties', [schema.maxProperties])
1243 if(schema.minProperties) {
1246 if(!value.hasOwnProperty(i)) continue;
1249 if(valid < schema.minProperties) {
1252 property: 'minProperties',
1253 message: this.translate('error_minProperties', [schema.minProperties])
1258 // Version 4 `required`
1259 if(typeof schema.required !== "undefined" && Array.isArray(schema.required)) {
1260 for(i=0; i<schema.required.length; i++) {
1261 // Arrays are the only missing "required" thing we report in the "object"
1262 // level control group error message area; all others appear in their own form control
1263 // control message area.
1264 if((typeof value[schema.required[i]] === "undefined") ||
1265 (Array.isArray(value[schema.required[i]]) && value[schema.required[i]].length == 0)) {
1267 if(typeof schema.properties[schema.required[i]].title !== "undefined") {
1268 parm_name = schema.properties[schema.required[i]].title;
1271 parm_name = schema.required[i];
1275 property: 'required',
1276 message: this.translate('error_required', [parm_name])
1283 var validated_properties = {};
1284 if(schema.properties) {
1285 if(typeof schema.required !== "undefined" && Array.isArray(schema.required)) {
1286 for(i=0; i<schema.required.length; i++) {
1287 var property = schema.required[i];
1288 validated_properties[property] = true;
1289 errors = errors.concat(this._validateSchema(schema.properties[property],value[property],path+'.'+property));
1293 // If an optional property is not an object and is not empty, we must run validation
1294 // on it as the user may have entered some data into it.
1296 for(i in schema.properties) {
1297 if(!schema.properties.hasOwnProperty(i) || validated_properties[i] === true) continue;
1298 if((typeof value[i] !== "object" && typeof value[i] !== "undefined" && value[i] !== null) ||
1299 (schema.properties[i].type === "array" && Array.isArray(value[i]) && value[i].length > 0)) {
1301 errors = errors.concat(this._validateSchema(schema.properties[i],value[i],path+'.'+i));
1303 validated_properties[i] = true;
1307 // `patternProperties`
1308 if(schema.patternProperties) {
1309 for(i in schema.patternProperties) {
1310 if(!schema.patternProperties.hasOwnProperty(i)) continue;
1311 var regex = new RegExp(i);
1313 // Check which properties match
1315 if(!value.hasOwnProperty(j)) continue;
1317 validated_properties[j] = true;
1318 errors = errors.concat(this._validateSchema(schema.patternProperties[i],value[j],path+'.'+j));
1324 // The no_additional_properties option currently doesn't work with extended schemas that use oneOf or anyOf
1325 if(typeof schema.additionalProperties === "undefined" && this.jsoneditor.options.no_additional_properties && !schema.oneOf && !schema.anyOf) {
1326 schema.additionalProperties = false;
1329 // `additionalProperties`
1330 if(typeof schema.additionalProperties !== "undefined") {
1332 if(!value.hasOwnProperty(i)) continue;
1333 if(!validated_properties[i]) {
1334 // No extra properties allowed
1335 if(!schema.additionalProperties) {
1338 property: 'additionalProperties',
1339 message: this.translate('error_additional_properties', [i])
1344 else if(schema.additionalProperties === true) {
1347 // Must match schema
1348 // TODO: incompatibility between version 3 and 4 of the spec
1350 errors = errors.concat(this._validateSchema(schema.additionalProperties,value[i],path+'.'+i));
1357 if(schema.dependencies) {
1358 for(i in schema.dependencies) {
1359 if(!schema.dependencies.hasOwnProperty(i)) continue;
1361 // Doesn't need to meet the dependency
1362 if(typeof value[i] === "undefined") continue;
1364 // Property dependency
1365 if(Array.isArray(schema.dependencies[i])) {
1366 for(j=0; j<schema.dependencies[i].length; j++) {
1367 if(typeof value[schema.dependencies[i][j]] === "undefined") {
1370 property: 'dependencies',
1371 message: this.translate('error_dependency', [schema.dependencies[i][j]])
1376 // Schema dependency
1378 errors = errors.concat(this._validateSchema(schema.dependencies[i],value,path));
1384 // Custom type validation (global)
1385 $each(JSONEditor.defaults.custom_validators,function(i,validator) {
1386 errors = errors.concat(validator.call(self,schema,value,path));
1388 // Custom type validation (instance specific)
1389 if(this.options.custom_validators) {
1390 $each(this.options.custom_validators,function(i,validator) {
1391 errors = errors.concat(validator.call(self,schema,value,path));
1397 _checkType: function(type, value) {
1399 if(typeof type === "string") {
1400 if(type==="string") return typeof value === "string";
1401 else if(type==="number") return typeof value === "number";
1402 else if(type==="qbldr") return typeof value === "string";
1403 else if(type==="integer") return typeof value === "number" && value === Math.floor(value);
1404 else if(type==="boolean") return typeof value === "boolean";
1405 else if(type==="array") return Array.isArray(value);
1406 else if(type === "object") return value !== null && !(Array.isArray(value)) && typeof value === "object";
1407 else if(type === "null") return value === null;
1412 return !this._validateSchema(type,value).length;
1418 * All editors should extend from this class
1420 JSONEditor.AbstractEditor = Class.extend({
1421 onChildEditorChange: function(editor) {
1422 this.onChange(true);
1424 notify: function() {
1425 if(this.path) this.jsoneditor.notifyWatchers(this.path);
1427 change: function() {
1428 if(this.parent) this.parent.onChildEditorChange(this);
1429 else if(this.jsoneditor) this.jsoneditor.onChange();
1431 onChange: function(bubble) {
1433 if(this.watch_listener) this.watch_listener();
1434 if(bubble) this.change();
1436 register: function() {
1437 this.jsoneditor.registerEditor(this);
1440 unregister: function() {
1441 if(!this.jsoneditor) return;
1442 this.jsoneditor.unregisterEditor(this);
1444 getNumColumns: function() {
1447 init: function(options) {
1448 this.jsoneditor = options.jsoneditor;
1450 this.theme = this.jsoneditor.theme;
1451 this.template_engine = this.jsoneditor.template;
1452 this.iconlib = this.jsoneditor.iconlib;
1454 this.translate = this.jsoneditor.translate || JSONEditor.defaults.translate;
1456 this.original_schema = options.schema;
1457 this.schema = this.jsoneditor.expandSchema(this.original_schema);
1459 this.options = $extend({}, (this.options || {}), (this.schema.options || {}), (options.schema.options || {}), options);
1461 if(!options.path && !this.schema.id) this.schema.id = 'root';
1462 this.path = options.path || 'root';
1463 this.formname = options.formname || this.path.replace(/\.([^.]+)/g,'[$1]');
1464 if(this.jsoneditor.options.form_name_root) this.formname = this.formname.replace(/^root\[/,this.jsoneditor.options.form_name_root+'[');
1465 this.key = this.path.split('.').pop();
1466 this.parent = options.parent;
1468 this.link_watchers = [];
1470 if(options.container) this.setContainer(options.container);
1471 this.registerDependencies();
1473 registerDependencies: function() {
1474 this.dependenciesFulfilled = true;
1475 var deps = this.options.dependencies;
1481 Object.keys(deps).forEach(function(dependency) {
1482 var path = self.path.split('.');
1483 path[path.length - 1] = dependency;
1484 path = path.join('.');
1485 var choices = deps[dependency];
1486 self.jsoneditor.watch(path, function() {
1487 self.checkDependency(path, choices);
1491 checkDependency: function(path, choices) {
1492 var wrapper = this.control || this.container;
1493 if (this.path === path || !wrapper) {
1498 var editor = this.jsoneditor.getEditor(path);
1499 var value = editor ? editor.getValue() : undefined;
1500 var previousStatus = this.dependenciesFulfilled;
1501 this.dependenciesFulfilled = false;
1503 if (!editor || !editor.dependenciesFulfilled) {
1504 this.dependenciesFulfilled = false;
1505 } else if (Array.isArray(choices)) {
1506 choices.some(function(choice) {
1507 if (value === choice) {
1508 self.dependenciesFulfilled = true;
1512 } else if (typeof choices === 'object') {
1513 if (typeof value !== 'object') {
1514 this.dependenciesFulfilled = choices === value;
1516 Object.keys(choices).some(function(key) {
1517 if (!choices.hasOwnProperty(key)) {
1520 if (!value.hasOwnProperty(key) || choices[key] !== value[key]) {
1521 self.dependenciesFulfilled = false;
1524 self.dependenciesFulfilled = true;
1527 } else if (typeof choices === 'string' || typeof choices === 'number') {
1528 this.dependenciesFulfilled = value === choices;
1529 } else if (typeof choices === 'boolean') {
1531 this.dependenciesFulfilled = value && value.length > 0;
1533 this.dependenciesFulfilled = !value || value.length === 0;
1537 if (this.dependenciesFulfilled !== previousStatus) {
1541 if (this.dependenciesFulfilled) {
1542 wrapper.style.display = 'block';
1544 wrapper.style.display = 'none';
1547 setContainer: function(container) {
1548 this.container = container;
1549 if(this.schema.id) this.container.setAttribute('data-schemaid',this.schema.id);
1550 if(this.schema.type && typeof this.schema.type === "string") this.container.setAttribute('data-schematype',this.schema.type);
1551 this.container.setAttribute('data-schemapath',this.path);
1552 this.container.style.padding = '4px';
1555 preBuild: function() {
1561 postBuild: function() {
1562 this.setupWatchListeners();
1564 this.setValue(this.getDefault(), true);
1565 this.updateHeaderText();
1567 this.onWatchedFieldChange();
1570 setupWatchListeners: function() {
1575 if(this.schema.vars) this.schema.watch = this.schema.vars;
1576 this.watched_values = {};
1577 this.watch_listener = function() {
1578 if(self.refreshWatchedFieldValues()) {
1579 self.onWatchedFieldChange();
1583 if(this.schema.hasOwnProperty('watch')) {
1584 var path,path_parts,first,root,adjusted_path;
1586 for(var name in this.schema.watch) {
1587 if(!this.schema.watch.hasOwnProperty(name)) continue;
1588 path = this.schema.watch[name];
1590 if(Array.isArray(path)) {
1591 if(path.length<2) continue;
1592 path_parts = [path[0]].concat(path[1].split('.'));
1595 path_parts = path.split('.');
1596 if(!self.theme.closest(self.container,'[data-schemaid="'+path_parts[0]+'"]')) path_parts.unshift('#');
1598 first = path_parts.shift();
1600 if(first === '#') first = self.jsoneditor.schema.id || 'root';
1602 // Find the root node for this template variable
1603 root = self.theme.closest(self.container,'[data-schemaid="'+first+'"]');
1604 if(!root) throw "Could not find ancestor node with id "+first;
1606 // Keep track of the root node and path for use when rendering the template
1607 adjusted_path = root.getAttribute('data-schemapath') + '.' + path_parts.join('.');
1609 self.jsoneditor.watch(adjusted_path,self.watch_listener);
1611 self.watched[name] = adjusted_path;
1616 if(this.schema.headerTemplate) {
1617 this.header_template = this.jsoneditor.compileTemplate(this.schema.headerTemplate, this.template_engine);
1621 addLinks: function() {
1623 if(!this.no_link_holder) {
1624 this.link_holder = this.theme.getLinksHolder();
1625 this.container.appendChild(this.link_holder);
1626 if(this.schema.links) {
1627 for(var i=0; i<this.schema.links.length; i++) {
1628 this.addLink(this.getLink(this.schema.links[i]));
1635 getButton: function(text, icon, title) {
1636 var btnClass = 'json-editor-btn-'+icon;
1637 if(!this.iconlib) icon = null;
1638 else icon = this.iconlib.getIcon(icon);
1640 if(!icon && title) {
1645 var btn = this.theme.getButton(text, icon, title);
1646 btn.className += ' ' + btnClass + ' ';
1649 setButtonText: function(button, text, icon, title) {
1650 if(!this.iconlib) icon = null;
1651 else icon = this.iconlib.getIcon(icon);
1653 if(!icon && title) {
1658 return this.theme.setButtonText(button, text, icon, title);
1660 addLink: function(link) {
1661 if(this.link_holder) this.link_holder.appendChild(link);
1663 getLink: function(data) {
1666 // Get mime type of the link
1667 var mime = data.mediaType || 'application/javascript';
1668 var type = mime.split('/')[0];
1670 // Template to generate the link href
1671 var href = this.jsoneditor.compileTemplate(data.href,this.template_engine);
1672 var relTemplate = this.jsoneditor.compileTemplate(data.rel ? data.rel : data.href,this.template_engine);
1674 // Template to generate the link's download attribute
1675 var download = null;
1676 if(data.download) download = data.download;
1678 if(download && download !== true) {
1679 download = this.jsoneditor.compileTemplate(download, this.template_engine);
1683 if(type === 'image') {
1684 holder = this.theme.getBlockLinkHolder();
1685 link = document.createElement('a');
1686 link.setAttribute('target','_blank');
1687 var image = document.createElement('img');
1689 this.theme.createImageLink(holder,link,image);
1691 // When a watched field changes, update the url
1692 this.link_watchers.push(function(vars) {
1693 var url = href(vars);
1694 var rel = relTemplate(vars);
1695 link.setAttribute('href',url);
1696 link.setAttribute('title',rel || url);
1697 image.setAttribute('src',url);
1700 // Audio/Video links
1701 else if(['audio','video'].indexOf(type) >=0) {
1702 holder = this.theme.getBlockLinkHolder();
1704 link = this.theme.getBlockLink();
1705 link.setAttribute('target','_blank');
1707 var media = document.createElement(type);
1708 media.setAttribute('controls','controls');
1710 this.theme.createMediaLink(holder,link,media);
1712 // When a watched field changes, update the url
1713 this.link_watchers.push(function(vars) {
1714 var url = href(vars);
1715 var rel = relTemplate(vars);
1716 link.setAttribute('href',url);
1717 link.textContent = rel || url;
1718 media.setAttribute('src',url);
1723 link = holder = this.theme.getBlockLink();
1724 holder.setAttribute('target','_blank');
1725 holder.textContent = data.rel;
1727 // When a watched field changes, update the url
1728 this.link_watchers.push(function(vars) {
1729 var url = href(vars);
1730 var rel = relTemplate(vars);
1731 holder.setAttribute('href',url);
1732 holder.textContent = rel || url;
1736 if(download && link) {
1737 if(download === true) {
1738 link.setAttribute('download','');
1741 this.link_watchers.push(function(vars) {
1742 link.setAttribute('download',download(vars));
1747 if(data.class) link.className = link.className + ' ' + data.class;
1751 refreshWatchedFieldValues: function() {
1752 if(!this.watched_values) return;
1754 var changed = false;
1759 for(var name in this.watched) {
1760 if(!this.watched.hasOwnProperty(name)) continue;
1761 editor = self.jsoneditor.getEditor(this.watched[name]);
1762 val = editor? editor.getValue() : null;
1763 if(self.watched_values[name] !== val) changed = true;
1764 watched[name] = val;
1768 watched.self = this.getValue();
1769 if(this.watched_values.self !== watched.self) changed = true;
1771 this.watched_values = watched;
1775 getWatchedFieldValues: function() {
1776 return this.watched_values;
1778 updateHeaderText: function() {
1780 // If the header has children, only update the text node's value
1781 if(this.header.children.length) {
1782 for(var i=0; i<this.header.childNodes.length; i++) {
1783 if(this.header.childNodes[i].nodeType===3) {
1784 this.header.childNodes[i].nodeValue = this.getHeaderText();
1789 // Otherwise, just update the entire node
1791 this.header.textContent = this.getHeaderText();
1795 getHeaderText: function(title_only) {
1796 if(this.header_text) return this.header_text;
1797 else if(title_only) return this.schema.title;
1798 else return this.getTitle();
1800 onWatchedFieldChange: function() {
1802 if(this.header_template) {
1803 vars = $extend(this.getWatchedFieldValues(),{
1808 title: this.getTitle()
1810 var header_text = this.header_template(vars);
1812 if(header_text !== this.header_text) {
1813 this.header_text = header_text;
1814 this.updateHeaderText();
1816 //this.fireChangeHeaderEvent();
1819 if(this.link_watchers.length) {
1820 vars = this.getWatchedFieldValues();
1821 for(var i=0; i<this.link_watchers.length; i++) {
1822 this.link_watchers[i](vars);
1826 setValue: function(value) {
1829 getValue: function() {
1830 if (!this.dependenciesFulfilled) {
1835 refreshValue: function() {
1838 getChildEditors: function() {
1841 destroy: function() {
1843 this.unregister(this);
1844 $each(this.watched,function(name,adjusted_path) {
1845 self.jsoneditor.unwatch(adjusted_path,self.watch_listener);
1847 this.watched = null;
1848 this.watched_values = null;
1849 this.watch_listener = null;
1850 this.header_text = null;
1851 this.header_template = null;
1853 if(this.container && this.container.parentNode) this.container.parentNode.removeChild(this.container);
1854 this.container = null;
1855 this.jsoneditor = null;
1861 getDefault: function() {
1862 if (typeof this.schema["default"] !== 'undefined') {
1863 return this.schema["default"];
1866 if (typeof this.schema["enum"] !== 'undefined') {
1867 return this.schema["enum"][0];
1870 var type = this.schema.type || this.schema.oneOf;
1871 if(type && Array.isArray(type)) type = type[0];
1872 if(type && typeof type === "object") type = type.type;
1873 if(type && Array.isArray(type)) type = type[0];
1875 if(typeof type === "string") {
1876 if(type === "number") return 0.0;
1877 if(type === "boolean") return false;
1878 if(type === "integer") return 0;
1879 if(type === "string") return "";
1880 if(type === "object") return {};
1881 if(type === "array") return [];
1886 getTitle: function() {
1887 return this.schema.title || this.key;
1889 enable: function() {
1890 this.disabled = false;
1892 disable: function() {
1893 this.disabled = true;
1895 isEnabled: function() {
1896 return !this.disabled;
1898 isRequired: function() {
1899 if(typeof this.schema.required === "boolean") return this.schema.required;
1900 else if(this.parent && this.parent.schema && Array.isArray(this.parent.schema.required)) return this.parent.schema.required.indexOf(this.key) > -1;
1901 else if(this.jsoneditor.options.required_by_default) return true;
1904 getDisplayText: function(arr) {
1908 // Determine how many times each attribute name is used.
1909 // This helps us pick the most distinct display text for the schemas.
1910 $each(arr,function(i,el) {
1912 used[el.title] = used[el.title] || 0;
1915 if(el.description) {
1916 used[el.description] = used[el.description] || 0;
1917 used[el.description]++;
1920 used[el.format] = used[el.format] || 0;
1924 used[el.type] = used[el.type] || 0;
1929 // Determine display text for each element of the array
1930 $each(arr,function(i,el) {
1933 // If it's a simple string
1934 if(typeof el === "string") name = el;
1936 else if(el.title && used[el.title]<=1) name = el.title;
1937 else if(el.format && used[el.format]<=1) name = el.format;
1938 else if(el.type && used[el.type]<=1) name = el.type;
1939 else if(el.description && used[el.description]<=1) name = el.descripton;
1940 else if(el.title) name = el.title;
1941 else if(el.format) name = el.format;
1942 else if(el.type) name = el.type;
1943 else if(el.description) name = el.description;
1944 else if(JSON.stringify(el).length < 50) name = JSON.stringify(el);
1950 // Replace identical display text with "text 1", "text 2", etc.
1952 $each(disp,function(i,name) {
1953 inc[name] = inc[name] || 0;
1956 if(used[name] > 1) disp[i] = name + " " + inc[name];
1961 getOption: function(key) {
1963 throw "getOption is deprecated";
1966 window.console.error(e);
1969 return this.options[key];
1971 showValidationErrors: function(errors) {
1976 JSONEditor.defaults.editors["null"] = JSONEditor.AbstractEditor.extend({
1977 getValue: function() {
1978 if (!this.dependenciesFulfilled) {
1983 setValue: function() {
1986 getNumColumns: function() {
1991 JSONEditor.defaults.editors.qbldr = JSONEditor.AbstractEditor.extend({
1992 register: function() {
1994 if(!this.input) return;
1995 this.input.setAttribute('name',this.formname);
1997 unregister: function() {
1999 if(!this.input) return;
2000 this.input.removeAttribute('name');
2002 setValue: function(value, initial) {
2005 if(typeof value === "undefined" || typeof this.jqbldrId === "undefined" || value === this.value) {
2009 if ((initial === true) && (value !== "") && (value !== null)) {
2010 $(this.jqbldrId).queryBuilder('off','rulesChanged');
2011 $(this.jqbldrId).queryBuilder('setRulesFromSQL', value);
2012 var filter_result = $(this.jqbldrId).queryBuilder('getSQL');
2013 value = filter_result === null ? null : filter_result.sql;
2014 $(this.jqbldrId).queryBuilder('on', 'rulesChanged', this.qbldrRulesChangedCb.bind(this));
2017 this.input.value = value;
2020 // Bubble this setValue to parents if the value changed
2021 this.onChange(true);
2023 getValue: function() {
2026 if (this.value === "" || this.value === null) {
2033 getNumColumns: function() {
2037 qbldrRulesChangedCb: function(eventObj) {
2040 $(this.jqbldrId).queryBuilder('off','rulesChanged');
2042 var filter_result = $(this.jqbldrId).queryBuilder('getSQL');
2044 if (filter_result !== null) {
2045 this.setValue(filter_result.sql);
2048 $(this.jqbldrId).queryBuilder('on', 'rulesChanged', this.qbldrRulesChangedCb.bind(this));
2052 preBuild: function() {
2059 this.qschema = this.schema.qschema;
2060 this.qbldrId = this.path;
2061 this.jqbldrId = '#' + this.qbldrId;
2062 this.jqbldrId = this.jqbldrId.replace(/\./g,'\\.');
2064 this.qgrid = this.theme.getGridContainer();
2065 this.qgrid.style.padding = '4px';
2066 this.qgrid.style.border = '1px solid #e3e3e3';
2068 this.gridrow1 = this.theme.getGridRow();
2069 this.gridrow1.style.padding = '4px';
2071 this.gridrow2 = this.theme.getGridRow();
2072 this.gridrow2.style.padding = '4px';
2074 this.title = this.getTitle();
2075 this.label = this.theme.getFormInputLabel(this.title);
2077 this.input = this.theme.getTextareaInput();
2078 this.input.disabled = 'true';
2080 this.control = this.theme.getFormControl(this.label, this.input, this.description);
2082 this.gridrow2.setAttribute('id',this.qbldrId);
2084 this.container.appendChild(this.qgrid); // attach the grid to container
2086 this.qgrid.appendChild(this.gridrow1); // attach gridrow1 to grid
2087 this.gridrow1.appendChild(this.control); // attach control form to gridrow1
2089 this.qgrid.appendChild(this.gridrow2);
2091 var options = { conditions: [ 'AND', 'OR'], sort_filters: true };
2093 $.extend(this.qschema, options);
2095 $(this.jqbldrId).queryBuilder(this.qschema);
2097 //$(this.jqbldrId).queryBuilder('on', 'rulesChanged', this.qbldrRulesChangedCb.bind(this));
2098 //$(this.jqbldrId).queryBuilder('on', 'afterUpdateRuleValue', this.qbldrRulesChangedCb.bind(this));
2099 $(this.jqbldrId).queryBuilder('on', 'rulesChanged', this.qbldrRulesChangedCb.bind(this));
2101 enable: function() {
2104 disable: function() {
2107 afterInputReady: function() {
2108 var self = this, options;
2109 self.theme.afterInputReady(self.input);
2111 refreshValue: function() {
2112 this.value = this.input.value;
2113 if(typeof this.value !== "string") this.value = '';
2115 destroy: function() {
2120 * This is overridden in derivative editors
2122 sanitize: function(value) {
2126 * Re-calculates the value if needed
2128 onWatchedFieldChange: function() {
2129 var self = this, vars, j;
2133 showValidationErrors: function(errors) {
2136 if(this.jsoneditor.options.show_errors === "always") {}
2137 else if(this.previous_error_setting===this.jsoneditor.options.show_errors) return;
2139 this.previous_error_setting = this.jsoneditor.options.show_errors;
2142 $each(errors,function(i,error) {
2143 if(error.path === self.path) {
2144 messages.push(error.message);
2148 this.input.controlgroup = this.control;
2150 if(messages.length) {
2151 this.theme.addInputError(this.input, messages.join('. ')+'.');
2154 this.theme.removeInputError(this.input);
2159 JSONEditor.defaults.editors.string = JSONEditor.AbstractEditor.extend({
2160 register: function() {
2162 if(!this.input) return;
2163 this.input.setAttribute('name',this.formname);
2165 unregister: function() {
2167 if(!this.input) return;
2168 this.input.removeAttribute('name');
2170 setValue: function(value,initial,from_template) {
2173 if(this.template && !from_template) {
2177 if(value === null || typeof value === 'undefined') value = "";
2178 else if(typeof value === "object") value = JSON.stringify(value);
2179 else if(typeof value !== "string") value = ""+value;
2181 if(value === this.serialized) return;
2183 // Sanitize value before setting it
2184 var sanitized = this.sanitize(value);
2186 if(this.input.value === sanitized) {
2190 this.input.value = sanitized;
2192 // If using SCEditor, update the WYSIWYG
2193 if(this.sceditor_instance) {
2194 this.sceditor_instance.val(sanitized);
2196 else if(this.SimpleMDE) {
2197 this.SimpleMDE.value(sanitized);
2199 else if(this.ace_editor) {
2200 this.ace_editor.setValue(sanitized);
2203 var changed = from_template || this.getValue() !== value;
2205 this.refreshValue();
2207 if(initial) this.is_dirty = false;
2208 else if(this.jsoneditor.options.show_errors === "change") this.is_dirty = true;
2210 if(this.adjust_height) this.adjust_height(this.input);
2212 // Bubble this setValue to parents if the value changed
2213 this.onChange(changed);
2215 getNumColumns: function() {
2216 var min = Math.ceil(Math.max(this.getTitle().length,this.schema.maxLength||0,this.schema.minLength||0)/5);
2219 if(this.input_type === 'textarea') num = 6;
2220 else if(['text','email'].indexOf(this.input_type) >= 0) num = 4;
2223 return Math.min(12,Math.max(min,num));
2227 if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
2228 if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
2229 if(this.options.infoText) this.infoButton = this.theme.getInfoButton(this.options.infoText);
2231 this.format = this.schema.format;
2232 if(!this.format && this.schema.media && this.schema.media.type) {
2233 this.format = this.schema.media.type.replace(/(^(application|text)\/(x-)?(script\.)?)|(-source$)/g,'');
2235 if(!this.format && this.options.default_format) {
2236 this.format = this.options.default_format;
2238 if(this.options.format) {
2239 this.format = this.options.format;
2245 if(this.format === 'textarea') {
2246 this.input_type = 'textarea';
2247 this.input = this.theme.getTextareaInput();
2250 else if(this.format === 'range') {
2251 this.input_type = 'range';
2252 var min = this.schema.minimum || 0;
2253 var max = this.schema.maximum || Math.max(100,min+1);
2255 if(this.schema.multipleOf) {
2256 if(min%this.schema.multipleOf) min = Math.ceil(min/this.schema.multipleOf)*this.schema.multipleOf;
2257 if(max%this.schema.multipleOf) max = Math.floor(max/this.schema.multipleOf)*this.schema.multipleOf;
2258 step = this.schema.multipleOf;
2261 this.input = this.theme.getRangeInput(min,max,step);
2315 ].indexOf(this.format) >= 0
2317 this.input_type = this.format;
2318 this.source_code = true;
2320 this.input = this.theme.getTextareaInput();
2324 this.input_type = this.format;
2325 this.input = this.theme.getFormInputField(this.input_type);
2328 // Normal text input
2330 this.input_type = 'text';
2331 this.input = this.theme.getFormInputField(this.input_type);
2334 // minLength, maxLength, and pattern
2335 if(typeof this.schema.maxLength !== "undefined") this.input.setAttribute('maxlength',this.schema.maxLength);
2336 if(typeof this.schema.pattern !== "undefined") this.input.setAttribute('pattern',this.schema.pattern);
2337 else if(typeof this.schema.minLength !== "undefined") this.input.setAttribute('pattern','.{'+this.schema.minLength+',}');
2339 if(this.options.compact) {
2340 this.container.className += ' compact';
2343 if(this.options.input_width) this.input.style.width = this.options.input_width;
2346 if(this.schema.readOnly || this.schema.readonly || this.schema.template) {
2347 this.always_disabled = true;
2348 this.input.disabled = true;
2352 .addEventListener('change',function(e) {
2354 e.stopPropagation();
2356 // Don't allow changing if this field is a template
2357 if(self.schema.template) {
2358 this.value = self.value;
2362 var val = this.value;
2365 var sanitized = self.sanitize(val);
2366 if(val !== sanitized) {
2367 this.value = sanitized;
2370 self.is_dirty = true;
2372 self.refreshValue();
2373 self.onChange(true);
2376 if(this.options.input_height) this.input.style.height = this.options.input_height;
2377 if(this.options.expand_height) {
2378 this.adjust_height = function(el) {
2380 var i, ch=el.offsetHeight;
2382 if(el.offsetHeight < el.scrollHeight) {
2384 while(el.offsetHeight < el.scrollHeight+3) {
2388 el.style.height = ch+'px';
2393 while(el.offsetHeight >= el.scrollHeight+3) {
2397 el.style.height = ch+'px';
2399 el.style.height = (ch+1)+'px';
2403 this.input.addEventListener('keyup',function(e) {
2404 self.adjust_height(this);
2406 this.input.addEventListener('change',function(e) {
2407 self.adjust_height(this);
2409 this.adjust_height();
2412 if(this.format) this.input.setAttribute('data-schemaformat',this.format);
2414 this.control = this.theme.getFormControl(this.label, this.input, this.description, this.infoButton);
2415 this.container.appendChild(this.control);
2417 // Any special formatting that needs to happen after the input is added to the dom
2418 window.requestAnimationFrame(function() {
2419 // Skip in case the input is only a temporary editor,
2420 // otherwise, in the case of an ace_editor creation,
2421 // it will generate an error trying to append it to the missing parentNode
2422 if(self.input.parentNode) self.afterInputReady();
2423 if(self.adjust_height) self.adjust_height(self.input);
2426 // Compile and store the template
2427 if(this.schema.template) {
2428 this.template = this.jsoneditor.compileTemplate(this.schema.template, this.template_engine);
2429 this.refreshValue();
2432 this.refreshValue();
2435 enable: function() {
2436 if(!this.always_disabled) {
2437 this.input.disabled = false;
2438 // TODO: WYSIWYG and Markdown editors
2442 disable: function(always_disabled) {
2443 if(always_disabled) this.always_disabled = true;
2444 this.input.disabled = true;
2445 // TODO: WYSIWYG and Markdown editors
2448 afterInputReady: function() {
2449 var self = this, options;
2452 if(this.source_code) {
2453 // WYSIWYG html and bbcode editor
2454 if(this.options.wysiwyg &&
2455 ['html','bbcode'].indexOf(this.input_type) >= 0 &&
2456 window.jQuery && window.jQuery.fn && window.jQuery.fn.sceditor
2458 options = $extend({},{
2459 plugins: self.input_type==='html'? 'xhtml' : 'bbcode',
2460 emoticonsEnabled: false,
2463 },JSONEditor.plugins.sceditor,self.options.sceditor_options||{});
2465 window.jQuery(self.input).sceditor(options);
2467 self.sceditor_instance = window.jQuery(self.input).sceditor('instance');
2469 self.sceditor_instance.blur(function() {
2470 // Get editor's value
2471 var val = window.jQuery("<div>"+self.sceditor_instance.val()+"</div>");
2472 // Remove sceditor spans/divs
2473 window.jQuery('#sceditor-start-marker,#sceditor-end-marker,.sceditor-nlf',val).remove();
2474 // Set the value and update
2475 self.input.value = val.html();
2476 self.value = self.input.value;
2477 self.is_dirty = true;
2478 self.onChange(true);
2481 // SimpleMDE for markdown (if it's loaded)
2482 else if (this.input_type === 'markdown' && window.SimpleMDE) {
2483 options = $extend({},JSONEditor.plugins.SimpleMDE,{
2487 this.SimpleMDE = new window.SimpleMDE((options));
2489 this.SimpleMDE.codemirror.on("change",function() {
2490 self.value = self.SimpleMDE.value();
2491 self.is_dirty = true;
2492 self.onChange(true);
2495 // ACE editor for everything else
2496 else if(window.ace) {
2497 var mode = this.input_type;
2498 // aliases for c/cpp
2499 if(mode === 'cpp' || mode === 'c++' || mode === 'c') {
2503 this.ace_container = document.createElement('div');
2504 this.ace_container.style.width = '100%';
2505 this.ace_container.style.position = 'relative';
2506 this.ace_container.style.height = '400px';
2507 this.input.parentNode.insertBefore(this.ace_container,this.input);
2508 this.input.style.display = 'none';
2509 this.ace_editor = window.ace.edit(this.ace_container);
2511 this.ace_editor.setValue(this.getValue());
2514 if(JSONEditor.plugins.ace.theme) this.ace_editor.setTheme('ace/theme/'+JSONEditor.plugins.ace.theme);
2516 this.ace_editor.getSession().setMode('ace/mode/' + this.schema.format);
2518 // Listen for changes
2519 this.ace_editor.on('change',function() {
2520 var val = self.ace_editor.getValue();
2521 self.input.value = val;
2522 self.refreshValue();
2523 self.is_dirty = true;
2524 self.onChange(true);
2529 self.theme.afterInputReady(self.input);
2531 refreshValue: function() {
2532 this.value = this.input.value;
2533 if(typeof this.value !== "string") this.value = '';
2534 this.serialized = this.value;
2536 destroy: function() {
2537 // If using SCEditor, destroy the editor instance
2538 if(this.sceditor_instance) {
2539 this.sceditor_instance.destroy();
2541 else if(this.SimpleMDE) {
2542 this.SimpleMDE.destroy();
2544 else if(this.ace_editor) {
2545 this.ace_editor.destroy();
2549 this.template = null;
2550 if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
2551 if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
2552 if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
2557 * This is overridden in derivative editors
2559 sanitize: function(value) {
2563 * Re-calculates the value if needed
2565 onWatchedFieldChange: function() {
2566 var self = this, vars, j;
2568 // If this editor needs to be rendered by a macro template
2570 vars = this.getWatchedFieldValues();
2571 this.setValue(this.template(vars),false,true);
2576 showValidationErrors: function(errors) {
2579 if(this.jsoneditor.options.show_errors === "always") {}
2580 else if(!this.is_dirty && this.previous_error_setting===this.jsoneditor.options.show_errors) return;
2582 this.previous_error_setting = this.jsoneditor.options.show_errors;
2585 $each(errors,function(i,error) {
2586 if(error.path === self.path) {
2587 messages.push(error.message);
2591 this.input.controlgroup = this.control;
2593 if(messages.length) {
2594 this.theme.addInputError(this.input, messages.join('. ')+'.');
2597 this.theme.removeInputError(this.input);
2603 * Created by Mehmet Baker on 12.04.2017
2605 JSONEditor.defaults.editors.hidden = JSONEditor.AbstractEditor.extend({
2606 register: function () {
2608 if (!this.input) return;
2609 this.input.setAttribute('name', this.formname);
2611 unregister: function () {
2613 if (!this.input) return;
2614 this.input.removeAttribute('name');
2616 setValue: function (value, initial, from_template) {
2619 if(this.template && !from_template) {
2623 if(value === null || typeof value === 'undefined') value = "";
2624 else if(typeof value === "object") value = JSON.stringify(value);
2625 else if(typeof value !== "string") value = ""+value;
2627 if(value === this.serialized) return;
2629 // Sanitize value before setting it
2630 var sanitized = this.sanitize(value);
2632 if(this.input.value === sanitized) {
2636 this.input.value = sanitized;
2638 var changed = from_template || this.getValue() !== value;
2640 this.refreshValue();
2642 if(initial) this.is_dirty = false;
2643 else if(this.jsoneditor.options.show_errors === "change") this.is_dirty = true;
2645 if(this.adjust_height) this.adjust_height(this.input);
2647 // Bubble this setValue to parents if the value changed
2648 this.onChange(changed);
2650 getNumColumns: function () {
2653 enable: function () {
2656 disable: function () {
2659 refreshValue: function () {
2660 this.value = this.input.value;
2661 if (typeof this.value !== "string") this.value = '';
2662 this.serialized = this.value;
2664 destroy: function () {
2665 this.template = null;
2666 if (this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
2667 if (this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
2668 if (this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
2673 * This is overridden in derivative editors
2675 sanitize: function (value) {
2679 * Re-calculates the value if needed
2681 onWatchedFieldChange: function () {
2682 var self = this, vars, j;
2684 // If this editor needs to be rendered by a macro template
2685 if (this.template) {
2686 vars = this.getWatchedFieldValues();
2687 this.setValue(this.template(vars), false, true);
2692 build: function () {
2695 this.format = this.schema.format;
2696 if (!this.format && this.options.default_format) {
2697 this.format = this.options.default_format;
2699 if (this.options.format) {
2700 this.format = this.options.format;
2703 this.input_type = 'hidden';
2704 this.input = this.theme.getFormInputField(this.input_type);
2706 if (this.format) this.input.setAttribute('data-schemaformat', this.format);
2708 this.container.appendChild(this.input);
2710 // Compile and store the template
2711 if (this.schema.template) {
2712 this.template = this.jsoneditor.compileTemplate(this.schema.template, this.template_engine);
2713 this.refreshValue();
2716 this.refreshValue();
2720 JSONEditor.defaults.editors.number = JSONEditor.defaults.editors.string.extend({
2724 if (typeof this.schema.minimum !== "undefined") {
2725 var minimum = this.schema.minimum;
2727 if (typeof this.schema.exclusiveMinimum !== "undefined") {
2731 this.input.setAttribute("min", minimum);
2734 if (typeof this.schema.maximum !== "undefined") {
2735 var maximum = this.schema.maximum;
2737 if (typeof this.schema.exclusiveMaximum !== "undefined") {
2741 this.input.setAttribute("max", maximum);
2744 if (typeof this.schema.step !== "undefined") {
2745 var step = this.schema.step || 1;
2746 this.input.setAttribute("step", step);
2750 sanitize: function(value) {
2751 return (value+"").replace(/[^0-9\.\-eE]/g,'');
2753 getNumColumns: function() {
2756 getValue: function() {
2757 if (!this.dependenciesFulfilled) {
2760 return this.value===''?undefined:this.value*1;
2764 JSONEditor.defaults.editors.integer = JSONEditor.defaults.editors.number.extend({
2765 sanitize: function(value) {
2767 return value.replace(/[^0-9\-]/g,'');
2769 getNumColumns: function() {
2774 JSONEditor.defaults.editors.rating = JSONEditor.defaults.editors.integer.extend({
2777 if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
2778 if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
2780 // Dynamically add the required CSS the first time this editor is used
2781 var styleId = 'json-editor-style-rating';
2782 var styles = document.getElementById(styleId);
2784 var style = document.createElement('style');
2786 style.type = 'text/css';
2788 ' .rating-container {' +
2789 ' display: inline-block;' +
2797 ' /* :not(:checked) is a filter, so that browsers that don’t support :checked don’t' +
2798 ' follow these rules. Every browser that supports :checked also supports :not(), so' +
2799 ' it doesn’t make the test unnecessarily selective */' +
2800 ' .rating:not(:checked) > input {' +
2801 ' position:absolute;' +
2803 ' clip:rect(0,0,0,0);' +
2806 ' .rating:not(:checked) > label {' +
2809 ' padding:0 .1em;' +
2810 ' overflow:hidden;' +
2811 ' white-space:nowrap;' +
2812 ' cursor:pointer;' +
2816 ' .rating:not(:checked) > label:before {' +
2817 ' content: \'★ \';' +
2820 ' .rating > input:checked ~ label {' +
2821 ' color: #FFB200;' +
2824 ' .rating:not([readOnly]):not(:checked) > label:hover,' +
2825 ' .rating:not([readOnly]):not(:checked) > label:hover ~ label {' +
2826 ' color: #FFDA00;' +
2829 ' .rating:not([readOnly]) > input:checked + label:hover,' +
2830 ' .rating:not([readOnly]) > input:checked + label:hover ~ label,' +
2831 ' .rating:not([readOnly]) > input:checked ~ label:hover,' +
2832 ' .rating:not([readOnly]) > input:checked ~ label:hover ~ label,' +
2833 ' .rating:not([readOnly]) > label:hover ~ input:checked ~ label {' +
2834 ' color: #FF8C0D;' +
2837 ' .rating:not([readOnly]) > label:active {' +
2838 ' position:relative;' +
2842 document.getElementsByTagName('head')[0].appendChild(style);
2845 this.input = this.theme.getFormInputField('hidden');
2846 this.container.appendChild(this.input);
2848 // Required to keep height
2849 var ratingContainer = document.createElement('div');
2850 ratingContainer.className = 'rating-container';
2852 // Contains options for rating
2853 var group = document.createElement('div');
2854 group.setAttribute('name', this.formname);
2855 group.className = 'rating';
2856 ratingContainer.appendChild(group);
2858 if(this.options.compact) this.container.setAttribute('class',this.container.getAttribute('class')+' compact');
2860 var max = this.schema.maximum ? this.schema.maximum : 5;
2861 if (this.schema.exclusiveMaximum) max--;
2864 for(i=max; i>0; i--) {
2865 var id = this.formname + i;
2866 var radioInput = this.theme.getFormInputField('radio');
2867 radioInput.setAttribute('id', id);
2868 radioInput.setAttribute('value', i);
2869 radioInput.setAttribute('name', this.formname);
2870 group.appendChild(radioInput);
2871 this.inputs.push(radioInput);
2873 var label = document.createElement('label');
2874 label.setAttribute('for', id);
2875 label.appendChild(document.createTextNode(i + (i == 1 ? ' star' : ' stars')));
2876 group.appendChild(label);
2879 if(this.schema.readOnly || this.schema.readonly) {
2880 this.always_disabled = true;
2881 $each(this.inputs,function(i,input) {
2882 group.setAttribute("readOnly", "readOnly");
2883 input.disabled = true;
2888 .addEventListener('change',function(e) {
2890 e.stopPropagation();
2892 self.input.value = e.srcElement.value;
2894 self.is_dirty = true;
2896 self.refreshValue();
2897 self.watch_listener();
2898 self.jsoneditor.notifyWatchers(self.path);
2899 if(self.parent) self.parent.onChildEditorChange(self);
2900 else self.jsoneditor.onChange();
2903 this.control = this.theme.getFormControl(this.label, ratingContainer, this.description);
2904 this.container.appendChild(this.control);
2906 this.refreshValue();
2908 setValue: function(val) {
2909 var sanitized = this.sanitize(val);
2910 if(this.value === sanitized) {
2914 $each(this.inputs,function(i,input) {
2915 if (input.value === sanitized) {
2916 input.checked = true;
2917 self.value = sanitized;
2918 self.input.value = self.value;
2919 self.watch_listener();
2920 self.jsoneditor.notifyWatchers(self.path);
2927 JSONEditor.defaults.editors.object = JSONEditor.AbstractEditor.extend({
2928 getDefault: function() {
2929 return $extend({},this.schema["default"] || {});
2931 getChildEditors: function() {
2932 return this.editors;
2934 register: function() {
2937 for(var i in this.editors) {
2938 if(!this.editors.hasOwnProperty(i)) continue;
2939 this.editors[i].register();
2943 unregister: function() {
2946 for(var i in this.editors) {
2947 if(!this.editors.hasOwnProperty(i)) continue;
2948 this.editors[i].unregister();
2952 getNumColumns: function() {
2953 return Math.max(Math.min(12,this.maxwidth),3);
2955 enable: function() {
2956 if(!this.always_disabled) {
2957 if(this.editjson_button) this.editjson_button.disabled = false;
2958 if(this.addproperty_button) this.addproperty_button.disabled = false;
2962 for(var i in this.editors) {
2963 if(!this.editors.hasOwnProperty(i)) continue;
2964 this.editors[i].enable();
2969 disable: function(always_disabled) {
2970 if(always_disabled) this.always_disabled = true;
2971 if(this.editjson_button) this.editjson_button.disabled = true;
2972 if(this.addproperty_button) this.addproperty_button.disabled = true;
2973 this.hideEditJSON();
2977 for(var i in this.editors) {
2978 if(!this.editors.hasOwnProperty(i)) continue;
2979 this.editors[i].disable(always_disabled);
2983 layoutEditors: function() {
2984 var self = this, i, j;
2986 if(!this.row_container) return;
2988 // Sort editors by propertyOrder
2989 this.property_order = Object.keys(this.editors);
2990 this.property_order = this.property_order.sort(function(a,b) {
2991 var ordera = self.editors[a].schema.propertyOrder;
2992 var orderb = self.editors[b].schema.propertyOrder;
2993 if(typeof ordera !== "number") ordera = 1000;
2994 if(typeof orderb !== "number") orderb = 1000;
2996 return ordera - orderb;
2999 var container = document.createElement('div');
3000 var isCategoriesFormat = (this.format === 'categories');
3002 if(this.format === 'grid') {
3004 $each(this.property_order, function(j,key) {
3005 var editor = self.editors[key];
3006 if(editor.property_removed) return;
3008 var width = editor.options.hidden? 0 : (editor.options.grid_columns || editor.getNumColumns());
3009 var height = editor.options.hidden? 0 : editor.container.offsetHeight;
3010 // See if the editor will fit in any of the existing rows first
3011 for(var i=0; i<rows.length; i++) {
3012 // If the editor will fit in the row horizontally
3013 if(rows[i].width + width <= 12) {
3014 // If the editor is close to the other elements in height
3015 // i.e. Don't put a really tall editor in an otherwise short row or vice versa
3016 if(!height || (rows[i].minh*0.5 < height && rows[i].maxh*2 > height)) {
3022 // If there isn't a spot in any of the existing rows, start a new row
3023 if(found === false) {
3030 found = rows.length-1;
3033 rows[found].editors.push({
3039 rows[found].width += width;
3040 rows[found].minh = Math.min(rows[found].minh,height);
3041 rows[found].maxh = Math.max(rows[found].maxh,height);
3044 // Make almost full rows width 12
3045 // Do this by increasing all editors' sizes proprotionately
3046 // Any left over space goes to the biggest editor
3047 // Don't touch rows with a width of 6 or less
3048 for(i=0; i<rows.length; i++) {
3049 if(rows[i].width < 12) {
3050 var biggest = false;
3052 for(j=0; j<rows[i].editors.length; j++) {
3053 if(biggest === false) biggest = j;
3054 else if(rows[i].editors[j].width > rows[i].editors[biggest].width) biggest = j;
3055 rows[i].editors[j].width *= 12/rows[i].width;
3056 rows[i].editors[j].width = Math.floor(rows[i].editors[j].width);
3057 new_width += rows[i].editors[j].width;
3059 if(new_width < 12) rows[i].editors[biggest].width += 12-new_width;
3064 // layout hasn't changed
3065 if(this.layout === JSON.stringify(rows)) return false;
3066 this.layout = JSON.stringify(rows);
3069 for(i=0; i<rows.length; i++) {
3070 var row = this.theme.getGridRow();
3071 container.appendChild(row);
3072 for(j=0; j<rows[i].editors.length; j++) {
3073 var key = rows[i].editors[j].key;
3074 var editor = this.editors[key];
3076 if(editor.options.hidden) editor.container.style.display = 'none';
3077 else this.theme.setGridColumnSize(editor.container,rows[i].editors[j].width);
3078 row.appendChild(editor.container);
3083 else if(isCategoriesFormat) {
3084 //A container for properties not object nor arrays
3085 var containerSimple = document.createElement('div');
3086 //This will be the place to (re)build tabs and panes
3087 //tabs_holder has 2 childs, [0]: ul.nav.nav-tabs and [1]: div.tab-content
3088 var newTabs_holder = this.theme.getTopTabHolder(this.schema.title);
3089 //child [1] of previous, stores panes
3090 var newTabPanesContainer = this.theme.getTopTabContentHolder(newTabs_holder);
3092 $each(this.property_order, function(i,key){
3093 var editor = self.editors[key];
3094 if(editor.property_removed) return;
3095 var aPane = self.theme.getTabContent();
3096 var isObjOrArray = editor.schema && (editor.schema.type === "object" || editor.schema.type === "array");
3098 aPane.isObjOrArray = isObjOrArray;
3099 var gridRow = self.theme.getGridRow();
3101 //this happens with added properties, they don't have a tab
3103 //Pass the pane which holds the editor
3104 if(typeof self.basicPane === 'undefined'){
3105 //There is no basicPane yet, so aPane will be it
3106 self.addRow(editor,newTabs_holder, aPane);
3109 self.addRow(editor,newTabs_holder, self.basicPane);
3113 aPane.id = editor.tab_text.textContent;
3115 //For simple properties, add them on the same panel (Basic)
3117 containerSimple.appendChild(gridRow);
3118 //There is already some panes
3119 if(newTabPanesContainer.childElementCount > 0){
3120 //If first pane is object or array, insert before a simple pane
3121 if(newTabPanesContainer.firstChild.isObjOrArray){
3122 //Append pane for simple properties
3123 aPane.appendChild(containerSimple);
3124 newTabPanesContainer.insertBefore(aPane,newTabPanesContainer.firstChild);
3126 self.theme.insertBasicTopTab(editor.tab,newTabs_holder);
3127 //newTabs_holder.firstChild.insertBefore(editor.tab,newTabs_holder.firstChild.firstChild);
3128 //Update the basicPane
3129 editor.basicPane = aPane;
3132 //We already have a first "Basic" pane, just add the new property to it, so
3136 //There is no pane, so add the first (simple) pane
3138 //Append pane for simple properties
3139 aPane.appendChild(containerSimple);
3140 newTabPanesContainer.appendChild(aPane);
3142 //newTabs_holder.firstChild.appendChild(editor.tab);
3143 self.theme.addTopTab(newTabs_holder,editor.tab);
3144 //Update the basicPane
3145 editor.basicPane = aPane;
3148 //Objects and arrays earn it's own panes
3150 aPane.appendChild(gridRow);
3151 newTabPanesContainer.appendChild(aPane);
3152 //newTabs_holder.firstChild.appendChild(editor.tab);
3153 self.theme.addTopTab(newTabs_holder,editor.tab);
3156 if(editor.options.hidden) editor.container.style.display = 'none';
3157 else self.theme.setGridColumnSize(editor.container,12);
3158 //Now, add the property editor to the row
3159 gridRow.appendChild(editor.container);
3160 //Update the container (same as self.rows[x].container)
3161 editor.container = aPane;
3166 while (this.tabPanesContainer.firstChild) {
3167 this.tabPanesContainer.removeChild(this.tabPanesContainer.firstChild);
3170 //Erase old tabs and set the new ones
3171 var parentTabs_holder = this.tabs_holder.parentNode;
3172 parentTabs_holder.removeChild(parentTabs_holder.firstChild);
3173 parentTabs_holder.appendChild(newTabs_holder);
3175 this.tabPanesContainer = newTabPanesContainer;
3176 this.tabs_holder = newTabs_holder;
3178 //Activate the first tab
3179 var firstTab = this.theme.getFirstTab(this.tabs_holder);
3181 $trigger(firstTab,'click');
3185 // !isCategoriesFormat
3187 $each(this.property_order, function(i,key) {
3188 var editor = self.editors[key];
3189 if(editor.property_removed) return;
3190 var row = self.theme.getGridRow();
3191 container.appendChild(row);
3193 if(editor.options.hidden) editor.container.style.display = 'none';
3194 else self.theme.setGridColumnSize(editor.container,12);
3195 row.appendChild(editor.container);
3198 //for grid and normal layout
3199 while (this.row_container.firstChild) {
3200 this.row_container.removeChild(this.row_container.firstChild);
3202 this.row_container.appendChild(container);
3204 getPropertySchema: function(key) {
3205 // Schema declared directly in properties
3206 var schema = this.schema.properties[key] || {};
3207 schema = $extend({},schema);
3208 var matched = this.schema.properties[key]? true : false;
3210 // Any matching patternProperties should be merged in
3211 if(this.schema.patternProperties) {
3212 for(var i in this.schema.patternProperties) {
3213 if(!this.schema.patternProperties.hasOwnProperty(i)) continue;
3214 var regex = new RegExp(i);
3215 if(regex.test(key)) {
3216 schema.allOf = schema.allOf || [];
3217 schema.allOf.push(this.schema.patternProperties[i]);
3223 // Hasn't matched other rules, use additionalProperties schema
3224 if(!matched && this.schema.additionalProperties && typeof this.schema.additionalProperties === "object") {
3225 schema = $extend({},this.schema.additionalProperties);
3230 preBuild: function() {
3234 this.cached_editors = {};
3237 this.format = this.options.layout || this.options.object_layout || this.schema.format || this.jsoneditor.options.object_layout || 'normal';
3239 this.schema.properties = this.schema.properties || {};
3244 // If the object should be rendered as a table row
3245 if(this.options.table_row) {
3246 $each(this.schema.properties, function(key,schema) {
3247 var editor = self.jsoneditor.getEditorClass(schema);
3248 self.editors[key] = self.jsoneditor.createEditor(editor,{
3249 jsoneditor: self.jsoneditor,
3251 path: self.path+'.'+key,
3256 self.editors[key].preBuild();
3258 var width = self.editors[key].options.hidden? 0 : (self.editors[key].options.grid_columns || self.editors[key].getNumColumns());
3260 self.minwidth += width;
3261 self.maxwidth += width;
3263 this.no_link_holder = true;
3265 // If the object should be rendered as a table
3266 else if(this.options.table) {
3267 // TODO: table display format
3268 throw "Not supported yet";
3270 // If the object should be rendered as a div
3272 if(!this.schema.defaultProperties) {
3273 if(this.jsoneditor.options.display_required_only || this.options.display_required_only) {
3274 this.schema.defaultProperties = [];
3275 $each(this.schema.properties, function(k,s) {
3276 if(self.isRequired({key: k, schema: s})) {
3277 self.schema.defaultProperties.push(k);
3282 self.schema.defaultProperties = Object.keys(self.schema.properties);
3286 // Increase the grid width to account for padding
3289 $each(this.schema.defaultProperties, function(i,key) {
3290 self.addObjectProperty(key, true);
3292 if(self.editors[key]) {
3293 self.minwidth = Math.max(self.minwidth,(self.editors[key].options.grid_columns || self.editors[key].getNumColumns()));
3294 self.maxwidth += (self.editors[key].options.grid_columns || self.editors[key].getNumColumns());
3299 // Sort editors by propertyOrder
3300 this.property_order = Object.keys(this.editors);
3301 this.property_order = this.property_order.sort(function(a,b) {
3302 var ordera = self.editors[a].schema.propertyOrder;
3303 var orderb = self.editors[b].schema.propertyOrder;
3304 if(typeof ordera !== "number") ordera = 1000;
3305 if(typeof orderb !== "number") orderb = 1000;
3307 return ordera - orderb;
3310 //"Borrow" from arrays code
3311 addTab: function(idx){
3313 var isObjOrArray = self.rows[idx].schema && (self.rows[idx].schema.type === "object" || self.rows[idx].schema.type === "array");
3314 if(self.tabs_holder) {
3315 self.rows[idx].tab_text = document.createElement('span');
3318 self.rows[idx].tab_text.textContent = (typeof self.schema.basicCategoryTitle === 'undefined') ? "Basic" : self.schema.basicCategoryTitle;
3320 self.rows[idx].tab_text.textContent = self.rows[idx].getHeaderText();
3322 self.rows[idx].tab = self.theme.getTopTab(self.rows[idx].tab_text,self.rows[idx].tab_text.textContent);
3323 self.rows[idx].tab.addEventListener('click', function(e) {
3324 self.active_tab = self.rows[idx].tab;
3327 e.stopPropagation();
3333 addRow: function(editor, tabHolder, holder) {
3335 var rowsLen = this.rows.length;
3336 var isObjOrArray = editor.schema.type === "object" || editor.schema.type === "array";
3339 self.rows[rowsLen] = editor;
3340 //container stores the editor corresponding pane to set the display style when refreshing Tabs
3341 self.rows[rowsLen].container = holder;
3345 //This is the first simple property to be added,
3346 //add a ("Basic") tab for it and save it's row number
3347 if(typeof self.basicTab === "undefined"){
3348 self.addTab(rowsLen);
3349 //Store the index row of the first simple property added
3350 self.basicTab = rowsLen;
3351 self.basicPane = holder;
3352 self.theme.addTopTab(tabHolder, self.rows[rowsLen].tab);
3356 //Any other simple property gets the same tab (and the same pane) as the first one,
3357 //so, when 'click' event is fired from a row, it gets the correct ("Basic") tab
3358 self.rows[rowsLen].tab = self.rows[self.basicTab].tab;
3359 self.rows[rowsLen].tab_text = self.rows[self.basicTab].tab_text;
3360 self.rows[rowsLen].container = self.rows[self.basicTab].container;
3364 self.addTab(rowsLen);
3365 self.theme.addTopTab(tabHolder, self.rows[rowsLen].tab);
3368 //Mark the active tab and make visible the corresponding pane, hide others
3369 refreshTabs: function(refresh_headers) {
3371 var basicTabPresent = typeof self.basicTab !== 'undefined';
3372 var basicTabRefreshed = false;
3374 $each(this.rows, function(i,row) {
3375 //If it's an orphan row (some property which has been deleted), return
3376 if(!row.tab || !row.container || !row.container.parentNode) return;
3378 if(basicTabPresent && row.tab == self.rows[self.basicTab].tab && basicTabRefreshed) return;
3380 if(refresh_headers) {
3381 row.tab_text.textContent = row.getHeaderText();
3384 //All rows of simple properties point to the same tab, so refresh just once
3385 if(basicTabPresent && row.tab == self.rows[self.basicTab].tab) basicTabRefreshed = true;
3387 if(row.tab === self.active_tab) {
3388 self.theme.markTabActive(row);
3391 self.theme.markTabInactive(row);
3399 var isCategoriesFormat = (this.format === 'categories');
3401 this.active_tab = null;
3403 // If the object should be rendered as a table row
3404 if(this.options.table_row) {
3405 this.editor_holder = this.container;
3406 $each(this.editors, function(key,editor) {
3407 var holder = self.theme.getTableCell();
3408 self.editor_holder.appendChild(holder);
3410 editor.setContainer(holder);
3414 if(self.editors[key].options.hidden) {
3415 holder.style.display = 'none';
3417 if(self.editors[key].options.input_width) {
3418 holder.style.width = self.editors[key].options.input_width;
3422 // If the object should be rendered as a table
3423 else if(this.options.table) {
3424 // TODO: table display format
3425 throw "Not supported yet";
3427 // If the object should be rendered as a div
3429 this.header = document.createElement('span');
3430 this.header.textContent = this.getTitle();
3431 this.title = this.theme.getHeader(this.header);
3432 this.container.appendChild(this.title);
3433 this.container.style.position = 'relative';
3436 this.editjson_holder = this.theme.getModal();
3437 this.editjson_textarea = this.theme.getTextareaInput();
3438 this.editjson_textarea.style.height = '170px';
3439 this.editjson_textarea.style.width = '300px';
3440 this.editjson_textarea.style.display = 'block';
3441 this.editjson_save = this.getButton('Save','save','Save');
3442 this.editjson_save.addEventListener('click',function(e) {
3444 e.stopPropagation();
3447 this.editjson_cancel = this.getButton('Cancel','cancel','Cancel');
3448 this.editjson_cancel.addEventListener('click',function(e) {
3450 e.stopPropagation();
3451 self.hideEditJSON();
3453 this.editjson_holder.appendChild(this.editjson_textarea);
3454 this.editjson_holder.appendChild(this.editjson_save);
3455 this.editjson_holder.appendChild(this.editjson_cancel);
3457 // Manage Properties modal
3458 this.addproperty_holder = this.theme.getModal();
3459 this.addproperty_list = document.createElement('div');
3460 this.addproperty_list.style.width = '295px';
3461 this.addproperty_list.style.maxHeight = '160px';
3462 this.addproperty_list.style.padding = '5px 0';
3463 this.addproperty_list.style.overflowY = 'auto';
3464 this.addproperty_list.style.overflowX = 'hidden';
3465 this.addproperty_list.style.paddingLeft = '5px';
3466 this.addproperty_list.setAttribute('class', 'property-selector');
3467 this.addproperty_add = this.getButton('add','add','add');
3468 this.addproperty_input = this.theme.getFormInputField('text');
3469 this.addproperty_input.setAttribute('placeholder','Property name...');
3470 this.addproperty_input.style.width = '220px';
3471 this.addproperty_input.style.marginBottom = '0';
3472 this.addproperty_input.style.display = 'inline-block';
3473 this.addproperty_add.addEventListener('click',function(e) {
3475 e.stopPropagation();
3476 if(self.addproperty_input.value) {
3477 if(self.editors[self.addproperty_input.value]) {
3478 window.alert('there is already a property with that name');
3482 self.addObjectProperty(self.addproperty_input.value);
3483 if(self.editors[self.addproperty_input.value]) {
3484 self.editors[self.addproperty_input.value].disable();
3486 self.onChange(true);
3489 this.addproperty_holder.appendChild(this.addproperty_list);
3490 this.addproperty_holder.appendChild(this.addproperty_input);
3491 this.addproperty_holder.appendChild(this.addproperty_add);
3492 var spacer = document.createElement('div');
3493 spacer.style.clear = 'both';
3494 this.addproperty_holder.appendChild(spacer);
3498 if(this.schema.description) {
3499 this.description = this.theme.getDescription(this.schema.description);
3500 this.container.appendChild(this.description);
3503 // Validation error placeholder area
3504 this.error_holder = document.createElement('div');
3505 this.container.appendChild(this.error_holder);
3507 // Container for child editor area
3508 this.editor_holder = this.theme.getIndentedPanel();
3509 this.container.appendChild(this.editor_holder);
3511 // Container for rows of child editors
3512 this.row_container = this.theme.getGridContainer();
3514 if(isCategoriesFormat) {
3515 this.tabs_holder = this.theme.getTopTabHolder(this.schema.title);
3516 this.tabPanesContainer = this.theme.getTopTabContentHolder(this.tabs_holder);
3517 this.editor_holder.appendChild(this.tabs_holder);
3520 this.tabs_holder = this.theme.getTabHolder(this.schema.title);
3521 this.tabPanesContainer = this.theme.getTabContentHolder(this.tabs_holder);
3522 this.editor_holder.appendChild(this.row_container);
3525 $each(this.editors, function(key,editor) {
3526 var aPane = self.theme.getTabContent();
3527 var holder = self.theme.getGridColumn();
3528 var isObjOrArray = (editor.schema && (editor.schema.type === 'object' || editor.schema.type === 'array')) ? true : false;
3529 aPane.isObjOrArray = isObjOrArray;
3531 if(isCategoriesFormat){
3533 var single_row_container = self.theme.getGridContainer();
3534 single_row_container.appendChild(holder);
3535 aPane.appendChild(single_row_container);
3536 self.tabPanesContainer.appendChild(aPane);
3537 self.row_container = single_row_container;
3540 if(typeof self.row_container_basic === 'undefined'){
3541 self.row_container_basic = self.theme.getGridContainer();
3542 aPane.appendChild(self.row_container_basic);
3543 if(self.tabPanesContainer.childElementCount == 0){
3544 self.tabPanesContainer.appendChild(aPane);
3547 self.tabPanesContainer.insertBefore(aPane,self.tabPanesContainer.childNodes[1]);
3550 self.row_container_basic.appendChild(holder);
3553 self.addRow(editor,self.tabs_holder,aPane);
3555 aPane.id = editor.schema.title; //editor.schema.path//tab_text.textContent
3559 self.row_container.appendChild(holder);
3562 editor.setContainer(holder);
3568 $trigger(this.rows[0].tab,'click');
3572 this.title_controls = this.theme.getHeaderButtonHolder();
3573 this.editjson_controls = this.theme.getHeaderButtonHolder();
3574 this.addproperty_controls = this.theme.getHeaderButtonHolder();
3575 this.title.appendChild(this.title_controls);
3576 this.title.appendChild(this.editjson_controls);
3577 this.title.appendChild(this.addproperty_controls);
3580 this.collapsed = false;
3581 this.toggle_button = this.getButton('', 'collapse', this.translate('button_collapse'));
3582 this.title_controls.appendChild(this.toggle_button);
3583 this.toggle_button.addEventListener('click',function(e) {
3585 e.stopPropagation();
3586 if(self.collapsed) {
3587 self.editor_holder.style.display = '';
3588 self.collapsed = false;
3589 self.setButtonText(self.toggle_button,'','collapse',self.translate('button_collapse'));
3592 self.editor_holder.style.display = 'none';
3593 self.collapsed = true;
3594 self.setButtonText(self.toggle_button,'','expand',self.translate('button_expand'));
3598 // If it should start collapsed
3599 if(this.options.collapsed) {
3600 $trigger(this.toggle_button,'click');
3603 // Collapse button disabled
3604 if(this.schema.options && typeof this.schema.options.disable_collapse !== "undefined") {
3605 if(this.schema.options.disable_collapse) this.toggle_button.style.display = 'none';
3607 else if(this.jsoneditor.options.disable_collapse) {
3608 this.toggle_button.style.display = 'none';
3612 this.editjson_button = this.getButton('JSON','edit','Edit JSON');
3613 this.editjson_button.addEventListener('click',function(e) {
3615 e.stopPropagation();
3616 self.toggleEditJSON();
3618 this.editjson_controls.appendChild(this.editjson_button);
3619 this.editjson_controls.appendChild(this.editjson_holder);
3621 // Edit JSON Buttton disabled
3622 if(this.schema.options && typeof this.schema.options.disable_edit_json !== "undefined") {
3623 if(this.schema.options.disable_edit_json) this.editjson_button.style.display = 'none';
3625 else if(this.jsoneditor.options.disable_edit_json) {
3626 this.editjson_button.style.display = 'none';
3629 // Object Properties Button
3630 this.addproperty_button = this.getButton('Properties','edit','Object Properties');
3631 this.addproperty_button.addEventListener('click',function(e) {
3633 e.stopPropagation();
3634 self.toggleAddProperty();
3636 this.addproperty_controls.appendChild(this.addproperty_button);
3637 this.addproperty_controls.appendChild(this.addproperty_holder);
3638 this.refreshAddProperties();
3641 // Fix table cell ordering
3642 if(this.options.table_row) {
3643 this.editor_holder = this.container;
3644 $each(this.property_order,function(i,key) {
3645 self.editor_holder.appendChild(self.editors[key].container);
3648 // Layout object editors in grid if needed
3651 this.layoutEditors();
3652 // Do it again now that we know the approximate heights of elements
3653 this.layoutEditors();
3656 showEditJSON: function() {
3657 if(!this.editjson_holder) return;
3658 this.hideAddProperty();
3660 // Position the form directly beneath the button
3661 // TODO: edge detection
3662 this.editjson_holder.style.left = this.editjson_button.offsetLeft+"px";
3663 this.editjson_holder.style.top = this.editjson_button.offsetTop + this.editjson_button.offsetHeight+"px";
3665 // Start the textarea with the current value
3666 this.editjson_textarea.value = JSON.stringify(this.getValue(),null,2);
3668 // Disable the rest of the form while editing JSON
3671 this.editjson_holder.style.display = '';
3672 this.editjson_button.disabled = false;
3673 this.editing_json = true;
3675 hideEditJSON: function() {
3676 if(!this.editjson_holder) return;
3677 if(!this.editing_json) return;
3679 this.editjson_holder.style.display = 'none';
3681 this.editing_json = false;
3683 saveJSON: function() {
3684 if(!this.editjson_holder) return;
3687 var json = JSON.parse(this.editjson_textarea.value);
3688 this.setValue(json);
3689 this.hideEditJSON();
3692 window.alert('invalid JSON');
3696 toggleEditJSON: function() {
3697 if(this.editing_json) this.hideEditJSON();
3698 else this.showEditJSON();
3700 insertPropertyControlUsingPropertyOrder: function (property, control, container) {
3702 if (this.schema.properties[property])
3703 propertyOrder = this.schema.properties[property].propertyOrder;
3704 if (typeof propertyOrder !== "number") propertyOrder = 1000;
3705 control.propertyOrder = propertyOrder;
3707 for (var i = 0; i < container.childNodes.length; i++) {
3708 var child = container.childNodes[i];
3709 if (control.propertyOrder < child.propertyOrder) {
3710 this.addproperty_list.insertBefore(control, child);
3716 this.addproperty_list.appendChild(control);
3719 addPropertyCheckbox: function(key) {
3721 var checkbox, label, labelText, control;
3723 checkbox = self.theme.getCheckbox();
3724 checkbox.style.width = 'auto';
3726 if (this.schema.properties[key] && this.schema.properties[key].title)
3727 labelText = this.schema.properties[key].title;
3731 label = self.theme.getCheckboxLabel(labelText);
3733 control = self.theme.getFormControl(label,checkbox);
3734 control.style.paddingBottom = control.style.marginBottom = control.style.paddingTop = control.style.marginTop = 0;
3735 control.style.height = 'auto';
3736 //control.style.overflowY = 'hidden';
3738 this.insertPropertyControlUsingPropertyOrder(key, control, this.addproperty_list);
3740 checkbox.checked = key in this.editors;
3741 checkbox.addEventListener('change',function() {
3742 if(checkbox.checked) {
3743 self.addObjectProperty(key);
3746 self.removeObjectProperty(key);
3748 self.onChange(true);
3750 self.addproperty_checkboxes[key] = checkbox;
3754 showAddProperty: function() {
3755 if(!this.addproperty_holder) return;
3756 this.hideEditJSON();
3758 // Position the form directly beneath the button
3759 // TODO: edge detection
3760 this.addproperty_holder.style.left = this.addproperty_button.offsetLeft+"px";
3761 this.addproperty_holder.style.top = this.addproperty_button.offsetTop + this.addproperty_button.offsetHeight+"px";
3763 // Disable the rest of the form while editing JSON
3766 this.adding_property = true;
3767 this.addproperty_button.disabled = false;
3768 this.addproperty_holder.style.display = '';
3769 this.refreshAddProperties();
3771 hideAddProperty: function() {
3772 if(!this.addproperty_holder) return;
3773 if(!this.adding_property) return;
3775 this.addproperty_holder.style.display = 'none';
3778 this.adding_property = false;
3780 toggleAddProperty: function() {
3781 if(this.adding_property) this.hideAddProperty();
3782 else this.showAddProperty();
3784 removeObjectProperty: function(property) {
3785 if(this.editors[property]) {
3786 this.editors[property].unregister();
3787 delete this.editors[property];
3789 this.refreshValue();
3790 this.layoutEditors();
3793 addObjectProperty: function(name, prebuild_only) {
3796 // Property is already added
3797 if(this.editors[name]) return;
3799 // Property was added before and is cached
3800 if(this.cached_editors[name]) {
3801 this.editors[name] = this.cached_editors[name];
3802 if(prebuild_only) return;
3803 this.editors[name].register();
3807 if(!this.canHaveAdditionalProperties() && (!this.schema.properties || !this.schema.properties[name])) {
3811 var schema = self.getPropertySchema(name);
3812 if(typeof schema.propertyOrder !== 'number'){
3813 // if the propertyOrder undefined, then set a smart default value.
3814 schema.propertyOrder = Object.keys(self.editors).length + 1000;
3819 var editor = self.jsoneditor.getEditorClass(schema);
3821 self.editors[name] = self.jsoneditor.createEditor(editor,{
3822 jsoneditor: self.jsoneditor,
3824 path: self.path+'.'+name,
3827 self.editors[name].preBuild();
3829 if(!prebuild_only) {
3830 var holder = self.theme.getChildEditorHolder();
3831 self.editor_holder.appendChild(holder);
3832 self.editors[name].setContainer(holder);
3833 self.editors[name].build();
3834 self.editors[name].postBuild();
3837 self.cached_editors[name] = self.editors[name];
3840 // If we're only prebuilding the editors, don't refresh values
3841 if(!prebuild_only) {
3842 self.refreshValue();
3843 self.layoutEditors();
3846 onChildEditorChange: function(editor) {
3847 this.refreshValue();
3848 this._super(editor);
3850 canHaveAdditionalProperties: function() {
3851 if (typeof this.schema.additionalProperties === "boolean") {//# sourceMappingURL=jsoneditor.js.map
3852 return this.schema.additionalProperties;
3854 return !this.jsoneditor.options.no_additional_properties;
3856 destroy: function() {
3857 $each(this.cached_editors, function(i,el) {
3860 if(this.editor_holder) this.editor_holder.innerHTML = '';
3861 if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
3862 if(this.error_holder && this.error_holder.parentNode) this.error_holder.parentNode.removeChild(this.error_holder);
3864 this.editors = null;
3865 this.cached_editors = null;
3866 if(this.editor_holder && this.editor_holder.parentNode) this.editor_holder.parentNode.removeChild(this.editor_holder);
3867 this.editor_holder = null;
3871 getValue: function() {
3872 if (!this.dependenciesFulfilled) {
3875 var result = this._super();
3876 if(this.jsoneditor.options.remove_empty_properties || this.options.remove_empty_properties) {
3877 for (var i in result) {
3878 if (result.hasOwnProperty(i)) {
3879 if ((typeof result[i] === 'undefined' || result[i] === '') ||
3880 (Object.keys(result[i]).length == 0 && result[i].constructor == Object) ||
3881 (Array.isArray(result[i]) && result[i].length == 0)) {
3890 refreshValue: function() {
3894 for(var i in this.editors) {
3895 if(!this.editors.hasOwnProperty(i)) continue;
3896 this.value[i] = this.editors[i].getValue();
3899 if(this.adding_property) this.refreshAddProperties();
3901 refreshAddProperties: function() {
3902 if(this.options.disable_properties || (this.options.disable_properties !== false && this.jsoneditor.options.disable_properties)) {
3903 this.addproperty_controls.style.display = 'none';
3907 var can_add = false, can_remove = false, num_props = 0, i, show_modal = false;
3909 // Get number of editors
3910 for(i in this.editors) {
3911 if(!this.editors.hasOwnProperty(i)) continue;
3915 // Determine if we can add back removed properties
3916 can_add = this.canHaveAdditionalProperties() && !(typeof this.schema.maxProperties !== "undefined" && num_props >= this.schema.maxProperties);
3918 if(this.addproperty_checkboxes) {
3919 this.addproperty_list.innerHTML = '';
3921 this.addproperty_checkboxes = {};
3923 // Check for which editors can't be removed or added back
3924 for(i in this.cached_editors) {
3925 if(!this.cached_editors.hasOwnProperty(i)) continue;
3927 this.addPropertyCheckbox(i);
3929 if(this.isRequired(this.cached_editors[i]) && i in this.editors) {
3930 this.addproperty_checkboxes[i].disabled = true;
3933 if(typeof this.schema.minProperties !== "undefined" && num_props <= this.schema.minProperties) {
3934 this.addproperty_checkboxes[i].disabled = this.addproperty_checkboxes[i].checked;
3935 if(!this.addproperty_checkboxes[i].checked) show_modal = true;
3937 else if(!(i in this.editors)) {
3938 if(!can_add && !this.schema.properties.hasOwnProperty(i)) {
3939 this.addproperty_checkboxes[i].disabled = true;
3942 this.addproperty_checkboxes[i].disabled = false;
3952 if(this.canHaveAdditionalProperties()) {
3956 // Additional addproperty checkboxes not tied to a current editor
3957 for(i in this.schema.properties) {
3958 if(!this.schema.properties.hasOwnProperty(i)) continue;
3959 if(this.cached_editors[i]) continue;
3961 this.addPropertyCheckbox(i);
3964 // If no editors can be added or removed, hide the modal button
3966 this.hideAddProperty();
3967 this.addproperty_controls.style.display = 'none';
3969 // If additional properties are disabled
3970 else if(!this.canHaveAdditionalProperties()) {
3971 this.addproperty_add.style.display = 'none';
3972 this.addproperty_input.style.display = 'none';
3974 // If no new properties can be added
3976 this.addproperty_add.disabled = true;
3978 // If new properties can be added
3980 this.addproperty_add.disabled = false;
3983 isRequired: function(editor) {
3984 if(typeof editor.schema.required === "boolean") return editor.schema.required;
3985 else if(Array.isArray(this.schema.required)) return this.schema.required.indexOf(editor.key) > -1;
3986 else if(this.jsoneditor.options.required_by_default) return true;
3989 setValue: function(value, initial) {
3991 value = value || {};
3993 if(typeof value !== "object" || Array.isArray(value)) value = {};
3995 // First, set the values for all of the defined properties
3996 $each(this.cached_editors, function(i,editor) {
3997 // Value explicitly set
3998 if(typeof value[i] !== "undefined") {
3999 self.addObjectProperty(i);
4000 editor.setValue(value[i],initial);
4002 // Otherwise, remove value unless this is the initial set or it's required
4003 else if(!initial && !self.isRequired(editor)) {
4004 self.removeObjectProperty(i);
4006 // Otherwise, set the value to the default
4008 editor.setValue(editor.getDefault(),initial);
4012 $each(value, function(i,val) {
4013 if(!self.cached_editors[i]) {
4014 self.addObjectProperty(i);
4015 if(self.editors[i]) self.editors[i].setValue(val,initial);
4019 this.refreshValue();
4020 this.layoutEditors();
4023 showValidationErrors: function(errors) {
4026 // Get all the errors that pertain to this editor
4028 var other_errors = [];
4029 $each(errors, function(i,error) {
4030 if(error.path === self.path) {
4031 my_errors.push(error);
4034 other_errors.push(error);
4038 // Show errors for this editor
4039 if(this.error_holder) {
4040 if(my_errors.length) {
4042 this.error_holder.innerHTML = '';
4043 this.error_holder.style.display = '';
4044 $each(my_errors, function(i,error) {
4045 self.error_holder.appendChild(self.theme.getErrorMessage(error.message));
4050 this.error_holder.style.display = 'none';
4054 // Show error for the table row if this is inside a table
4055 if(this.options.table_row) {
4056 if(my_errors.length) {
4057 this.theme.addTableRowError(this.container);
4060 this.theme.removeTableRowError(this.container);
4064 // Show errors for child editors
4065 $each(this.editors, function(i,editor) {
4066 editor.showValidationErrors(other_errors);
4071 JSONEditor.defaults.editors.array = JSONEditor.AbstractEditor.extend({
4072 getDefault: function() {
4073 return this.schema["default"] || [];
4075 register: function() {
4078 for(var i=0; i<this.rows.length; i++) {
4079 this.rows[i].register();
4083 unregister: function() {
4086 for(var i=0; i<this.rows.length; i++) {
4087 this.rows[i].unregister();
4091 getNumColumns: function() {
4092 var info = this.getItemInfo(0);
4093 // Tabs require extra horizontal space
4094 if(this.tabs_holder && this.schema.format !== 'tabs-top') {
4095 return Math.max(Math.min(12,info.width+2),4);
4101 enable: function() {
4102 if(!this.always_disabled) {
4103 if(this.add_row_button) this.add_row_button.disabled = false;
4104 if(this.remove_all_rows_button) this.remove_all_rows_button.disabled = false;
4105 if(this.delete_last_row_button) this.delete_last_row_button.disabled = false;
4108 for(var i=0; i<this.rows.length; i++) {
4109 this.rows[i].enable();
4111 if(this.rows[i].moveup_button) this.rows[i].moveup_button.disabled = false;
4112 if(this.rows[i].movedown_button) this.rows[i].movedown_button.disabled = false;
4113 if(this.rows[i].delete_button) this.rows[i].delete_button.disabled = false;
4119 disable: function(always_disabled) {
4120 if(always_disabled) this.always_disabled = true;
4121 if(this.add_row_button) this.add_row_button.disabled = true;
4122 if(this.remove_all_rows_button) this.remove_all_rows_button.disabled = true;
4123 if(this.delete_last_row_button) this.delete_last_row_button.disabled = true;
4126 for(var i=0; i<this.rows.length; i++) {
4127 this.rows[i].disable(always_disabled);
4129 if(this.rows[i].moveup_button) this.rows[i].moveup_button.disabled = true;
4130 if(this.rows[i].movedown_button) this.rows[i].movedown_button.disabled = true;
4131 if(this.rows[i].delete_button) this.rows[i].delete_button.disabled = true;
4136 preBuild: function() {
4140 this.row_cache = [];
4142 this.hide_delete_buttons = this.options.disable_array_delete || this.jsoneditor.options.disable_array_delete;
4143 this.hide_delete_all_rows_buttons = this.hide_delete_buttons || this.options.disable_array_delete_all_rows || this.jsoneditor.options.disable_array_delete_all_rows;
4144 this.hide_delete_last_row_buttons = this.hide_delete_buttons || this.options.disable_array_delete_last_row || this.jsoneditor.options.disable_array_delete_last_row;
4145 this.hide_move_buttons = this.options.disable_array_reorder || this.jsoneditor.options.disable_array_reorder;
4146 this.hide_add_button = this.options.disable_array_add || this.jsoneditor.options.disable_array_add;
4147 this.show_copy_button = this.options.enable_array_copy || this.jsoneditor.options.enable_array_copy;
4152 if(!this.options.compact) {
4153 this.header = document.createElement('span');
4154 this.header.textContent = this.getTitle();
4155 this.title = this.theme.getHeader(this.header);
4156 this.container.appendChild(this.title);
4157 this.title_controls = this.theme.getHeaderButtonHolder();
4158 this.title.appendChild(this.title_controls);
4159 if(this.schema.description) {
4160 this.description = this.theme.getDescription(this.schema.description);
4161 this.container.appendChild(this.description);
4163 this.error_holder = document.createElement('div');
4164 this.container.appendChild(this.error_holder);
4166 if(this.schema.format === 'tabs-top') {
4167 this.controls = this.theme.getHeaderButtonHolder();
4168 this.title.appendChild(this.controls);
4169 this.tabs_holder = this.theme.getTopTabHolder(this.getItemTitle());
4170 this.container.appendChild(this.tabs_holder);
4171 this.row_holder = this.theme.getTopTabContentHolder(this.tabs_holder);
4173 this.active_tab = null;
4175 else if(this.schema.format === 'tabs') {
4176 this.controls = this.theme.getHeaderButtonHolder();
4177 this.title.appendChild(this.controls);
4178 this.tabs_holder = this.theme.getTabHolder(this.getItemTitle());
4179 this.container.appendChild(this.tabs_holder);
4180 this.row_holder = this.theme.getTabContentHolder(this.tabs_holder);
4182 this.active_tab = null;
4185 this.panel = this.theme.getIndentedPanel();
4186 this.container.appendChild(this.panel);
4187 this.row_holder = document.createElement('div');
4188 this.panel.appendChild(this.row_holder);
4189 this.controls = this.theme.getButtonHolder();
4190 this.panel.appendChild(this.controls);
4194 this.panel = this.theme.getIndentedPanel();
4195 this.container.appendChild(this.panel);
4196 this.controls = this.theme.getButtonHolder();
4197 this.panel.appendChild(this.controls);
4198 this.row_holder = document.createElement('div');
4199 this.panel.appendChild(this.row_holder);
4205 onChildEditorChange: function(editor) {
4206 this.refreshValue();
4207 this.refreshTabs(true);
4208 this._super(editor);
4210 getItemTitle: function() {
4211 if(!this.item_title) {
4212 if(this.schema.items && !Array.isArray(this.schema.items)) {
4213 var tmp = this.jsoneditor.expandRefs(this.schema.items);
4214 this.item_title = tmp.title || 'item';
4217 this.item_title = 'item';
4220 return this.item_title;
4222 getItemSchema: function(i) {
4223 if(Array.isArray(this.schema.items)) {
4224 if(i >= this.schema.items.length) {
4225 if(this.schema.additionalItems===true) {
4228 else if(this.schema.additionalItems) {
4229 return $extend({},this.schema.additionalItems);
4233 return $extend({},this.schema.items[i]);
4236 else if(this.schema.items) {
4237 return $extend({},this.schema.items);
4243 getItemInfo: function(i) {
4244 var schema = this.getItemSchema(i);
4246 // Check if it's cached
4247 this.item_info = this.item_info || {};
4248 var stringified = JSON.stringify(schema);
4249 if(typeof this.item_info[stringified] !== "undefined") return this.item_info[stringified];
4251 // Get the schema for this item
4252 schema = this.jsoneditor.expandRefs(schema);
4254 this.item_info[stringified] = {
4255 title: schema.title || "item",
4256 'default': schema["default"],
4258 child_editors: schema.properties || schema.items
4261 return this.item_info[stringified];
4263 getElementEditor: function(i) {
4264 var item_info = this.getItemInfo(i);
4265 var schema = this.getItemSchema(i);
4266 schema = this.jsoneditor.expandRefs(schema);
4267 schema.title = item_info.title+' '+(i+1);
4269 var editor = this.jsoneditor.getEditorClass(schema);
4272 if(this.tabs_holder) {
4273 if(this.schema.format === 'tabs-top') {
4274 holder = this.theme.getTopTabContent();
4277 holder = this.theme.getTabContent();
4279 holder.id = this.path+'.'+i;
4281 else if(item_info.child_editors) {
4282 holder = this.theme.getChildEditorHolder();
4285 holder = this.theme.getIndentedPanel();
4288 this.row_holder.appendChild(holder);
4290 var ret = this.jsoneditor.createEditor(editor,{
4291 jsoneditor: this.jsoneditor,
4294 path: this.path+'.'+i,
4302 if(!ret.title_controls) {
4303 ret.array_controls = this.theme.getButtonHolder();
4304 holder.appendChild(ret.array_controls);
4309 destroy: function() {
4311 if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
4312 if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
4313 if(this.row_holder && this.row_holder.parentNode) this.row_holder.parentNode.removeChild(this.row_holder);
4314 if(this.controls && this.controls.parentNode) this.controls.parentNode.removeChild(this.controls);
4315 if(this.panel && this.panel.parentNode) this.panel.parentNode.removeChild(this.panel);
4317 this.rows = this.row_cache = this.title = this.description = this.row_holder = this.panel = this.controls = null;
4321 empty: function(hard) {
4322 if(!this.rows) return;
4324 $each(this.rows,function(i,row) {
4326 if(row.tab && row.tab.parentNode) row.tab.parentNode.removeChild(row.tab);
4327 self.destroyRow(row,true);
4328 self.row_cache[i] = null;
4330 self.rows[i] = null;
4333 if(hard) self.row_cache = [];
4335 destroyRow: function(row,hard) {
4336 var holder = row.container;
4339 if(holder.parentNode) holder.parentNode.removeChild(holder);
4340 if(row.tab && row.tab.parentNode) row.tab.parentNode.removeChild(row.tab);
4343 if(row.tab) row.tab.style.display = 'none';
4344 holder.style.display = 'none';
4348 getMax: function() {
4349 if((Array.isArray(this.schema.items)) && this.schema.additionalItems === false) {
4350 return Math.min(this.schema.items.length,this.schema.maxItems || Infinity);
4353 return this.schema.maxItems || Infinity;
4356 refreshTabs: function(refresh_headers) {
4358 $each(this.rows, function(i,row) {
4359 if(!row.tab) return;
4361 if(refresh_headers) {
4362 row.tab_text.textContent = row.getHeaderText();
4365 if(row.tab === self.active_tab) {
4366 self.theme.markTabActive(row);
4369 self.theme.markTabInactive(row);
4374 setValue: function(value, initial) {
4375 // Update the array's value, adding/removing rows when necessary
4376 value = value || [];
4378 if(!(Array.isArray(value))) value = [value];
4380 var serialized = JSON.stringify(value);
4381 if(serialized === this.serialized) return;
4383 // Make sure value has between minItems and maxItems items in it
4384 if(this.schema.minItems) {
4385 while(value.length < this.schema.minItems) {
4386 value.push(this.getItemInfo(value.length)["default"]);
4389 if(this.getMax() && value.length > this.getMax()) {
4390 value = value.slice(0,this.getMax());
4394 $each(value,function(i,val) {
4396 // TODO: don't set the row's value if it hasn't changed
4397 self.rows[i].setValue(val,initial);
4399 else if(self.row_cache[i]) {
4400 self.rows[i] = self.row_cache[i];
4401 self.rows[i].setValue(val,initial);
4402 self.rows[i].container.style.display = '';
4403 if(self.rows[i].tab) self.rows[i].tab.style.display = '';
4404 self.rows[i].register();
4407 self.addRow(val,initial);
4411 for(var j=value.length; j<self.rows.length; j++) {
4412 self.destroyRow(self.rows[j]);
4413 self.rows[j] = null;
4415 self.rows = self.rows.slice(0,value.length);
4417 // Set the active tab
4418 var new_active_tab = null;
4419 $each(self.rows, function(i,row) {
4420 if(row.tab === self.active_tab) {
4421 new_active_tab = row.tab;
4425 if(!new_active_tab && self.rows.length) new_active_tab = self.rows[0].tab;
4427 self.active_tab = new_active_tab;
4429 self.refreshValue(initial);
4430 self.refreshTabs(true);
4437 refreshValue: function(force) {
4439 var oldi = this.value? this.value.length : 0;
4442 $each(this.rows,function(i,editor) {
4443 // Get the value for this editor
4444 self.value[i] = editor.getValue();
4447 if(oldi !== this.value.length || force) {
4448 // If we currently have minItems items in the array
4449 var minItems = this.schema.minItems && this.schema.minItems >= this.rows.length;
4451 $each(this.rows,function(i,editor) {
4452 // Hide the move down button for the last row
4453 if(editor.movedown_button) {
4454 if(i === self.rows.length - 1) {
4455 editor.movedown_button.style.display = 'none';
4458 editor.movedown_button.style.display = '';
4462 // Hide the delete button if we have minItems items
4463 if(editor.delete_button) {
4465 editor.delete_button.style.display = 'none';
4468 editor.delete_button.style.display = '';
4472 // Get the value for this editor
4473 self.value[i] = editor.getValue();
4476 var controls_needed = false;
4478 if(!this.value.length) {
4479 this.delete_last_row_button.style.display = 'none';
4480 this.remove_all_rows_button.style.display = 'none';
4482 else if(this.value.length === 1) {
4483 this.remove_all_rows_button.style.display = 'none';
4485 // If there are minItems items in the array, or configured to hide the delete_last_row button, hide the delete button beneath the rows
4486 if(minItems || this.hide_delete_last_row_buttons) {
4487 this.delete_last_row_button.style.display = 'none';
4490 this.delete_last_row_button.style.display = '';
4491 controls_needed = true;
4495 if(minItems || this.hide_delete_last_row_buttons) {
4496 this.delete_last_row_button.style.display = 'none';
4499 this.delete_last_row_button.style.display = '';
4500 controls_needed = true;
4503 if(minItems || this.hide_delete_all_rows_buttons) {
4504 this.remove_all_rows_button.style.display = 'none';
4507 this.remove_all_rows_button.style.display = '';
4508 controls_needed = true;
4512 // If there are maxItems in the array, hide the add button beneath the rows
4513 if((this.getMax() && this.getMax() <= this.rows.length) || this.hide_add_button){
4514 this.add_row_button.style.display = 'none';
4517 this.add_row_button.style.display = '';
4518 controls_needed = true;
4521 if(!this.collapsed && controls_needed) {
4522 this.controls.style.display = 'inline-block';
4525 this.controls.style.display = 'none';
4529 addRow: function(value, initial) {
4531 var i = this.rows.length;
4533 self.rows[i] = this.getElementEditor(i);
4534 self.row_cache[i] = self.rows[i];
4536 if(self.tabs_holder) {
4537 self.rows[i].tab_text = document.createElement('span');
4538 self.rows[i].tab_text.textContent = self.rows[i].getHeaderText();
4539 if(self.schema.format === 'tabs-top'){
4540 self.rows[i].tab = self.theme.getTopTab(self.rows[i].tab_text,self.rows[i].path);
4541 self.theme.addTopTab(self.tabs_holder, self.rows[i].tab);
4544 self.rows[i].tab = self.theme.getTab(self.rows[i].tab_text,self.rows[i].path);
4545 self.theme.addTab(self.tabs_holder, self.rows[i].tab);
4547 self.rows[i].tab.addEventListener('click', function(e) {
4548 self.active_tab = self.rows[i].tab;
4551 e.stopPropagation();
4556 var controls_holder = self.rows[i].title_controls || self.rows[i].array_controls;
4558 // Buttons to delete row, move row up, and move row down
4559 if(!self.hide_delete_buttons) {
4560 self.rows[i].delete_button = this.getButton(self.getItemTitle(),'delete',this.translate('button_delete_row_title',[self.getItemTitle()]));
4561 self.rows[i].delete_button.className += ' delete';
4562 self.rows[i].delete_button.setAttribute('data-i',i);
4563 self.rows[i].delete_button.addEventListener('click',function(e) {
4565 e.stopPropagation();
4567 if (self.jsoneditor.options.prompt_before_delete === true) {
4568 if (confirm("Confirm to remove.") === false) {
4573 var i = this.getAttribute('data-i')*1;
4575 var value = self.getValue();
4578 var new_active_tab = null;
4579 $each(value,function(j,row) {
4581 // If the one we're deleting is the active tab
4582 if(self.rows[j].tab === self.active_tab) {
4583 // Make the next tab active if there is one
4584 // Note: the next tab is going to be the current tab after deletion
4585 if(self.rows[j+1]) new_active_tab = self.rows[j].tab;
4586 // Otherwise, make the previous tab active if there is one
4587 else if(j) new_active_tab = self.rows[j-1].tab;
4590 return; // If this is the one we're deleting
4594 self.setValue(newval);
4595 if(new_active_tab) {
4596 self.active_tab = new_active_tab;
4600 self.onChange(true);
4603 if(controls_holder) {
4604 controls_holder.appendChild(self.rows[i].delete_button);
4608 //Button to copy an array element and add it as last element
4609 if(self.show_copy_button){
4610 self.rows[i].copy_button = this.getButton(self.getItemTitle(),'copy','Copy '+self.getItemTitle());
4611 self.rows[i].copy_button.className += ' copy';
4612 self.rows[i].copy_button.setAttribute('data-i',i);
4613 self.rows[i].copy_button.addEventListener('click',function(e) {
4614 var value = self.getValue();
4616 e.stopPropagation();
4617 var i = this.getAttribute('data-i')*1;
4619 $each(value,function(j,row) {
4626 self.setValue(value);
4627 self.refreshValue(true);
4628 self.onChange(true);
4632 controls_holder.appendChild(self.rows[i].copy_button);
4636 if(i && !self.hide_move_buttons) {
4637 self.rows[i].moveup_button = this.getButton('','moveup',this.translate('button_move_up_title'));
4638 self.rows[i].moveup_button.className += ' moveup';
4639 self.rows[i].moveup_button.setAttribute('data-i',i);
4640 self.rows[i].moveup_button.addEventListener('click',function(e) {
4642 e.stopPropagation();
4643 var i = this.getAttribute('data-i')*1;
4646 var rows = self.getValue();
4647 var tmp = rows[i-1];
4648 rows[i-1] = rows[i];
4651 self.setValue(rows);
4652 self.active_tab = self.rows[i-1].tab;
4655 self.onChange(true);
4658 if(controls_holder) {
4659 controls_holder.appendChild(self.rows[i].moveup_button);
4663 if(!self.hide_move_buttons) {
4664 self.rows[i].movedown_button = this.getButton('','movedown',this.translate('button_move_down_title'));
4665 self.rows[i].movedown_button.className += ' movedown';
4666 self.rows[i].movedown_button.setAttribute('data-i',i);
4667 self.rows[i].movedown_button.addEventListener('click',function(e) {
4669 e.stopPropagation();
4670 var i = this.getAttribute('data-i')*1;
4672 var rows = self.getValue();
4673 if(i>=rows.length-1) return;
4674 var tmp = rows[i+1];
4675 rows[i+1] = rows[i];
4678 self.setValue(rows);
4679 self.active_tab = self.rows[i+1].tab;
4681 self.onChange(true);
4684 if(controls_holder) {
4685 controls_holder.appendChild(self.rows[i].movedown_button);
4689 if(value) self.rows[i].setValue(value, initial);
4692 addControls: function() {
4695 this.collapsed = false;
4696 this.toggle_button = this.getButton('','collapse',this.translate('button_collapse'));
4697 this.title_controls.appendChild(this.toggle_button);
4698 var row_holder_display = self.row_holder.style.display;
4699 var controls_display = self.controls.style.display;
4700 this.toggle_button.addEventListener('click',function(e) {
4702 e.stopPropagation();
4703 if(self.collapsed) {
4704 self.collapsed = false;
4705 if(self.panel) self.panel.style.display = '';
4706 self.row_holder.style.display = row_holder_display;
4707 if(self.tabs_holder) self.tabs_holder.style.display = '';
4708 self.controls.style.display = controls_display;
4709 self.setButtonText(this,'','collapse',self.translate('button_collapse'));
4712 self.collapsed = true;
4713 self.row_holder.style.display = 'none';
4714 if(self.tabs_holder) self.tabs_holder.style.display = 'none';
4715 self.controls.style.display = 'none';
4716 if(self.panel) self.panel.style.display = 'none';
4717 self.setButtonText(this,'','expand',self.translate('button_expand'));
4721 // If it should start collapsed
4722 if(this.options.collapsed) {
4723 $trigger(this.toggle_button,'click');
4726 // Collapse button disabled
4727 if(this.schema.options && typeof this.schema.options.disable_collapse !== "undefined") {
4728 if(this.schema.options.disable_collapse) this.toggle_button.style.display = 'none';
4730 else if(this.jsoneditor.options.disable_collapse) {
4731 this.toggle_button.style.display = 'none';
4734 // Add "new row" and "delete last" buttons below editor
4735 this.add_row_button = this.getButton(this.getItemTitle(),'add',this.translate('button_add_row_title',[this.getItemTitle()]));
4737 this.add_row_button.addEventListener('click',function(e) {
4739 e.stopPropagation();
4740 var i = self.rows.length;
4741 if(self.row_cache[i]) {
4742 self.rows[i] = self.row_cache[i];
4743 self.rows[i].setValue(self.rows[i].getDefault(), true);
4744 self.rows[i].container.style.display = '';
4745 if(self.rows[i].tab) self.rows[i].tab.style.display = '';
4746 self.rows[i].register();
4751 self.active_tab = self.rows[i].tab;
4753 self.refreshValue();
4754 self.onChange(true);
4756 self.controls.appendChild(this.add_row_button);
4758 this.delete_last_row_button = this.getButton(this.translate('button_delete_last',[this.getItemTitle()]),'delete',this.translate('button_delete_last_title',[this.getItemTitle()]));
4759 this.delete_last_row_button.addEventListener('click',function(e) {
4761 e.stopPropagation();
4763 if (self.jsoneditor.options.prompt_before_delete === true) {
4764 if (confirm("Confirm to remove.") === false) {
4769 var rows = self.getValue();
4771 var new_active_tab = null;
4772 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;
4775 self.setValue(rows);
4776 if(new_active_tab) {
4777 self.active_tab = new_active_tab;
4780 self.onChange(true);
4782 self.controls.appendChild(this.delete_last_row_button);
4784 this.remove_all_rows_button = this.getButton(this.translate('button_delete_all'),'delete',this.translate('button_delete_all_title'));
4785 this.remove_all_rows_button.addEventListener('click',function(e) {
4787 e.stopPropagation();
4789 if (self.jsoneditor.options.prompt_before_delete === true) {
4790 if (confirm("Confirm to remove.") === false) {
4796 self.onChange(true);
4798 self.controls.appendChild(this.remove_all_rows_button);
4801 this.add_row_button.style.width = '100%';
4802 this.add_row_button.style.textAlign = 'left';
4803 this.add_row_button.style.marginBottom = '3px';
4805 this.delete_last_row_button.style.width = '100%';
4806 this.delete_last_row_button.style.textAlign = 'left';
4807 this.delete_last_row_button.style.marginBottom = '3px';
4809 this.remove_all_rows_button.style.width = '100%';
4810 this.remove_all_rows_button.style.textAlign = 'left';
4811 this.remove_all_rows_button.style.marginBottom = '3px';
4814 showValidationErrors: function(errors) {
4817 // Get all the errors that pertain to this editor
4819 var other_errors = [];
4820 $each(errors, function(i,error) {
4821 if(error.path === self.path) {
4822 my_errors.push(error);
4825 other_errors.push(error);
4829 // Show errors for this editor
4830 if(this.error_holder) {
4831 if(my_errors.length) {
4833 this.error_holder.innerHTML = '';
4834 this.error_holder.style.display = '';
4835 $each(my_errors, function(i,error) {
4836 self.error_holder.appendChild(self.theme.getErrorMessage(error.message));
4841 this.error_holder.style.display = 'none';
4845 // Show errors for child editors
4846 $each(this.rows, function(i,row) {
4847 row.showValidationErrors(other_errors);
4852 JSONEditor.defaults.editors.table = JSONEditor.defaults.editors.array.extend({
4853 register: function() {
4856 for(var i=0; i<this.rows.length; i++) {
4857 this.rows[i].register();
4861 unregister: function() {
4864 for(var i=0; i<this.rows.length; i++) {
4865 this.rows[i].unregister();
4869 getNumColumns: function() {
4870 return Math.max(Math.min(12,this.width),3);
4872 preBuild: function() {
4873 var item_schema = this.jsoneditor.expandRefs(this.schema.items || {});
4875 this.item_title = item_schema.title || 'row';
4876 this.item_default = item_schema["default"] || null;
4877 this.item_has_child_editors = item_schema.properties || item_schema.items;
4883 this.table = this.theme.getTable();
4884 this.container.appendChild(this.table);
4885 this.thead = this.theme.getTableHead();
4886 this.table.appendChild(this.thead);
4887 this.header_row = this.theme.getTableRow();
4888 this.thead.appendChild(this.header_row);
4889 this.row_holder = this.theme.getTableBody();
4890 this.table.appendChild(this.row_holder);
4892 // Determine the default value of array element
4893 var tmp = this.getElementEditor(0,true);
4894 this.item_default = tmp.getDefault();
4895 this.width = tmp.getNumColumns() + 2;
4897 if(!this.options.compact) {
4898 this.title = this.theme.getHeader(this.getTitle());
4899 this.container.appendChild(this.title);
4900 this.title_controls = this.theme.getHeaderButtonHolder();
4901 this.title.appendChild(this.title_controls);
4902 if(this.schema.description) {
4903 this.description = this.theme.getDescription(this.schema.description);
4904 this.container.appendChild(this.description);
4906 this.panel = this.theme.getIndentedPanel();
4907 this.container.appendChild(this.panel);
4908 this.error_holder = document.createElement('div');
4909 this.panel.appendChild(this.error_holder);
4912 this.panel = document.createElement('div');
4913 this.container.appendChild(this.panel);
4916 this.panel.appendChild(this.table);
4917 this.controls = this.theme.getButtonHolder();
4918 this.panel.appendChild(this.controls);
4920 if(this.item_has_child_editors) {
4921 var ce = tmp.getChildEditors();
4922 var order = tmp.property_order || Object.keys(ce);
4923 for(var i=0; i<order.length; i++) {
4924 var th = self.theme.getTableHeaderCell(ce[order[i]].getTitle());
4925 if(ce[order[i]].options.hidden) th.style.display = 'none';
4926 self.header_row.appendChild(th);
4930 self.header_row.appendChild(self.theme.getTableHeaderCell(this.item_title));
4934 this.row_holder.innerHTML = '';
4936 // Row Controls column
4937 this.controls_header_cell = self.theme.getTableHeaderCell(" ");
4938 self.header_row.appendChild(this.controls_header_cell);
4943 onChildEditorChange: function(editor) {
4944 this.refreshValue();
4947 getItemDefault: function() {
4948 return $extend({},{"default":this.item_default})["default"];
4950 getItemTitle: function() {
4951 return this.item_title;
4953 getElementEditor: function(i,ignore) {
4954 var schema_copy = $extend({},this.schema.items);
4955 var editor = this.jsoneditor.getEditorClass(schema_copy, this.jsoneditor);
4956 var row = this.row_holder.appendChild(this.theme.getTableRow());
4958 if(!this.item_has_child_editors) {
4959 holder = this.theme.getTableCell();
4960 row.appendChild(holder);
4963 var ret = this.jsoneditor.createEditor(editor,{
4964 jsoneditor: this.jsoneditor,
4965 schema: schema_copy,
4967 path: this.path+'.'+i,
4978 ret.controls_cell = row.appendChild(this.theme.getTableCell());
4980 ret.table_controls = this.theme.getButtonHolder();
4981 ret.controls_cell.appendChild(ret.table_controls);
4982 ret.table_controls.style.margin = 0;
4983 ret.table_controls.style.padding = 0;
4988 destroy: function() {
4989 this.innerHTML = '';
4990 if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
4991 if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
4992 if(this.row_holder && this.row_holder.parentNode) this.row_holder.parentNode.removeChild(this.row_holder);
4993 if(this.table && this.table.parentNode) this.table.parentNode.removeChild(this.table);
4994 if(this.panel && this.panel.parentNode) this.panel.parentNode.removeChild(this.panel);
4996 this.rows = this.title = this.description = this.row_holder = this.table = this.panel = null;
5000 setValue: function(value, initial) {
5001 // Update the array's value, adding/removing rows when necessary
5002 value = value || [];
5004 // Make sure value has between minItems and maxItems items in it
5005 if(this.schema.minItems) {
5006 while(value.length < this.schema.minItems) {
5007 value.push(this.getItemDefault());
5010 if(this.schema.maxItems && value.length > this.schema.maxItems) {
5011 value = value.slice(0,this.schema.maxItems);
5014 var serialized = JSON.stringify(value);
5015 if(serialized === this.serialized) return;
5017 var numrows_changed = false;
5020 $each(value,function(i,val) {
5022 // TODO: don't set the row's value if it hasn't changed
5023 self.rows[i].setValue(val);
5027 numrows_changed = true;
5031 for(var j=value.length; j<self.rows.length; j++) {
5032 var holder = self.rows[j].container;
5033 if(!self.item_has_child_editors) {
5034 self.rows[j].row.parentNode.removeChild(self.rows[j].row);
5036 self.rows[j].destroy();
5037 if(holder.parentNode) holder.parentNode.removeChild(holder);
5038 self.rows[j] = null;
5039 numrows_changed = true;
5041 self.rows = self.rows.slice(0,value.length);
5043 self.refreshValue();
5044 if(numrows_changed || initial) self.refreshRowButtons();
5050 refreshRowButtons: function() {
5053 // If we currently have minItems items in the array
5054 var minItems = this.schema.minItems && this.schema.minItems >= this.rows.length;
5056 var need_row_buttons = false;
5057 $each(this.rows,function(i,editor) {
5058 // Hide the move down button for the last row
5059 if(editor.movedown_button) {
5060 if(i === self.rows.length - 1) {
5061 editor.movedown_button.style.display = 'none';
5064 need_row_buttons = true;
5065 editor.movedown_button.style.display = '';
5069 // Hide the delete button if we have minItems items
5070 if(editor.delete_button) {
5072 editor.delete_button.style.display = 'none';
5075 need_row_buttons = true;
5076 editor.delete_button.style.display = '';
5080 if(editor.moveup_button) {
5081 need_row_buttons = true;
5085 // Show/hide controls column in table
5086 $each(this.rows,function(i,editor) {
5087 if(need_row_buttons) {
5088 editor.controls_cell.style.display = '';
5091 editor.controls_cell.style.display = 'none';
5094 if(need_row_buttons) {
5095 this.controls_header_cell.style.display = '';
5098 this.controls_header_cell.style.display = 'none';
5101 var controls_needed = false;
5103 if(!this.value.length) {
5104 this.delete_last_row_button.style.display = 'none';
5105 this.remove_all_rows_button.style.display = 'none';
5106 this.table.style.display = 'none';
5108 else if(this.value.length === 1) {
5109 this.table.style.display = '';
5110 this.remove_all_rows_button.style.display = 'none';
5112 // If there are minItems items in the array, or configured to hide the delete_last_row button, hide the delete button beneath the rows
5113 if(minItems || this.hide_delete_last_row_buttons) {
5114 this.delete_last_row_button.style.display = 'none';
5117 this.delete_last_row_button.style.display = '';
5118 controls_needed = true;
5122 this.table.style.display = '';
5124 if(minItems || this.hide_delete_last_row_buttons) {
5125 this.delete_last_row_button.style.display = 'none';
5128 this.delete_last_row_button.style.display = '';
5129 controls_needed = true;
5132 if(minItems || this.hide_delete_all_rows_buttons) {
5133 this.remove_all_rows_button.style.display = 'none';
5136 this.remove_all_rows_button.style.display = '';
5137 controls_needed = true;
5141 // If there are maxItems in the array, hide the add button beneath the rows
5142 if((this.schema.maxItems && this.schema.maxItems <= this.rows.length) || this.hide_add_button) {
5143 this.add_row_button.style.display = 'none';
5146 this.add_row_button.style.display = '';
5147 controls_needed = true;
5150 if(!controls_needed) {
5151 this.controls.style.display = 'none';
5154 this.controls.style.display = '';
5157 refreshValue: function() {
5161 $each(this.rows,function(i,editor) {
5162 // Get the value for this editor
5163 self.value[i] = editor.getValue();
5165 this.serialized = JSON.stringify(this.value);
5167 addRow: function(value) {
5169 var i = this.rows.length;
5171 self.rows[i] = this.getElementEditor(i);
5173 var controls_holder = self.rows[i].table_controls;
5175 // Buttons to delete row, move row up, and move row down
5176 if(!this.hide_delete_buttons) {
5177 self.rows[i].delete_button = this.getButton('','delete',this.translate('button_delete_row_title_short'));
5178 self.rows[i].delete_button.className += ' delete';
5179 self.rows[i].delete_button.setAttribute('data-i',i);
5180 self.rows[i].delete_button.addEventListener('click',function(e) {
5182 e.stopPropagation();
5183 var i = this.getAttribute('data-i')*1;
5185 var value = self.getValue();
5188 $each(value,function(j,row) {
5189 if(j===i) return; // If this is the one we're deleting
5192 self.setValue(newval);
5193 self.onChange(true);
5195 controls_holder.appendChild(self.rows[i].delete_button);
5199 if(i && !this.hide_move_buttons) {
5200 self.rows[i].moveup_button = this.getButton('','moveup',this.translate('button_move_up_title'));
5201 self.rows[i].moveup_button.className += ' moveup';
5202 self.rows[i].moveup_button.setAttribute('data-i',i);
5203 self.rows[i].moveup_button.addEventListener('click',function(e) {
5205 e.stopPropagation();
5206 var i = this.getAttribute('data-i')*1;
5209 var rows = self.getValue();
5210 var tmp = rows[i-1];
5211 rows[i-1] = rows[i];
5214 self.setValue(rows);
5215 self.onChange(true);
5217 controls_holder.appendChild(self.rows[i].moveup_button);
5220 if(!this.hide_move_buttons) {
5221 self.rows[i].movedown_button = this.getButton('','movedown',this.translate('button_move_down_title'));
5222 self.rows[i].movedown_button.className += ' movedown';
5223 self.rows[i].movedown_button.setAttribute('data-i',i);
5224 self.rows[i].movedown_button.addEventListener('click',function(e) {
5226 e.stopPropagation();
5227 var i = this.getAttribute('data-i')*1;
5228 var rows = self.getValue();
5229 if(i>=rows.length-1) return;
5230 var tmp = rows[i+1];
5231 rows[i+1] = rows[i];
5234 self.setValue(rows);
5235 self.onChange(true);
5237 controls_holder.appendChild(self.rows[i].movedown_button);
5240 if(value) self.rows[i].setValue(value);
5242 addControls: function() {
5245 this.collapsed = false;
5246 this.toggle_button = this.getButton('','collapse',this.translate('button_collapse'));
5247 if(this.title_controls) {
5248 this.title_controls.appendChild(this.toggle_button);
5249 this.toggle_button.addEventListener('click',function(e) {
5251 e.stopPropagation();
5253 if(self.collapsed) {
5254 self.collapsed = false;
5255 self.panel.style.display = '';
5256 self.setButtonText(this,'','collapse',self.translate('button_collapse'));
5259 self.collapsed = true;
5260 self.panel.style.display = 'none';
5261 self.setButtonText(this,'','expand',self.translate('button_expand'));
5265 // If it should start collapsed
5266 if(this.options.collapsed) {
5267 $trigger(this.toggle_button,'click');
5270 // Collapse button disabled
5271 if(this.schema.options && typeof this.schema.options.disable_collapse !== "undefined") {
5272 if(this.schema.options.disable_collapse) this.toggle_button.style.display = 'none';
5274 else if(this.jsoneditor.options.disable_collapse) {
5275 this.toggle_button.style.display = 'none';
5279 // Add "new row" and "delete last" buttons below editor
5280 this.add_row_button = this.getButton(this.getItemTitle(),'add',this.translate('button_add_row_title',[this.getItemTitle()]));
5281 this.add_row_button.addEventListener('click',function(e) {
5283 e.stopPropagation();
5286 self.refreshValue();
5287 self.refreshRowButtons();
5288 self.onChange(true);
5290 self.controls.appendChild(this.add_row_button);
5292 this.delete_last_row_button = this.getButton(this.translate('button_delete_last',[this.getItemTitle()]),'delete',this.translate('button_delete_last_title',[this.getItemTitle()]));
5293 this.delete_last_row_button.addEventListener('click',function(e) {
5295 e.stopPropagation();
5297 var rows = self.getValue();
5299 self.setValue(rows);
5300 self.onChange(true);
5302 self.controls.appendChild(this.delete_last_row_button);
5304 this.remove_all_rows_button = this.getButton(this.translate('button_delete_all'),'delete',this.translate('button_delete_all_title'));
5305 this.remove_all_rows_button.addEventListener('click',function(e) {
5307 e.stopPropagation();
5310 self.onChange(true);
5312 self.controls.appendChild(this.remove_all_rows_button);
5316 // Multiple Editor (for when `type` is an array, also when `oneOf` is present)
5317 JSONEditor.defaults.editors.multiple = JSONEditor.AbstractEditor.extend({
5318 register: function() {
5320 for(var i=0; i<this.editors.length; i++) {
5321 if(!this.editors[i]) continue;
5322 this.editors[i].unregister();
5324 if(this.editors[this.type]) this.editors[this.type].register();
5328 unregister: function() {
5331 for(var i=0; i<this.editors.length; i++) {
5332 if(!this.editors[i]) continue;
5333 this.editors[i].unregister();
5337 getNumColumns: function() {
5338 if(!this.editors[this.type]) return 4;
5339 return Math.max(this.editors[this.type].getNumColumns(),4);
5341 enable: function() {
5342 if(!this.always_disabled) {
5344 for(var i=0; i<this.editors.length; i++) {
5345 if(!this.editors[i]) continue;
5346 this.editors[i].enable();
5349 this.switcher.disabled = false;
5353 disable: function(always_disabled) {
5354 if(always_disabled) this.always_disabled = true;
5356 for(var i=0; i<this.editors.length; i++) {
5357 if(!this.editors[i]) continue;
5358 this.editors[i].disable(always_disabled);
5361 this.switcher.disabled = true;
5364 switchEditor: function(i) {
5367 if(!this.editors[i]) {
5368 this.buildChildEditor(i);
5371 var current_value = self.getValue();
5377 $each(self.editors,function(type,editor) {
5379 if(self.type === type) {
5380 if(self.keep_values) editor.setValue(current_value,true);
5381 editor.container.style.display = '';
5383 else editor.container.style.display = 'none';
5385 self.refreshValue();
5386 self.refreshHeaderText();
5388 buildChildEditor: function(i) {
5390 var type = this.types[i];
5391 var holder = self.theme.getChildEditorHolder();
5392 self.editor_holder.appendChild(holder);
5396 if(typeof type === "string") {
5397 schema = $extend({},self.schema);
5401 schema = $extend({},self.schema,type);
5402 schema = self.jsoneditor.expandRefs(schema);
5404 // If we need to merge `required` arrays
5405 if(type && type.required && Array.isArray(type.required) && self.schema.required && Array.isArray(self.schema.required)) {
5406 schema.required = self.schema.required.concat(type.required);
5410 var editor = self.jsoneditor.getEditorClass(schema);
5412 self.editors[i] = self.jsoneditor.createEditor(editor,{
5413 jsoneditor: self.jsoneditor,
5420 self.editors[i].preBuild();
5421 self.editors[i].build();
5422 self.editors[i].postBuild();
5424 if(self.editors[i].header) self.editors[i].header.style.display = 'none';
5426 self.editors[i].option = self.switcher_options[i];
5428 holder.addEventListener('change_header_text',function() {
5429 self.refreshHeaderText();
5432 if(i !== self.type) holder.style.display = 'none';
5434 preBuild: function() {
5440 this.validators = [];
5442 this.keep_values = true;
5443 if(typeof this.jsoneditor.options.keep_oneof_values !== "undefined") this.keep_values = this.jsoneditor.options.keep_oneof_values;
5444 if(typeof this.options.keep_oneof_values !== "undefined") this.keep_values = this.options.keep_oneof_values;
5446 if(this.schema.oneOf) {
5448 this.types = this.schema.oneOf;
5449 delete this.schema.oneOf;
5451 else if(this.schema.anyOf) {
5453 this.types = this.schema.anyOf;
5454 delete this.schema.anyOf;
5457 if(!this.schema.type || this.schema.type === "any") {
5458 this.types = ['string','number','integer','boolean','object','array','null'];
5460 // If any of these primitive types are disallowed
5461 if(this.schema.disallow) {
5462 var disallow = this.schema.disallow;
5463 if(typeof disallow !== 'object' || !(Array.isArray(disallow))) {
5464 disallow = [disallow];
5466 var allowed_types = [];
5467 $each(this.types,function(i,type) {
5468 if(disallow.indexOf(type) === -1) allowed_types.push(type);
5470 this.types = allowed_types;
5473 else if(Array.isArray(this.schema.type)) {
5474 this.types = this.schema.type;
5477 this.types = [this.schema.type];
5479 delete this.schema.type;
5482 this.display_text = this.getDisplayText(this.types);
5486 var container = this.container;
5488 this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
5489 this.container.appendChild(this.header);
5491 this.switcher = this.theme.getSwitcher(this.display_text);
5492 container.appendChild(this.switcher);
5493 this.switcher.addEventListener('change',function(e) {
5495 e.stopPropagation();
5497 self.switchEditor(self.display_text.indexOf(this.value));
5498 self.onChange(true);
5501 this.editor_holder = document.createElement('div');
5502 container.appendChild(this.editor_holder);
5505 var validator_options = {};
5506 if(self.jsoneditor.options.custom_validators) {
5507 validator_options.custom_validators = self.jsoneditor.options.custom_validators;
5510 this.switcher_options = this.theme.getSwitcherOptions(this.switcher);
5511 $each(this.types,function(i,type) {
5512 self.editors[i] = false;
5516 if(typeof type === "string") {
5517 schema = $extend({},self.schema);
5521 schema = $extend({},self.schema,type);
5523 // If we need to merge `required` arrays
5524 if(type.required && Array.isArray(type.required) && self.schema.required && Array.isArray(self.schema.required)) {
5525 schema.required = self.schema.required.concat(type.required);
5529 self.validators[i] = new JSONEditor.Validator(self.jsoneditor,schema,validator_options);
5532 this.switchEditor(0);
5534 onChildEditorChange: function(editor) {
5535 if(this.editors[this.type]) {
5536 this.refreshValue();
5537 this.refreshHeaderText();
5542 refreshHeaderText: function() {
5543 var display_text = this.getDisplayText(this.types);
5544 $each(this.switcher_options, function(i,option) {
5545 option.textContent = display_text[i];
5548 refreshValue: function() {
5549 this.value = this.editors[this.type].getValue();
5551 setValue: function(val,initial) {
5552 // Determine type by getting the first one that validates
5554 var prev_type = this.type;
5555 $each(this.validators, function(i,validator) {
5556 if(!validator.validate(val).length) {
5558 self.switcher.value = self.display_text[i];
5563 var type_changed = this.type != prev_type;
5565 this.switchEditor(this.type);
5568 this.editors[this.type].setValue(val,initial);
5570 this.refreshValue();
5571 self.onChange(type_changed);
5573 destroy: function() {
5574 $each(this.editors, function(type,editor) {
5575 if(editor) editor.destroy();
5577 if(this.editor_holder && this.editor_holder.parentNode) this.editor_holder.parentNode.removeChild(this.editor_holder);
5578 if(this.switcher && this.switcher.parentNode) this.switcher.parentNode.removeChild(this.switcher);
5581 showValidationErrors: function(errors) {
5584 // oneOf and anyOf error paths need to remove the oneOf[i] part before passing to child editors
5585 if(this.oneOf || this.anyOf) {
5586 var check_part = this.oneOf? 'oneOf' : 'anyOf';
5587 $each(this.editors,function(i,editor) {
5589 var check = self.path+'.'+check_part+'['+i+']';
5590 var new_errors = [];
5591 $each(errors, function(j,error) {
5592 if(error.path.substr(0,check.length)===check) {
5593 var new_error = $extend({},error);
5594 new_error.path = self.path+new_error.path.substr(check.length);
5595 new_errors.push(new_error);
5599 editor.showValidationErrors(new_errors);
5603 $each(this.editors,function(type,editor) {
5605 editor.showValidationErrors(errors);
5611 // Enum Editor (used for objects and arrays with enumerated values)
5612 JSONEditor.defaults.editors["enum"] = JSONEditor.AbstractEditor.extend({
5613 getNumColumns: function() {
5617 var container = this.container;
5618 this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
5619 this.container.appendChild(this.title);
5621 this.options.enum_titles = this.options.enum_titles || [];
5623 this["enum"] = this.schema["enum"];
5625 this.select_options = [];
5626 this.html_values = [];
5629 for(var i=0; i<this["enum"].length; i++) {
5630 this.select_options[i] = this.options.enum_titles[i] || "Value "+(i+1);
5631 this.html_values[i] = this.getHTML(this["enum"][i]);
5635 this.switcher = this.theme.getSwitcher(this.select_options);
5636 this.container.appendChild(this.switcher);
5639 this.display_area = this.theme.getIndentedPanel();
5640 this.container.appendChild(this.display_area);
5642 if(this.options.hide_display) this.display_area.style.display = "none";
5644 this.switcher.addEventListener('change',function() {
5645 self.selected = self.select_options.indexOf(this.value);
5646 self.value = self["enum"][self.selected];
5647 self.refreshValue();
5648 self.onChange(true);
5650 this.value = this["enum"][0];
5651 this.refreshValue();
5653 if(this["enum"].length === 1) this.switcher.style.display = 'none';
5655 refreshValue: function() {
5658 var stringified = JSON.stringify(this.value);
5659 $each(this["enum"], function(i, el) {
5660 if(stringified === JSON.stringify(el)) {
5666 if(self.selected<0) {
5667 self.setValue(self["enum"][0]);
5671 this.switcher.value = this.select_options[this.selected];
5672 this.display_area.innerHTML = this.html_values[this.selected];
5674 enable: function() {
5675 if(!this.always_disabled) {
5676 this.switcher.disabled = false;
5680 disable: function(always_disabled) {
5681 if(always_disabled) this.always_disabled = true;
5682 this.switcher.disabled = true;
5685 getHTML: function(el) {
5689 return '<em>null</em>';
5692 else if(typeof el === "object") {
5696 $each(el,function(i,child) {
5697 var html = self.getHTML(child);
5699 // Add the keys to object children
5700 if(!(Array.isArray(el))) {
5702 html = '<div><em>'+i+'</em>: '+html+'</div>';
5706 ret += '<li>'+html+'</li>';
5709 if(Array.isArray(el)) ret = '<ol>'+ret+'</ol>';
5710 else ret = "<ul style='margin-top:0;margin-bottom:0;padding-top:0;padding-bottom:0;'>"+ret+'</ul>';
5715 else if(typeof el === "boolean") {
5716 return el? 'true' : 'false';
5719 else if(typeof el === "string") {
5720 return el.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
5727 setValue: function(val) {
5728 if(this.value !== val) {
5730 this.refreshValue();
5734 destroy: function() {
5735 if(this.display_area && this.display_area.parentNode) this.display_area.parentNode.removeChild(this.display_area);
5736 if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
5737 if(this.switcher && this.switcher.parentNode) this.switcher.parentNode.removeChild(this.switcher);
5743 JSONEditor.defaults.editors.select = JSONEditor.AbstractEditor.extend({
5744 setValue: function(value,initial) {
5745 value = this.typecast(value||'');
5747 // Sanitize value before setting it
5748 var sanitized = value;
5749 if(this.enum_values.indexOf(sanitized) < 0) {
5750 sanitized = this.enum_values[0];
5753 if(this.value === sanitized) {
5757 this.input.value = this.enum_options[this.enum_values.indexOf(sanitized)];
5760 this.select2.val(this.input.value).trigger("change");
5762 this.select2.select2('val',this.input.value);
5764 this.value = sanitized;
5768 register: function() {
5770 if(!this.input) return;
5771 this.input.setAttribute('name',this.formname);
5773 unregister: function() {
5775 if(!this.input) return;
5776 this.input.removeAttribute('name');
5778 getNumColumns: function() {
5779 if(!this.enum_options) return 3;
5780 var longest_text = this.getTitle().length;
5781 for(var i=0; i<this.enum_options.length; i++) {
5782 longest_text = Math.max(longest_text,this.enum_options[i].length+4);
5784 return Math.min(12,Math.max(longest_text/7,2));
5786 typecast: function(value) {
5787 if(this.schema.type === "boolean") {
5790 else if(this.schema.type === "number") {
5793 else if(this.schema.type === "integer") {
5794 return Math.floor(value*1);
5800 getValue: function() {
5801 if (!this.dependenciesFulfilled) {
5804 return this.typecast(this.value);
5806 preBuild: function() {
5808 this.input_type = 'select';
5809 this.enum_options = [];
5810 this.enum_values = [];
5811 this.enum_display = [];
5814 // Enum options enumerated
5815 if(this.schema["enum"]) {
5816 var display = this.schema.options && this.schema.options.enum_titles || [];
5818 $each(this.schema["enum"],function(i,option) {
5819 self.enum_options[i] = ""+option;
5820 self.enum_display[i] = ""+(display[i] || option);
5821 self.enum_values[i] = self.typecast(option);
5824 if(!this.isRequired()){
5825 self.enum_display.unshift(' ');
5826 self.enum_options.unshift('undefined');
5827 self.enum_values.unshift(undefined);
5832 else if(this.schema.type === "boolean") {
5833 self.enum_display = this.schema.options && this.schema.options.enum_titles || ['true','false'];
5834 self.enum_options = ['1',''];
5835 self.enum_values = [true,false];
5837 if(!this.isRequired()){
5838 self.enum_display.unshift(' ');
5839 self.enum_options.unshift('undefined');
5840 self.enum_values.unshift(undefined);
5845 else if(this.schema.enumSource) {
5846 this.enumSource = [];
5847 this.enum_display = [];
5848 this.enum_options = [];
5849 this.enum_values = [];
5851 // Shortcut declaration for using a single array
5852 if(!(Array.isArray(this.schema.enumSource))) {
5853 if(this.schema.enumValue) {
5856 source: this.schema.enumSource,
5857 value: this.schema.enumValue
5864 source: this.schema.enumSource
5870 for(i=0; i<this.schema.enumSource.length; i++) {
5871 // Shorthand for watched variable
5872 if(typeof this.schema.enumSource[i] === "string") {
5873 this.enumSource[i] = {
5874 source: this.schema.enumSource[i]
5877 // Make a copy of the schema
5878 else if(!(Array.isArray(this.schema.enumSource[i]))) {
5879 this.enumSource[i] = $extend({},this.schema.enumSource[i]);
5882 this.enumSource[i] = this.schema.enumSource[i];
5887 // Now, enumSource is an array of sources
5888 // Walk through this array and fix up the values
5889 for(i=0; i<this.enumSource.length; i++) {
5890 if(this.enumSource[i].value) {
5891 this.enumSource[i].value = this.jsoneditor.compileTemplate(this.enumSource[i].value, this.template_engine);
5893 if(this.enumSource[i].title) {
5894 this.enumSource[i].title = this.jsoneditor.compileTemplate(this.enumSource[i].title, this.template_engine);
5896 if(this.enumSource[i].filter) {
5897 this.enumSource[i].filter = this.jsoneditor.compileTemplate(this.enumSource[i].filter, this.template_engine);
5901 // Other, not supported
5903 throw "'select' editor requires the enum property to be set.";
5908 if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
5909 if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
5910 if(this.options.infoText) this.infoButton = this.theme.getInfoButton(this.options.infoText);
5911 if(this.options.compact) this.container.className += ' compact';
5913 this.input = this.theme.getSelectInput(this.enum_options);
5914 this.theme.setSelectOptions(this.input,this.enum_options,this.enum_display);
5916 if(this.schema.readOnly || this.schema.readonly) {
5917 this.always_disabled = true;
5918 this.input.disabled = true;
5921 this.input.addEventListener('change',function(e) {
5923 e.stopPropagation();
5924 self.onInputChange();
5927 this.control = this.theme.getFormControl(this.label, this.input, this.description, this.infoButton);
5928 this.input.controlgroup = this.control;
5929 this.container.appendChild(this.control);
5931 this.value = this.enum_values[0];
5933 onInputChange: function() {
5934 var val = this.typecast(this.input.value);
5937 // Invalid option, use first option instead
5938 if(this.enum_options.indexOf(val) === -1) {
5939 new_val = this.enum_values[0];
5942 new_val = this.enum_values[this.enum_options.indexOf(val)];
5945 // If valid hasn't changed
5946 if(new_val === this.value) return;
5948 // Store new value and propogate change event
5949 this.value = new_val;
5950 this.onChange(true);
5952 setupSelect2: function() {
5953 // If the Select2 library is loaded use it when we have lots of items
5954 if(window.jQuery && window.jQuery.fn && window.jQuery.fn.select2 && (this.enum_options.length > 2 || (this.enum_options.length && this.enumSource))) {
5955 var options = $extend({},JSONEditor.plugins.select2);
5956 if(this.schema.options && this.schema.options.select2_options) options = $extend(options,this.schema.options.select2_options);
5957 this.select2 = window.jQuery(this.input).select2(options);
5958 this.select2v4 = this.select2.select2.hasOwnProperty("amd");
5961 this.select2.on('select2-blur',function() {
5963 self.input.value = self.select2.val();
5965 self.input.value = self.select2.select2('val');
5967 self.onInputChange();
5970 this.select2.on('change',function() {
5972 self.input.value = self.select2.val();
5974 self.input.value = self.select2.select2('val');
5976 self.onInputChange();
5980 this.select2 = null;
5983 postBuild: function() {
5985 this.theme.afterInputReady(this.input);
5986 this.setupSelect2();
5988 onWatchedFieldChange: function() {
5989 var self = this, vars, j;
5991 // If this editor uses a dynamic select box
5992 if(this.enumSource) {
5993 vars = this.getWatchedFieldValues();
5994 var select_options = [];
5995 var select_titles = [];
5997 for(var i=0; i<this.enumSource.length; i++) {
5999 if(Array.isArray(this.enumSource[i])) {
6000 select_options = select_options.concat(this.enumSource[i]);
6001 select_titles = select_titles.concat(this.enumSource[i]);
6005 // Static list of items
6006 if(Array.isArray(this.enumSource[i].source)) {
6007 items = this.enumSource[i].source;
6010 items = vars[this.enumSource[i].source];
6014 // Only use a predefined part of the array
6015 if(this.enumSource[i].slice) {
6016 items = Array.prototype.slice.apply(items,this.enumSource[i].slice);
6019 if(this.enumSource[i].filter) {
6021 for(j=0; j<items.length; j++) {
6022 if(this.enumSource[i].filter({i:j,item:items[j],watched:vars})) new_items.push(items[j]);
6027 var item_titles = [];
6028 var item_values = [];
6029 for(j=0; j<items.length; j++) {
6030 var item = items[j];
6033 if(this.enumSource[i].value) {
6034 item_values[j] = this.enumSource[i].value({
6039 // Use value directly
6041 item_values[j] = items[j];
6045 if(this.enumSource[i].title) {
6046 item_titles[j] = this.enumSource[i].title({
6051 // Use value as the title also
6053 item_titles[j] = item_values[j];
6059 select_options = select_options.concat(item_values);
6060 select_titles = select_titles.concat(item_titles);
6065 var prev_value = this.value;
6067 this.theme.setSelectOptions(this.input, select_options, select_titles);
6068 this.enum_options = select_options;
6069 this.enum_display = select_titles;
6070 this.enum_values = select_options;
6073 this.select2.select2('destroy');
6076 // If the previous value is still in the new select options, stick with it
6077 if(select_options.indexOf(prev_value) !== -1) {
6078 this.input.value = prev_value;
6079 this.value = prev_value;
6081 // Otherwise, set the value to the first select option
6083 this.input.value = select_options[0];
6084 this.value = this.typecast(select_options[0] || "");
6085 if(this.parent) this.parent.onChildEditorChange(this);
6086 else this.jsoneditor.onChange();
6087 this.jsoneditor.notifyWatchers(this.path);
6090 this.setupSelect2();
6095 enable: function() {
6096 if(!this.always_disabled) {
6097 this.input.disabled = false;
6100 this.select2.prop("disabled",false);
6102 this.select2.select2("enable",true);
6107 disable: function(always_disabled) {
6108 if(always_disabled) this.always_disabled = true;
6109 this.input.disabled = true;
6112 this.select2.prop("disabled",true);
6114 this.select2.select2("enable",false);
6118 destroy: function() {
6119 if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
6120 if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
6121 if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
6123 this.select2.select2('destroy');
6124 this.select2 = null;
6129 showValidationErrors: function (errors) {
6132 if (this.jsoneditor.options.show_errors === "always") {}
6133 else if (!this.is_dirty && this.previous_error_setting === this.jsoneditor.options.show_errors) {
6137 this.previous_error_setting = this.jsoneditor.options.show_errors;
6140 $each(errors, function (i, error) {
6141 if (error.path === self.path) {
6142 messages.push(error.message);
6146 this.input.controlgroup = this.control;
6148 if (messages.length) {
6149 this.theme.addInputError(this.input, messages.join('. ') + '.');
6152 this.theme.removeInputError(this.input);
6157 JSONEditor.defaults.editors.selectize = JSONEditor.AbstractEditor.extend({
6158 setValue: function(value,initial) {
6159 value = this.typecast(value||'');
6161 // Sanitize value before setting it
6162 var sanitized = value;
6163 if(this.enum_values.indexOf(sanitized) < 0) {
6164 sanitized = this.enum_values[0];
6167 if(this.value === sanitized) {
6171 this.input.value = this.enum_options[this.enum_values.indexOf(sanitized)];
6173 if(this.selectize) {
6174 this.selectize[0].selectize.addItem(sanitized);
6177 this.value = sanitized;
6180 register: function() {
6182 if(!this.input) return;
6183 this.input.setAttribute('name',this.formname);
6185 unregister: function() {
6187 if(!this.input) return;
6188 this.input.removeAttribute('name');
6190 getNumColumns: function() {
6191 if(!this.enum_options) return 3;
6192 var longest_text = this.getTitle().length;
6193 for(var i=0; i<this.enum_options.length; i++) {
6194 longest_text = Math.max(longest_text,this.enum_options[i].length+4);
6196 return Math.min(12,Math.max(longest_text/7,2));
6198 typecast: function(value) {
6199 if(this.schema.type === "boolean") {
6202 else if(this.schema.type === "number") {
6205 else if(this.schema.type === "integer") {
6206 return Math.floor(value*1);
6212 getValue: function() {
6213 if (!this.dependenciesFulfilled) {
6218 preBuild: function() {
6220 this.input_type = 'select';
6221 this.enum_options = [];
6222 this.enum_values = [];
6223 this.enum_display = [];
6226 // Enum options enumerated
6227 if(this.schema.enum) {
6228 var display = this.schema.options && this.schema.options.enum_titles || [];
6230 $each(this.schema.enum,function(i,option) {
6231 self.enum_options[i] = ""+option;
6232 self.enum_display[i] = ""+(display[i] || option);
6233 self.enum_values[i] = self.typecast(option);
6237 else if(this.schema.type === "boolean") {
6238 self.enum_display = this.schema.options && this.schema.options.enum_titles || ['true','false'];
6239 self.enum_options = ['1','0'];
6240 self.enum_values = [true,false];
6243 else if(this.schema.enumSource) {
6244 this.enumSource = [];
6245 this.enum_display = [];
6246 this.enum_options = [];
6247 this.enum_values = [];
6249 // Shortcut declaration for using a single array
6250 if(!(Array.isArray(this.schema.enumSource))) {
6251 if(this.schema.enumValue) {
6254 source: this.schema.enumSource,
6255 value: this.schema.enumValue
6262 source: this.schema.enumSource
6268 for(i=0; i<this.schema.enumSource.length; i++) {
6269 // Shorthand for watched variable
6270 if(typeof this.schema.enumSource[i] === "string") {
6271 this.enumSource[i] = {
6272 source: this.schema.enumSource[i]
6275 // Make a copy of the schema
6276 else if(!(Array.isArray(this.schema.enumSource[i]))) {
6277 this.enumSource[i] = $extend({},this.schema.enumSource[i]);
6280 this.enumSource[i] = this.schema.enumSource[i];
6285 // Now, enumSource is an array of sources
6286 // Walk through this array and fix up the values
6287 for(i=0; i<this.enumSource.length; i++) {
6288 if(this.enumSource[i].value) {
6289 this.enumSource[i].value = this.jsoneditor.compileTemplate(this.enumSource[i].value, this.template_engine);
6291 if(this.enumSource[i].title) {
6292 this.enumSource[i].title = this.jsoneditor.compileTemplate(this.enumSource[i].title, this.template_engine);
6294 if(this.enumSource[i].filter) {
6295 this.enumSource[i].filter = this.jsoneditor.compileTemplate(this.enumSource[i].filter, this.template_engine);
6299 // Other, not supported
6301 throw "'select' editor requires the enum property to be set.";
6306 if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
6307 if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
6308 if(this.options.infoText) this.infoButton = this.theme.getInfoButton(this.options.infoText);
6310 if(this.options.compact) this.container.className += ' compact';
6312 this.input = this.theme.getSelectInput(this.enum_options);
6313 this.theme.setSelectOptions(this.input,this.enum_options,this.enum_display);
6315 if(this.schema.readOnly || this.schema.readonly) {
6316 this.always_disabled = true;
6317 this.input.disabled = true;
6320 this.input.addEventListener('change',function(e) {
6322 e.stopPropagation();
6323 self.onInputChange();
6326 this.control = this.theme.getFormControl(this.label, this.input, this.description, this.infoButton);
6327 this.container.appendChild(this.control);
6329 this.value = this.enum_values[0];
6331 onInputChange: function() {
6332 //console.log("onInputChange");
6333 var val = this.input.value;
6335 var sanitized = val;
6336 if(this.enum_options.indexOf(val) === -1) {
6337 sanitized = this.enum_options[0];
6340 //this.value = this.enum_values[this.enum_options.indexOf(val)];
6342 this.onChange(true);
6344 setupSelectize: function() {
6345 // If the Selectize library is loaded use it when we have lots of items
6347 if(window.jQuery && window.jQuery.fn && window.jQuery.fn.selectize && (this.enum_options.length >= 2 || (this.enum_options.length && this.enumSource))) {
6348 var options = $extend({},JSONEditor.plugins.selectize);
6349 if(this.schema.options && this.schema.options.selectize_options) options = $extend(options,this.schema.options.selectize_options);
6350 this.selectize = window.jQuery(this.input).selectize($extend(options,
6352 // set the create option to true by default, or to the user specified value if defined
6353 create: ( options.create === undefined ? true : options.create),
6354 onChange : function() {
6355 self.onInputChange();
6360 this.selectize = null;
6363 postBuild: function() {
6365 this.theme.afterInputReady(this.input);
6366 this.setupSelectize();
6368 onWatchedFieldChange: function() {
6369 var self = this, vars, j;
6371 // If this editor uses a dynamic select box
6372 if(this.enumSource) {
6373 vars = this.getWatchedFieldValues();
6374 var select_options = [];
6375 var select_titles = [];
6377 for(var i=0; i<this.enumSource.length; i++) {
6379 if(Array.isArray(this.enumSource[i])) {
6380 select_options = select_options.concat(this.enumSource[i]);
6381 select_titles = select_titles.concat(this.enumSource[i]);
6384 else if(vars[this.enumSource[i].source]) {
6385 var items = vars[this.enumSource[i].source];
6387 // Only use a predefined part of the array
6388 if(this.enumSource[i].slice) {
6389 items = Array.prototype.slice.apply(items,this.enumSource[i].slice);
6392 if(this.enumSource[i].filter) {
6394 for(j=0; j<items.length; j++) {
6395 if(this.enumSource[i].filter({i:j,item:items[j]})) new_items.push(items[j]);
6400 var item_titles = [];
6401 var item_values = [];
6402 for(j=0; j<items.length; j++) {
6403 var item = items[j];
6406 if(this.enumSource[i].value) {
6407 item_values[j] = this.enumSource[i].value({
6412 // Use value directly
6414 item_values[j] = items[j];
6418 if(this.enumSource[i].title) {
6419 item_titles[j] = this.enumSource[i].title({
6424 // Use value as the title also
6426 item_titles[j] = item_values[j];
6432 select_options = select_options.concat(item_values);
6433 select_titles = select_titles.concat(item_titles);
6437 var prev_value = this.value;
6439 // Check to see if this item is in the list
6440 // Note: We have to skip empty string for watch lists to work properly
6441 if ((prev_value !== undefined) && (prev_value !== "") && (select_options.indexOf(prev_value) === -1)) {
6442 // item is not in the list. Add it.
6443 select_options = select_options.concat(prev_value);
6444 select_titles = select_titles.concat(prev_value);
6447 this.theme.setSelectOptions(this.input, select_options, select_titles);
6448 this.enum_options = select_options;
6449 this.enum_display = select_titles;
6450 this.enum_values = select_options;
6452 // If the previous value is still in the new select options, stick with it
6453 if(select_options.indexOf(prev_value) !== -1) {
6454 this.input.value = prev_value;
6455 this.value = prev_value;
6458 // Otherwise, set the value to the first select option
6460 this.input.value = select_options[0];
6461 this.value = select_options[0] || "";
6462 if(this.parent) this.parent.onChildEditorChange(this);
6463 else this.jsoneditor.onChange();
6464 this.jsoneditor.notifyWatchers(this.path);
6467 if(this.selectize) {
6468 // Update the Selectize options
6469 this.updateSelectizeOptions(select_options);
6472 this.setupSelectize();
6478 updateSelectizeOptions: function(select_options) {
6479 var selectized = this.selectize[0].selectize,
6483 selectized.clearOptions();
6484 for(var n in select_options) {
6485 selectized.addOption({value:select_options[n],text:select_options[n]});
6487 selectized.addItem(this.value);
6488 selectized.on('change',function() {
6489 self.onInputChange();
6492 enable: function() {
6493 if(!this.always_disabled) {
6494 this.input.disabled = false;
6495 if(this.selectize) {
6496 this.selectize[0].selectize.unlock();
6501 disable: function(always_disabled) {
6502 if(always_disabled) this.always_disabled = true;
6503 this.input.disabled = true;
6504 if(this.selectize) {
6505 this.selectize[0].selectize.lock();
6509 destroy: function() {
6510 if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
6511 if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
6512 if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
6513 if(this.selectize) {
6514 this.selectize[0].selectize.destroy();
6515 this.selectize = null;
6521 JSONEditor.defaults.editors.multiselect = JSONEditor.AbstractEditor.extend({
6522 preBuild: function() {
6526 this.select_options = {};
6527 this.select_values = {};
6529 var items_schema = this.jsoneditor.expandRefs(this.schema.items || {});
6531 var e = items_schema["enum"] || [];
6532 var t = items_schema.options? items_schema.options.enum_titles || [] : [];
6533 this.option_keys = [];
6534 this.option_titles = [];
6535 for(i=0; i<e.length; i++) {
6536 // If the sanitized value is different from the enum value, don't include it
6537 if(this.sanitize(e[i]) !== e[i]) continue;
6539 this.option_keys.push(e[i]+"");
6540 this.option_titles.push((t[i]||e[i])+"");
6541 this.select_values[e[i]+""] = e[i];
6546 if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
6547 if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
6549 if((!this.schema.format && this.option_keys.length < 8) || this.schema.format === "checkbox") {
6550 this.input_type = 'checkboxes';
6554 for(i=0; i<this.option_keys.length; i++) {
6555 this.inputs[this.option_keys[i]] = this.theme.getCheckbox();
6556 this.select_options[this.option_keys[i]] = this.inputs[this.option_keys[i]];
6557 var label = this.theme.getCheckboxLabel(this.option_titles[i]);
6558 this.controls[this.option_keys[i]] = this.theme.getFormControl(label, this.inputs[this.option_keys[i]]);
6561 this.control = this.theme.getMultiCheckboxHolder(this.controls,this.label,this.description);
6564 this.input_type = 'select';
6565 this.input = this.theme.getSelectInput(this.option_keys);
6566 this.theme.setSelectOptions(this.input,this.option_keys,this.option_titles);
6567 this.input.multiple = true;
6568 this.input.size = Math.min(10,this.option_keys.length);
6570 for(i=0; i<this.option_keys.length; i++) {
6571 this.select_options[this.option_keys[i]] = this.input.children[i];
6574 if(this.schema.readOnly || this.schema.readonly) {
6575 this.always_disabled = true;
6576 this.input.disabled = true;
6579 this.control = this.theme.getFormControl(this.label, this.input, this.description);
6582 this.container.appendChild(this.control);
6583 this.control.addEventListener('change',function(e) {
6585 e.stopPropagation();
6588 for(i = 0; i<self.option_keys.length; i++) {
6589 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]]);
6592 self.updateValue(new_value);
6593 self.onChange(true);
6596 setValue: function(value, initial) {
6598 value = value || [];
6599 if(typeof value !== "object") value = [value];
6600 else if(!(Array.isArray(value))) value = [];
6602 // Make sure we are dealing with an array of strings so we can check for strict equality
6603 for(i=0; i<value.length; i++) {
6604 if(typeof value[i] !== "string") value[i] += "";
6607 // Update selected status of options
6608 for(i in this.select_options) {
6609 if(!this.select_options.hasOwnProperty(i)) continue;
6611 this.select_options[i][this.input_type === "select"? "selected" : "checked"] = (value.indexOf(i) !== -1);
6614 this.updateValue(value);
6617 setupSelect2: function() {
6618 if(window.jQuery && window.jQuery.fn && window.jQuery.fn.select2) {
6619 var options = window.jQuery.extend({},JSONEditor.plugins.select2);
6620 if(this.schema.options && this.schema.options.select2_options) options = $extend(options,this.schema.options.select2_options);
6621 this.select2 = window.jQuery(this.input).select2(options);
6622 this.select2v4 = this.select2.select2.hasOwnProperty("amd");
6625 this.select2.on('select2-blur',function() {
6627 self.value = self.select2.val();
6629 self.value = self.select2.select2('val');
6631 self.onChange(true);
6634 this.select2.on('change',function() {
6636 self.value = self.select2.val();
6638 self.value = self.select2.select2('val');
6640 self.onChange(true);
6644 this.select2 = null;
6647 onInputChange: function() {
6648 this.value = this.input.value;
6649 this.onChange(true);
6651 postBuild: function() {
6653 this.setupSelect2();
6655 register: function() {
6657 if(!this.input) return;
6658 this.input.setAttribute('name',this.formname);
6660 unregister: function() {
6662 if(!this.input) return;
6663 this.input.removeAttribute('name');
6665 getNumColumns: function() {
6666 var longest_text = this.getTitle().length;
6667 for(var i in this.select_values) {
6668 if(!this.select_values.hasOwnProperty(i)) continue;
6669 longest_text = Math.max(longest_text,(this.select_values[i]+"").length+4);
6672 return Math.min(12,Math.max(longest_text/7,2));
6674 updateValue: function(value) {
6675 var changed = false;
6677 for(var i=0; i<value.length; i++) {
6678 if(!this.select_options[value[i]+""]) {
6682 var sanitized = this.sanitize(this.select_values[value[i]]);
6683 new_value.push(sanitized);
6684 if(sanitized !== value[i]) changed = true;
6686 this.value = new_value;
6690 this.select2.val(this.value).trigger("change");
6692 this.select2.select2('val',this.value);
6697 sanitize: function(value) {
6698 if(this.schema.items.type === "number") {
6701 else if(this.schema.items.type === "integer") {
6702 return Math.floor(value*1);
6708 enable: function() {
6709 if(!this.always_disabled) {
6711 this.input.disabled = false;
6713 else if(this.inputs) {
6714 for(var i in this.inputs) {
6715 if(!this.inputs.hasOwnProperty(i)) continue;
6716 this.inputs[i].disabled = false;
6721 this.select2.prop("disabled",false);
6723 this.select2.select2("enable",true);
6728 disable: function(always_disabled) {
6729 if(always_disabled) this.always_disabled = true;
6731 this.input.disabled = true;
6733 else if(this.inputs) {
6734 for(var i in this.inputs) {
6735 if(!this.inputs.hasOwnProperty(i)) continue;
6736 this.inputs[i].disabled = true;
6741 this.select2.prop("disabled",true);
6743 this.select2.select2("enable",false);
6747 destroy: function() {
6749 this.select2.select2('destroy');
6750 this.select2 = null;
6756 JSONEditor.defaults.editors.base64 = JSONEditor.AbstractEditor.extend({
6757 getNumColumns: function() {
6762 this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
6763 if(this.options.infoText) this.infoButton = this.theme.getInfoButton(this.options.infoText);
6765 // Input that holds the base64 string
6766 this.input = this.theme.getFormInputField('hidden');
6767 this.container.appendChild(this.input);
6769 // Don't show uploader if this is readonly
6770 if(!this.schema.readOnly && !this.schema.readonly) {
6771 if(!window.FileReader) throw "FileReader required for base64 editor";
6774 this.uploader = this.theme.getFormInputField('file');
6776 this.uploader.addEventListener('change',function(e) {
6778 e.stopPropagation();
6780 if(this.files && this.files.length) {
6781 var fr = new FileReader();
6782 fr.onload = function(evt) {
6783 self.value = evt.target.result;
6784 self.refreshPreview();
6785 self.onChange(true);
6788 fr.readAsDataURL(this.files[0]);
6793 this.preview = this.theme.getFormInputDescription(this.schema.description);
6794 this.container.appendChild(this.preview);
6796 this.control = this.theme.getFormControl(this.label, this.uploader||this.input, this.preview, this.infoButton);
6797 this.container.appendChild(this.control);
6799 refreshPreview: function() {
6800 if(this.last_preview === this.value) return;
6801 this.last_preview = this.value;
6803 this.preview.innerHTML = '';
6805 if(!this.value) return;
6807 var mime = this.value.match(/^data:([^;,]+)[;,]/);
6808 if(mime) mime = mime[1];
6811 this.preview.innerHTML = '<em>Invalid data URI</em>';
6814 this.preview.innerHTML = '<strong>Type:</strong> '+mime+', <strong>Size:</strong> '+Math.floor((this.value.length-this.value.split(',')[0].length-1)/1.33333)+' bytes';
6815 if(mime.substr(0,5)==="image") {
6816 this.preview.innerHTML += '<br>';
6817 var img = document.createElement('img');
6818 img.style.maxWidth = '100%';
6819 img.style.maxHeight = '100px';
6820 img.src = this.value;
6821 this.preview.appendChild(img);
6825 enable: function() {
6826 if(!this.always_disabled) {
6827 if(this.uploader) this.uploader.disabled = false;
6831 disable: function(always_disabled) {
6832 if(always_disabled) this.always_disabled = true;
6833 if(this.uploader) this.uploader.disabled = true;
6836 setValue: function(val) {
6837 if(this.value !== val) {
6839 this.input.value = this.value;
6840 this.refreshPreview();
6844 destroy: function() {
6845 if(this.preview && this.preview.parentNode) this.preview.parentNode.removeChild(this.preview);
6846 if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
6847 if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
6848 if(this.uploader && this.uploader.parentNode) this.uploader.parentNode.removeChild(this.uploader);
6854 JSONEditor.defaults.editors.upload = JSONEditor.AbstractEditor.extend({
6855 getNumColumns: function() {
6860 this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
6862 // Input that holds the base64 string
6863 this.input = this.theme.getFormInputField('hidden');
6864 this.container.appendChild(this.input);
6866 // Don't show uploader if this is readonly
6867 if(!this.schema.readOnly && !this.schema.readonly) {
6869 if(!this.jsoneditor.options.upload) throw "Upload handler required for upload editor";
6872 this.uploader = this.theme.getFormInputField('file');
6874 this.uploader.addEventListener('change',function(e) {
6876 e.stopPropagation();
6878 if(this.files && this.files.length) {
6879 var fr = new FileReader();
6880 fr.onload = function(evt) {
6881 self.preview_value = evt.target.result;
6882 self.refreshPreview();
6883 self.onChange(true);
6886 fr.readAsDataURL(this.files[0]);
6891 var description = this.schema.description;
6892 if (!description) description = '';
6894 this.preview = this.theme.getFormInputDescription(description);
6895 this.container.appendChild(this.preview);
6897 this.control = this.theme.getFormControl(this.label, this.uploader||this.input, this.preview);
6898 this.container.appendChild(this.control);
6900 refreshPreview: function() {
6901 if(this.last_preview === this.preview_value) return;
6902 this.last_preview = this.preview_value;
6904 this.preview.innerHTML = '';
6906 if(!this.preview_value) return;
6910 var mime = this.preview_value.match(/^data:([^;,]+)[;,]/);
6911 if(mime) mime = mime[1];
6912 if(!mime) mime = 'unknown';
6914 var file = this.uploader.files[0];
6916 this.preview.innerHTML = '<strong>Type:</strong> '+mime+', <strong>Size:</strong> '+file.size+' bytes';
6917 if(mime.substr(0,5)==="image") {
6918 this.preview.innerHTML += '<br>';
6919 var img = document.createElement('img');
6920 img.style.maxWidth = '100%';
6921 img.style.maxHeight = '100px';
6922 img.src = this.preview_value;
6923 this.preview.appendChild(img);
6926 this.preview.innerHTML += '<br>';
6927 var uploadButton = this.getButton('Upload', 'upload', 'Upload');
6928 this.preview.appendChild(uploadButton);
6929 uploadButton.addEventListener('click',function(event) {
6930 event.preventDefault();
6932 uploadButton.setAttribute("disabled", "disabled");
6933 self.theme.removeInputError(self.uploader);
6935 if (self.theme.getProgressBar) {
6936 self.progressBar = self.theme.getProgressBar();
6937 self.preview.appendChild(self.progressBar);
6940 self.jsoneditor.options.upload(self.path, file, {
6941 success: function(url) {
6944 if(self.parent) self.parent.onChildEditorChange(self);
6945 else self.jsoneditor.onChange();
6947 if (self.progressBar) self.preview.removeChild(self.progressBar);
6948 uploadButton.removeAttribute("disabled");
6950 failure: function(error) {
6951 self.theme.addInputError(self.uploader, error);
6952 if (self.progressBar) self.preview.removeChild(self.progressBar);
6953 uploadButton.removeAttribute("disabled");
6955 updateProgress: function(progress) {
6956 if (self.progressBar) {
6957 if (progress) self.theme.updateProgressBar(self.progressBar, progress);
6958 else self.theme.updateProgressBarUnknown(self.progressBar);
6964 if(this.jsoneditor.options.auto_upload || this.schema.options.auto_upload) {
6965 uploadButton.dispatchEvent(new MouseEvent('click'));
6966 this.preview.removeChild(uploadButton);
6969 enable: function() {
6970 if(!this.always_disabled) {
6971 if(this.uploader) this.uploader.disabled = false;
6975 disable: function(always_disabled) {
6976 if(always_disabled) this.always_disabled = true;
6977 if(this.uploader) this.uploader.disabled = true;
6980 setValue: function(val) {
6981 if(this.value !== val) {
6983 this.input.value = this.value;
6987 destroy: function() {
6988 if(this.preview && this.preview.parentNode) this.preview.parentNode.removeChild(this.preview);
6989 if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
6990 if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
6991 if(this.uploader && this.uploader.parentNode) this.uploader.parentNode.removeChild(this.uploader);
6997 JSONEditor.defaults.editors.checkbox = JSONEditor.AbstractEditor.extend({
6998 setValue: function(value,initial) {
6999 this.value = !!value;
7000 this.input.checked = this.value;
7003 register: function() {
7005 if(!this.input) return;
7006 this.input.setAttribute('name',this.formname);
7008 unregister: function() {
7010 if(!this.input) return;
7011 this.input.removeAttribute('name');
7013 getNumColumns: function() {
7014 return Math.min(12,Math.max(this.getTitle().length/7,2));
7018 if(!this.options.compact) {
7019 this.label = this.header = this.theme.getCheckboxLabel(this.getTitle());
7021 if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
7022 if(this.options.infoText) this.infoButton = this.theme.getInfoButton(this.options.infoText);
7023 if(this.options.compact) this.container.className += ' compact';
7025 this.input = this.theme.getCheckbox();
7026 this.control = this.theme.getFormControl(this.label, this.input, this.description, this.infoButton);
7028 if(this.schema.readOnly || this.schema.readonly) {
7029 this.always_disabled = true;
7030 this.input.disabled = true;
7033 this.input.addEventListener('change',function(e) {
7035 e.stopPropagation();
7036 self.value = this.checked;
7037 self.onChange(true);
7040 this.container.appendChild(this.control);
7042 enable: function() {
7043 if(!this.always_disabled) {
7044 this.input.disabled = false;
7048 disable: function(always_disabled) {
7049 if(always_disabled) this.always_disabled = true;
7050 this.input.disabled = true;
7053 destroy: function() {
7054 if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
7055 if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
7056 if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
7059 showValidationErrors: function (errors) {
7062 if (this.jsoneditor.options.show_errors === "always") {}
7064 else if (!this.is_dirty && this.previous_error_setting === this.jsoneditor.options.show_errors) {
7068 this.previous_error_setting = this.jsoneditor.options.show_errors;
7071 $each(errors, function (i, error) {
7072 if (error.path === self.path) {
7073 messages.push(error.message);
7077 this.input.controlgroup = this.control;
7079 if (messages.length) {
7080 this.theme.addInputError(this.input, messages.join('. ') + '.');
7083 this.theme.removeInputError(this.input);
7088 JSONEditor.defaults.editors.arraySelectize = JSONEditor.AbstractEditor.extend({
7090 this.title = this.theme.getFormInputLabel(this.getTitle());
7092 this.title_controls = this.theme.getHeaderButtonHolder();
7093 this.title.appendChild(this.title_controls);
7094 this.error_holder = document.createElement('div');
7096 if(this.schema.description) {
7097 this.description = this.theme.getDescription(this.schema.description);
7100 this.input = document.createElement('select');
7101 this.input.setAttribute('multiple', 'multiple');
7103 var group = this.theme.getFormControl(this.title, this.input, this.description);
7105 this.container.appendChild(group);
7106 this.container.appendChild(this.error_holder);
7108 window.jQuery(this.input).selectize({
7114 postBuild: function() {
7116 this.input.selectize.on('change', function(event) {
7117 self.refreshValue();
7118 self.onChange(true);
7121 destroy: function() {
7123 if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
7124 if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
7125 if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
7129 empty: function(hard) {},
7130 setValue: function(value, initial) {
7132 // Update the array's value, adding/removing rows when necessary
7133 value = value || [];
7134 if(!(Array.isArray(value))) value = [value];
7136 this.input.selectize.clearOptions();
7137 this.input.selectize.clear(true);
7139 value.forEach(function(item) {
7140 self.input.selectize.addOption({text: item, value: item});
7142 this.input.selectize.setValue(value);
7144 this.refreshValue(initial);
7146 refreshValue: function(force) {
7147 this.value = this.input.selectize.getValue();
7149 showValidationErrors: function(errors) {
7152 // Get all the errors that pertain to this editor
7154 var other_errors = [];
7155 $each(errors, function(i,error) {
7156 if(error.path === self.path) {
7157 my_errors.push(error);
7160 other_errors.push(error);
7164 // Show errors for this editor
7165 if(this.error_holder) {
7167 if(my_errors.length) {
7169 this.error_holder.innerHTML = '';
7170 this.error_holder.style.display = '';
7171 $each(my_errors, function(i,error) {
7172 self.error_holder.appendChild(self.theme.getErrorMessage(error.message));
7177 this.error_holder.style.display = 'none';
7183 var matchKey = (function () {
7184 var elem = document.documentElement;
7186 if (elem.matches) return 'matches';
7187 else if (elem.webkitMatchesSelector) return 'webkitMatchesSelector';
7188 else if (elem.mozMatchesSelector) return 'mozMatchesSelector';
7189 else if (elem.msMatchesSelector) return 'msMatchesSelector';
7190 else if (elem.oMatchesSelector) return 'oMatchesSelector';
7193 JSONEditor.AbstractTheme = Class.extend({
7194 getContainer: function() {
7195 return document.createElement('div');
7197 getFloatRightLinkHolder: function() {
7198 var el = document.createElement('div');
7199 el.style = el.style || {};
7200 el.style.cssFloat = 'right';
7201 el.style.marginLeft = '10px';
7204 getModal: function() {
7205 var el = document.createElement('div');
7206 el.style.backgroundColor = 'white';
7207 el.style.border = '1px solid black';
7208 el.style.boxShadow = '3px 3px black';
7209 el.style.position = 'absolute';
7210 el.style.zIndex = '10';
7211 el.style.display = 'none';
7214 getGridContainer: function() {
7215 var el = document.createElement('div');
7218 getGridRow: function() {
7219 var el = document.createElement('div');
7220 el.className = 'row';
7223 getGridColumn: function() {
7224 var el = document.createElement('div');
7227 setGridColumnSize: function(el,size) {
7230 getLink: function(text) {
7231 var el = document.createElement('a');
7232 el.setAttribute('href','#');
7233 el.appendChild(document.createTextNode(text));
7236 disableHeader: function(header) {
7237 header.style.color = '#ccc';
7239 disableLabel: function(label) {
7240 label.style.color = '#ccc';
7242 enableHeader: function(header) {
7243 header.style.color = '';
7245 enableLabel: function(label) {
7246 label.style.color = '';
7248 getInfoButton: function(text) {
7249 var icon = document.createElement('span');
7250 icon.innerText = "ⓘ";
7251 icon.style.fontSize = "16px";
7252 icon.style.fontWeight = "bold";
7253 icon.style.padding = ".25rem";
7254 icon.style.position = "relative";
7255 icon.style.display = "inline-block";
7257 var tooltip = document.createElement('span');
7258 tooltip.style.fontSize = "12px";
7259 icon.style.fontWeight = "normal";
7260 tooltip.style["font-family"] = "sans-serif";
7261 tooltip.style.visibility = "hidden";
7262 tooltip.style["background-color"] = "rgba(50, 50, 50, .75)";
7263 tooltip.style.margin = "0 .25rem";
7264 tooltip.style.color = "#FAFAFA";
7265 tooltip.style.padding = ".5rem 1rem";
7266 tooltip.style["border-radius"] = ".25rem";
7267 tooltip.style.width = "20rem";
7268 tooltip.style.position = "absolute";
7269 tooltip.innerText = text;
7270 icon.onmouseover = function() {
7271 tooltip.style.visibility = "visible";
7273 icon.onmouseleave = function() {
7274 tooltip.style.visibility = "hidden";
7277 icon.appendChild(tooltip);
7281 getFormInputLabel: function(text) {
7282 var el = document.createElement('label');
7283 el.appendChild(document.createTextNode(text));
7286 getCheckboxLabel: function(text) {
7287 var el = this.getFormInputLabel(text);
7288 el.style.fontWeight = 'normal';
7291 getHeader: function(text) {
7292 var el = document.createElement('h3');
7293 if(typeof text === "string") {
7294 el.textContent = text;
7295 el.style.fontWeight = 'bold';
7296 el.style.fontSize = '12px';
7297 el.style.padding = '4px';
7300 el.appendChild(text);
7305 getCheckbox: function() {
7306 var el = this.getFormInputField('checkbox');
7307 el.style.display = 'inline-block';
7308 el.style.width = 'auto';
7311 getMultiCheckboxHolder: function(controls,label,description) {
7312 var el = document.createElement('div');
7315 label.style.display = 'block';
7316 el.appendChild(label);
7319 for(var i in controls) {
7320 if(!controls.hasOwnProperty(i)) continue;
7321 controls[i].style.display = 'inline-block';
7322 controls[i].style.marginRight = '20px';
7323 el.appendChild(controls[i]);
7326 if(description) el.appendChild(description);
7330 getSelectInput: function(options) {
7331 var select = document.createElement('select');
7332 if(options) this.setSelectOptions(select, options);
7335 getSwitcher: function(options) {
7336 var switcher = this.getSelectInput(options);
7337 switcher.style.backgroundColor = 'transparent';
7338 switcher.style.display = 'inline-block';
7339 switcher.style.fontStyle = 'italic';
7340 switcher.style.fontWeight = 'normal';
7341 switcher.style.height = 'auto';
7342 switcher.style.marginBottom = 0;
7343 switcher.style.marginLeft = '5px';
7344 switcher.style.padding = '0 0 0 3px';
7345 switcher.style.width = 'auto';
7348 getSwitcherOptions: function(switcher) {
7349 return switcher.getElementsByTagName('option');
7351 setSwitcherOptions: function(switcher, options, titles) {
7352 this.setSelectOptions(switcher, options, titles);
7354 setSelectOptions: function(select, options, titles) {
7355 titles = titles || [];
7356 select.innerHTML = '';
7357 for(var i=0; i<options.length; i++) {
7358 var option = document.createElement('option');
7359 option.setAttribute('value',options[i]);
7360 option.textContent = titles[i] || options[i];
7361 select.appendChild(option);
7364 getTextareaInput: function(rows, cols) {
7365 var el = document.createElement('textarea');
7366 el.style = el.style || {};
7367 el.style.width = '100%';
7368 el.style.height = '50px';
7369 el.style.fontWeight = 'bold';
7370 el.style.fontSize = '1em';
7371 el.style.boxSizing = 'border-box';
7372 if(typeof rows === undefined) { rows = 1 };
7373 if(typeof cols === undefined) { cols = 80 };
7377 el.readonly = 'true';
7380 getRangeInput: function(min,max,step) {
7381 var el = this.getFormInputField('range');
7382 el.setAttribute('min',min);
7383 el.setAttribute('max',max);
7384 el.setAttribute('step',step);
7387 getFormInputField: function(type) {
7388 var el = document.createElement('input');
7389 el.setAttribute('type',type);
7392 afterInputReady: function(input) {
7395 getFormControl: function(label, input, description, infoText) {
7396 var el = document.createElement('div');
7397 el.className = 'form-control';
7398 if(label) el.appendChild(label);
7399 if(input.type === 'checkbox' && label) {
7400 label.insertBefore(input,label.firstChild);
7401 if(infoText) label.appendChild(infoText);
7404 if(infoText) label.appendChild(infoText);
7405 el.appendChild(input);
7408 if(description) el.appendChild(description);
7411 getIndentedPanel: function() {
7412 var el = document.createElement('div');
7413 el.style = el.style || {};
7414 el.style.paddingLeft = '10px';
7415 el.style.marginLeft = '10px';
7416 el.style.borderLeft = '1px solid #ccc';
7419 getTopIndentedPanel: function() {
7420 var el = document.createElement('div');
7421 el.style = el.style || {};
7422 el.style.paddingLeft = '10px';
7423 el.style.marginLeft = '10px';
7426 getChildEditorHolder: function() {
7427 return document.createElement('div');
7429 getDescription: function(text) {
7430 var el = document.createElement('p');
7431 el.innerHTML = text;
7434 getCheckboxDescription: function(text) {
7435 return this.getDescription(text);
7437 getFormInputDescription: function(text) {
7438 return this.getDescription(text);
7440 getHeaderButtonHolder: function() {
7441 return this.getButtonHolder();
7443 getButtonHolder: function() {
7444 return document.createElement('div');
7446 getButton: function(text, icon, title) {
7447 var el = document.createElement('button');
7449 this.setButtonText(el,text,icon,title);
7452 setButtonText: function(button, text, icon, title) {
7453 button.innerHTML = '';
7455 button.appendChild(icon);
7456 button.innerHTML += ' ';
7458 button.appendChild(document.createTextNode(text));
7459 if(title) button.setAttribute('title',title);
7461 getTable: function() {
7462 return document.createElement('table');
7464 getTableRow: function() {
7465 return document.createElement('tr');
7467 getTableHead: function() {
7468 return document.createElement('thead');
7470 getTableBody: function() {
7471 return document.createElement('tbody');
7473 getTableHeaderCell: function(text) {
7474 var el = document.createElement('th');
7475 el.textContent = text;
7478 getTableCell: function() {
7479 var el = document.createElement('td');
7482 getErrorMessage: function(text) {
7483 var el = document.createElement('p');
7484 el.style = el.style || {};
7485 el.style.color = 'red';
7486 el.appendChild(document.createTextNode(text));
7489 addInputError: function(input, text) {
7491 removeInputError: function(input) {
7493 addTableRowError: function(row) {
7495 removeTableRowError: function(row) {
7497 getTabHolder: function(propertyName) {
7498 var pName = (typeof propertyName === 'undefined')? "" : propertyName;
7499 var el = document.createElement('div');
7500 el.innerHTML = "<div style='float: left; width: 130px;' class='tabs' id='" + pName + "'></div><div class='content' style='margin-left: 120px;' id='" + pName + "'></div><div style='clear:both;'></div>";
7503 getTopTabHolder: function(propertyName) {
7504 var pName = (typeof propertyName === 'undefined')? "" : propertyName;
7505 var el = document.createElement('div');
7506 el.innerHTML = "<div class='tabs' style='margin-left: 10px;' id='" + pName + "'></div><div style='clear:both;'></div><div class='content' id='" + pName + "'></div>";
7509 applyStyles: function(el,styles) {
7510 for(var i in styles) {
7511 if(!styles.hasOwnProperty(i)) continue;
7512 el.style[i] = styles[i];
7515 closest: function(elem, selector) {
7516 while (elem && elem !== document) {
7517 if (elem[matchKey]) {
7518 if (elem[matchKey](selector)) {
7521 elem = elem.parentNode;
7530 insertBasicTopTab: function(tab, newTabs_holder ) {
7531 newTabs_holder.firstChild.insertBefore(tab,newTabs_holder.firstChild.firstChild);
7533 getTab: function(span, tabId) {
7534 var el = document.createElement('div');
7535 el.appendChild(span);
7537 el.style = el.style || {};
7538 this.applyStyles(el,{
7539 border: '1px solid #ccc',
7540 borderWidth: '1px 0 1px 1px',
7541 textAlign: 'center',
7543 borderRadius: '5px',
7544 borderBottomRightRadius: 0,
7545 borderTopRightRadius: 0,
7551 getTopTab: function(span, tabId) {
7552 var el = document.createElement('div');
7554 el.appendChild(span);
7555 el.style = el.style || {};
7556 this.applyStyles(el,{
7558 border: '1px solid #ccc',
7559 borderWidth: '1px 1px 0px 1px',
7560 textAlign: 'center',
7562 borderRadius: '5px',
7565 borderBottomRightRadius: 0,
7566 borderBottomLeftRadius: 0,
7572 getTabContentHolder: function(tab_holder) {
7573 return tab_holder.children[1];
7575 getTopTabContentHolder: function(tab_holder) {
7576 return tab_holder.children[1];
7578 getTabContent: function() {
7579 return this.getIndentedPanel();
7581 getTopTabContent: function() {
7582 return this.getTopIndentedPanel();
7584 markTabActive: function(row) {
7585 this.applyStyles(row.tab,{
7589 row.container.style.display = '';
7591 markTabInactive: function(row) {
7592 this.applyStyles(row.tab,{
7596 row.container.style.display = 'none';
7598 addTab: function(holder, tab) {
7599 holder.children[0].appendChild(tab);
7601 addTopTab: function(holder, tab) {
7602 holder.children[0].appendChild(tab);
7604 getBlockLink: function() {
7605 var link = document.createElement('a');
7606 link.style.display = 'block';
7609 getBlockLinkHolder: function() {
7610 var el = document.createElement('div');
7613 getLinksHolder: function() {
7614 var el = document.createElement('div');
7617 createMediaLink: function(holder,link,media) {
7618 holder.appendChild(link);
7619 media.style.width='100%';
7620 holder.appendChild(media);
7622 createImageLink: function(holder,link,image) {
7623 holder.appendChild(link);
7624 link.appendChild(image);
7626 getFirstTab: function(holder){
7627 return holder.firstChild.firstChild;
7631 JSONEditor.defaults.themes.bootstrap2 = JSONEditor.AbstractTheme.extend({
7632 getRangeInput: function(min, max, step) {
7633 // TODO: use bootstrap slider
7634 return this._super(min, max, step);
7636 getGridContainer: function() {
7637 var el = document.createElement('div');
7638 el.className = 'container-fluid';
7639 el.style.padding = '4px';
7642 getGridRow: function() {
7643 var el = document.createElement('div');
7644 el.className = 'row-fluid';
7647 getFormInputLabel: function(text) {
7648 var el = this._super(text);
7649 el.style.display = 'inline-block';
7650 el.style.fontWeight = 'bold';
7653 setGridColumnSize: function(el,size) {
7654 el.className = 'span'+size;
7656 getSelectInput: function(options) {
7657 var input = this._super(options);
7658 input.style.width = 'auto';
7659 input.style.maxWidth = '98%';
7662 getFormInputField: function(type) {
7663 var el = this._super(type);
7664 el.style.width = '98%';
7667 afterInputReady: function(input) {
7668 if(input.controlgroup) return;
7669 input.controlgroup = this.closest(input,'.control-group');
7670 input.controls = this.closest(input,'.controls');
7671 if(this.closest(input,'.compact')) {
7672 input.controlgroup.className = input.controlgroup.className.replace(/control-group/g,'').replace(/[ ]{2,}/g,' ');
7673 input.controls.className = input.controlgroup.className.replace(/controls/g,'').replace(/[ ]{2,}/g,' ');
7674 input.style.marginBottom = 0;
7676 if (this.queuedInputErrorText) {
7677 var text = this.queuedInputErrorText;
7678 delete this.queuedInputErrorText;
7679 this.addInputError(input,text);
7682 // TODO: use bootstrap slider
7684 getIndentedPanel: function() {
7685 var el = document.createElement('div');
7686 el.className = 'well well-small';
7687 el.style.padding = '4px';
7690 getInfoButton: function(text) {
7691 var icon = document.createElement('span');
7692 icon.className = "icon-info-sign pull-right";
7693 icon.style.padding = ".25rem";
7694 icon.style.position = "relative";
7695 icon.style.display = "inline-block";
7697 var tooltip = document.createElement('span');
7698 tooltip.style["font-family"] = "sans-serif";
7699 tooltip.style.visibility = "hidden";
7700 tooltip.style["background-color"] = "rgba(50, 50, 50, .75)";
7701 tooltip.style.margin = "0 .25rem";
7702 tooltip.style.color = "#FAFAFA";
7703 tooltip.style.padding = ".5rem 1rem";
7704 tooltip.style["border-radius"] = ".25rem";
7705 tooltip.style.width = "25rem";
7706 tooltip.style.transform = "translateX(-27rem) translateY(-.5rem)";
7707 tooltip.style.position = "absolute";
7708 tooltip.innerText = text;
7709 icon.onmouseover = function() {
7710 tooltip.style.visibility = "visible";
7712 icon.onmouseleave = function() {
7713 tooltip.style.visibility = "hidden";
7716 icon.appendChild(tooltip);
7720 getFormInputDescription: function(text) {
7721 var el = document.createElement('p');
7722 el.className = 'help-inline';
7723 el.textContent = text;
7726 getFormControl: function(label, input, description, infoText) {
7727 var ret = document.createElement('div');
7728 ret.className = 'control-group';
7730 var controls = document.createElement('div');
7731 controls.className = 'controls';
7733 if(label && input.getAttribute('type') === 'checkbox') {
7734 ret.appendChild(controls);
7735 label.className += ' checkbox';
7736 label.appendChild(input);
7737 controls.appendChild(label);
7738 if(infoText) controls.appendChild(infoText);
7739 controls.style.height = '30px';
7743 label.className += ' control-label';
7744 ret.appendChild(label);
7746 if(infoText) controls.appendChild(infoText);
7747 controls.appendChild(input);
7748 ret.appendChild(controls);
7751 if(description) controls.appendChild(description);
7755 getHeaderButtonHolder: function() {
7756 var el = this.getButtonHolder();
7757 el.style.marginLeft = '10px';
7760 getButtonHolder: function() {
7761 var el = document.createElement('div');
7762 el.className = 'btn-group';
7765 getButton: function(text, icon, title) {
7766 var el = this._super(text, icon, title);
7767 el.className += ' btn btn-default';
7768 el.style.backgroundColor = '#f2bfab';
7769 el.style.border = '1px solid #ddd';
7772 getTable: function() {
7773 var el = document.createElement('table');
7774 el.className = 'table table-bordered';
7775 el.style.width = 'auto';
7776 el.style.maxWidth = 'none';
7779 addInputError: function(input,text) {
7780 if(!input.controlgroup) {
7781 this.queuedInputErrorText = text;
7784 if(!input.controlgroup || !input.controls) return;
7785 input.controlgroup.className += ' error';
7787 input.errmsg = document.createElement('p');
7788 input.errmsg.className = 'help-block errormsg';
7789 input.controls.appendChild(input.errmsg);
7792 input.errmsg.style.display = '';
7795 input.errmsg.textContent = text;
7797 removeInputError: function(input) {
7798 if(!input.controlgroup) {
7799 delete this.queuedInputErrorText;
7801 if(!input.errmsg) return;
7802 input.errmsg.style.display = 'none';
7803 input.controlgroup.className = input.controlgroup.className.replace(/\s?error/g,'');
7805 getTabHolder: function(propertyName) {
7806 var pName = (typeof propertyName === 'undefined')? "" : propertyName;
7807 var el = document.createElement('div');
7808 el.className = 'tabbable tabs-left';
7809 el.innerHTML = "<ul class='nav nav-tabs' id='" + pName + "'></ul><div class='tab-content well well-small' id='" + pName + "'></div>";
7812 getTopTabHolder: function(propertyName) {
7813 var pName = (typeof propertyName === 'undefined')? "" : propertyName;
7814 var el = document.createElement('div');
7815 el.className = 'tabbable tabs-over';
7816 el.innerHTML = "<ul class='nav nav-tabs' id='" + pName + "'></ul><div class='tab-content well well-small' id='" + pName + "'></div>";
7819 getTab: function(text,tabId) {
7820 var el = document.createElement('li');
7821 el.className = 'nav-item';
7822 var a = document.createElement('a');
7823 a.setAttribute('href','#' + tabId);
7824 a.appendChild(text);
7828 getTopTab: function(text,tabId) {
7829 var el = document.createElement('li');
7830 el.className = 'nav-item';
7831 var a = document.createElement('a');
7832 a.setAttribute('href','#' + tabId);
7833 a.appendChild(text);
7837 getTabContentHolder: function(tab_holder) {
7838 return tab_holder.children[1];
7840 getTopTabContentHolder: function(tab_holder) {
7841 return tab_holder.children[1];
7843 getTabContent: function() {
7844 var el = document.createElement('div');
7845 el.className = 'tab-pane';
7848 getTopTabContent: function() {
7849 var el = document.createElement('div');
7850 el.className = 'tab-pane';
7853 markTabActive: function(row) {
7854 row.tab.className = row.tab.className.replace(/\s?active/g,'');
7855 row.tab.className += ' active';
7856 row.container.className = row.container.className.replace(/\s?active/g,'');
7857 row.container.className += ' active';
7859 markTabInactive: function(row) {
7860 row.tab.className = row.tab.className.replace(/\s?active/g,'');
7861 row.container.className = row.container.className.replace(/\s?active/g,'');
7863 addTab: function(holder, tab) {
7864 holder.children[0].appendChild(tab);
7866 addTopTab: function(holder, tab) {
7867 holder.children[0].appendChild(tab);
7869 getProgressBar: function() {
7870 var container = document.createElement('div');
7871 container.className = 'progress';
7873 var bar = document.createElement('div');
7874 bar.className = 'bar';
7875 bar.style.width = '0%';
7876 container.appendChild(bar);
7880 updateProgressBar: function(progressBar, progress) {
7881 if (!progressBar) return;
7883 progressBar.firstChild.style.width = progress + "%";
7885 updateProgressBarUnknown: function(progressBar) {
7886 if (!progressBar) return;
7888 progressBar.className = 'progress progress-striped active';
7889 progressBar.firstChild.style.width = '100%';
7893 JSONEditor.defaults.themes.bootstrap3 = JSONEditor.AbstractTheme.extend({
7894 getSelectInput: function(options) {
7895 var el = this._super(options);
7896 el.className += 'form-control';
7897 //el.style.width = 'auto';
7900 getGridContainer: function() {
7901 var el = document.createElement('div');
7902 el.className = 'container-fluid';
7903 el.style.padding = '4px';
7906 getGridRow: function() {
7907 var el = document.createElement('div');
7908 el.className = 'row-fluid';
7909 el.style.padding = '4px';
7912 setGridColumnSize: function(el,size) {
7913 el.className = 'col-md-'+size;
7915 afterInputReady: function(input) {
7916 if(input.controlgroup) return;
7917 input.controlgroup = this.closest(input,'.form-group');
7918 if(this.closest(input,'.compact')) {
7919 input.controlgroup.style.marginBottom = 0;
7921 if (this.queuedInputErrorText) {
7922 var text = this.queuedInputErrorText;
7923 delete this.queuedInputErrorText;
7924 this.addInputError(input,text);
7927 // TODO: use bootstrap slider
7929 getRangeInput: function(min, max, step) {
7930 // TODO: use better slider
7931 return this._super(min, max, step);
7933 getFormInputField: function(type) {
7934 var el = this._super(type);
7935 if(type !== 'checkbox') {
7936 el.className += 'form-control';
7940 getFormControl: function(label, input, description, infoText) {
7941 var group = document.createElement('div');
7943 if(label && input.type === 'checkbox') {
7944 group.className += ' checkbox';
7945 label.appendChild(input);
7946 label.style.fontSize = '12px';
7947 group.style.marginTop = '0';
7948 if(infoText) group.appendChild(infoText);
7949 group.appendChild(label);
7950 input.style.position = 'relative';
7951 input.style.cssFloat = 'left';
7954 group.className += ' form-group';
7956 label.className += ' control-label';
7957 group.appendChild(label);
7960 if(infoText) group.appendChild(infoText);
7961 group.appendChild(input);
7964 if(description) group.appendChild(description);
7968 getIndentedPanel: function() {
7969 var el = document.createElement('div');
7970 el.className = 'well well-sm';
7971 el.style.padding = '4px';
7974 getInfoButton: function(text) {
7975 var icon = document.createElement('span');
7976 icon.className = "glyphicon glyphicon-info-sign pull-right";
7977 icon.style.padding = ".25rem";
7978 icon.style.position = "relative";
7979 icon.style.display = "inline-block";
7981 var tooltip = document.createElement('span');
7982 tooltip.style["font-family"] = "sans-serif";
7983 tooltip.style.visibility = "hidden";
7984 tooltip.style["background-color"] = "rgba(50, 50, 50, .75)";
7985 tooltip.style.margin = "0 .25rem";
7986 tooltip.style.color = "#FAFAFA";
7987 tooltip.style.padding = ".5rem 1rem";
7988 tooltip.style["border-radius"] = ".25rem";
7989 tooltip.style.width = "25rem";
7990 tooltip.style.transform = "translateX(-27rem) translateY(-.5rem)";
7991 tooltip.style.position = "absolute";
7992 tooltip.innerText = text;
7993 icon.onmouseover = function() {
7994 tooltip.style.visibility = "visible";
7996 icon.onmouseleave = function() {
7997 tooltip.style.visibility = "hidden";
8000 icon.appendChild(tooltip);
8004 getFormInputDescription: function(text) {
8005 var el = document.createElement('p');
8006 el.className = 'help-block';
8007 el.innerHTML = text;
8010 getHeaderButtonHolder: function() {
8011 var el = this.getButtonHolder();
8012 el.style.marginLeft = '5px';
8015 getButtonHolder: function() {
8016 var el = document.createElement('div');
8017 el.className = 'btn-group';
8020 getButton: function(text, icon, title) {
8021 var el = this._super(text, icon, title);
8022 el.className += ' btn btn-default';
8023 el.style.backgroundColor = '#f2bfab';
8024 el.style.border = '1px solid #ddd';
8027 getTable: function() {
8028 var el = document.createElement('table');
8029 el.className = 'table table-bordered';
8030 el.style.width = 'auto';
8031 el.style.maxWidth = 'none';
8035 addInputError: function(input,text) {
8036 if(!input.controlgroup) {
8037 this.queuedInputErrorText = text;
8040 input.controlgroup.className = input.controlgroup.className.replace(/\s?has-error/g,'');
8041 input.controlgroup.className += ' has-error';
8043 input.errmsg = document.createElement('p');
8044 input.errmsg.className = 'help-block errormsg';
8045 input.controlgroup.appendChild(input.errmsg);
8048 input.errmsg.style.display = '';
8051 input.errmsg.textContent = text;
8053 removeInputError: function(input) {
8054 if(!input.controlgroup) {
8055 delete this.queuedInputErrorText;
8057 if(!input.errmsg) return;
8058 input.errmsg.style.display = 'none';
8059 input.controlgroup.className = input.controlgroup.className.replace(/\s?has-error/g,'');
8061 getTabHolder: function(propertyName) {
8062 var pName = (typeof propertyName === 'undefined')? "" : propertyName;
8063 var el = document.createElement('div');
8064 el.innerHTML = "<div class='list-group pull-left' id='" + pName + "'></div><div class='col-sm-10 pull-left' id='" + pName + "'></div>";
8067 getTopTabHolder: function(propertyName) {
8068 var pName = (typeof propertyName === 'undefined')? "" : propertyName;
8069 var el = document.createElement('div');
8070 el.innerHTML = "<ul class='nav nav-tabs' style='padding: 4px;' id='" + pName + "'></ul><div class='tab-content' style='overflow:visible;' id='" + pName + "'></div>";
8073 getTab: function(text, tabId) {
8074 var el = document.createElement('a');
8075 el.className = 'list-group-item';
8076 el.setAttribute('href','#'+tabId);
8077 el.appendChild(text);
8080 getTopTab: function(text, tabId) {
8081 var el = document.createElement('li');
8082 var a = document.createElement('a');
8083 a.setAttribute('href','#'+tabId);
8084 a.appendChild(text);
8088 markTabActive: function(row) {
8089 row.tab.className = row.tab.className.replace(/\s?active/g,'');
8090 row.tab.className += ' active';
8091 row.container.style.display = '';
8093 markTabInactive: function(row) {
8094 row.tab.className = row.tab.className.replace(/\s?active/g,'');
8095 row.container.style.display = 'none';
8097 getProgressBar: function() {
8098 var min = 0, max = 100, start = 0;
8100 var container = document.createElement('div');
8101 container.className = 'progress';
8103 var bar = document.createElement('div');
8104 bar.className = 'progress-bar';
8105 bar.setAttribute('role', 'progressbar');
8106 bar.setAttribute('aria-valuenow', start);
8107 bar.setAttribute('aria-valuemin', min);
8108 bar.setAttribute('aria-valuenax', max);
8109 bar.innerHTML = start + "%";
8110 container.appendChild(bar);
8114 updateProgressBar: function(progressBar, progress) {
8115 if (!progressBar) return;
8117 var bar = progressBar.firstChild;
8118 var percentage = progress + "%";
8119 bar.setAttribute('aria-valuenow', progress);
8120 bar.style.width = percentage;
8121 bar.innerHTML = percentage;
8123 updateProgressBarUnknown: function(progressBar) {
8124 if (!progressBar) return;
8126 var bar = progressBar.firstChild;
8127 progressBar.className = 'progress progress-striped active';
8128 bar.removeAttribute('aria-valuenow');
8129 bar.style.width = '100%';
8134 JSONEditor.defaults.themes.bootstrap4 = JSONEditor.AbstractTheme.extend({
8135 getSelectInput: function(options) {
8136 var el = this._super(options);
8137 el.className += "form-control";
8138 //el.style.width = 'auto';
8141 setGridColumnSize: function(el, size) {
8142 el.className = "col-md-" + size;
8144 afterInputReady: function(input) {
8145 if (input.controlgroup) return;
8146 input.controlgroup = this.closest(input, ".form-group");
8147 if (this.closest(input, ".compact")) {
8148 input.controlgroup.style.marginBottom = 0;
8151 // TODO: use bootstrap slider
8153 getTextareaInput: function() {
8154 var el = document.createElement("textarea");
8155 el.className = "form-control";
8158 getRangeInput: function(min, max, step) {
8159 // TODO: use better slider
8160 return this._super(min, max, step);
8162 getFormInputField: function(type) {
8163 var el = this._super(type);
8164 if (type !== "checkbox") {
8165 el.className += "form-control";
8169 getFormControl: function(label, input, description) {
8170 var group = document.createElement("div");
8172 if (label && input.type === "checkbox") {
8173 group.className += " checkbox";
8174 label.appendChild(input);
8175 label.style.fontSize = "12px";
8176 group.style.marginTop = "0";
8177 group.appendChild(label);
8178 input.style.position = "relative";
8179 input.style.cssFloat = "left";
8181 group.className += " form-group";
8183 label.className += " form-control-label";
8184 group.appendChild(label);
8186 group.appendChild(input);
8189 if (description) group.appendChild(description);
8193 getIndentedPanel: function() {
8194 var el = document.createElement("div");
8195 el.className = "card card-body bg-light";
8198 getFormInputDescription: function(text) {
8199 var el = document.createElement("p");
8200 el.className = "form-text";
8201 el.innerHTML = text;
8204 getHeaderButtonHolder: function() {
8205 var el = this.getButtonHolder();
8206 el.style.marginLeft = "10px";
8209 getButtonHolder: function() {
8210 var el = document.createElement("div");
8211 el.className = "btn-group";
8214 getButton: function(text, icon, title) {
8215 var el = this._super(text, icon, title);
8216 el.className += "btn btn-secondary";
8219 getTable: function() {
8220 var el = document.createElement("table");
8221 el.className = "table-bordered table-sm";
8222 el.style.width = "auto";
8223 el.style.maxWidth = "none";
8227 addInputError: function(input, text) {
8228 if (!input.controlgroup) return;
8229 input.controlgroup.className += " has-error";
8230 if (!input.errmsg) {
8231 input.errmsg = document.createElement("p");
8232 input.errmsg.className = "form-text errormsg";
8233 input.controlgroup.appendChild(input.errmsg);
8235 input.errmsg.style.display = "";
8238 input.errmsg.textContent = text;
8240 removeInputError: function(input) {
8241 if (!input.errmsg) return;
8242 input.errmsg.style.display = "none";
8243 input.controlgroup.className = input.controlgroup.className.replace(
8248 getTabHolder: function(propertyName) {
8249 var el = document.createElement("div");
8250 var pName = (typeof propertyName === 'undefined')? "" : propertyName;
8252 "<ul class='nav flex-column nav-pills col-md-2' style='padding: 0px;' id='" + pName + "'></ul><div class='tab-content col-md-10' style='padding:5px;' id='" + pName + "'></div>";
8253 el.className = "row";
8256 getTopTabHolder: function(propertyName) {
8257 var pName = (typeof propertyName === 'undefined')? "" : propertyName;
8258 var el = document.createElement('div');
8259 el.innerHTML = "<ul class='nav nav-tabs' id='" + pName + "'></ul><div class='card-body' id='" + pName + "'></div>";
8262 getTab: function(text,tabId) {
8263 var liel = document.createElement('li');
8264 liel.className = 'nav-item';
8265 var ael = document.createElement("a");
8266 ael.className = "nav-link";
8267 ael.setAttribute("style",'padding:10px;');
8268 ael.setAttribute("href", "#" + tabId);
8269 ael.appendChild(text);
8270 liel.appendChild(ael);
8273 getTopTab: function(text, tabId) {
8274 var el = document.createElement('li');
8275 el.className = 'nav-item';
8276 var a = document.createElement('a');
8277 a.className = 'nav-link';
8278 a.setAttribute('href','#'+tabId);
8279 a.appendChild(text);
8283 markTabActive: function(row) {
8284 var el = row.tab.firstChild;
8285 el.className = el.className.replace(/\s?active/g,'');
8286 el.className += " active";
8287 row.container.style.display = '';
8289 markTabInactive: function(row) {
8290 var el = row.tab.firstChild;
8291 el.className = el.className.replace(/\s?active/g,'');
8292 row.container.style.display = 'none';
8294 getProgressBar: function() {
8299 var container = document.createElement("div");
8300 container.className = "progress";
8302 var bar = document.createElement("div");
8303 bar.className = "progress-bar";
8304 bar.setAttribute("role", "progressbar");
8305 bar.setAttribute("aria-valuenow", start);
8306 bar.setAttribute("aria-valuemin", min);
8307 bar.setAttribute("aria-valuenax", max);
8308 bar.innerHTML = start + "%";
8309 container.appendChild(bar);
8313 updateProgressBar: function(progressBar, progress) {
8314 if (!progressBar) return;
8316 var bar = progressBar.firstChild;
8317 var percentage = progress + "%";
8318 bar.setAttribute("aria-valuenow", progress);
8319 bar.style.width = percentage;
8320 bar.innerHTML = percentage;
8322 updateProgressBarUnknown: function(progressBar) {
8323 if (!progressBar) return;
8325 var bar = progressBar.firstChild;
8326 progressBar.className = "progress progress-striped active";
8327 bar.removeAttribute("aria-valuenow");
8328 bar.style.width = "100%";
8333 // Base Foundation theme
8334 JSONEditor.defaults.themes.foundation = JSONEditor.AbstractTheme.extend({
8335 getChildEditorHolder: function() {
8336 var el = document.createElement('div');
8337 el.style.marginBottom = '15px';
8340 getSelectInput: function(options) {
8341 var el = this._super(options);
8342 el.style.minWidth = 'none';
8343 el.style.padding = '5px';
8344 el.style.marginTop = '3px';
8347 getSwitcher: function(options) {
8348 var el = this._super(options);
8349 el.style.paddingRight = '8px';
8352 afterInputReady: function(input) {
8353 if(input.group) return;
8354 if(this.closest(input,'.compact')) {
8355 input.style.marginBottom = 0;
8357 input.group = this.closest(input,'.form-control');
8358 if (this.queuedInputErrorText) {
8359 var text = this.queuedInputErrorText;
8360 delete this.queuedInputErrorText;
8361 this.addInputError(input,text);
8364 getFormInputLabel: function(text) {
8365 var el = this._super(text);
8366 el.style.display = 'inline-block';
8369 getFormInputField: function(type) {
8370 var el = this._super(type);
8371 el.style.width = '100%';
8372 el.style.marginBottom = type==='checkbox'? '0' : '12px';
8375 getFormInputDescription: function(text) {
8376 var el = document.createElement('p');
8377 el.textContent = text;
8378 el.style.marginTop = '-10px';
8379 el.style.fontStyle = 'italic';
8382 getIndentedPanel: function() {
8383 var el = document.createElement('div');
8384 el.className = 'panel';
8385 el.style.paddingBottom = 0;
8388 getHeaderButtonHolder: function() {
8389 var el = this.getButtonHolder();
8390 el.style.display = 'inline-block';
8391 el.style.marginLeft = '10px';
8392 el.style.verticalAlign = 'middle';
8395 getButtonHolder: function() {
8396 var el = document.createElement('div');
8397 el.className = 'button-group';
8400 getButton: function(text, icon, title) {
8401 var el = this._super(text, icon, title);
8402 el.className += ' small button';
8405 addInputError: function(input,text) {
8407 this.queuedInputErrorText = text;
8410 input.group.className += ' error';
8413 input.insertAdjacentHTML('afterend','<small class="error"></small>');
8414 input.errmsg = input.parentNode.getElementsByClassName('error')[0];
8417 input.errmsg.style.display = '';
8420 input.errmsg.textContent = text;
8422 removeInputError: function(input) {
8424 delete this.queuedInputErrorText;
8426 if(!input.errmsg) return;
8427 input.group.className = input.group.className.replace(/ error/g,'');
8428 input.errmsg.style.display = 'none';
8430 getProgressBar: function() {
8431 var progressBar = document.createElement('div');
8432 progressBar.className = 'progress';
8434 var meter = document.createElement('span');
8435 meter.className = 'meter';
8436 meter.style.width = '0%';
8437 progressBar.appendChild(meter);
8440 updateProgressBar: function(progressBar, progress) {
8441 if (!progressBar) return;
8442 progressBar.firstChild.style.width = progress + '%';
8444 updateProgressBarUnknown: function(progressBar) {
8445 if (!progressBar) return;
8446 progressBar.firstChild.style.width = '100%';
8450 // Foundation 3 Specific Theme
8451 JSONEditor.defaults.themes.foundation3 = JSONEditor.defaults.themes.foundation.extend({
8452 getHeaderButtonHolder: function() {
8453 var el = this._super();
8454 el.style.fontSize = '.6em';
8457 getFormInputLabel: function(text) {
8458 var el = this._super(text);
8459 el.style.fontWeight = 'bold';
8462 getTabHolder: function(propertyName) {
8463 var pName = (typeof propertyName === 'undefined')? "" : propertyName;
8464 var el = document.createElement('div');
8465 el.className = 'row';
8466 el.innerHTML = '<dl class="tabs vertical two columns" id="' + pName + '"></dl><div class="tabs-content ten columns" id="' + pName + '"></div>';
8469 getTopTabHolder: function(propertyName) {
8470 var pName = (typeof propertyName === 'undefined')? "" : propertyName;
8471 var el = document.createElement('div');
8472 el.className = 'row';
8473 el.innerHTML = '<dl class="tabs horizontal" style="padding-left: 10px; margin-left: 10px;" id="' + pName + '"></dl><div class="tabs-content twelve columns" style="padding: 10px; margin-left: 10px;" id="' + pName + '"></div>';
8476 setGridColumnSize: function(el,size) {
8477 var sizes = ['zero','one','two','three','four','five','six','seven','eight','nine','ten','eleven','twelve'];
8478 el.className = 'columns '+sizes[size];
8480 getTab: function(text, tabId) {
8481 var el = document.createElement('dd');
8482 var a = document.createElement('a');
8483 a.setAttribute('href','#'+tabId);
8484 a.appendChild(text);
8488 getTopTab: function(text, tabId) {
8489 var el = document.createElement('dd');
8490 var a = document.createElement('a');
8491 a.setAttribute('href','#'+tabId);
8492 a.appendChild(text);
8496 getTabContentHolder: function(tab_holder) {
8497 return tab_holder.children[1];
8499 getTopTabContentHolder: function(tab_holder) {
8500 return tab_holder.children[1];
8502 getTabContent: function() {
8503 var el = document.createElement('div');
8504 el.className = 'content active';
8505 el.style.paddingLeft = '5px';
8508 getTopTabContent: function() {
8509 var el = document.createElement('div');
8510 el.className = 'content active';
8511 el.style.paddingLeft = '5px';
8514 markTabActive: function(row) {
8515 row.tab.className = row.tab.className.replace(/\s?active/g,'');
8516 row.tab.className += ' active';
8517 row.container.style.display = '';
8519 markTabInactive: function(row) {
8520 row.tab.className = row.tab.className.replace(/\s?active/g,'');
8521 row.container.style.display = 'none';
8523 addTab: function(holder, tab) {
8524 holder.children[0].appendChild(tab);
8526 addTopTab: function(holder, tab) {
8527 holder.children[0].appendChild(tab);
8531 // Foundation 4 Specific Theme
8532 JSONEditor.defaults.themes.foundation4 = JSONEditor.defaults.themes.foundation.extend({
8533 getHeaderButtonHolder: function() {
8534 var el = this._super();
8535 el.style.fontSize = '.6em';
8538 setGridColumnSize: function(el,size) {
8539 el.className = 'columns large-'+size;
8541 getFormInputDescription: function(text) {
8542 var el = this._super(text);
8543 el.style.fontSize = '.8rem';
8546 getFormInputLabel: function(text) {
8547 var el = this._super(text);
8548 el.style.fontWeight = 'bold';
8553 // Foundation 5 Specific Theme
8554 JSONEditor.defaults.themes.foundation5 = JSONEditor.defaults.themes.foundation.extend({
8555 getFormInputDescription: function(text) {
8556 var el = this._super(text);
8557 el.style.fontSize = '.8rem';
8560 setGridColumnSize: function(el,size) {
8561 el.className = 'columns medium-'+size;
8563 getButton: function(text, icon, title) {
8564 var el = this._super(text,icon,title);
8565 el.className = el.className.replace(/\s*small/g,'') + ' tiny';
8568 getTabHolder: function(propertyName) {
8569 var pName = (typeof propertyName === 'undefined')? "" : propertyName;
8570 var el = document.createElement('div');
8571 el.innerHTML = '<dl class="tabs vertical" id="' + pName + '"></dl><div class="tabs-content vertical" id="' + pName + '"></div>';
8574 getTopTabHolder: function(propertyName) {
8575 var pName = (typeof propertyName === 'undefined')? "" : propertyName;
8576 var el = document.createElement('div');
8577 el.className = 'row';
8578 el.innerHTML = '<dl class="tabs horizontal" style="padding-left: 10px;" id="' + pName + '"></dl><div class="tabs-content horizontal" style="padding: 10px;" id="' + pName + '"></div>';
8581 getTab: function(text, tabId) {
8582 var el = document.createElement('dd');
8583 var a = document.createElement('a');
8584 a.setAttribute('href','#'+tabId);
8585 a.appendChild(text);
8589 getTopTab: function(text, tabId) {
8590 var el = document.createElement('dd');
8591 var a = document.createElement('a');
8592 a.setAttribute('href','#'+tabId);
8593 a.appendChild(text);
8597 getTabContentHolder: function(tab_holder) {
8598 return tab_holder.children[1];
8600 getTopTabContentHolder: function(tab_holder) {
8601 return tab_holder.children[1];
8603 getTabContent: function() {
8604 var el = document.createElement('div');
8605 el.className = 'tab-content active';
8606 el.style.paddingLeft = '5px';
8609 getTopTabContent: function() {
8610 var el = document.createElement('div');
8611 el.className = 'tab-content active';
8612 el.style.paddingLeft = '5px';
8615 markTabActive: function(row) {
8616 row.tab.className = row.tab.className.replace(/\s?active/g,'');
8617 row.tab.className += ' active';
8618 row.container.style.display = '';
8620 markTabInactive: function(row) {
8621 row.tab.className = row.tab.className.replace(/\s?active/g,'');
8622 row.container.style.display = 'none';
8624 addTab: function(holder, tab) {
8625 holder.children[0].appendChild(tab);
8627 addTopTab: function(holder, tab) {
8628 holder.children[0].appendChild(tab);
8633 JSONEditor.defaults.themes.foundation6 = JSONEditor.defaults.themes.foundation5.extend({
8634 getIndentedPanel: function() {
8635 var el = document.createElement('div');
8636 el.className = 'callout secondary';
8637 el.className.style = 'padding-left: 10px; margin-left: 10px;';
8640 getButtonHolder: function() {
8641 var el = document.createElement('div');
8642 el.className = 'button-group tiny';
8643 el.style.marginBottom = 0;
8646 getFormInputLabel: function(text) {
8647 var el = this._super(text);
8648 el.style.display = 'block';
8651 getFormControl: function(label, input, description, infoText) {
8652 var el = document.createElement('div');
8653 el.className = 'form-control';
8654 if(label) el.appendChild(label);
8655 if(input.type === 'checkbox') {
8656 label.insertBefore(input,label.firstChild);
8659 if(infoText) label.appendChild(infoText);
8660 label.appendChild(input);
8662 if(infoText) el.appendChild(infoText);
8663 el.appendChild(input);
8666 if(description) label.appendChild(description);
8669 addInputError: function(input,text) {
8670 if(!input.group) return;
8671 input.group.className += ' error';
8674 var errorEl = document.createElement('span');
8675 errorEl.className = 'form-error is-visible';
8676 input.group.getElementsByTagName('label')[0].appendChild(errorEl);
8678 input.className = input.className + ' is-invalid-input';
8680 input.errmsg = errorEl;
8683 input.errmsg.style.display = '';
8684 input.className = '';
8687 input.errmsg.textContent = text;
8689 removeInputError: function(input) {
8690 if(!input.errmsg) return;
8691 input.className = input.className.replace(/ is-invalid-input/g,'');
8692 if(input.errmsg.parentNode) {
8693 input.errmsg.parentNode.removeChild(input.errmsg);
8696 getTabHolder: function(propertyName) {
8697 var pName = (typeof propertyName === 'undefined')? "" : propertyName;
8698 var el = document.createElement('div');
8699 el.className = 'grid-x';
8700 el.innerHTML = '<div class="medium-2 cell" style="float: left;"><ul class="vertical tabs" data-tabs id="' + pName + '"></ul></div><div class="medium-10 cell" style="float: left;"><div class="tabs-content" data-tabs-content="'+pName+'"></div></div>';
8703 getTopTabHolder: function(propertyName) {
8704 var pName = (typeof propertyName === 'undefined')? "" : propertyName;
8705 var el = document.createElement('div');
8706 el.className = 'grid-y';
8707 el.innerHTML = '<div className="cell"><ul class="tabs" data-tabs id="' + pName + '"></ul><div class="tabs-content" data-tabs-content="' + pName + '"></div></div>';
8712 insertBasicTopTab: function(tab, newTabs_holder ) {
8713 newTabs_holder.firstChild.firstChild.insertBefore(tab,newTabs_holder.firstChild.firstChild.firstChild);
8715 getTab: function(text, tabId) {
8716 var el = document.createElement('li');
8717 el.className = 'tabs-title';
8718 var a = document.createElement('a');
8719 a.setAttribute('href','#'+tabId);
8720 a.appendChild(text);
8724 getTopTab: function(text, tabId) {
8725 var el = document.createElement('li');
8726 el.className = 'tabs-title';
8727 var a = document.createElement('a');
8728 a.setAttribute('href','#' + tabId);
8729 a.appendChild(text);
8733 getTabContentHolder: function(tab_holder) {
8734 return tab_holder.children[1].firstChild;
8736 getTopTabContentHolder: function(tab_holder) {
8737 return tab_holder.firstChild.children[1];
8739 getTabContent: function() {
8740 var el = document.createElement('div');
8741 el.className = 'tabs-panel';
8742 el.style.paddingLeft = '5px';
8745 getTopTabContent: function() {
8746 var el = document.createElement('div');
8747 el.className = 'tabs-panel';
8748 el.style.paddingLeft = '5px';
8751 markTabActive: function(row) {
8752 row.tab.className = row.tab.className.replace(/\s?is-active/g,'');
8753 row.tab.className += ' is-active';
8754 row.tab.firstChild.setAttribute('aria-selected', 'true');
8756 row.container.className = row.container.className.replace(/\s?is-active/g,'');
8757 row.container.className += ' is-active';
8758 row.container.setAttribute('aria-selected', 'true');
8760 markTabInactive: function(row) {
8761 row.tab.className = row.tab.className.replace(/\s?is-active/g,'');
8762 row.tab.firstChild.removeAttribute('aria-selected');
8764 row.container.className = row.container.className.replace(/\s?is-active/g,'');
8765 row.container.removeAttribute('aria-selected');
8767 addTab: function(holder, tab) {
8768 holder.children[0].firstChild.appendChild(tab);
8770 addTopTab: function(holder, tab) {
8771 holder.firstChild.children[0].appendChild(tab);
8773 getFirstTab: function(holder){
8774 return holder.firstChild.firstChild.firstChild;
8778 JSONEditor.defaults.themes.html = JSONEditor.AbstractTheme.extend({
8779 getFormInputLabel: function(text) {
8780 var el = this._super(text);
8781 el.style.display = 'block';
8782 el.style.marginBottom = '3px';
8783 el.style.fontWeight = 'bold';
8786 getFormInputDescription: function(text) {
8787 var el = this._super(text);
8788 el.style.fontSize = '.8em';
8789 el.style.margin = 0;
8790 el.style.display = 'inline-block';
8791 el.style.fontStyle = 'italic';
8794 getIndentedPanel: function() {
8795 var el = this._super();
8796 el.style.border = '1px solid #ddd';
8797 el.style.padding = '5px';
8798 el.style.margin = '10px';
8799 el.style.borderRadius = '3px';
8802 getTopIndentedPanel: function() {
8803 return this.getIndentedPanel();
8805 getChildEditorHolder: function() {
8806 var el = this._super();
8807 el.style.marginBottom = '8px';
8810 getHeaderButtonHolder: function() {
8811 var el = this.getButtonHolder();
8812 el.style.display = 'inline-block';
8813 el.style.marginLeft = '10px';
8814 el.style.fontSize = '.8em';
8815 el.style.verticalAlign = 'middle';
8818 getTable: function() {
8819 var el = this._super();
8820 el.style.borderBottom = '1px solid #ccc';
8821 el.style.marginBottom = '5px';
8824 addInputError: function(input, text) {
8825 input.style.borderColor = 'red';
8828 var group = this.closest(input,'.form-control');
8829 input.errmsg = document.createElement('div');
8830 input.errmsg.setAttribute('class','errmsg');
8831 input.errmsg.style = input.errmsg.style || {};
8832 input.errmsg.style.color = 'red';
8833 group.appendChild(input.errmsg);
8836 input.errmsg.style.display = 'block';
8839 input.errmsg.innerHTML = '';
8840 input.errmsg.appendChild(document.createTextNode(text));
8842 removeInputError: function(input) {
8843 input.style.borderColor = '';
8844 if(input.errmsg) input.errmsg.style.display = 'none';
8846 getProgressBar: function() {
8847 var max = 100, start = 0;
8849 var progressBar = document.createElement('progress');
8850 progressBar.setAttribute('max', max);
8851 progressBar.setAttribute('value', start);
8854 updateProgressBar: function(progressBar, progress) {
8855 if (!progressBar) return;
8856 progressBar.setAttribute('value', progress);
8858 updateProgressBarUnknown: function(progressBar) {
8859 if (!progressBar) return;
8860 progressBar.removeAttribute('value');
8864 JSONEditor.defaults.themes.jqueryui = JSONEditor.AbstractTheme.extend({
8865 getTable: function() {
8866 var el = this._super();
8867 el.setAttribute('cellpadding',5);
8868 el.setAttribute('cellspacing',0);
8871 getTableHeaderCell: function(text) {
8872 var el = this._super(text);
8873 el.className = 'ui-state-active';
8874 el.style.fontWeight = 'bold';
8877 getTableCell: function() {
8878 var el = this._super();
8879 el.className = 'ui-widget-content';
8882 getHeaderButtonHolder: function() {
8883 var el = this.getButtonHolder();
8884 el.style.marginLeft = '10px';
8885 el.style.fontSize = '.6em';
8886 el.style.display = 'inline-block';
8889 getFormInputDescription: function(text) {
8890 var el = this.getDescription(text);
8891 el.style.marginLeft = '10px';
8892 el.style.display = 'inline-block';
8895 getFormControl: function(label, input, description, infoText) {
8896 var el = this._super(label,input,description, infoText);
8897 if(input.type === 'checkbox') {
8898 el.style.lineHeight = '25px';
8900 el.style.padding = '3px 0';
8903 el.style.padding = '4px';
8907 getDescription: function(text) {
8908 var el = document.createElement('span');
8909 el.style.fontSize = '.8em';
8910 el.style.fontStyle = 'italic';
8911 el.textContent = text;
8914 getButtonHolder: function() {
8915 var el = document.createElement('div');
8916 el.className = 'ui-buttonset';
8917 el.style.fontSize = '.7em';
8920 getFormInputLabel: function(text) {
8921 var el = document.createElement('label');
8922 el.style.fontWeight = 'bold';
8923 el.style.display = 'block';
8924 el.textContent = text;
8927 getButton: function(text, icon, title) {
8928 var button = document.createElement("button");
8929 button.className = 'ui-button ui-widget ui-state-default ui-corner-all';
8933 button.className += ' ui-button-icon-only';
8934 icon.className += ' ui-button-icon-primary ui-icon-primary';
8935 button.appendChild(icon);
8939 button.className += ' ui-button-text-icon-primary';
8940 icon.className += ' ui-button-icon-primary ui-icon-primary';
8941 button.appendChild(icon);
8945 button.className += ' ui-button-text-only';
8948 var el = document.createElement('span');
8949 el.className = 'ui-button-text';
8950 el.textContent = text||title||".";
8951 button.appendChild(el);
8953 button.setAttribute('title',title);
8957 setButtonText: function(button,text, icon, title) {
8958 button.innerHTML = '';
8959 button.className = 'ui-button ui-widget ui-state-default ui-corner-all';
8963 button.className += ' ui-button-icon-only';
8964 icon.className += ' ui-button-icon-primary ui-icon-primary';
8965 button.appendChild(icon);
8969 button.className += ' ui-button-text-icon-primary';
8970 icon.className += ' ui-button-icon-primary ui-icon-primary';
8971 button.appendChild(icon);
8975 button.className += ' ui-button-text-only';
8978 var el = document.createElement('span');
8979 el.className = 'ui-button-text';
8980 el.textContent = text||title||".";
8981 button.appendChild(el);
8983 button.setAttribute('title',title);
8985 getIndentedPanel: function() {
8986 var el = document.createElement('div');
8987 el.className = 'ui-widget-content ui-corner-all';
8988 el.style.padding = '1em 1.4em';
8989 el.style.marginBottom = '20px';
8992 afterInputReady: function(input) {
8993 if(input.controls) return;
8994 input.controls = this.closest(input,'.form-control');
8995 if (this.queuedInputErrorText) {
8996 var text = this.queuedInputErrorText;
8997 delete this.queuedInputErrorText;
8998 this.addInputError(input,text);
9001 addInputError: function(input,text) {
9002 if(!input.controls) {
9003 this.queuedInputErrorText = text;
9007 input.errmsg = document.createElement('div');
9008 input.errmsg.className = 'ui-state-error';
9009 input.controls.appendChild(input.errmsg);
9012 input.errmsg.style.display = '';
9015 input.errmsg.textContent = text;
9017 removeInputError: function(input) {
9018 if(!input.controls) {
9019 delete this.queuedInputErrorText;
9021 if(!input.errmsg) return;
9022 input.errmsg.style.display = 'none';
9024 markTabActive: function(row) {
9025 row.tab.className = row.tab.className.replace(/\s?ui-widget-header/g,'').replace(/\s?ui-state-active/g,'')+' ui-state-active';
9026 row.container.style.display = '';
9028 markTabInactive: function(row) {
9029 row.tab.className = row.tab.className.replace(/\s?ui-state-active/g,'').replace(/\s?ui-widget-header/g,'')+' ui-widget-header';
9030 row.container.style.display = 'none';
9034 JSONEditor.defaults.themes.barebones = JSONEditor.AbstractTheme.extend({
9035 getFormInputLabel: function (text) {
9036 var el = this._super(text);
9039 getFormInputDescription: function (text) {
9040 var el = this._super(text);
9043 getIndentedPanel: function () {
9044 var el = this._super();
9047 getChildEditorHolder: function () {
9048 var el = this._super();
9051 getHeaderButtonHolder: function () {
9052 var el = this.getButtonHolder();
9055 getTable: function () {
9056 var el = this._super();
9059 addInputError: function (input, text) {
9060 if (!input.errmsg) {
9061 var group = this.closest(input, '.form-control');
9062 input.errmsg = document.createElement('div');
9063 input.errmsg.setAttribute('class', 'errmsg');
9064 group.appendChild(input.errmsg);
9067 input.errmsg.style.display = 'block';
9070 input.errmsg.innerHTML = '';
9071 input.errmsg.appendChild(document.createTextNode(text));
9073 removeInputError: function (input) {
9074 input.style.borderColor = '';
9075 if (input.errmsg) input.errmsg.style.display = 'none';
9077 getProgressBar: function () {
9078 var max = 100, start = 0;
9080 var progressBar = document.createElement('progress');
9081 progressBar.setAttribute('max', max);
9082 progressBar.setAttribute('value', start);
9085 updateProgressBar: function (progressBar, progress) {
9086 if (!progressBar) return;
9087 progressBar.setAttribute('value', progress);
9089 updateProgressBarUnknown: function (progressBar) {
9090 if (!progressBar) return;
9091 progressBar.removeAttribute('value');
9095 JSONEditor.defaults.themes.materialize = JSONEditor.AbstractTheme.extend({
9098 * Applies grid size to specified element.
9100 * @param {HTMLElement} el The DOM element to have specified size applied.
9101 * @param {int} size The grid column size.
9102 * @see http://materializecss.com/grid.html
9104 setGridColumnSize: function(el, size) {
9105 el.className = 'col s' + size;
9109 * Gets a wrapped button element for a header.
9111 * @returns {HTMLElement} The wrapped button element.
9113 getHeaderButtonHolder: function() {
9114 return this.getButtonHolder();
9118 * Gets a wrapped button element.
9120 * @returns {HTMLElement} The wrapped button element.
9122 getButtonHolder: function() {
9123 return document.createElement('span');
9127 * Gets a single button element.
9129 * @param {string} text The button text.
9130 * @param {HTMLElement} icon The icon object.
9131 * @param {string} title The button title.
9132 * @returns {HTMLElement} The button object.
9133 * @see http://materializecss.com/buttons.html
9135 getButton: function(text, icon, title) {
9139 icon.className += ' left';
9140 icon.style.marginRight = '5px';
9143 // Create and return button.
9144 var el = this._super(text, icon, title);
9145 el.className = 'waves-effect waves-light btn';
9146 el.style.fontSize = '0.75rem';
9147 el.style.height = '20px';
9148 el.style.lineHeight = '20px';
9149 el.style.marginLeft = '4px';
9150 el.style.padding = '0 0.5rem';
9156 * Gets a form control object consisiting of several sub objects.
9158 * @param {HTMLElement} label The label element.
9159 * @param {HTMLElement} input The input element.
9160 * @param {string} description The element description.
9161 * @param {string} infoText The element information text.
9162 * @returns {HTMLElement} The assembled DOM element.
9163 * @see http://materializecss.com/forms.html
9165 getFormControl: function(label, input, description, infoText) {
9170 // Checkboxes get wrapped in p elements.
9171 if (type && type === 'checkbox') {
9173 ctrl = document.createElement('p');
9174 ctrl.appendChild(input);
9176 label.setAttribute('for', input.id);
9177 ctrl.appendChild(label);
9183 // Anything else gets wrapped in divs.
9184 ctrl = this._super(label, input, description, infoText);
9186 // Not .input-field for select wrappers.
9187 if (!type || !type.startsWith('select'))
9188 ctrl.className = 'input-field';
9190 // Color needs special attention.
9191 if (type && type === 'color') {
9192 input.style.height = '3rem';
9193 input.style.width = '100%';
9194 input.style.margin = '5px 0 20px 0';
9195 input.style.padding = '3px';
9198 label.style.transform = 'translateY(-14px) scale(0.8)';
9199 label.style['-webkit-transform'] = 'translateY(-14px) scale(0.8)';
9200 label.style['-webkit-transform-origin'] = '0 0';
9201 label.style['transform-origin'] = '0 0';
9209 getDescription: function(text) {
9210 var el = document.createElement('div');
9211 el.className = 'grey-text';
9212 el.style.marginTop = '-15px';
9213 el.innerHTML = text;
9218 * Gets a header element.
9220 * @param {string|HTMLElement} text The header text or element.
9221 * @returns {HTMLElement} The header element.
9223 getHeader: function(text) {
9225 var el = document.createElement('h5');
9227 if (typeof text === 'string') {
9228 el.textContent = text;
9230 el.appendChild(text);
9237 getChildEditorHolder: function() {
9239 var el = document.createElement('div');
9240 el.marginBottom = '10px';
9245 getIndentedPanel: function() {
9246 var el = document.createElement("div");
9247 el.className = "card-panel";
9251 getTable: function() {
9253 var el = document.createElement('table');
9254 el.className = 'striped bordered';
9255 el.style.marginBottom = '10px';
9260 getTableRow: function() {
9261 return document.createElement('tr');
9264 getTableHead: function() {
9265 return document.createElement('thead');
9268 getTableBody: function() {
9269 return document.createElement('tbody');
9272 getTableHeaderCell: function(text) {
9274 var el = document.createElement('th');
9275 el.textContent = text;
9280 getTableCell: function() {
9282 var el = document.createElement('td');
9288 * Gets the tab holder element.
9290 * @returns {HTMLElement} The tab holder component.
9291 * @see https://github.com/Dogfalo/materialize/issues/2542#issuecomment-233458602
9293 getTabHolder: function() {
9296 '<div class="col s2">',
9297 ' <ul class="tabs" style="height: auto; margin-top: 0.82rem; -ms-flex-direction: column; -webkit-flex-direction: column; flex-direction: column; display: -webkit-flex; display: flex;">',
9300 '<div class="col s10">',
9304 var el = document.createElement('div');
9305 el.className = 'row card-panel';
9306 el.innerHTML = html;
9312 * Add specified tab to specified holder element.
9314 * @param {HTMLElement} holder The tab holder element.
9315 * @param {HTMLElement} tab The tab to add.
9317 addTab: function(holder, tab) {
9318 holder.children[0].children[0].appendChild(tab);
9322 * Gets a single tab element.
9324 * @param {HTMLElement} span The tab's content.
9325 * @returns {HTMLElement} The tab element.
9326 * @see https://github.com/Dogfalo/materialize/issues/2542#issuecomment-233458602
9328 getTab: function(span) {
9330 var el = document.createElement('li');
9331 el.className = 'tab';
9332 this.applyStyles(el, {
9340 el.appendChild(span);
9345 * Marks specified tab as active.
9347 * @returns {HTMLElement} The tab element.
9348 * @see https://github.com/Dogfalo/materialize/issues/2542#issuecomment-233458602
9350 markTabActive: function(tab) {
9352 this.applyStyles(tab, {
9359 color: 'rgba(238,110,115,1)',
9360 transition: 'border-color .5s ease',
9361 borderRight: '3px solid #424242'
9367 * Marks specified tab as inactive.
9369 * @returns {HTMLElement} The tab element.
9370 * @see https://github.com/Dogfalo/materialize/issues/2542#issuecomment-233458602
9372 markTabInactive: function(tab) {
9374 this.applyStyles(tab, {
9381 color: 'rgba(238,110,115,0.7)'
9387 * Returns the element that holds the tab contents.
9389 * @param {HTMLElement} tabHolder The full tab holder element.
9390 * @returns {HTMLElement} The content element inside specified tab holder.
9392 getTabContentHolder: function(tabHolder) {
9393 return tabHolder.children[1];
9397 * Creates and returns a tab content element.
9399 * @returns {HTMLElement} The new tab content element.
9401 getTabContent: function() {
9402 return document.createElement('div');
9406 * Adds an error message to the specified input element.
9408 * @param {HTMLElement} input The input element that caused the error.
9409 * @param {string} text The error message.
9411 addInputError: function(input, text) {
9413 // Get the parent element. Should most likely be a <div class="input-field" ... />.
9414 var parent = input.parentNode,
9417 if (!parent) return;
9419 // Remove any previous error.
9420 this.removeInputError(input);
9422 // Append an error message div.
9423 el = document.createElement('div');
9424 el.className = 'error-text red-text';
9425 el.textContent = text;
9426 parent.appendChild(el);
9431 * Removes any error message from the specified input element.
9433 * @param {HTMLElement} input The input element that previously caused the error.
9435 removeInputError: function(input) {
9437 // Get the parent element. Should most likely be a <div class="input-field" ... />.
9438 var parent = input.parentElement,
9441 if (!parent) return;
9443 // Remove all elements having class .error-text.
9444 els = parent.getElementsByClassName('error-text');
9445 for (var i = 0; i < els.length; i++)
9446 parent.removeChild(els[i]);
9450 addTableRowError: function(row) {
9453 removeTableRowError: function(row) {
9457 * Gets a select DOM element.
9459 * @param {object} options The option values.
9460 * @return {HTMLElement} The DOM element.
9461 * @see http://materializecss.com/forms.html#select
9463 getSelectInput: function(options) {
9465 var select = this._super(options);
9466 select.className = 'browser-default';
9472 * Gets a textarea DOM element.
9474 * @returns {HTMLElement} The DOM element.
9475 * @see http://materializecss.com/forms.html#textarea
9477 getTextareaInput: function() {
9478 var el = document.createElement('textarea');
9479 el.style.marginBottom = '5px';
9480 el.style.fontSize = '1rem';
9481 el.style.fontFamily = 'monospace';
9485 getCheckbox: function() {
9487 var el = this.getFormInputField('checkbox');
9488 el.id = this.createUuid();
9494 * Gets the modal element for displaying Edit JSON and Properties dialogs.
9496 * @returns {HTMLElement} The modal DOM element.
9497 * @see http://materializecss.com/cards.html
9499 getModal: function() {
9501 var el = document.createElement('div');
9502 el.className = 'card-panel z-depth-3';
9503 el.style.padding = '5px';
9504 el.style.position = 'absolute';
9505 el.style.zIndex = '10';
9506 el.style.display = 'none';
9512 * Creates and returns a RFC4122 version 4 compliant unique id.
9514 * @returns {string} A GUID.
9515 * @see https://stackoverflow.com/a/2117523
9517 createUuid: function() {
9519 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
9520 var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
9521 return v.toString(16);
9528 JSONEditor.AbstractIconLib = Class.extend({
9541 getIconClass: function(key) {
9542 if(this.mapping[key]) return this.icon_prefix+this.mapping[key];
9545 getIcon: function(key) {
9546 var iconclass = this.getIconClass(key);
9548 if(!iconclass) return null;
9550 var i = document.createElement('i');
9551 i.className = iconclass;
9556 JSONEditor.defaults.iconlibs.bootstrap2 = JSONEditor.AbstractIconLib.extend({
9558 collapse: 'chevron-down',
9559 expand: 'chevron-up',
9563 cancel: 'ban-circle',
9566 movedown: 'arrow-down'
9568 icon_prefix: 'glyphicon glyphicon-'
9571 JSONEditor.defaults.iconlibs.bootstrap3 = JSONEditor.AbstractIconLib.extend({
9573 collapse: 'chevron-down',
9574 expand: 'chevron-right',
9578 cancel: 'floppy-remove',
9579 save: 'floppy-saved',
9581 movedown: 'arrow-down'
9583 icon_prefix: 'glyphicon glyphicon-'
9586 JSONEditor.defaults.iconlibs.fontawesome3 = JSONEditor.AbstractIconLib.extend({
9588 collapse: 'chevron-down',
9589 expand: 'chevron-right',
9593 cancel: 'ban-circle',
9596 movedown: 'arrow-down'
9598 icon_prefix: 'icon-'
9601 JSONEditor.defaults.iconlibs.fontawesome4 = JSONEditor.AbstractIconLib.extend({
9603 collapse: 'caret-square-o-down',
9604 expand: 'caret-square-o-right',
9611 movedown: 'arrow-down',
9614 icon_prefix: 'fa fa-'
9617 JSONEditor.defaults.iconlibs.foundation2 = JSONEditor.AbstractIconLib.extend({
9627 movedown: 'down-arrow'
9629 icon_prefix: 'foundicon-'
9632 JSONEditor.defaults.iconlibs.foundation3 = JSONEditor.AbstractIconLib.extend({
9642 movedown: 'arrow-down'
9647 JSONEditor.defaults.iconlibs.jqueryui = JSONEditor.AbstractIconLib.extend({
9649 collapse: 'triangle-1-s',
9650 expand: 'triangle-1-e',
9654 cancel: 'closethick',
9656 moveup: 'arrowthick-1-n',
9657 movedown: 'arrowthick-1-s'
9659 icon_prefix: 'ui-icon ui-icon-'
9662 JSONEditor.defaults.iconlibs.materialicons = JSONEditor.AbstractIconLib.extend({
9665 collapse: 'arrow_drop_up',
9666 expand: 'arrow_drop_down',
9672 moveup: 'arrow_upward',
9673 movedown: 'arrow_downward',
9674 copy: 'content_copy'
9677 icon_class: 'material-icons',
9680 getIconClass: function(key) {
9682 // This method is unused.
9684 return this.icon_class;
9687 getIcon: function(key) {
9690 var mapping = this.mapping[key];
9691 if (!mapping) return null;
9693 // @see http://materializecss.com/icons.html
9694 var i = document.createElement('i');
9695 i.className = this.icon_class;
9696 var t = document.createTextNode(mapping);
9703 JSONEditor.defaults.templates["default"] = function() {
9705 compile: function(template) {
9706 var matches = template.match(/{{\s*([a-zA-Z0-9\-_ \.]+)\s*}}/g);
9707 var l = matches && matches.length;
9709 // Shortcut if the template contains no variables
9710 if(!l) return function() { return template; };
9712 // Pre-compute the search/replace functions
9713 // This drastically speeds up template execution
9714 var replacements = [];
9715 var get_replacement = function(i) {
9716 var p = matches[i].replace(/[{}]+/g,'').trim().split('.');
9722 func = function(vars) {
9724 for(i=0; i<n; i++) {
9733 func = function(vars) {
9743 for(var i=0; i<l; i++) {
9747 // The compiled function
9748 return function(vars) {
9749 var ret = template+"";
9751 for(i=0; i<l; i++) {
9752 r = replacements[i];
9753 ret = ret.replace(r.s, r.r(vars));
9761 JSONEditor.defaults.templates.ejs = function() {
9762 if(!window.EJS) return false;
9765 compile: function(template) {
9766 var compiled = new window.EJS({
9770 return function(context) {
9771 return compiled.render(context);
9777 JSONEditor.defaults.templates.handlebars = function() {
9778 return window.Handlebars;
9781 JSONEditor.defaults.templates.hogan = function() {
9782 if(!window.Hogan) return false;
9785 compile: function(template) {
9786 var compiled = window.Hogan.compile(template);
9787 return function(context) {
9788 return compiled.render(context);
9794 JSONEditor.defaults.templates.lodash = function() {
9795 if(!window._) return false;
9798 compile: function(template) {
9799 return function(context) {
9800 return window._.template(template)(context);
9806 JSONEditor.defaults.templates.markup = function() {
9807 if(!window.Mark || !window.Mark.up) return false;
9810 compile: function(template) {
9811 return function(context) {
9812 return window.Mark.up(template,context);
9818 JSONEditor.defaults.templates.mustache = function() {
9819 if(!window.Mustache) return false;
9822 compile: function(template) {
9823 return function(view) {
9824 return window.Mustache.render(template, view);
9830 JSONEditor.defaults.templates.swig = function() {
9834 JSONEditor.defaults.templates.underscore = function() {
9835 if(!window._) return false;
9838 compile: function(template) {
9839 return function(context) {
9840 return window._.template(template, context);
9846 // Set the default theme
9847 JSONEditor.defaults.theme = 'html';
9849 // Set the default template engine
9850 JSONEditor.defaults.template = 'default';
9852 // Default options when initializing JSON Editor
9853 JSONEditor.defaults.options = {};
9855 JSONEditor.defaults.options.prompt_before_delete = true;
9857 // String translate function
9858 JSONEditor.defaults.translate = function(key, variables) {
9859 var lang = JSONEditor.defaults.languages[JSONEditor.defaults.language];
9860 if(!lang) throw "Unknown language "+JSONEditor.defaults.language;
9862 var string = lang[key] || JSONEditor.defaults.languages[JSONEditor.defaults.default_language][key];
9864 if(typeof string === "undefined") throw "Unknown translate string "+key;
9867 for(var i=0; i<variables.length; i++) {
9868 string = string.replace(new RegExp('\\{\\{'+i+'}}','g'),variables[i]);
9875 // Translation strings and default languages
9876 JSONEditor.defaults.default_language = 'en';
9877 JSONEditor.defaults.language = JSONEditor.defaults.default_language;
9878 JSONEditor.defaults.languages.en = {
9880 * When a property is not set
9882 error_notset: 'Please populate the required property "{{0}}"',
9884 * When a string must not be empty
9886 error_notempty: 'Please populate the required property "{{0}}"',
9888 * When a value is not one of the enumerated values
9890 error_enum: "{{0}} must be one of the enumerated values",
9892 * When a value doesn't validate any schema of a 'anyOf' combination
9894 error_anyOf: "Value must validate against at least one of the provided schemas",
9896 * When a value doesn't validate
9897 * @variables This key takes one variable: The number of schemas the value does not validate
9899 error_oneOf: 'Value must validate against exactly one of the provided schemas. It currently validates against {{0}} of the schemas.',
9901 * When a value does not validate a 'not' schema
9903 error_not: "Value must not validate against the provided schema",
9905 * When a value does not match any of the provided types
9907 error_type_union: "Value must be one of the provided types",
9909 * When a value does not match the given type
9910 * @variables This key takes one variable: The type the value should be of
9912 error_type: "Value must be of type {{0}}",
9914 * When the value validates one of the disallowed types
9916 error_disallow_union: "Value must not be one of the provided disallowed types",
9918 * When the value validates a disallowed type
9919 * @variables This key takes one variable: The type the value should not be of
9921 error_disallow: "Value must not be of type {{0}}",
9923 * When a value is not a multiple of or divisible by a given number
9924 * @variables This key takes one variable: The number mentioned above
9926 error_multipleOf: "Value must be a multiple of {{0}}",
9928 * When a value is greater than it's supposed to be (exclusive)
9929 * @variables This key takes one variable: The maximum
9931 error_maximum_excl: "{{0}} must be less than {{1}}",
9933 * When a value is greater than it's supposed to be (inclusive)
9934 * @variables This key takes one variable: The maximum
9936 error_maximum_incl: "{{0}} must be at most {{1}}",
9938 * When a value is lesser than it's supposed to be (exclusive)
9939 * @variables This key takes one variable: The minimum
9941 error_minimum_excl: "{{0}} must be greater than {{1}}",
9943 * When a value is lesser than it's supposed to be (inclusive)
9944 * @variables This key takes one variable: The minimum
9946 error_minimum_incl: "{{0}} must be at least {{1}}",
9948 * When a value have too many characters
9949 * @variables This key takes one variable: The maximum character count
9951 error_maxLength: "{{0}} must be at most {{1}} characters long",
9953 * When a value does not have enough characters
9954 * @variables This key takes one variable: The minimum character count
9956 error_minLength: "{{0}} must be at least {{1}} characters long",
9958 * When a value does not match a given pattern
9960 error_pattern: "{{0}} must match the pattern {{1}}",
9962 * When an array has additional items whereas it is not supposed to
9964 error_additionalItems: "No additional items allowed in this array",
9966 * When there are to many items in an array
9967 * @variables This key takes one variable: The maximum item count
9969 error_maxItems: "{{0}} must have at most {{1}} items",
9971 * When there are not enough items in an array
9972 * @variables This key takes one variable: The minimum item count
9974 error_minItems: "{{0}} must have at least {{1}} items",
9976 * When an array is supposed to have unique items but has duplicates
9978 error_uniqueItems: "Each tab of {{0}} must specify a unique combination of parameters",
9980 * When there are too many properties in an object
9981 * @variables This key takes one variable: The maximum property count
9983 error_maxProperties: "Object must have at most {{0}} properties",
9985 * When there are not enough properties in an object
9986 * @variables This key takes one variable: The minimum property count
9988 error_minProperties: "Object must have at least {{0}} properties",
9990 * When a required property is not defined
9991 * @variables This key takes one variable: The name of the missing property
9993 error_required: 'Please populate the required property "{{0}}"',
9995 * When there is an additional property is set whereas there should be none
9996 * @variables This key takes one variable: The name of the additional property
9998 error_additional_properties: "No additional properties allowed, but property {{0}} is set",
10000 * When a dependency is not resolved
10001 * @variables This key takes one variable: The name of the missing property for the dependency
10003 error_dependency: "Must have property {{0}}",
10005 * Text on Delete All buttons
10007 button_delete_all: "All",
10009 * Title on Delete All buttons
10011 button_delete_all_title: "Delete All",
10013 * Text on Delete Last buttons
10014 * @variable This key takes one variable: The title of object to delete
10016 button_delete_last: "Last {{0}}",
10018 * Title on Delete Last buttons
10019 * @variable This key takes one variable: The title of object to delete
10021 button_delete_last_title: "Delete Last {{0}}",
10023 * Title on Add Row buttons
10024 * @variable This key takes one variable: The title of object to add
10026 button_add_row_title: "Add {{0}}",
10028 * Title on Move Down buttons
10030 button_move_down_title: "Move down",
10032 * Title on Move Up buttons
10034 button_move_up_title: "Move up",
10036 * Title on Delete Row buttons
10037 * @variable This key takes one variable: The title of object to delete
10039 button_delete_row_title: "Delete {{0}}",
10041 * Title on Delete Row buttons, short version (no parameter with the object title)
10043 button_delete_row_title_short: "Delete",
10045 * Title on Collapse buttons
10047 button_collapse: "Collapse",
10049 * Title on Expand buttons
10051 button_expand: "Expand"
10054 // Miscellaneous Plugin Settings
10055 JSONEditor.plugins = {
10072 // Default per-editor options
10073 $each(JSONEditor.defaults.editors, function(i,editor) {
10074 JSONEditor.defaults.editors[i].options = editor.options || {};
10077 // Set the default resolvers
10078 // Use "multiple" as a fall back for everything
10079 JSONEditor.defaults.resolvers.unshift(function(schema) {
10080 if(schema.type === "qbldr") return "qbldr";
10082 JSONEditor.defaults.resolvers.unshift(function(schema) {
10083 if(typeof schema.type !== "string") return "multiple";
10085 // If the type is not set but properties are defined, we can infer the type is actually object
10086 JSONEditor.defaults.resolvers.unshift(function(schema) {
10087 // If the schema is a simple type
10088 if(!schema.type && schema.properties ) return "object";
10090 // If the type is set and it's a basic type, use the primitive editor
10091 JSONEditor.defaults.resolvers.unshift(function(schema) {
10092 // If the schema is a simple type
10093 if(typeof schema.type === "string") return schema.type;
10095 // Use a specialized editor for ratings
10096 JSONEditor.defaults.resolvers.unshift(function(schema) {
10097 if(schema.type === "integer" && schema.format === "rating") return "rating";
10099 // Use the select editor for all boolean values
10100 JSONEditor.defaults.resolvers.unshift(function(schema) {
10101 if(schema.type === 'boolean') {
10102 // If explicitly set to 'checkbox', use that
10103 if(schema.format === "checkbox" || (schema.options && schema.options.checkbox)) {
10106 // Otherwise, default to select menu
10107 return (JSONEditor.plugins.selectize.enable) ? 'selectize' : 'select';
10110 // Use the multiple editor for schemas where the `type` is set to "any"
10111 JSONEditor.defaults.resolvers.unshift(function(schema) {
10112 // If the schema can be of any type
10113 if(schema.type === "any") return "multiple";
10115 // Editor for base64 encoded files
10116 JSONEditor.defaults.resolvers.unshift(function(schema) {
10117 // If the schema can be of any type
10118 if(schema.type === "string" && schema.media && schema.media.binaryEncoding==="base64") {
10122 // Editor for uploading files
10123 JSONEditor.defaults.resolvers.unshift(function(schema) {
10124 if(schema.type === "string" && schema.format === "url" && schema.options && schema.options.upload === true) {
10125 if(window.FileReader) return "upload";
10128 // Use the table editor for arrays with the format set to `table`
10129 JSONEditor.defaults.resolvers.unshift(function(schema) {
10130 // Type `array` with format set to `table`
10131 if(schema.type === "array" && schema.format === "table") {
10135 // Use the `select` editor for dynamic enumSource enums
10136 JSONEditor.defaults.resolvers.unshift(function(schema) {
10137 if(schema.enumSource) return (JSONEditor.plugins.selectize.enable) ? 'selectize' : 'select';
10139 // Use the `enum` or `select` editors for schemas with enumerated properties
10140 JSONEditor.defaults.resolvers.unshift(function(schema) {
10141 if(schema["enum"]) {
10142 if(schema.type === "array" || schema.type === "object") {
10145 else if(schema.type === "number" || schema.type === "integer" || schema.type === "string") {
10146 return (JSONEditor.plugins.selectize.enable) ? 'selectize' : 'select';
10150 // Specialized editors for arrays of strings
10151 JSONEditor.defaults.resolvers.unshift(function(schema) {
10152 if(schema.type === "array" && schema.items && !(Array.isArray(schema.items)) && schema.uniqueItems && ['string','number','integer'].indexOf(schema.items.type) >= 0) {
10153 // For enumerated strings, number, or integers
10154 if(schema.items.enum) {
10155 return 'multiselect';
10157 // For non-enumerated strings (tag editor)
10158 else if(JSONEditor.plugins.selectize.enable && schema.items.type === "string") {
10159 return 'arraySelectize';
10163 // Use the multiple editor for schemas with `oneOf` set
10164 JSONEditor.defaults.resolvers.unshift(function(schema) {
10165 // If this schema uses `oneOf` or `anyOf`
10166 if(schema.oneOf || schema.anyOf) return "multiple";
10170 * This is a small wrapper for using JSON Editor like a typical jQuery plugin.
10173 if(window.jQuery || window.Zepto) {
10174 var $ = window.jQuery || window.Zepto;
10175 $.jsoneditor = JSONEditor.defaults;
10177 $.fn.jsoneditor = function(options) {
10179 var editor = this.data('jsoneditor');
10180 if(options === 'value') {
10181 if(!editor) throw "Must initialize jsoneditor before getting/setting the value";
10184 if(arguments.length > 1) {
10185 editor.setValue(arguments[1]);
10189 return editor.getValue();
10192 else if(options === 'validate') {
10193 if(!editor) throw "Must initialize jsoneditor before validating";
10195 // Validate a specific value
10196 if(arguments.length > 1) {
10197 return editor.validate(arguments[1]);
10199 // Validate current value
10201 return editor.validate();
10204 else if(options === 'destroy') {
10207 this.data('jsoneditor',null);
10217 editor = new JSONEditor(this.get(0),options);
10218 this.data('jsoneditor',editor);
10220 // Setup event listeners
10221 editor.on('change',function() {
10222 self.trigger('change');
10224 editor.on('ready',function() {
10225 self.trigger('ready');
10234 window.JSONEditor = JSONEditor;