7774 lines
232 KiB
JavaScript
7774 lines
232 KiB
JavaScript
|
/*! JSON Editor v0.7.23 - JSON Schema -> HTML Editor
|
||
|
* By Jeremy Dorn - https://github.com/jdorn/json-editor/
|
||
|
* Released under the MIT license
|
||
|
*
|
||
|
* Date: 2015-09-27
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* See README.md for requirements and usage info
|
||
|
*/
|
||
|
|
||
|
(function() {
|
||
|
|
||
|
/*jshint loopfunc: true */
|
||
|
/* Simple JavaScript Inheritance
|
||
|
* By John Resig http://ejohn.org/
|
||
|
* MIT Licensed.
|
||
|
*/
|
||
|
// Inspired by base2 and Prototype
|
||
|
var Class;
|
||
|
(function(){
|
||
|
var initializing = false, fnTest = /xyz/.test(function(){window.postMessage("xyz");}) ? /\b_super\b/ : /.*/;
|
||
|
|
||
|
// The base Class implementation (does nothing)
|
||
|
Class = function(){};
|
||
|
|
||
|
// Create a new Class that inherits from this class
|
||
|
Class.extend = function(prop) {
|
||
|
var _super = this.prototype;
|
||
|
|
||
|
// Instantiate a base class (but only create the instance,
|
||
|
// don't run the init constructor)
|
||
|
initializing = true;
|
||
|
var prototype = new this();
|
||
|
initializing = false;
|
||
|
|
||
|
// Copy the properties over onto the new prototype
|
||
|
for (var name in prop) {
|
||
|
// Check if we're overwriting an existing function
|
||
|
prototype[name] = typeof prop[name] == "function" &&
|
||
|
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
|
||
|
(function(name, fn){
|
||
|
return function() {
|
||
|
var tmp = this._super;
|
||
|
|
||
|
// Add a new ._super() method that is the same method
|
||
|
// but on the super-class
|
||
|
this._super = _super[name];
|
||
|
|
||
|
// The method only need to be bound temporarily, so we
|
||
|
// remove it when we're done executing
|
||
|
var ret = fn.apply(this, arguments);
|
||
|
this._super = tmp;
|
||
|
|
||
|
return ret;
|
||
|
};
|
||
|
})(name, prop[name]) :
|
||
|
prop[name];
|
||
|
}
|
||
|
|
||
|
// The dummy class constructor
|
||
|
function Class() {
|
||
|
// All construction is actually done in the init method
|
||
|
if ( !initializing && this.init )
|
||
|
this.init.apply(this, arguments);
|
||
|
}
|
||
|
|
||
|
// Populate our constructed prototype object
|
||
|
Class.prototype = prototype;
|
||
|
|
||
|
// Enforce the constructor to be what we expect
|
||
|
Class.prototype.constructor = Class;
|
||
|
|
||
|
// And make this class extendable
|
||
|
Class.extend = arguments.callee;
|
||
|
|
||
|
return Class;
|
||
|
};
|
||
|
|
||
|
return Class;
|
||
|
})();
|
||
|
|
||
|
// CustomEvent constructor polyfill
|
||
|
// From MDN
|
||
|
(function () {
|
||
|
function CustomEvent ( event, params ) {
|
||
|
params = params || { bubbles: false, cancelable: false, detail: undefined };
|
||
|
var evt = document.createEvent( 'CustomEvent' );
|
||
|
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
|
||
|
return evt;
|
||
|
}
|
||
|
|
||
|
CustomEvent.prototype = window.Event.prototype;
|
||
|
|
||
|
window.CustomEvent = CustomEvent;
|
||
|
})();
|
||
|
|
||
|
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
|
||
|
// MIT license
|
||
|
(function() {
|
||
|
var lastTime = 0;
|
||
|
var vendors = ['ms', 'moz', 'webkit', 'o'];
|
||
|
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
|
||
|
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
|
||
|
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] ||
|
||
|
window[vendors[x]+'CancelRequestAnimationFrame'];
|
||
|
}
|
||
|
|
||
|
if (!window.requestAnimationFrame)
|
||
|
window.requestAnimationFrame = function(callback, element) {
|
||
|
var currTime = new Date().getTime();
|
||
|
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
||
|
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
|
||
|
timeToCall);
|
||
|
lastTime = currTime + timeToCall;
|
||
|
return id;
|
||
|
};
|
||
|
|
||
|
if (!window.cancelAnimationFrame)
|
||
|
window.cancelAnimationFrame = function(id) {
|
||
|
clearTimeout(id);
|
||
|
};
|
||
|
}());
|
||
|
|
||
|
// Array.isArray polyfill
|
||
|
// From MDN
|
||
|
(function() {
|
||
|
if(!Array.isArray) {
|
||
|
Array.isArray = function(arg) {
|
||
|
return Object.prototype.toString.call(arg) === '[object Array]';
|
||
|
};
|
||
|
}
|
||
|
}());
|
||
|
/**
|
||
|
* Taken from jQuery 2.1.3
|
||
|
*
|
||
|
* @param obj
|
||
|
* @returns {boolean}
|
||
|
*/
|
||
|
var $isplainobject = function( obj ) {
|
||
|
// Not plain objects:
|
||
|
// - Any object or value whose internal [[Class]] property is not "[object Object]"
|
||
|
// - DOM nodes
|
||
|
// - window
|
||
|
if (typeof obj !== "object" || obj.nodeType || (obj !== null && obj === obj.window)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (obj.constructor && !Object.prototype.hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// If the function hasn't returned already, we're confident that
|
||
|
// |obj| is a plain object, created by {} or constructed with new Object
|
||
|
return true;
|
||
|
};
|
||
|
|
||
|
var $extend = function(destination) {
|
||
|
var source, i,property;
|
||
|
for(i=1; i<arguments.length; i++) {
|
||
|
source = arguments[i];
|
||
|
for (property in source) {
|
||
|
if(!source.hasOwnProperty(property)) continue;
|
||
|
if(source[property] && $isplainobject(source[property])) {
|
||
|
if(!destination.hasOwnProperty(property)) destination[property] = {};
|
||
|
$extend(destination[property], source[property]);
|
||
|
}
|
||
|
else {
|
||
|
destination[property] = source[property];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return destination;
|
||
|
};
|
||
|
|
||
|
var $each = function(obj,callback) {
|
||
|
if(!obj || typeof obj !== "object") return;
|
||
|
var i;
|
||
|
if(Array.isArray(obj) || (typeof obj.length === 'number' && obj.length > 0 && (obj.length - 1) in obj)) {
|
||
|
for(i=0; i<obj.length; i++) {
|
||
|
if(callback(i,obj[i])===false) return;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
if (Object.keys) {
|
||
|
var keys = Object.keys(obj);
|
||
|
for(i=0; i<keys.length; i++) {
|
||
|
if(callback(keys[i],obj[keys[i]])===false) return;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
for(i in obj) {
|
||
|
if(!obj.hasOwnProperty(i)) continue;
|
||
|
if(callback(i,obj[i])===false) return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var $trigger = function(el,event) {
|
||
|
var e = document.createEvent('HTMLEvents');
|
||
|
e.initEvent(event, true, true);
|
||
|
el.dispatchEvent(e);
|
||
|
};
|
||
|
var $triggerc = function(el,event) {
|
||
|
var e = new CustomEvent(event,{
|
||
|
bubbles: true,
|
||
|
cancelable: true
|
||
|
});
|
||
|
|
||
|
el.dispatchEvent(e);
|
||
|
};
|
||
|
|
||
|
var JSONEditor = function(element,options) {
|
||
|
if (!(element instanceof Element)) {
|
||
|
throw new Error('element should be an instance of Element');
|
||
|
}
|
||
|
options = $extend({},JSONEditor.defaults.options,options||{});
|
||
|
this.element = element;
|
||
|
this.options = options;
|
||
|
this.init();
|
||
|
};
|
||
|
JSONEditor.prototype = {
|
||
|
// necessary since we remove the ctor property by doing a literal assignment. Without this
|
||
|
// the $isplainobject function will think that this is a plain object.
|
||
|
constructor: JSONEditor,
|
||
|
init: function() {
|
||
|
var self = this;
|
||
|
|
||
|
this.ready = false;
|
||
|
|
||
|
var theme_class = JSONEditor.defaults.themes[this.options.theme || JSONEditor.defaults.theme];
|
||
|
if(!theme_class) throw "Unknown theme " + (this.options.theme || JSONEditor.defaults.theme);
|
||
|
|
||
|
this.schema = this.options.schema;
|
||
|
this.theme = new theme_class();
|
||
|
this.template = this.options.template;
|
||
|
this.refs = this.options.refs || {};
|
||
|
this.uuid = 0;
|
||
|
this.__data = {};
|
||
|
|
||
|
var icon_class = JSONEditor.defaults.iconlibs[this.options.iconlib || JSONEditor.defaults.iconlib];
|
||
|
if(icon_class) this.iconlib = new icon_class();
|
||
|
|
||
|
this.root_container = this.theme.getContainer();
|
||
|
this.element.appendChild(this.root_container);
|
||
|
|
||
|
this.translate = this.options.translate || JSONEditor.defaults.translate;
|
||
|
|
||
|
// Fetch all external refs via ajax
|
||
|
this._loadExternalRefs(this.schema, function() {
|
||
|
self._getDefinitions(self.schema);
|
||
|
|
||
|
// Validator options
|
||
|
var validator_options = {};
|
||
|
if(self.options.custom_validators) {
|
||
|
validator_options.custom_validators = self.options.custom_validators;
|
||
|
}
|
||
|
self.validator = new JSONEditor.Validator(self,null,validator_options);
|
||
|
|
||
|
// Create the root editor
|
||
|
var editor_class = self.getEditorClass(self.schema);
|
||
|
self.root = self.createEditor(editor_class, {
|
||
|
jsoneditor: self,
|
||
|
schema: self.schema,
|
||
|
required: true,
|
||
|
container: self.root_container
|
||
|
});
|
||
|
|
||
|
self.root.preBuild();
|
||
|
self.root.build();
|
||
|
self.root.postBuild();
|
||
|
|
||
|
// Starting data
|
||
|
if(self.options.startval) self.root.setValue(self.options.startval);
|
||
|
|
||
|
self.validation_results = self.validator.validate(self.root.getValue());
|
||
|
self.root.showValidationErrors(self.validation_results);
|
||
|
self.ready = true;
|
||
|
|
||
|
// Fire ready event asynchronously
|
||
|
window.requestAnimationFrame(function() {
|
||
|
if(!self.ready) return;
|
||
|
self.validation_results = self.validator.validate(self.root.getValue());
|
||
|
self.root.showValidationErrors(self.validation_results);
|
||
|
self.trigger('ready');
|
||
|
self.trigger('change');
|
||
|
});
|
||
|
});
|
||
|
},
|
||
|
getValue: function() {
|
||
|
if(!this.ready) throw "JSON Editor not ready yet. Listen for 'ready' event before getting the value";
|
||
|
|
||
|
return this.root.getValue();
|
||
|
},
|
||
|
setValue: function(value) {
|
||
|
if(!this.ready) throw "JSON Editor not ready yet. Listen for 'ready' event before setting the value";
|
||
|
|
||
|
this.root.setValue(value);
|
||
|
return this;
|
||
|
},
|
||
|
validate: function(value) {
|
||
|
if(!this.ready) throw "JSON Editor not ready yet. Listen for 'ready' event before validating";
|
||
|
|
||
|
// Custom value
|
||
|
if(arguments.length === 1) {
|
||
|
return this.validator.validate(value);
|
||
|
}
|
||
|
// Current value (use cached result)
|
||
|
else {
|
||
|
return this.validation_results;
|
||
|
}
|
||
|
},
|
||
|
destroy: function() {
|
||
|
if(this.destroyed) return;
|
||
|
if(!this.ready) return;
|
||
|
|
||
|
this.schema = null;
|
||
|
this.options = null;
|
||
|
this.root.destroy();
|
||
|
this.root = null;
|
||
|
this.root_container = null;
|
||
|
this.validator = null;
|
||
|
this.validation_results = null;
|
||
|
this.theme = null;
|
||
|
this.iconlib = null;
|
||
|
this.template = null;
|
||
|
this.__data = null;
|
||
|
this.ready = false;
|
||
|
this.element.innerHTML = '';
|
||
|
|
||
|
this.destroyed = true;
|
||
|
},
|
||
|
on: function(event, callback) {
|
||
|
this.callbacks = this.callbacks || {};
|
||
|
this.callbacks[event] = this.callbacks[event] || [];
|
||
|
this.callbacks[event].push(callback);
|
||
|
|
||
|
return this;
|
||
|
},
|
||
|
off: function(event, callback) {
|
||
|
// Specific callback
|
||
|
if(event && callback) {
|
||
|
this.callbacks = this.callbacks || {};
|
||
|
this.callbacks[event] = this.callbacks[event] || [];
|
||
|
var newcallbacks = [];
|
||
|
for(var i=0; i<this.callbacks[event].length; i++) {
|
||
|
if(this.callbacks[event][i]===callback) continue;
|
||
|
newcallbacks.push(this.callbacks[event][i]);
|
||
|
}
|
||
|
this.callbacks[event] = newcallbacks;
|
||
|
}
|
||
|
// All callbacks for a specific event
|
||
|
else if(event) {
|
||
|
this.callbacks = this.callbacks || {};
|
||
|
this.callbacks[event] = [];
|
||
|
}
|
||
|
// All callbacks for all events
|
||
|
else {
|
||
|
this.callbacks = {};
|
||
|
}
|
||
|
|
||
|
return this;
|
||
|
},
|
||
|
trigger: function(event) {
|
||
|
if(this.callbacks && this.callbacks[event] && this.callbacks[event].length) {
|
||
|
for(var i=0; i<this.callbacks[event].length; i++) {
|
||
|
this.callbacks[event][i]();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return this;
|
||
|
},
|
||
|
setOption: function(option, value) {
|
||
|
if(option === "show_errors") {
|
||
|
this.options.show_errors = value;
|
||
|
this.onChange();
|
||
|
}
|
||
|
// Only the `show_errors` option is supported for now
|
||
|
else {
|
||
|
throw "Option "+option+" must be set during instantiation and cannot be changed later";
|
||
|
}
|
||
|
|
||
|
return this;
|
||
|
},
|
||
|
getEditorClass: function(schema) {
|
||
|
var classname;
|
||
|
|
||
|
schema = this.expandSchema(schema);
|
||
|
|
||
|
$each(JSONEditor.defaults.resolvers,function(i,resolver) {
|
||
|
var tmp = resolver(schema);
|
||
|
if(tmp) {
|
||
|
if(JSONEditor.defaults.editors[tmp]) {
|
||
|
classname = tmp;
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
if(!classname) throw "Unknown editor for schema "+JSON.stringify(schema);
|
||
|
if(!JSONEditor.defaults.editors[classname]) throw "Unknown editor "+classname;
|
||
|
|
||
|
return JSONEditor.defaults.editors[classname];
|
||
|
},
|
||
|
createEditor: function(editor_class, options) {
|
||
|
options = $extend({},editor_class.options||{},options);
|
||
|
return new editor_class(options);
|
||
|
},
|
||
|
onChange: function() {
|
||
|
if(!this.ready) return;
|
||
|
|
||
|
if(this.firing_change) return;
|
||
|
this.firing_change = true;
|
||
|
|
||
|
var self = this;
|
||
|
|
||
|
window.requestAnimationFrame(function() {
|
||
|
self.firing_change = false;
|
||
|
if(!self.ready) return;
|
||
|
|
||
|
// Validate and cache results
|
||
|
self.validation_results = self.validator.validate(self.root.getValue());
|
||
|
|
||
|
if(self.options.show_errors !== "never") {
|
||
|
self.root.showValidationErrors(self.validation_results);
|
||
|
}
|
||
|
else {
|
||
|
self.root.showValidationErrors([]);
|
||
|
}
|
||
|
|
||
|
// Fire change event
|
||
|
self.trigger('change');
|
||
|
});
|
||
|
|
||
|
return this;
|
||
|
},
|
||
|
compileTemplate: function(template, name) {
|
||
|
name = name || JSONEditor.defaults.template;
|
||
|
|
||
|
var engine;
|
||
|
|
||
|
// Specifying a preset engine
|
||
|
if(typeof name === 'string') {
|
||
|
if(!JSONEditor.defaults.templates[name]) throw "Unknown template engine "+name;
|
||
|
engine = JSONEditor.defaults.templates[name]();
|
||
|
|
||
|
if(!engine) throw "Template engine "+name+" missing required library.";
|
||
|
}
|
||
|
// Specifying a custom engine
|
||
|
else {
|
||
|
engine = name;
|
||
|
}
|
||
|
|
||
|
if(!engine) throw "No template engine set";
|
||
|
if(!engine.compile) throw "Invalid template engine set";
|
||
|
|
||
|
return engine.compile(template);
|
||
|
},
|
||
|
_data: function(el,key,value) {
|
||
|
// Setting data
|
||
|
if(arguments.length === 3) {
|
||
|
var uuid;
|
||
|
if(el.hasAttribute('data-jsoneditor-'+key)) {
|
||
|
uuid = el.getAttribute('data-jsoneditor-'+key);
|
||
|
}
|
||
|
else {
|
||
|
uuid = this.uuid++;
|
||
|
el.setAttribute('data-jsoneditor-'+key,uuid);
|
||
|
}
|
||
|
|
||
|
this.__data[uuid] = value;
|
||
|
}
|
||
|
// Getting data
|
||
|
else {
|
||
|
// No data stored
|
||
|
if(!el.hasAttribute('data-jsoneditor-'+key)) return null;
|
||
|
|
||
|
return this.__data[el.getAttribute('data-jsoneditor-'+key)];
|
||
|
}
|
||
|
},
|
||
|
registerEditor: function(editor) {
|
||
|
this.editors = this.editors || {};
|
||
|
this.editors[editor.path] = editor;
|
||
|
return this;
|
||
|
},
|
||
|
unregisterEditor: function(editor) {
|
||
|
this.editors = this.editors || {};
|
||
|
this.editors[editor.path] = null;
|
||
|
return this;
|
||
|
},
|
||
|
getEditor: function(path) {
|
||
|
if(!this.editors) return;
|
||
|
return this.editors[path];
|
||
|
},
|
||
|
watch: function(path,callback) {
|
||
|
this.watchlist = this.watchlist || {};
|
||
|
this.watchlist[path] = this.watchlist[path] || [];
|
||
|
this.watchlist[path].push(callback);
|
||
|
|
||
|
return this;
|
||
|
},
|
||
|
unwatch: function(path,callback) {
|
||
|
if(!this.watchlist || !this.watchlist[path]) return this;
|
||
|
// If removing all callbacks for a path
|
||
|
if(!callback) {
|
||
|
this.watchlist[path] = null;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
var newlist = [];
|
||
|
for(var i=0; i<this.watchlist[path].length; i++) {
|
||
|
if(this.watchlist[path][i] === callback) continue;
|
||
|
else newlist.push(this.watchlist[path][i]);
|
||
|
}
|
||
|
this.watchlist[path] = newlist.length? newlist : null;
|
||
|
return this;
|
||
|
},
|
||
|
notifyWatchers: function(path) {
|
||
|
if(!this.watchlist || !this.watchlist[path]) return this;
|
||
|
for(var i=0; i<this.watchlist[path].length; i++) {
|
||
|
this.watchlist[path][i]();
|
||
|
}
|
||
|
},
|
||
|
isEnabled: function() {
|
||
|
return !this.root || this.root.isEnabled();
|
||
|
},
|
||
|
enable: function() {
|
||
|
this.root.enable();
|
||
|
},
|
||
|
disable: function() {
|
||
|
this.root.disable();
|
||
|
},
|
||
|
_getDefinitions: function(schema,path) {
|
||
|
path = path || '#/definitions/';
|
||
|
if(schema.definitions) {
|
||
|
for(var i in schema.definitions) {
|
||
|
if(!schema.definitions.hasOwnProperty(i)) continue;
|
||
|
this.refs[path+i] = schema.definitions[i];
|
||
|
if(schema.definitions[i].definitions) {
|
||
|
this._getDefinitions(schema.definitions[i],path+i+'/definitions/');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
_getExternalRefs: function(schema) {
|
||
|
var refs = {};
|
||
|
var merge_refs = function(newrefs) {
|
||
|
for(var i in newrefs) {
|
||
|
if(newrefs.hasOwnProperty(i)) {
|
||
|
refs[i] = true;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if(schema.$ref && typeof schema.$ref !== "object" && schema.$ref.substr(0,1) !== "#" && !this.refs[schema.$ref]) {
|
||
|
refs[schema.$ref] = true;
|
||
|
}
|
||
|
|
||
|
for(var i in schema) {
|
||
|
if(!schema.hasOwnProperty(i)) continue;
|
||
|
if(schema[i] && typeof schema[i] === "object" && Array.isArray(schema[i])) {
|
||
|
for(var j=0; j<schema[i].length; j++) {
|
||
|
if(typeof schema[i][j]==="object") {
|
||
|
merge_refs(this._getExternalRefs(schema[i][j]));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if(schema[i] && typeof schema[i] === "object") {
|
||
|
merge_refs(this._getExternalRefs(schema[i]));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return refs;
|
||
|
},
|
||
|
_loadExternalRefs: function(schema, callback) {
|
||
|
var self = this;
|
||
|
var refs = this._getExternalRefs(schema);
|
||
|
|
||
|
var done = 0, waiting = 0, callback_fired = false;
|
||
|
|
||
|
$each(refs,function(url) {
|
||
|
if(self.refs[url]) return;
|
||
|
if(!self.options.ajax) throw "Must set ajax option to true to load external ref "+url;
|
||
|
self.refs[url] = 'loading';
|
||
|
waiting++;
|
||
|
|
||
|
var r = new XMLHttpRequest();
|
||
|
r.open("GET", url, true);
|
||
|
r.onreadystatechange = function () {
|
||
|
if (r.readyState != 4) return;
|
||
|
// Request succeeded
|
||
|
if(r.status === 200) {
|
||
|
var response;
|
||
|
try {
|
||
|
response = JSON.parse(r.responseText);
|
||
|
}
|
||
|
catch(e) {
|
||
|
window.console.log(e);
|
||
|
throw "Failed to parse external ref "+url;
|
||
|
}
|
||
|
if(!response || typeof response !== "object") throw "External ref does not contain a valid schema - "+url;
|
||
|
|
||
|
self.refs[url] = response;
|
||
|
self._loadExternalRefs(response,function() {
|
||
|
done++;
|
||
|
if(done >= waiting && !callback_fired) {
|
||
|
callback_fired = true;
|
||
|
callback();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
// Request failed
|
||
|
else {
|
||
|
window.console.log(r);
|
||
|
throw "Failed to fetch ref via ajax- "+url;
|
||
|
}
|
||
|
};
|
||
|
r.send();
|
||
|
});
|
||
|
|
||
|
if(!waiting) {
|
||
|
callback();
|
||
|
}
|
||
|
},
|
||
|
expandRefs: function(schema) {
|
||
|
schema = $extend({},schema);
|
||
|
|
||
|
while (schema.$ref) {
|
||
|
var ref = schema.$ref;
|
||
|
delete schema.$ref;
|
||
|
|
||
|
if(!this.refs[ref]) ref = decodeURIComponent(ref);
|
||
|
|
||
|
schema = this.extendSchemas(schema,this.refs[ref]);
|
||
|
}
|
||
|
return schema;
|
||
|
},
|
||
|
expandSchema: function(schema) {
|
||
|
var self = this;
|
||
|
var extended = $extend({},schema);
|
||
|
var i;
|
||
|
|
||
|
// Version 3 `type`
|
||
|
if(typeof schema.type === 'object') {
|
||
|
// Array of types
|
||
|
if(Array.isArray(schema.type)) {
|
||
|
$each(schema.type, function(key,value) {
|
||
|
// Schema
|
||
|
if(typeof value === 'object') {
|
||
|
schema.type[key] = self.expandSchema(value);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
// Schema
|
||
|
else {
|
||
|
schema.type = self.expandSchema(schema.type);
|
||
|
}
|
||
|
}
|
||
|
// Version 3 `disallow`
|
||
|
if(typeof schema.disallow === 'object') {
|
||
|
// Array of types
|
||
|
if(Array.isArray(schema.disallow)) {
|
||
|
$each(schema.disallow, function(key,value) {
|
||
|
// Schema
|
||
|
if(typeof value === 'object') {
|
||
|
schema.disallow[key] = self.expandSchema(value);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
// Schema
|
||
|
else {
|
||
|
schema.disallow = self.expandSchema(schema.disallow);
|
||
|
}
|
||
|
}
|
||
|
// Version 4 `anyOf`
|
||
|
if(schema.anyOf) {
|
||
|
$each(schema.anyOf, function(key,value) {
|
||
|
schema.anyOf[key] = self.expandSchema(value);
|
||
|
});
|
||
|
}
|
||
|
// Version 4 `dependencies` (schema dependencies)
|
||
|
if(schema.dependencies) {
|
||
|
$each(schema.dependencies,function(key,value) {
|
||
|
if(typeof value === "object" && !(Array.isArray(value))) {
|
||
|
schema.dependencies[key] = self.expandSchema(value);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
// Version 4 `not`
|
||
|
if(schema.not) {
|
||
|
schema.not = this.expandSchema(schema.not);
|
||
|
}
|
||
|
|
||
|
// allOf schemas should be merged into the parent
|
||
|
if(schema.allOf) {
|
||
|
for(i=0; i<schema.allOf.length; i++) {
|
||
|
extended = this.extendSchemas(extended,this.expandSchema(schema.allOf[i]));
|
||
|
}
|
||
|
delete extended.allOf;
|
||
|
}
|
||
|
// extends schemas should be merged into parent
|
||
|
if(schema["extends"]) {
|
||
|
// If extends is a schema
|
||
|
if(!(Array.isArray(schema["extends"]))) {
|
||
|
extended = this.extendSchemas(extended,this.expandSchema(schema["extends"]));
|
||
|
}
|
||
|
// If extends is an array of schemas
|
||
|
else {
|
||
|
for(i=0; i<schema["extends"].length; i++) {
|
||
|
extended = this.extendSchemas(extended,this.expandSchema(schema["extends"][i]));
|
||
|
}
|
||
|
}
|
||
|
delete extended["extends"];
|
||
|
}
|
||
|
// parent should be merged into oneOf schemas
|
||
|
if(schema.oneOf) {
|
||
|
var tmp = $extend({},extended);
|
||
|
delete tmp.oneOf;
|
||
|
for(i=0; i<schema.oneOf.length; i++) {
|
||
|
extended.oneOf[i] = this.extendSchemas(this.expandSchema(schema.oneOf[i]),tmp);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return this.expandRefs(extended);
|
||
|
},
|
||
|
extendSchemas: function(obj1, obj2) {
|
||
|
obj1 = $extend({},obj1);
|
||
|
obj2 = $extend({},obj2);
|
||
|
|
||
|
var self = this;
|
||
|
var extended = {};
|
||
|
$each(obj1, function(prop,val) {
|
||
|
// If this key is also defined in obj2, merge them
|
||
|
if(typeof obj2[prop] !== "undefined") {
|
||
|
// Required arrays should be unioned together
|
||
|
if(prop === 'required' && typeof val === "object" && Array.isArray(val)) {
|
||
|
// Union arrays and unique
|
||
|
extended.required = val.concat(obj2[prop]).reduce(function(p, c) {
|
||
|
if (p.indexOf(c) < 0) p.push(c);
|
||
|
return p;
|
||
|
}, []);
|
||
|
}
|
||
|
// Type should be intersected and is either an array or string
|
||
|
else if(prop === 'type' && (typeof val === "string" || Array.isArray(val))) {
|
||
|
// Make sure we're dealing with arrays
|
||
|
if(typeof val === "string") val = [val];
|
||
|
if(typeof obj2.type === "string") obj2.type = [obj2.type];
|
||
|
|
||
|
|
||
|
extended.type = val.filter(function(n) {
|
||
|
return obj2.type.indexOf(n) !== -1;
|
||
|
});
|
||
|
|
||
|
// If there's only 1 type and it's a primitive, use a string instead of array
|
||
|
if(extended.type.length === 1 && typeof extended.type[0] === "string") {
|
||
|
extended.type = extended.type[0];
|
||
|
}
|
||
|
}
|
||
|
// All other arrays should be intersected (enum, etc.)
|
||
|
else if(typeof val === "object" && Array.isArray(val)){
|
||
|
extended[prop] = val.filter(function(n) {
|
||
|
return obj2[prop].indexOf(n) !== -1;
|
||
|
});
|
||
|
}
|
||
|
// Objects should be recursively merged
|
||
|
else if(typeof val === "object" && val !== null) {
|
||
|
extended[prop] = self.extendSchemas(val,obj2[prop]);
|
||
|
}
|
||
|
// Otherwise, use the first value
|
||
|
else {
|
||
|
extended[prop] = val;
|
||
|
}
|
||
|
}
|
||
|
// Otherwise, just use the one in obj1
|
||
|
else {
|
||
|
extended[prop] = val;
|
||
|
}
|
||
|
});
|
||
|
// Properties in obj2 that aren't in obj1
|
||
|
$each(obj2, function(prop,val) {
|
||
|
if(typeof obj1[prop] === "undefined") {
|
||
|
extended[prop] = val;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return extended;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
JSONEditor.defaults = {
|
||
|
themes: {},
|
||
|
templates: {},
|
||
|
iconlibs: {},
|
||
|
editors: {},
|
||
|
languages: {},
|
||
|
resolvers: [],
|
||
|
custom_validators: []
|
||
|
};
|
||
|
|
||
|
JSONEditor.Validator = Class.extend({
|
||
|
init: function(jsoneditor,schema,options) {
|
||
|
this.jsoneditor = jsoneditor;
|
||
|
this.schema = schema || this.jsoneditor.schema;
|
||
|
this.options = options || {};
|
||
|
this.translate = this.jsoneditor.translate || JSONEditor.defaults.translate;
|
||
|
},
|
||
|
validate: function(value) {
|
||
|
return this._validateSchema(this.schema, value);
|
||
|
},
|
||
|
_validateSchema: function(schema,value,path) {
|
||
|
var self = this;
|
||
|
var errors = [];
|
||
|
var valid, i, j;
|
||
|
var stringified = JSON.stringify(value);
|
||
|
|
||
|
path = path || 'root';
|
||
|
|
||
|
// Work on a copy of the schema
|
||
|
schema = $extend({},this.jsoneditor.expandRefs(schema));
|
||
|
|
||
|
/*
|
||
|
* Type Agnostic Validation
|
||
|
*/
|
||
|
|
||
|
// Version 3 `required`
|
||
|
if(schema.required && schema.required === true) {
|
||
|
if(typeof value === "undefined") {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'required',
|
||
|
message: this.translate("error_notset")
|
||
|
});
|
||
|
|
||
|
// Can't do any more validation at this point
|
||
|
return errors;
|
||
|
}
|
||
|
}
|
||
|
// Value not defined
|
||
|
else if(typeof value === "undefined") {
|
||
|
// If required_by_default is set, all fields are required
|
||
|
if(this.jsoneditor.options.required_by_default) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'required',
|
||
|
message: this.translate("error_notset")
|
||
|
});
|
||
|
}
|
||
|
// Not required, no further validation needed
|
||
|
else {
|
||
|
return errors;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `enum`
|
||
|
if(schema["enum"]) {
|
||
|
valid = false;
|
||
|
for(i=0; i<schema["enum"].length; i++) {
|
||
|
if(stringified === JSON.stringify(schema["enum"][i])) valid = true;
|
||
|
}
|
||
|
if(!valid) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'enum',
|
||
|
message: this.translate("error_enum")
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `extends` (version 3)
|
||
|
if(schema["extends"]) {
|
||
|
for(i=0; i<schema["extends"].length; i++) {
|
||
|
errors = errors.concat(this._validateSchema(schema["extends"][i],value,path));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `allOf`
|
||
|
if(schema.allOf) {
|
||
|
for(i=0; i<schema.allOf.length; i++) {
|
||
|
errors = errors.concat(this._validateSchema(schema.allOf[i],value,path));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `anyOf`
|
||
|
if(schema.anyOf) {
|
||
|
valid = false;
|
||
|
for(i=0; i<schema.anyOf.length; i++) {
|
||
|
if(!this._validateSchema(schema.anyOf[i],value,path).length) {
|
||
|
valid = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(!valid) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'anyOf',
|
||
|
message: this.translate('error_anyOf')
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `oneOf`
|
||
|
if(schema.oneOf) {
|
||
|
valid = 0;
|
||
|
var oneof_errors = [];
|
||
|
for(i=0; i<schema.oneOf.length; i++) {
|
||
|
// Set the error paths to be path.oneOf[i].rest.of.path
|
||
|
var tmp = this._validateSchema(schema.oneOf[i],value,path);
|
||
|
if(!tmp.length) {
|
||
|
valid++;
|
||
|
}
|
||
|
|
||
|
for(j=0; j<tmp.length; j++) {
|
||
|
tmp[j].path = path+'.oneOf['+i+']'+tmp[j].path.substr(path.length);
|
||
|
}
|
||
|
oneof_errors = oneof_errors.concat(tmp);
|
||
|
|
||
|
}
|
||
|
if(valid !== 1) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'oneOf',
|
||
|
message: this.translate('error_oneOf', [valid])
|
||
|
});
|
||
|
errors = errors.concat(oneof_errors);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `not`
|
||
|
if(schema.not) {
|
||
|
if(!this._validateSchema(schema.not,value,path).length) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'not',
|
||
|
message: this.translate('error_not')
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `type` (both Version 3 and Version 4 support)
|
||
|
if(schema.type) {
|
||
|
// Union type
|
||
|
if(Array.isArray(schema.type)) {
|
||
|
valid = false;
|
||
|
for(i=0;i<schema.type.length;i++) {
|
||
|
if(this._checkType(schema.type[i], value)) {
|
||
|
valid = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(!valid) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'type',
|
||
|
message: this.translate('error_type_union')
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
// Simple type
|
||
|
else {
|
||
|
if(!this._checkType(schema.type, value)) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'type',
|
||
|
message: this.translate('error_type', [schema.type])
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// `disallow` (version 3)
|
||
|
if(schema.disallow) {
|
||
|
// Union type
|
||
|
if(Array.isArray(schema.disallow)) {
|
||
|
valid = true;
|
||
|
for(i=0;i<schema.disallow.length;i++) {
|
||
|
if(this._checkType(schema.disallow[i], value)) {
|
||
|
valid = false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(!valid) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'disallow',
|
||
|
message: this.translate('error_disallow_union')
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
// Simple type
|
||
|
else {
|
||
|
if(this._checkType(schema.disallow, value)) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'disallow',
|
||
|
message: this.translate('error_disallow', [schema.disallow])
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Type Specific Validation
|
||
|
*/
|
||
|
|
||
|
// Number Specific Validation
|
||
|
if(typeof value === "number") {
|
||
|
// `multipleOf` and `divisibleBy`
|
||
|
if(schema.multipleOf || schema.divisibleBy) {
|
||
|
valid = value / (schema.multipleOf || schema.divisibleBy);
|
||
|
if(valid !== Math.floor(valid)) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: schema.multipleOf? 'multipleOf' : 'divisibleBy',
|
||
|
message: this.translate('error_multipleOf', [schema.multipleOf || schema.divisibleBy])
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `maximum`
|
||
|
if(schema.hasOwnProperty('maximum')) {
|
||
|
if(schema.exclusiveMaximum && value >= schema.maximum) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'maximum',
|
||
|
message: this.translate('error_maximum_excl', [schema.maximum])
|
||
|
});
|
||
|
}
|
||
|
else if(!schema.exclusiveMaximum && value > schema.maximum) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'maximum',
|
||
|
message: this.translate('error_maximum_incl', [schema.maximum])
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `minimum`
|
||
|
if(schema.hasOwnProperty('minimum')) {
|
||
|
if(schema.exclusiveMinimum && value <= schema.minimum) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'minimum',
|
||
|
message: this.translate('error_minimum_excl', [schema.minimum])
|
||
|
});
|
||
|
}
|
||
|
else if(!schema.exclusiveMinimum && value < schema.minimum) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'minimum',
|
||
|
message: this.translate('error_minimum_incl', [schema.minimum])
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// String specific validation
|
||
|
else if(typeof value === "string") {
|
||
|
// `maxLength`
|
||
|
if(schema.maxLength) {
|
||
|
if((value+"").length > schema.maxLength) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'maxLength',
|
||
|
message: this.translate('error_maxLength', [schema.maxLength])
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `minLength`
|
||
|
if(schema.minLength) {
|
||
|
if((value+"").length < schema.minLength) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'minLength',
|
||
|
message: this.translate((schema.minLength===1?'error_notempty':'error_minLength'), [schema.minLength])
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `pattern`
|
||
|
if(schema.pattern) {
|
||
|
if(!(new RegExp(schema.pattern)).test(value)) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'pattern',
|
||
|
message: this.translate('error_pattern')
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Array specific validation
|
||
|
else if(typeof value === "object" && value !== null && Array.isArray(value)) {
|
||
|
// `items` and `additionalItems`
|
||
|
if(schema.items) {
|
||
|
// `items` is an array
|
||
|
if(Array.isArray(schema.items)) {
|
||
|
for(i=0; i<value.length; i++) {
|
||
|
// If this item has a specific schema tied to it
|
||
|
// Validate against it
|
||
|
if(schema.items[i]) {
|
||
|
errors = errors.concat(this._validateSchema(schema.items[i],value[i],path+'.'+i));
|
||
|
}
|
||
|
// If all additional items are allowed
|
||
|
else if(schema.additionalItems === true) {
|
||
|
break;
|
||
|
}
|
||
|
// If additional items is a schema
|
||
|
// TODO: Incompatibility between version 3 and 4 of the spec
|
||
|
else if(schema.additionalItems) {
|
||
|
errors = errors.concat(this._validateSchema(schema.additionalItems,value[i],path+'.'+i));
|
||
|
}
|
||
|
// If no additional items are allowed
|
||
|
else if(schema.additionalItems === false) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'additionalItems',
|
||
|
message: this.translate('error_additionalItems')
|
||
|
});
|
||
|
break;
|
||
|
}
|
||
|
// Default for `additionalItems` is an empty schema
|
||
|
else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// `items` is a schema
|
||
|
else {
|
||
|
// Each item in the array must validate against the schema
|
||
|
for(i=0; i<value.length; i++) {
|
||
|
errors = errors.concat(this._validateSchema(schema.items,value[i],path+'.'+i));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `maxItems`
|
||
|
if(schema.maxItems) {
|
||
|
if(value.length > schema.maxItems) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'maxItems',
|
||
|
message: this.translate('error_maxItems', [schema.maxItems])
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `minItems`
|
||
|
if(schema.minItems) {
|
||
|
if(value.length < schema.minItems) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'minItems',
|
||
|
message: this.translate('error_minItems', [schema.minItems])
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `uniqueItems`
|
||
|
if(schema.uniqueItems) {
|
||
|
var seen = {};
|
||
|
for(i=0; i<value.length; i++) {
|
||
|
valid = JSON.stringify(value[i]);
|
||
|
if(seen[valid]) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'uniqueItems',
|
||
|
message: this.translate('error_uniqueItems')
|
||
|
});
|
||
|
break;
|
||
|
}
|
||
|
seen[valid] = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Object specific validation
|
||
|
else if(typeof value === "object" && value !== null) {
|
||
|
// `maxProperties`
|
||
|
if(schema.maxProperties) {
|
||
|
valid = 0;
|
||
|
for(i in value) {
|
||
|
if(!value.hasOwnProperty(i)) continue;
|
||
|
valid++;
|
||
|
}
|
||
|
if(valid > schema.maxProperties) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'maxProperties',
|
||
|
message: this.translate('error_maxProperties', [schema.maxProperties])
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `minProperties`
|
||
|
if(schema.minProperties) {
|
||
|
valid = 0;
|
||
|
for(i in value) {
|
||
|
if(!value.hasOwnProperty(i)) continue;
|
||
|
valid++;
|
||
|
}
|
||
|
if(valid < schema.minProperties) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'minProperties',
|
||
|
message: this.translate('error_minProperties', [schema.minProperties])
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Version 4 `required`
|
||
|
if(schema.required && Array.isArray(schema.required)) {
|
||
|
for(i=0; i<schema.required.length; i++) {
|
||
|
if(typeof value[schema.required[i]] === "undefined") {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'required',
|
||
|
message: this.translate('error_required', [schema.required[i]])
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `properties`
|
||
|
var validated_properties = {};
|
||
|
if(schema.properties) {
|
||
|
for(i in schema.properties) {
|
||
|
if(!schema.properties.hasOwnProperty(i)) continue;
|
||
|
validated_properties[i] = true;
|
||
|
errors = errors.concat(this._validateSchema(schema.properties[i],value[i],path+'.'+i));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `patternProperties`
|
||
|
if(schema.patternProperties) {
|
||
|
for(i in schema.patternProperties) {
|
||
|
if(!schema.patternProperties.hasOwnProperty(i)) continue;
|
||
|
|
||
|
var regex = new RegExp(i);
|
||
|
|
||
|
// Check which properties match
|
||
|
for(j in value) {
|
||
|
if(!value.hasOwnProperty(j)) continue;
|
||
|
if(regex.test(j)) {
|
||
|
validated_properties[j] = true;
|
||
|
errors = errors.concat(this._validateSchema(schema.patternProperties[i],value[j],path+'.'+j));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The no_additional_properties option currently doesn't work with extended schemas that use oneOf or anyOf
|
||
|
if(typeof schema.additionalProperties === "undefined" && this.jsoneditor.options.no_additional_properties && !schema.oneOf && !schema.anyOf) {
|
||
|
schema.additionalProperties = false;
|
||
|
}
|
||
|
|
||
|
// `additionalProperties`
|
||
|
if(typeof schema.additionalProperties !== "undefined") {
|
||
|
for(i in value) {
|
||
|
if(!value.hasOwnProperty(i)) continue;
|
||
|
if(!validated_properties[i]) {
|
||
|
// No extra properties allowed
|
||
|
if(!schema.additionalProperties) {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'additionalProperties',
|
||
|
message: this.translate('error_additional_properties', [i])
|
||
|
});
|
||
|
break;
|
||
|
}
|
||
|
// Allowed
|
||
|
else if(schema.additionalProperties === true) {
|
||
|
break;
|
||
|
}
|
||
|
// Must match schema
|
||
|
// TODO: incompatibility between version 3 and 4 of the spec
|
||
|
else {
|
||
|
errors = errors.concat(this._validateSchema(schema.additionalProperties,value[i],path+'.'+i));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `dependencies`
|
||
|
if(schema.dependencies) {
|
||
|
for(i in schema.dependencies) {
|
||
|
if(!schema.dependencies.hasOwnProperty(i)) continue;
|
||
|
|
||
|
// Doesn't need to meet the dependency
|
||
|
if(typeof value[i] === "undefined") continue;
|
||
|
|
||
|
// Property dependency
|
||
|
if(Array.isArray(schema.dependencies[i])) {
|
||
|
for(j=0; j<schema.dependencies[i].length; j++) {
|
||
|
if(typeof value[schema.dependencies[i][j]] === "undefined") {
|
||
|
errors.push({
|
||
|
path: path,
|
||
|
property: 'dependencies',
|
||
|
message: this.translate('error_dependency', [schema.dependencies[i][j]])
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Schema dependency
|
||
|
else {
|
||
|
errors = errors.concat(this._validateSchema(schema.dependencies[i],value,path));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Custom type validation (global)
|
||
|
$each(JSONEditor.defaults.custom_validators,function(i,validator) {
|
||
|
errors = errors.concat(validator.call(self,schema,value,path));
|
||
|
});
|
||
|
// Custom type validation (instance specific)
|
||
|
if(this.options.custom_validators) {
|
||
|
$each(this.options.custom_validators,function(i,validator) {
|
||
|
errors = errors.concat(validator.call(self,schema,value,path));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return errors;
|
||
|
},
|
||
|
_checkType: function(type, value) {
|
||
|
// Simple types
|
||
|
if(typeof type === "string") {
|
||
|
if(type==="string") return typeof value === "string";
|
||
|
else if(type==="number") return typeof value === "number";
|
||
|
else if(type==="integer") return typeof value === "number" && value === Math.floor(value);
|
||
|
else if(type==="boolean") return typeof value === "boolean";
|
||
|
else if(type==="array") return Array.isArray(value);
|
||
|
else if(type === "object") return value !== null && !(Array.isArray(value)) && typeof value === "object";
|
||
|
else if(type === "null") return value === null;
|
||
|
else return true;
|
||
|
}
|
||
|
// Schema
|
||
|
else {
|
||
|
return !this._validateSchema(type,value).length;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* All editors should extend from this class
|
||
|
*/
|
||
|
JSONEditor.AbstractEditor = Class.extend({
|
||
|
onChildEditorChange: function(editor) {
|
||
|
this.onChange(true);
|
||
|
},
|
||
|
notify: function() {
|
||
|
this.jsoneditor.notifyWatchers(this.path);
|
||
|
},
|
||
|
change: function() {
|
||
|
if(this.parent) this.parent.onChildEditorChange(this);
|
||
|
else this.jsoneditor.onChange();
|
||
|
},
|
||
|
onChange: function(bubble) {
|
||
|
this.notify();
|
||
|
if(this.watch_listener) this.watch_listener();
|
||
|
if(bubble) this.change();
|
||
|
},
|
||
|
register: function() {
|
||
|
this.jsoneditor.registerEditor(this);
|
||
|
this.onChange();
|
||
|
},
|
||
|
unregister: function() {
|
||
|
if(!this.jsoneditor) return;
|
||
|
this.jsoneditor.unregisterEditor(this);
|
||
|
},
|
||
|
getNumColumns: function() {
|
||
|
return 12;
|
||
|
},
|
||
|
init: function(options) {
|
||
|
this.jsoneditor = options.jsoneditor;
|
||
|
|
||
|
this.theme = this.jsoneditor.theme;
|
||
|
this.template_engine = this.jsoneditor.template;
|
||
|
this.iconlib = this.jsoneditor.iconlib;
|
||
|
|
||
|
this.original_schema = options.schema;
|
||
|
this.schema = this.jsoneditor.expandSchema(this.original_schema);
|
||
|
|
||
|
this.options = $extend({}, (this.options || {}), (options.schema.options || {}), options);
|
||
|
|
||
|
if(!options.path && !this.schema.id) this.schema.id = 'root';
|
||
|
this.path = options.path || 'root';
|
||
|
this.formname = options.formname || this.path.replace(/\.([^.]+)/g,'[$1]');
|
||
|
if(this.jsoneditor.options.form_name_root) this.formname = this.formname.replace(/^root\[/,this.jsoneditor.options.form_name_root+'[');
|
||
|
this.key = this.path.split('.').pop();
|
||
|
this.parent = options.parent;
|
||
|
|
||
|
this.link_watchers = [];
|
||
|
|
||
|
if(options.container) this.setContainer(options.container);
|
||
|
},
|
||
|
setContainer: function(container) {
|
||
|
this.container = container;
|
||
|
if(this.schema.id) this.container.setAttribute('data-schemaid',this.schema.id);
|
||
|
if(this.schema.type && typeof this.schema.type === "string") this.container.setAttribute('data-schematype',this.schema.type);
|
||
|
this.container.setAttribute('data-schemapath',this.path);
|
||
|
},
|
||
|
|
||
|
preBuild: function() {
|
||
|
|
||
|
},
|
||
|
build: function() {
|
||
|
|
||
|
},
|
||
|
postBuild: function() {
|
||
|
this.setupWatchListeners();
|
||
|
this.addLinks();
|
||
|
this.setValue(this.getDefault(), true);
|
||
|
this.updateHeaderText();
|
||
|
this.register();
|
||
|
this.onWatchedFieldChange();
|
||
|
},
|
||
|
|
||
|
setupWatchListeners: function() {
|
||
|
var self = this;
|
||
|
|
||
|
// Watched fields
|
||
|
this.watched = {};
|
||
|
if(this.schema.vars) this.schema.watch = this.schema.vars;
|
||
|
this.watched_values = {};
|
||
|
this.watch_listener = function() {
|
||
|
if(self.refreshWatchedFieldValues()) {
|
||
|
self.onWatchedFieldChange();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
this.register();
|
||
|
if(this.schema.hasOwnProperty('watch')) {
|
||
|
var path,path_parts,first,root,adjusted_path;
|
||
|
|
||
|
for(var name in this.schema.watch) {
|
||
|
if(!this.schema.watch.hasOwnProperty(name)) continue;
|
||
|
path = this.schema.watch[name];
|
||
|
|
||
|
if(Array.isArray(path)) {
|
||
|
path_parts = [path[0]].concat(path[1].split('.'));
|
||
|
}
|
||
|
else {
|
||
|
path_parts = path.split('.');
|
||
|
if(!self.theme.closest(self.container,'[data-schemaid="'+path_parts[0]+'"]')) path_parts.unshift('#');
|
||
|
}
|
||
|
first = path_parts.shift();
|
||
|
|
||
|
if(first === '#') first = self.jsoneditor.schema.id || 'root';
|
||
|
|
||
|
// Find the root node for this template variable
|
||
|
root = self.theme.closest(self.container,'[data-schemaid="'+first+'"]');
|
||
|
if(!root) throw "Could not find ancestor node with id "+first;
|
||
|
|
||
|
// Keep track of the root node and path for use when rendering the template
|
||
|
adjusted_path = root.getAttribute('data-schemapath') + '.' + path_parts.join('.');
|
||
|
|
||
|
self.jsoneditor.watch(adjusted_path,self.watch_listener);
|
||
|
|
||
|
self.watched[name] = adjusted_path;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Dynamic header
|
||
|
if(this.schema.headerTemplate) {
|
||
|
this.header_template = this.jsoneditor.compileTemplate(this.schema.headerTemplate, this.template_engine);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
addLinks: function() {
|
||
|
// Add links
|
||
|
if(!this.no_link_holder) {
|
||
|
this.link_holder = this.theme.getLinksHolder();
|
||
|
this.container.appendChild(this.link_holder);
|
||
|
if(this.schema.links) {
|
||
|
for(var i=0; i<this.schema.links.length; i++) {
|
||
|
this.addLink(this.getLink(this.schema.links[i]));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
|
||
|
getButton: function(text, icon, title) {
|
||
|
var btnClass = 'json-editor-btn-'+icon;
|
||
|
if(!this.iconlib) icon = null;
|
||
|
else icon = this.iconlib.getIcon(icon);
|
||
|
|
||
|
if(!icon && title) {
|
||
|
text = title;
|
||
|
title = null;
|
||
|
}
|
||
|
|
||
|
var btn = this.theme.getButton(text, icon, title);
|
||
|
btn.className += ' ' + btnClass + ' ';
|
||
|
return btn;
|
||
|
},
|
||
|
setButtonText: function(button, text, icon, title) {
|
||
|
if(!this.iconlib) icon = null;
|
||
|
else icon = this.iconlib.getIcon(icon);
|
||
|
|
||
|
if(!icon && title) {
|
||
|
text = title;
|
||
|
title = null;
|
||
|
}
|
||
|
|
||
|
return this.theme.setButtonText(button, text, icon, title);
|
||
|
},
|
||
|
addLink: function(link) {
|
||
|
if(this.link_holder) this.link_holder.appendChild(link);
|
||
|
},
|
||
|
getLink: function(data) {
|
||
|
var holder, link;
|
||
|
|
||
|
// Get mime type of the link
|
||
|
var mime = data.mediaType || 'application/javascript';
|
||
|
var type = mime.split('/')[0];
|
||
|
|
||
|
// Template to generate the link href
|
||
|
var href = this.jsoneditor.compileTemplate(data.href,this.template_engine);
|
||
|
|
||
|
// Image links
|
||
|
if(type === 'image') {
|
||
|
holder = this.theme.getBlockLinkHolder();
|
||
|
link = document.createElement('a');
|
||
|
link.setAttribute('target','_blank');
|
||
|
var image = document.createElement('img');
|
||
|
|
||
|
this.theme.createImageLink(holder,link,image);
|
||
|
|
||
|
// When a watched field changes, update the url
|
||
|
this.link_watchers.push(function(vars) {
|
||
|
var url = href(vars);
|
||
|
link.setAttribute('href',url);
|
||
|
link.setAttribute('title',data.rel || url);
|
||
|
image.setAttribute('src',url);
|
||
|
});
|
||
|
}
|
||
|
// Audio/Video links
|
||
|
else if(['audio','video'].indexOf(type) >=0) {
|
||
|
holder = this.theme.getBlockLinkHolder();
|
||
|
|
||
|
link = this.theme.getBlockLink();
|
||
|
link.setAttribute('target','_blank');
|
||
|
|
||
|
var media = document.createElement(type);
|
||
|
media.setAttribute('controls','controls');
|
||
|
|
||
|
this.theme.createMediaLink(holder,link,media);
|
||
|
|
||
|
// When a watched field changes, update the url
|
||
|
this.link_watchers.push(function(vars) {
|
||
|
var url = href(vars);
|
||
|
link.setAttribute('href',url);
|
||
|
link.textContent = data.rel || url;
|
||
|
media.setAttribute('src',url);
|
||
|
});
|
||
|
}
|
||
|
// Text links
|
||
|
else {
|
||
|
holder = this.theme.getBlockLink();
|
||
|
holder.setAttribute('target','_blank');
|
||
|
holder.textContent = data.rel;
|
||
|
|
||
|
// When a watched field changes, update the url
|
||
|
this.link_watchers.push(function(vars) {
|
||
|
var url = href(vars);
|
||
|
holder.setAttribute('href',url);
|
||
|
holder.textContent = data.rel || url;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return holder;
|
||
|
},
|
||
|
refreshWatchedFieldValues: function() {
|
||
|
if(!this.watched_values) return;
|
||
|
var watched = {};
|
||
|
var changed = false;
|
||
|
var self = this;
|
||
|
|
||
|
if(this.watched) {
|
||
|
var val,editor;
|
||
|
for(var name in this.watched) {
|
||
|
if(!this.watched.hasOwnProperty(name)) continue;
|
||
|
editor = self.jsoneditor.getEditor(this.watched[name]);
|
||
|
val = editor? editor.getValue() : null;
|
||
|
if(self.watched_values[name] !== val) changed = true;
|
||
|
watched[name] = val;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
watched.self = this.getValue();
|
||
|
if(this.watched_values.self !== watched.self) changed = true;
|
||
|
|
||
|
this.watched_values = watched;
|
||
|
|
||
|
return changed;
|
||
|
},
|
||
|
getWatchedFieldValues: function() {
|
||
|
return this.watched_values;
|
||
|
},
|
||
|
updateHeaderText: function() {
|
||
|
if(this.header) {
|
||
|
// If the header has children, only update the text node's value
|
||
|
if(this.header.children.length) {
|
||
|
for(var i=0; i<this.header.childNodes.length; i++) {
|
||
|
if(this.header.childNodes[i].nodeType===3) {
|
||
|
this.header.childNodes[i].nodeValue = this.getHeaderText();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Otherwise, just update the entire node
|
||
|
else {
|
||
|
this.header.textContent = this.getHeaderText();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
getHeaderText: function(title_only) {
|
||
|
if(this.header_text) return this.header_text;
|
||
|
else if(title_only) return this.schema.title;
|
||
|
else return this.getTitle();
|
||
|
},
|
||
|
onWatchedFieldChange: function() {
|
||
|
var vars;
|
||
|
if(this.header_template) {
|
||
|
vars = $extend(this.getWatchedFieldValues(),{
|
||
|
key: this.key,
|
||
|
i: this.key,
|
||
|
i0: (this.key*1),
|
||
|
i1: (this.key*1+1),
|
||
|
title: this.getTitle()
|
||
|
});
|
||
|
var header_text = this.header_template(vars);
|
||
|
|
||
|
if(header_text !== this.header_text) {
|
||
|
this.header_text = header_text;
|
||
|
this.updateHeaderText();
|
||
|
this.notify();
|
||
|
//this.fireChangeHeaderEvent();
|
||
|
}
|
||
|
}
|
||
|
if(this.link_watchers.length) {
|
||
|
vars = this.getWatchedFieldValues();
|
||
|
for(var i=0; i<this.link_watchers.length; i++) {
|
||
|
this.link_watchers[i](vars);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
setValue: function(value) {
|
||
|
this.value = value;
|
||
|
},
|
||
|
getValue: function() {
|
||
|
return this.value;
|
||
|
},
|
||
|
refreshValue: function() {
|
||
|
|
||
|
},
|
||
|
getChildEditors: function() {
|
||
|
return false;
|
||
|
},
|
||
|
destroy: function() {
|
||
|
var self = this;
|
||
|
this.unregister(this);
|
||
|
$each(this.watched,function(name,adjusted_path) {
|
||
|
self.jsoneditor.unwatch(adjusted_path,self.watch_listener);
|
||
|
});
|
||
|
this.watched = null;
|
||
|
this.watched_values = null;
|
||
|
this.watch_listener = null;
|
||
|
this.header_text = null;
|
||
|
this.header_template = null;
|
||
|
this.value = null;
|
||
|
if(this.container && this.container.parentNode) this.container.parentNode.removeChild(this.container);
|
||
|
this.container = null;
|
||
|
this.jsoneditor = null;
|
||
|
this.schema = null;
|
||
|
this.path = null;
|
||
|
this.key = null;
|
||
|
this.parent = null;
|
||
|
},
|
||
|
getDefault: function() {
|
||
|
if(this.schema["default"]) return this.schema["default"];
|
||
|
if(this.schema["enum"]) return this.schema["enum"][0];
|
||
|
|
||
|
var type = this.schema.type || this.schema.oneOf;
|
||
|
if(type && Array.isArray(type)) type = type[0];
|
||
|
if(type && typeof type === "object") type = type.type;
|
||
|
if(type && Array.isArray(type)) type = type[0];
|
||
|
|
||
|
if(typeof type === "string") {
|
||
|
if(type === "number") return 0.0;
|
||
|
if(type === "boolean") return false;
|
||
|
if(type === "integer") return 0;
|
||
|
if(type === "string") return "";
|
||
|
if(type === "object") return {};
|
||
|
if(type === "array") return [];
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
},
|
||
|
getTitle: function() {
|
||
|
return this.schema.title || this.key;
|
||
|
},
|
||
|
enable: function() {
|
||
|
this.disabled = false;
|
||
|
},
|
||
|
disable: function() {
|
||
|
this.disabled = true;
|
||
|
},
|
||
|
isEnabled: function() {
|
||
|
return !this.disabled;
|
||
|
},
|
||
|
isRequired: function() {
|
||
|
if(typeof this.schema.required === "boolean") return this.schema.required;
|
||
|
else if(this.parent && this.parent.schema && Array.isArray(this.parent.schema.required)) return this.parent.schema.required.indexOf(this.key) > -1;
|
||
|
else if(this.jsoneditor.options.required_by_default) return true;
|
||
|
else return false;
|
||
|
},
|
||
|
getDisplayText: function(arr) {
|
||
|
var disp = [];
|
||
|
var used = {};
|
||
|
|
||
|
// Determine how many times each attribute name is used.
|
||
|
// This helps us pick the most distinct display text for the schemas.
|
||
|
$each(arr,function(i,el) {
|
||
|
if(el.title) {
|
||
|
used[el.title] = used[el.title] || 0;
|
||
|
used[el.title]++;
|
||
|
}
|
||
|
if(el.description) {
|
||
|
used[el.description] = used[el.description] || 0;
|
||
|
used[el.description]++;
|
||
|
}
|
||
|
if(el.format) {
|
||
|
used[el.format] = used[el.format] || 0;
|
||
|
used[el.format]++;
|
||
|
}
|
||
|
if(el.type) {
|
||
|
used[el.type] = used[el.type] || 0;
|
||
|
used[el.type]++;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Determine display text for each element of the array
|
||
|
$each(arr,function(i,el) {
|
||
|
var name;
|
||
|
|
||
|
// If it's a simple string
|
||
|
if(typeof el === "string") name = el;
|
||
|
// Object
|
||
|
else if(el.title && used[el.title]<=1) name = el.title;
|
||
|
else if(el.format && used[el.format]<=1) name = el.format;
|
||
|
else if(el.type && used[el.type]<=1) name = el.type;
|
||
|
else if(el.description && used[el.description]<=1) name = el.descripton;
|
||
|
else if(el.title) name = el.title;
|
||
|
else if(el.format) name = el.format;
|
||
|
else if(el.type) name = el.type;
|
||
|
else if(el.description) name = el.description;
|
||
|
else if(JSON.stringify(el).length < 50) name = JSON.stringify(el);
|
||
|
else name = "type";
|
||
|
|
||
|
disp.push(name);
|
||
|
});
|
||
|
|
||
|
// Replace identical display text with "text 1", "text 2", etc.
|
||
|
var inc = {};
|
||
|
$each(disp,function(i,name) {
|
||
|
inc[name] = inc[name] || 0;
|
||
|
inc[name]++;
|
||
|
|
||
|
if(used[name] > 1) disp[i] = name + " " + inc[name];
|
||
|
});
|
||
|
|
||
|
return disp;
|
||
|
},
|
||
|
getOption: function(key) {
|
||
|
try {
|
||
|
throw "getOption is deprecated";
|
||
|
}
|
||
|
catch(e) {
|
||
|
window.console.error(e);
|
||
|
}
|
||
|
|
||
|
return this.options[key];
|
||
|
},
|
||
|
showValidationErrors: function(errors) {
|
||
|
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.editors["null"] = JSONEditor.AbstractEditor.extend({
|
||
|
getValue: function() {
|
||
|
return null;
|
||
|
},
|
||
|
setValue: function() {
|
||
|
this.onChange();
|
||
|
},
|
||
|
getNumColumns: function() {
|
||
|
return 2;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.editors.string = JSONEditor.AbstractEditor.extend({
|
||
|
register: function() {
|
||
|
this._super();
|
||
|
if(!this.input) return;
|
||
|
this.input.setAttribute('name',this.formname);
|
||
|
},
|
||
|
unregister: function() {
|
||
|
this._super();
|
||
|
if(!this.input) return;
|
||
|
this.input.removeAttribute('name');
|
||
|
},
|
||
|
setValue: function(value,initial,from_template) {
|
||
|
var self = this;
|
||
|
|
||
|
if(this.template && !from_template) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(value === null || typeof value === 'undefined') value = "";
|
||
|
else if(typeof value === "object") value = JSON.stringify(value);
|
||
|
else if(typeof value !== "string") value = ""+value;
|
||
|
|
||
|
if(value === this.serialized) return;
|
||
|
|
||
|
// Sanitize value before setting it
|
||
|
var sanitized = this.sanitize(value);
|
||
|
|
||
|
if(this.input.value === sanitized) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this.input.value = sanitized;
|
||
|
|
||
|
// If using SCEditor, update the WYSIWYG
|
||
|
if(this.sceditor_instance) {
|
||
|
this.sceditor_instance.val(sanitized);
|
||
|
}
|
||
|
else if(this.epiceditor) {
|
||
|
this.epiceditor.importFile(null,sanitized);
|
||
|
}
|
||
|
else if(this.ace_editor) {
|
||
|
this.ace_editor.setValue(sanitized);
|
||
|
}
|
||
|
|
||
|
var changed = from_template || this.getValue() !== value;
|
||
|
|
||
|
this.refreshValue();
|
||
|
|
||
|
if(initial) this.is_dirty = false;
|
||
|
else if(this.jsoneditor.options.show_errors === "change") this.is_dirty = true;
|
||
|
|
||
|
if(this.adjust_height) this.adjust_height(this.input);
|
||
|
|
||
|
// Bubble this setValue to parents if the value changed
|
||
|
this.onChange(changed);
|
||
|
},
|
||
|
getNumColumns: function() {
|
||
|
var min = Math.ceil(Math.max(this.getTitle().length,this.schema.maxLength||0,this.schema.minLength||0)/5);
|
||
|
var num;
|
||
|
|
||
|
if(this.input_type === 'textarea') num = 6;
|
||
|
else if(['text','email'].indexOf(this.input_type) >= 0) num = 4;
|
||
|
else num = 2;
|
||
|
|
||
|
return Math.min(12,Math.max(min,num));
|
||
|
},
|
||
|
build: function() {
|
||
|
var self = this, i;
|
||
|
if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
|
||
|
if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
|
||
|
|
||
|
this.format = this.schema.format;
|
||
|
if(!this.format && this.schema.media && this.schema.media.type) {
|
||
|
this.format = this.schema.media.type.replace(/(^(application|text)\/(x-)?(script\.)?)|(-source$)/g,'');
|
||
|
}
|
||
|
if(!this.format && this.options.default_format) {
|
||
|
this.format = this.options.default_format;
|
||
|
}
|
||
|
if(this.options.format) {
|
||
|
this.format = this.options.format;
|
||
|
}
|
||
|
|
||
|
// Specific format
|
||
|
if(this.format) {
|
||
|
// Text Area
|
||
|
if(this.format === 'textarea') {
|
||
|
this.input_type = 'textarea';
|
||
|
this.input = this.theme.getTextareaInput();
|
||
|
}
|
||
|
// Range Input
|
||
|
else if(this.format === 'range') {
|
||
|
this.input_type = 'range';
|
||
|
var min = this.schema.minimum || 0;
|
||
|
var max = this.schema.maximum || Math.max(100,min+1);
|
||
|
var step = 1;
|
||
|
if(this.schema.multipleOf) {
|
||
|
if(min%this.schema.multipleOf) min = Math.ceil(min/this.schema.multipleOf)*this.schema.multipleOf;
|
||
|
if(max%this.schema.multipleOf) max = Math.floor(max/this.schema.multipleOf)*this.schema.multipleOf;
|
||
|
step = this.schema.multipleOf;
|
||
|
}
|
||
|
|
||
|
this.input = this.theme.getRangeInput(min,max,step);
|
||
|
}
|
||
|
// Source Code
|
||
|
else if([
|
||
|
'actionscript',
|
||
|
'batchfile',
|
||
|
'bbcode',
|
||
|
'c',
|
||
|
'c++',
|
||
|
'cpp',
|
||
|
'coffee',
|
||
|
'csharp',
|
||
|
'css',
|
||
|
'dart',
|
||
|
'django',
|
||
|
'ejs',
|
||
|
'erlang',
|
||
|
'golang',
|
||
|
'handlebars',
|
||
|
'haskell',
|
||
|
'haxe',
|
||
|
'html',
|
||
|
'ini',
|
||
|
'jade',
|
||
|
'java',
|
||
|
'javascript',
|
||
|
'json',
|
||
|
'less',
|
||
|
'lisp',
|
||
|
'lua',
|
||
|
'makefile',
|
||
|
'markdown',
|
||
|
'matlab',
|
||
|
'mysql',
|
||
|
'objectivec',
|
||
|
'pascal',
|
||
|
'perl',
|
||
|
'pgsql',
|
||
|
'php',
|
||
|
'python',
|
||
|
'r',
|
||
|
'ruby',
|
||
|
'sass',
|
||
|
'scala',
|
||
|
'scss',
|
||
|
'smarty',
|
||
|
'sql',
|
||
|
'stylus',
|
||
|
'svg',
|
||
|
'twig',
|
||
|
'vbscript',
|
||
|
'xml',
|
||
|
'yaml'
|
||
|
].indexOf(this.format) >= 0
|
||
|
) {
|
||
|
this.input_type = this.format;
|
||
|
this.source_code = true;
|
||
|
|
||
|
this.input = this.theme.getTextareaInput();
|
||
|
}
|
||
|
// HTML5 Input type
|
||
|
else {
|
||
|
this.input_type = this.format;
|
||
|
this.input = this.theme.getFormInputField(this.input_type);
|
||
|
}
|
||
|
}
|
||
|
// Normal text input
|
||
|
else {
|
||
|
this.input_type = 'text';
|
||
|
this.input = this.theme.getFormInputField(this.input_type);
|
||
|
}
|
||
|
|
||
|
// minLength, maxLength, and pattern
|
||
|
if(typeof this.schema.maxLength !== "undefined") this.input.setAttribute('maxlength',this.schema.maxLength);
|
||
|
if(typeof this.schema.pattern !== "undefined") this.input.setAttribute('pattern',this.schema.pattern);
|
||
|
else if(typeof this.schema.minLength !== "undefined") this.input.setAttribute('pattern','.{'+this.schema.minLength+',}');
|
||
|
|
||
|
if(this.options.compact) {
|
||
|
this.container.className += ' compact';
|
||
|
}
|
||
|
else {
|
||
|
if(this.options.input_width) this.input.style.width = this.options.input_width;
|
||
|
}
|
||
|
|
||
|
if(this.schema.readOnly || this.schema.readonly || this.schema.template) {
|
||
|
this.always_disabled = true;
|
||
|
this.input.disabled = true;
|
||
|
}
|
||
|
|
||
|
this.input
|
||
|
.addEventListener('change',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
// Don't allow changing if this field is a template
|
||
|
if(self.schema.template) {
|
||
|
this.value = self.value;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var val = this.value;
|
||
|
|
||
|
// sanitize value
|
||
|
var sanitized = self.sanitize(val);
|
||
|
if(val !== sanitized) {
|
||
|
this.value = sanitized;
|
||
|
}
|
||
|
|
||
|
self.is_dirty = true;
|
||
|
|
||
|
self.refreshValue();
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
|
||
|
if(this.options.input_height) this.input.style.height = this.options.input_height;
|
||
|
if(this.options.expand_height) {
|
||
|
this.adjust_height = function(el) {
|
||
|
if(!el) return;
|
||
|
var i, ch=el.offsetHeight;
|
||
|
// Input too short
|
||
|
if(el.offsetHeight < el.scrollHeight) {
|
||
|
i=0;
|
||
|
while(el.offsetHeight < el.scrollHeight+3) {
|
||
|
if(i>100) break;
|
||
|
i++;
|
||
|
ch++;
|
||
|
el.style.height = ch+'px';
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
i=0;
|
||
|
while(el.offsetHeight >= el.scrollHeight+3) {
|
||
|
if(i>100) break;
|
||
|
i++;
|
||
|
ch--;
|
||
|
el.style.height = ch+'px';
|
||
|
}
|
||
|
el.style.height = (ch+1)+'px';
|
||
|
}
|
||
|
};
|
||
|
|
||
|
this.input.addEventListener('keyup',function(e) {
|
||
|
self.adjust_height(this);
|
||
|
});
|
||
|
this.input.addEventListener('change',function(e) {
|
||
|
self.adjust_height(this);
|
||
|
});
|
||
|
this.adjust_height();
|
||
|
}
|
||
|
|
||
|
if(this.format) this.input.setAttribute('data-schemaformat',this.format);
|
||
|
|
||
|
this.control = this.theme.getFormControl(this.label, this.input, this.description);
|
||
|
this.container.appendChild(this.control);
|
||
|
|
||
|
// Any special formatting that needs to happen after the input is added to the dom
|
||
|
window.requestAnimationFrame(function() {
|
||
|
// Skip in case the input is only a temporary editor,
|
||
|
// otherwise, in the case of an ace_editor creation,
|
||
|
// it will generate an error trying to append it to the missing parentNode
|
||
|
if(self.input.parentNode) self.afterInputReady();
|
||
|
if(self.adjust_height) self.adjust_height(self.input);
|
||
|
});
|
||
|
|
||
|
// Compile and store the template
|
||
|
if(this.schema.template) {
|
||
|
this.template = this.jsoneditor.compileTemplate(this.schema.template, this.template_engine);
|
||
|
this.refreshValue();
|
||
|
}
|
||
|
else {
|
||
|
this.refreshValue();
|
||
|
}
|
||
|
},
|
||
|
enable: function() {
|
||
|
if(!this.always_disabled) {
|
||
|
this.input.disabled = false;
|
||
|
// TODO: WYSIWYG and Markdown editors
|
||
|
}
|
||
|
this._super();
|
||
|
},
|
||
|
disable: function() {
|
||
|
this.input.disabled = true;
|
||
|
// TODO: WYSIWYG and Markdown editors
|
||
|
this._super();
|
||
|
},
|
||
|
afterInputReady: function() {
|
||
|
var self = this, options;
|
||
|
|
||
|
// Code editor
|
||
|
if(this.source_code) {
|
||
|
// WYSIWYG html and bbcode editor
|
||
|
if(this.options.wysiwyg &&
|
||
|
['html','bbcode'].indexOf(this.input_type) >= 0 &&
|
||
|
window.jQuery && window.jQuery.fn && window.jQuery.fn.sceditor
|
||
|
) {
|
||
|
options = $extend({},{
|
||
|
plugins: self.input_type==='html'? 'xhtml' : 'bbcode',
|
||
|
emoticonsEnabled: false,
|
||
|
width: '100%',
|
||
|
height: 300
|
||
|
},JSONEditor.plugins.sceditor,self.options.sceditor_options||{});
|
||
|
|
||
|
window.jQuery(self.input).sceditor(options);
|
||
|
|
||
|
self.sceditor_instance = window.jQuery(self.input).sceditor('instance');
|
||
|
|
||
|
self.sceditor_instance.blur(function() {
|
||
|
// Get editor's value
|
||
|
var val = window.jQuery("<div>"+self.sceditor_instance.val()+"</div>");
|
||
|
// Remove sceditor spans/divs
|
||
|
window.jQuery('#sceditor-start-marker,#sceditor-end-marker,.sceditor-nlf',val).remove();
|
||
|
// Set the value and update
|
||
|
self.input.value = val.html();
|
||
|
self.value = self.input.value;
|
||
|
self.is_dirty = true;
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
}
|
||
|
// EpicEditor for markdown (if it's loaded)
|
||
|
else if (this.input_type === 'markdown' && window.EpicEditor) {
|
||
|
this.epiceditor_container = document.createElement('div');
|
||
|
this.input.parentNode.insertBefore(this.epiceditor_container,this.input);
|
||
|
this.input.style.display = 'none';
|
||
|
|
||
|
options = $extend({},JSONEditor.plugins.epiceditor,{
|
||
|
container: this.epiceditor_container,
|
||
|
clientSideStorage: false
|
||
|
});
|
||
|
|
||
|
this.epiceditor = new window.EpicEditor(options).load();
|
||
|
|
||
|
this.epiceditor.importFile(null,this.getValue());
|
||
|
|
||
|
this.epiceditor.on('update',function() {
|
||
|
var val = self.epiceditor.exportFile();
|
||
|
self.input.value = val;
|
||
|
self.value = val;
|
||
|
self.is_dirty = true;
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
}
|
||
|
// ACE editor for everything else
|
||
|
else if(window.ace) {
|
||
|
var mode = this.input_type;
|
||
|
// aliases for c/cpp
|
||
|
if(mode === 'cpp' || mode === 'c++' || mode === 'c') {
|
||
|
mode = 'c_cpp';
|
||
|
}
|
||
|
|
||
|
this.ace_container = document.createElement('div');
|
||
|
this.ace_container.style.width = '100%';
|
||
|
this.ace_container.style.position = 'relative';
|
||
|
this.ace_container.style.height = '400px';
|
||
|
this.input.parentNode.insertBefore(this.ace_container,this.input);
|
||
|
this.input.style.display = 'none';
|
||
|
this.ace_editor = window.ace.edit(this.ace_container);
|
||
|
|
||
|
this.ace_editor.setValue(this.getValue());
|
||
|
|
||
|
// The theme
|
||
|
if(JSONEditor.plugins.ace.theme) this.ace_editor.setTheme('ace/theme/'+JSONEditor.plugins.ace.theme);
|
||
|
// The mode
|
||
|
mode = window.ace.require("ace/mode/"+mode);
|
||
|
if(mode) this.ace_editor.getSession().setMode(new mode.Mode());
|
||
|
|
||
|
// Listen for changes
|
||
|
this.ace_editor.on('change',function() {
|
||
|
var val = self.ace_editor.getValue();
|
||
|
self.input.value = val;
|
||
|
self.refreshValue();
|
||
|
self.is_dirty = true;
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
self.theme.afterInputReady(self.input);
|
||
|
},
|
||
|
refreshValue: function() {
|
||
|
this.value = this.input.value;
|
||
|
if(typeof this.value !== "string") this.value = '';
|
||
|
this.serialized = this.value;
|
||
|
},
|
||
|
destroy: function() {
|
||
|
// If using SCEditor, destroy the editor instance
|
||
|
if(this.sceditor_instance) {
|
||
|
this.sceditor_instance.destroy();
|
||
|
}
|
||
|
else if(this.epiceditor) {
|
||
|
this.epiceditor.unload();
|
||
|
}
|
||
|
else if(this.ace_editor) {
|
||
|
this.ace_editor.destroy();
|
||
|
}
|
||
|
|
||
|
|
||
|
this.template = null;
|
||
|
if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
|
||
|
if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
|
||
|
if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
|
||
|
|
||
|
this._super();
|
||
|
},
|
||
|
/**
|
||
|
* This is overridden in derivative editors
|
||
|
*/
|
||
|
sanitize: function(value) {
|
||
|
return value;
|
||
|
},
|
||
|
/**
|
||
|
* Re-calculates the value if needed
|
||
|
*/
|
||
|
onWatchedFieldChange: function() {
|
||
|
var self = this, vars, j;
|
||
|
|
||
|
// If this editor needs to be rendered by a macro template
|
||
|
if(this.template) {
|
||
|
vars = this.getWatchedFieldValues();
|
||
|
this.setValue(this.template(vars),false,true);
|
||
|
}
|
||
|
|
||
|
this._super();
|
||
|
},
|
||
|
showValidationErrors: function(errors) {
|
||
|
var self = this;
|
||
|
|
||
|
if(this.jsoneditor.options.show_errors === "always") {}
|
||
|
else if(!this.is_dirty && this.previous_error_setting===this.jsoneditor.options.show_errors) return;
|
||
|
|
||
|
this.previous_error_setting = this.jsoneditor.options.show_errors;
|
||
|
|
||
|
var messages = [];
|
||
|
$each(errors,function(i,error) {
|
||
|
if(error.path === self.path) {
|
||
|
messages.push(error.message);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
if(messages.length) {
|
||
|
this.theme.addInputError(this.input, messages.join('. ')+'.');
|
||
|
}
|
||
|
else {
|
||
|
this.theme.removeInputError(this.input);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.editors.number = JSONEditor.defaults.editors.string.extend({
|
||
|
sanitize: function(value) {
|
||
|
return (value+"").replace(/[^0-9\.\-eE]/g,'');
|
||
|
},
|
||
|
getNumColumns: function() {
|
||
|
return 2;
|
||
|
},
|
||
|
getValue: function() {
|
||
|
return this.value*1;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.editors.integer = JSONEditor.defaults.editors.number.extend({
|
||
|
sanitize: function(value) {
|
||
|
value = value + "";
|
||
|
return value.replace(/[^0-9\-]/g,'');
|
||
|
},
|
||
|
getNumColumns: function() {
|
||
|
return 2;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.editors.object = JSONEditor.AbstractEditor.extend({
|
||
|
getDefault: function() {
|
||
|
return $extend({},this.schema["default"] || {});
|
||
|
},
|
||
|
getChildEditors: function() {
|
||
|
return this.editors;
|
||
|
},
|
||
|
register: function() {
|
||
|
this._super();
|
||
|
if(this.editors) {
|
||
|
for(var i in this.editors) {
|
||
|
if(!this.editors.hasOwnProperty(i)) continue;
|
||
|
this.editors[i].register();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
unregister: function() {
|
||
|
this._super();
|
||
|
if(this.editors) {
|
||
|
for(var i in this.editors) {
|
||
|
if(!this.editors.hasOwnProperty(i)) continue;
|
||
|
this.editors[i].unregister();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
getNumColumns: function() {
|
||
|
return Math.max(Math.min(12,this.maxwidth),3);
|
||
|
},
|
||
|
enable: function() {
|
||
|
if(this.editjson_button) this.editjson_button.disabled = false;
|
||
|
if(this.addproperty_button) this.addproperty_button.disabled = false;
|
||
|
|
||
|
this._super();
|
||
|
if(this.editors) {
|
||
|
for(var i in this.editors) {
|
||
|
if(!this.editors.hasOwnProperty(i)) continue;
|
||
|
this.editors[i].enable();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
disable: function() {
|
||
|
if(this.editjson_button) this.editjson_button.disabled = true;
|
||
|
if(this.addproperty_button) this.addproperty_button.disabled = true;
|
||
|
this.hideEditJSON();
|
||
|
|
||
|
this._super();
|
||
|
if(this.editors) {
|
||
|
for(var i in this.editors) {
|
||
|
if(!this.editors.hasOwnProperty(i)) continue;
|
||
|
this.editors[i].disable();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
layoutEditors: function() {
|
||
|
var self = this, i, j;
|
||
|
|
||
|
if(!this.row_container) return;
|
||
|
|
||
|
// Sort editors by propertyOrder
|
||
|
this.property_order = Object.keys(this.editors);
|
||
|
this.property_order = this.property_order.sort(function(a,b) {
|
||
|
var ordera = self.editors[a].schema.propertyOrder;
|
||
|
var orderb = self.editors[b].schema.propertyOrder;
|
||
|
if(typeof ordera !== "number") ordera = 1000;
|
||
|
if(typeof orderb !== "number") orderb = 1000;
|
||
|
|
||
|
return ordera - orderb;
|
||
|
});
|
||
|
|
||
|
var container;
|
||
|
|
||
|
if(this.format === 'grid') {
|
||
|
var rows = [];
|
||
|
$each(this.property_order, function(j,key) {
|
||
|
var editor = self.editors[key];
|
||
|
if(editor.property_removed) return;
|
||
|
var found = false;
|
||
|
var width = editor.options.hidden? 0 : (editor.options.grid_columns || editor.getNumColumns());
|
||
|
var height = editor.options.hidden? 0 : editor.container.offsetHeight;
|
||
|
// See if the editor will fit in any of the existing rows first
|
||
|
for(var i=0; i<rows.length; i++) {
|
||
|
// If the editor will fit in the row horizontally
|
||
|
if(rows[i].width + width <= 12) {
|
||
|
// If the editor is close to the other elements in height
|
||
|
// i.e. Don't put a really tall editor in an otherwise short row or vice versa
|
||
|
if(!height || (rows[i].minh*0.5 < height && rows[i].maxh*2 > height)) {
|
||
|
found = i;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If there isn't a spot in any of the existing rows, start a new row
|
||
|
if(found === false) {
|
||
|
rows.push({
|
||
|
width: 0,
|
||
|
minh: 999999,
|
||
|
maxh: 0,
|
||
|
editors: []
|
||
|
});
|
||
|
found = rows.length-1;
|
||
|
}
|
||
|
|
||
|
rows[found].editors.push({
|
||
|
key: key,
|
||
|
//editor: editor,
|
||
|
width: width,
|
||
|
height: height
|
||
|
});
|
||
|
rows[found].width += width;
|
||
|
rows[found].minh = Math.min(rows[found].minh,height);
|
||
|
rows[found].maxh = Math.max(rows[found].maxh,height);
|
||
|
});
|
||
|
|
||
|
// Make almost full rows width 12
|
||
|
// Do this by increasing all editors' sizes proprotionately
|
||
|
// Any left over space goes to the biggest editor
|
||
|
// Don't touch rows with a width of 6 or less
|
||
|
for(i=0; i<rows.length; i++) {
|
||
|
if(rows[i].width < 12) {
|
||
|
var biggest = false;
|
||
|
var new_width = 0;
|
||
|
for(j=0; j<rows[i].editors.length; j++) {
|
||
|
if(biggest === false) biggest = j;
|
||
|
else if(rows[i].editors[j].width > rows[i].editors[biggest].width) biggest = j;
|
||
|
rows[i].editors[j].width *= 12/rows[i].width;
|
||
|
rows[i].editors[j].width = Math.floor(rows[i].editors[j].width);
|
||
|
new_width += rows[i].editors[j].width;
|
||
|
}
|
||
|
if(new_width < 12) rows[i].editors[biggest].width += 12-new_width;
|
||
|
rows[i].width = 12;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// layout hasn't changed
|
||
|
if(this.layout === JSON.stringify(rows)) return false;
|
||
|
this.layout = JSON.stringify(rows);
|
||
|
|
||
|
// Layout the form
|
||
|
container = document.createElement('div');
|
||
|
for(i=0; i<rows.length; i++) {
|
||
|
var row = this.theme.getGridRow();
|
||
|
container.appendChild(row);
|
||
|
for(j=0; j<rows[i].editors.length; j++) {
|
||
|
var key = rows[i].editors[j].key;
|
||
|
var editor = this.editors[key];
|
||
|
|
||
|
if(editor.options.hidden) editor.container.style.display = 'none';
|
||
|
else this.theme.setGridColumnSize(editor.container,rows[i].editors[j].width);
|
||
|
row.appendChild(editor.container);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Normal layout
|
||
|
else {
|
||
|
container = document.createElement('div');
|
||
|
$each(this.property_order, function(i,key) {
|
||
|
var editor = self.editors[key];
|
||
|
if(editor.property_removed) return;
|
||
|
var row = self.theme.getGridRow();
|
||
|
container.appendChild(row);
|
||
|
|
||
|
if(editor.options.hidden) editor.container.style.display = 'none';
|
||
|
else self.theme.setGridColumnSize(editor.container,12);
|
||
|
row.appendChild(editor.container);
|
||
|
});
|
||
|
}
|
||
|
this.row_container.innerHTML = '';
|
||
|
this.row_container.appendChild(container);
|
||
|
},
|
||
|
getPropertySchema: function(key) {
|
||
|
// Schema declared directly in properties
|
||
|
var schema = this.schema.properties[key] || {};
|
||
|
schema = $extend({},schema);
|
||
|
var matched = this.schema.properties[key]? true : false;
|
||
|
|
||
|
// Any matching patternProperties should be merged in
|
||
|
if(this.schema.patternProperties) {
|
||
|
for(var i in this.schema.patternProperties) {
|
||
|
if(!this.schema.patternProperties.hasOwnProperty(i)) continue;
|
||
|
var regex = new RegExp(i);
|
||
|
if(regex.test(key)) {
|
||
|
schema.allOf = schema.allOf || [];
|
||
|
schema.allOf.push(this.schema.patternProperties[i]);
|
||
|
matched = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Hasn't matched other rules, use additionalProperties schema
|
||
|
if(!matched && this.schema.additionalProperties && typeof this.schema.additionalProperties === "object") {
|
||
|
schema = $extend({},this.schema.additionalProperties);
|
||
|
}
|
||
|
|
||
|
return schema;
|
||
|
},
|
||
|
preBuild: function() {
|
||
|
this._super();
|
||
|
|
||
|
this.editors = {};
|
||
|
this.cached_editors = {};
|
||
|
var self = this;
|
||
|
|
||
|
this.format = this.options.layout || this.options.object_layout || this.schema.format || this.jsoneditor.options.object_layout || 'normal';
|
||
|
|
||
|
this.schema.properties = this.schema.properties || {};
|
||
|
|
||
|
this.minwidth = 0;
|
||
|
this.maxwidth = 0;
|
||
|
|
||
|
// If the object should be rendered as a table row
|
||
|
if(this.options.table_row) {
|
||
|
$each(this.schema.properties, function(key,schema) {
|
||
|
var editor = self.jsoneditor.getEditorClass(schema);
|
||
|
self.editors[key] = self.jsoneditor.createEditor(editor,{
|
||
|
jsoneditor: self.jsoneditor,
|
||
|
schema: schema,
|
||
|
path: self.path+'.'+key,
|
||
|
parent: self,
|
||
|
compact: true,
|
||
|
required: true
|
||
|
});
|
||
|
self.editors[key].preBuild();
|
||
|
|
||
|
var width = self.editors[key].options.hidden? 0 : (self.editors[key].options.grid_columns || self.editors[key].getNumColumns());
|
||
|
|
||
|
self.minwidth += width;
|
||
|
self.maxwidth += width;
|
||
|
});
|
||
|
this.no_link_holder = true;
|
||
|
}
|
||
|
// If the object should be rendered as a table
|
||
|
else if(this.options.table) {
|
||
|
// TODO: table display format
|
||
|
throw "Not supported yet";
|
||
|
}
|
||
|
// If the object should be rendered as a div
|
||
|
else {
|
||
|
this.defaultProperties = this.schema.defaultProperties || Object.keys(this.schema.properties);
|
||
|
|
||
|
// Increase the grid width to account for padding
|
||
|
self.maxwidth += 1;
|
||
|
|
||
|
$each(this.defaultProperties, function(i,key) {
|
||
|
self.addObjectProperty(key, true);
|
||
|
|
||
|
if(self.editors[key]) {
|
||
|
self.minwidth = Math.max(self.minwidth,(self.editors[key].options.grid_columns || self.editors[key].getNumColumns()));
|
||
|
self.maxwidth += (self.editors[key].options.grid_columns || self.editors[key].getNumColumns());
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Sort editors by propertyOrder
|
||
|
this.property_order = Object.keys(this.editors);
|
||
|
this.property_order = this.property_order.sort(function(a,b) {
|
||
|
var ordera = self.editors[a].schema.propertyOrder;
|
||
|
var orderb = self.editors[b].schema.propertyOrder;
|
||
|
if(typeof ordera !== "number") ordera = 1000;
|
||
|
if(typeof orderb !== "number") orderb = 1000;
|
||
|
|
||
|
return ordera - orderb;
|
||
|
});
|
||
|
},
|
||
|
build: function() {
|
||
|
var self = this;
|
||
|
|
||
|
// If the object should be rendered as a table row
|
||
|
if(this.options.table_row) {
|
||
|
this.editor_holder = this.container;
|
||
|
$each(this.editors, function(key,editor) {
|
||
|
var holder = self.theme.getTableCell();
|
||
|
self.editor_holder.appendChild(holder);
|
||
|
|
||
|
editor.setContainer(holder);
|
||
|
editor.build();
|
||
|
editor.postBuild();
|
||
|
|
||
|
if(self.editors[key].options.hidden) {
|
||
|
holder.style.display = 'none';
|
||
|
}
|
||
|
if(self.editors[key].options.input_width) {
|
||
|
holder.style.width = self.editors[key].options.input_width;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
// If the object should be rendered as a table
|
||
|
else if(this.options.table) {
|
||
|
// TODO: table display format
|
||
|
throw "Not supported yet";
|
||
|
}
|
||
|
// If the object should be rendered as a div
|
||
|
else {
|
||
|
this.header = document.createElement('span');
|
||
|
this.header.textContent = this.getTitle();
|
||
|
this.title = this.theme.getHeader(this.header);
|
||
|
this.container.appendChild(this.title);
|
||
|
this.container.style.position = 'relative';
|
||
|
|
||
|
// Edit JSON modal
|
||
|
this.editjson_holder = this.theme.getModal();
|
||
|
this.editjson_textarea = this.theme.getTextareaInput();
|
||
|
this.editjson_textarea.style.height = '170px';
|
||
|
this.editjson_textarea.style.width = '300px';
|
||
|
this.editjson_textarea.style.display = 'block';
|
||
|
this.editjson_save = this.getButton('Save','save','Save');
|
||
|
this.editjson_save.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
self.saveJSON();
|
||
|
});
|
||
|
this.editjson_cancel = this.getButton('Cancel','cancel','Cancel');
|
||
|
this.editjson_cancel.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
self.hideEditJSON();
|
||
|
});
|
||
|
this.editjson_holder.appendChild(this.editjson_textarea);
|
||
|
this.editjson_holder.appendChild(this.editjson_save);
|
||
|
this.editjson_holder.appendChild(this.editjson_cancel);
|
||
|
|
||
|
// Manage Properties modal
|
||
|
this.addproperty_holder = this.theme.getModal();
|
||
|
this.addproperty_list = document.createElement('div');
|
||
|
this.addproperty_list.style.width = '295px';
|
||
|
this.addproperty_list.style.maxHeight = '160px';
|
||
|
this.addproperty_list.style.padding = '5px 0';
|
||
|
this.addproperty_list.style.overflowY = 'auto';
|
||
|
this.addproperty_list.style.overflowX = 'hidden';
|
||
|
this.addproperty_list.style.paddingLeft = '5px';
|
||
|
this.addproperty_list.setAttribute('class', 'property-selector');
|
||
|
this.addproperty_add = this.getButton('add','add','add');
|
||
|
this.addproperty_input = this.theme.getFormInputField('text');
|
||
|
this.addproperty_input.setAttribute('placeholder','Property name...');
|
||
|
this.addproperty_input.style.width = '220px';
|
||
|
this.addproperty_input.style.marginBottom = '0';
|
||
|
this.addproperty_input.style.display = 'inline-block';
|
||
|
this.addproperty_add.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
if(self.addproperty_input.value) {
|
||
|
if(self.editors[self.addproperty_input.value]) {
|
||
|
window.alert('there is already a property with that name');
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
self.addObjectProperty(self.addproperty_input.value);
|
||
|
if(self.editors[self.addproperty_input.value]) {
|
||
|
self.editors[self.addproperty_input.value].disable();
|
||
|
}
|
||
|
self.onChange(true);
|
||
|
}
|
||
|
});
|
||
|
this.addproperty_holder.appendChild(this.addproperty_list);
|
||
|
this.addproperty_holder.appendChild(this.addproperty_input);
|
||
|
this.addproperty_holder.appendChild(this.addproperty_add);
|
||
|
var spacer = document.createElement('div');
|
||
|
spacer.style.clear = 'both';
|
||
|
this.addproperty_holder.appendChild(spacer);
|
||
|
|
||
|
|
||
|
// Description
|
||
|
if(this.schema.description) {
|
||
|
this.description = this.theme.getDescription(this.schema.description);
|
||
|
this.container.appendChild(this.description);
|
||
|
}
|
||
|
|
||
|
// Validation error placeholder area
|
||
|
this.error_holder = document.createElement('div');
|
||
|
this.container.appendChild(this.error_holder);
|
||
|
|
||
|
// Container for child editor area
|
||
|
this.editor_holder = this.theme.getIndentedPanel();
|
||
|
this.container.appendChild(this.editor_holder);
|
||
|
|
||
|
// Container for rows of child editors
|
||
|
this.row_container = this.theme.getGridContainer();
|
||
|
this.editor_holder.appendChild(this.row_container);
|
||
|
|
||
|
$each(this.editors, function(key,editor) {
|
||
|
var holder = self.theme.getGridColumn();
|
||
|
self.row_container.appendChild(holder);
|
||
|
|
||
|
editor.setContainer(holder);
|
||
|
editor.build();
|
||
|
editor.postBuild();
|
||
|
});
|
||
|
|
||
|
// Control buttons
|
||
|
this.title_controls = this.theme.getHeaderButtonHolder();
|
||
|
this.editjson_controls = this.theme.getHeaderButtonHolder();
|
||
|
this.addproperty_controls = this.theme.getHeaderButtonHolder();
|
||
|
this.title.appendChild(this.title_controls);
|
||
|
this.title.appendChild(this.editjson_controls);
|
||
|
this.title.appendChild(this.addproperty_controls);
|
||
|
|
||
|
// Show/Hide button
|
||
|
this.collapsed = false;
|
||
|
this.toggle_button = this.getButton('','collapse','Collapse');
|
||
|
this.title_controls.appendChild(this.toggle_button);
|
||
|
this.toggle_button.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
if(self.collapsed) {
|
||
|
self.editor_holder.style.display = '';
|
||
|
self.collapsed = false;
|
||
|
self.setButtonText(self.toggle_button,'','collapse','Collapse');
|
||
|
}
|
||
|
else {
|
||
|
self.editor_holder.style.display = 'none';
|
||
|
self.collapsed = true;
|
||
|
self.setButtonText(self.toggle_button,'','expand','Expand');
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// If it should start collapsed
|
||
|
if(this.options.collapsed) {
|
||
|
$trigger(this.toggle_button,'click');
|
||
|
}
|
||
|
|
||
|
// Collapse button disabled
|
||
|
if(this.schema.options && typeof this.schema.options.disable_collapse !== "undefined") {
|
||
|
if(this.schema.options.disable_collapse) this.toggle_button.style.display = 'none';
|
||
|
}
|
||
|
else if(this.jsoneditor.options.disable_collapse) {
|
||
|
this.toggle_button.style.display = 'none';
|
||
|
}
|
||
|
|
||
|
// Edit JSON Button
|
||
|
this.editjson_button = this.getButton('JSON','edit','Edit JSON');
|
||
|
this.editjson_button.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
self.toggleEditJSON();
|
||
|
});
|
||
|
this.editjson_controls.appendChild(this.editjson_button);
|
||
|
this.editjson_controls.appendChild(this.editjson_holder);
|
||
|
|
||
|
// Edit JSON Buttton disabled
|
||
|
if(this.schema.options && typeof this.schema.options.disable_edit_json !== "undefined") {
|
||
|
if(this.schema.options.disable_edit_json) this.editjson_button.style.display = 'none';
|
||
|
}
|
||
|
else if(this.jsoneditor.options.disable_edit_json) {
|
||
|
this.editjson_button.style.display = 'none';
|
||
|
}
|
||
|
|
||
|
// Object Properties Button
|
||
|
this.addproperty_button = this.getButton('Properties','edit','Object Properties');
|
||
|
this.addproperty_button.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
self.toggleAddProperty();
|
||
|
});
|
||
|
this.addproperty_controls.appendChild(this.addproperty_button);
|
||
|
this.addproperty_controls.appendChild(this.addproperty_holder);
|
||
|
this.refreshAddProperties();
|
||
|
}
|
||
|
|
||
|
// Fix table cell ordering
|
||
|
if(this.options.table_row) {
|
||
|
this.editor_holder = this.container;
|
||
|
$each(this.property_order,function(i,key) {
|
||
|
self.editor_holder.appendChild(self.editors[key].container);
|
||
|
});
|
||
|
}
|
||
|
// Layout object editors in grid if needed
|
||
|
else {
|
||
|
// Initial layout
|
||
|
this.layoutEditors();
|
||
|
// Do it again now that we know the approximate heights of elements
|
||
|
this.layoutEditors();
|
||
|
}
|
||
|
},
|
||
|
showEditJSON: function() {
|
||
|
if(!this.editjson_holder) return;
|
||
|
this.hideAddProperty();
|
||
|
|
||
|
// Position the form directly beneath the button
|
||
|
// TODO: edge detection
|
||
|
this.editjson_holder.style.left = this.editjson_button.offsetLeft+"px";
|
||
|
this.editjson_holder.style.top = this.editjson_button.offsetTop + this.editjson_button.offsetHeight+"px";
|
||
|
|
||
|
// Start the textarea with the current value
|
||
|
this.editjson_textarea.value = JSON.stringify(this.getValue(),null,2);
|
||
|
|
||
|
// Disable the rest of the form while editing JSON
|
||
|
this.disable();
|
||
|
|
||
|
this.editjson_holder.style.display = '';
|
||
|
this.editjson_button.disabled = false;
|
||
|
this.editing_json = true;
|
||
|
},
|
||
|
hideEditJSON: function() {
|
||
|
if(!this.editjson_holder) return;
|
||
|
if(!this.editing_json) return;
|
||
|
|
||
|
this.editjson_holder.style.display = 'none';
|
||
|
this.enable();
|
||
|
this.editing_json = false;
|
||
|
},
|
||
|
saveJSON: function() {
|
||
|
if(!this.editjson_holder) return;
|
||
|
|
||
|
try {
|
||
|
var json = JSON.parse(this.editjson_textarea.value);
|
||
|
this.setValue(json);
|
||
|
this.hideEditJSON();
|
||
|
}
|
||
|
catch(e) {
|
||
|
window.alert('invalid JSON');
|
||
|
throw e;
|
||
|
}
|
||
|
},
|
||
|
toggleEditJSON: function() {
|
||
|
if(this.editing_json) this.hideEditJSON();
|
||
|
else this.showEditJSON();
|
||
|
},
|
||
|
insertPropertyControlUsingPropertyOrder: function (property, control, container) {
|
||
|
var propertyOrder;
|
||
|
if (this.schema.properties[property])
|
||
|
propertyOrder = this.schema.properties[property].propertyOrder;
|
||
|
if (typeof propertyOrder !== "number") propertyOrder = 1000;
|
||
|
control.propertyOrder = propertyOrder;
|
||
|
|
||
|
for (var i = 0; i < container.childNodes.length; i++) {
|
||
|
var child = container.childNodes[i];
|
||
|
if (control.propertyOrder < child.propertyOrder) {
|
||
|
this.addproperty_list.insertBefore(control, child);
|
||
|
control = null;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (control) {
|
||
|
this.addproperty_list.appendChild(control);
|
||
|
}
|
||
|
},
|
||
|
addPropertyCheckbox: function(key) {
|
||
|
var self = this;
|
||
|
var checkbox, label, labelText, control;
|
||
|
|
||
|
checkbox = self.theme.getCheckbox();
|
||
|
checkbox.style.width = 'auto';
|
||
|
|
||
|
if (this.schema.properties[key] && this.schema.properties[key].title)
|
||
|
labelText = this.schema.properties[key].title;
|
||
|
else
|
||
|
labelText = key;
|
||
|
|
||
|
label = self.theme.getCheckboxLabel(labelText);
|
||
|
|
||
|
control = self.theme.getFormControl(label,checkbox);
|
||
|
control.style.paddingBottom = control.style.marginBottom = control.style.paddingTop = control.style.marginTop = 0;
|
||
|
control.style.height = 'auto';
|
||
|
//control.style.overflowY = 'hidden';
|
||
|
|
||
|
this.insertPropertyControlUsingPropertyOrder(key, control, this.addproperty_list);
|
||
|
|
||
|
checkbox.checked = key in this.editors;
|
||
|
checkbox.addEventListener('change',function() {
|
||
|
if(checkbox.checked) {
|
||
|
self.addObjectProperty(key);
|
||
|
}
|
||
|
else {
|
||
|
self.removeObjectProperty(key);
|
||
|
}
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
self.addproperty_checkboxes[key] = checkbox;
|
||
|
|
||
|
return checkbox;
|
||
|
},
|
||
|
showAddProperty: function() {
|
||
|
if(!this.addproperty_holder) return;
|
||
|
this.hideEditJSON();
|
||
|
|
||
|
// Position the form directly beneath the button
|
||
|
// TODO: edge detection
|
||
|
this.addproperty_holder.style.left = this.addproperty_button.offsetLeft+"px";
|
||
|
this.addproperty_holder.style.top = this.addproperty_button.offsetTop + this.addproperty_button.offsetHeight+"px";
|
||
|
|
||
|
// Disable the rest of the form while editing JSON
|
||
|
this.disable();
|
||
|
|
||
|
this.adding_property = true;
|
||
|
this.addproperty_button.disabled = false;
|
||
|
this.addproperty_holder.style.display = '';
|
||
|
this.refreshAddProperties();
|
||
|
},
|
||
|
hideAddProperty: function() {
|
||
|
if(!this.addproperty_holder) return;
|
||
|
if(!this.adding_property) return;
|
||
|
|
||
|
this.addproperty_holder.style.display = 'none';
|
||
|
this.enable();
|
||
|
|
||
|
this.adding_property = false;
|
||
|
},
|
||
|
toggleAddProperty: function() {
|
||
|
if(this.adding_property) this.hideAddProperty();
|
||
|
else this.showAddProperty();
|
||
|
},
|
||
|
removeObjectProperty: function(property) {
|
||
|
if(this.editors[property]) {
|
||
|
this.editors[property].unregister();
|
||
|
delete this.editors[property];
|
||
|
|
||
|
this.refreshValue();
|
||
|
this.layoutEditors();
|
||
|
}
|
||
|
},
|
||
|
addObjectProperty: function(name, prebuild_only) {
|
||
|
var self = this;
|
||
|
|
||
|
// Property is already added
|
||
|
if(this.editors[name]) return;
|
||
|
|
||
|
// Property was added before and is cached
|
||
|
if(this.cached_editors[name]) {
|
||
|
this.editors[name] = this.cached_editors[name];
|
||
|
if(prebuild_only) return;
|
||
|
this.editors[name].register();
|
||
|
}
|
||
|
// New property
|
||
|
else {
|
||
|
if(!this.canHaveAdditionalProperties() && (!this.schema.properties || !this.schema.properties[name])) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var schema = self.getPropertySchema(name);
|
||
|
|
||
|
|
||
|
// Add the property
|
||
|
var editor = self.jsoneditor.getEditorClass(schema);
|
||
|
|
||
|
self.editors[name] = self.jsoneditor.createEditor(editor,{
|
||
|
jsoneditor: self.jsoneditor,
|
||
|
schema: schema,
|
||
|
path: self.path+'.'+name,
|
||
|
parent: self
|
||
|
});
|
||
|
self.editors[name].preBuild();
|
||
|
|
||
|
if(!prebuild_only) {
|
||
|
var holder = self.theme.getChildEditorHolder();
|
||
|
self.editor_holder.appendChild(holder);
|
||
|
self.editors[name].setContainer(holder);
|
||
|
self.editors[name].build();
|
||
|
self.editors[name].postBuild();
|
||
|
}
|
||
|
|
||
|
self.cached_editors[name] = self.editors[name];
|
||
|
}
|
||
|
|
||
|
// If we're only prebuilding the editors, don't refresh values
|
||
|
if(!prebuild_only) {
|
||
|
self.refreshValue();
|
||
|
self.layoutEditors();
|
||
|
}
|
||
|
},
|
||
|
onChildEditorChange: function(editor) {
|
||
|
this.refreshValue();
|
||
|
this._super(editor);
|
||
|
},
|
||
|
canHaveAdditionalProperties: function() {
|
||
|
if (typeof this.schema.additionalProperties === "boolean") {
|
||
|
return this.schema.additionalProperties;
|
||
|
}
|
||
|
return !this.jsoneditor.options.no_additional_properties;
|
||
|
},
|
||
|
destroy: function() {
|
||
|
$each(this.cached_editors, function(i,el) {
|
||
|
el.destroy();
|
||
|
});
|
||
|
if(this.editor_holder) this.editor_holder.innerHTML = '';
|
||
|
if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
|
||
|
if(this.error_holder && this.error_holder.parentNode) this.error_holder.parentNode.removeChild(this.error_holder);
|
||
|
|
||
|
this.editors = null;
|
||
|
this.cached_editors = null;
|
||
|
if(this.editor_holder && this.editor_holder.parentNode) this.editor_holder.parentNode.removeChild(this.editor_holder);
|
||
|
this.editor_holder = null;
|
||
|
|
||
|
this._super();
|
||
|
},
|
||
|
getValue: function() {
|
||
|
var result = this._super();
|
||
|
if(this.jsoneditor.options.remove_empty_properties || this.options.remove_empty_properties) {
|
||
|
for(var i in result) {
|
||
|
if(result.hasOwnProperty(i)) {
|
||
|
if(!result[i]) delete result[i];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
},
|
||
|
refreshValue: function() {
|
||
|
this.value = {};
|
||
|
var self = this;
|
||
|
|
||
|
for(var i in this.editors) {
|
||
|
if(!this.editors.hasOwnProperty(i)) continue;
|
||
|
this.value[i] = this.editors[i].getValue();
|
||
|
}
|
||
|
|
||
|
if(this.adding_property) this.refreshAddProperties();
|
||
|
},
|
||
|
refreshAddProperties: function() {
|
||
|
if(this.options.disable_properties || (this.options.disable_properties !== false && this.jsoneditor.options.disable_properties)) {
|
||
|
this.addproperty_controls.style.display = 'none';
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var can_add = false, can_remove = false, num_props = 0, i, show_modal = false;
|
||
|
|
||
|
// Get number of editors
|
||
|
for(i in this.editors) {
|
||
|
if(!this.editors.hasOwnProperty(i)) continue;
|
||
|
num_props++;
|
||
|
}
|
||
|
|
||
|
// Determine if we can add back removed properties
|
||
|
can_add = this.canHaveAdditionalProperties() && !(typeof this.schema.maxProperties !== "undefined" && num_props >= this.schema.maxProperties);
|
||
|
|
||
|
if(this.addproperty_checkboxes) {
|
||
|
this.addproperty_list.innerHTML = '';
|
||
|
}
|
||
|
this.addproperty_checkboxes = {};
|
||
|
|
||
|
// Check for which editors can't be removed or added back
|
||
|
for(i in this.cached_editors) {
|
||
|
if(!this.cached_editors.hasOwnProperty(i)) continue;
|
||
|
|
||
|
this.addPropertyCheckbox(i);
|
||
|
|
||
|
if(this.isRequired(this.cached_editors[i]) && i in this.editors) {
|
||
|
this.addproperty_checkboxes[i].disabled = true;
|
||
|
}
|
||
|
|
||
|
if(typeof this.schema.minProperties !== "undefined" && num_props <= this.schema.minProperties) {
|
||
|
this.addproperty_checkboxes[i].disabled = this.addproperty_checkboxes[i].checked;
|
||
|
if(!this.addproperty_checkboxes[i].checked) show_modal = true;
|
||
|
}
|
||
|
else if(!(i in this.editors)) {
|
||
|
if(!can_add && !this.schema.properties.hasOwnProperty(i)) {
|
||
|
this.addproperty_checkboxes[i].disabled = true;
|
||
|
}
|
||
|
else {
|
||
|
this.addproperty_checkboxes[i].disabled = false;
|
||
|
show_modal = true;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
show_modal = true;
|
||
|
can_remove = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(this.canHaveAdditionalProperties()) {
|
||
|
show_modal = true;
|
||
|
}
|
||
|
|
||
|
// Additional addproperty checkboxes not tied to a current editor
|
||
|
for(i in this.schema.properties) {
|
||
|
if(!this.schema.properties.hasOwnProperty(i)) continue;
|
||
|
if(this.cached_editors[i]) continue;
|
||
|
show_modal = true;
|
||
|
this.addPropertyCheckbox(i);
|
||
|
}
|
||
|
|
||
|
// If no editors can be added or removed, hide the modal button
|
||
|
if(!show_modal) {
|
||
|
this.hideAddProperty();
|
||
|
this.addproperty_controls.style.display = 'none';
|
||
|
}
|
||
|
// If additional properties are disabled
|
||
|
else if(!this.canHaveAdditionalProperties()) {
|
||
|
this.addproperty_add.style.display = 'none';
|
||
|
this.addproperty_input.style.display = 'none';
|
||
|
}
|
||
|
// If no new properties can be added
|
||
|
else if(!can_add) {
|
||
|
this.addproperty_add.disabled = true;
|
||
|
}
|
||
|
// If new properties can be added
|
||
|
else {
|
||
|
this.addproperty_add.disabled = false;
|
||
|
}
|
||
|
},
|
||
|
isRequired: function(editor) {
|
||
|
if(typeof editor.schema.required === "boolean") return editor.schema.required;
|
||
|
else if(Array.isArray(this.schema.required)) return this.schema.required.indexOf(editor.key) > -1;
|
||
|
else if(this.jsoneditor.options.required_by_default) return true;
|
||
|
else return false;
|
||
|
},
|
||
|
setValue: function(value, initial) {
|
||
|
var self = this;
|
||
|
value = value || {};
|
||
|
|
||
|
if(typeof value !== "object" || Array.isArray(value)) value = {};
|
||
|
|
||
|
// First, set the values for all of the defined properties
|
||
|
$each(this.cached_editors, function(i,editor) {
|
||
|
// Value explicitly set
|
||
|
if(typeof value[i] !== "undefined") {
|
||
|
self.addObjectProperty(i);
|
||
|
editor.setValue(value[i],initial);
|
||
|
}
|
||
|
// Otherwise, remove value unless this is the initial set or it's required
|
||
|
else if(!initial && !self.isRequired(editor)) {
|
||
|
self.removeObjectProperty(i);
|
||
|
}
|
||
|
// Otherwise, set the value to the default
|
||
|
else {
|
||
|
editor.setValue(editor.getDefault(),initial);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
$each(value, function(i,val) {
|
||
|
if(!self.cached_editors[i]) {
|
||
|
self.addObjectProperty(i);
|
||
|
if(self.editors[i]) self.editors[i].setValue(val,initial);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
this.refreshValue();
|
||
|
this.layoutEditors();
|
||
|
this.onChange();
|
||
|
},
|
||
|
showValidationErrors: function(errors) {
|
||
|
var self = this;
|
||
|
|
||
|
// Get all the errors that pertain to this editor
|
||
|
var my_errors = [];
|
||
|
var other_errors = [];
|
||
|
$each(errors, function(i,error) {
|
||
|
if(error.path === self.path) {
|
||
|
my_errors.push(error);
|
||
|
}
|
||
|
else {
|
||
|
other_errors.push(error);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Show errors for this editor
|
||
|
if(this.error_holder) {
|
||
|
if(my_errors.length) {
|
||
|
var message = [];
|
||
|
this.error_holder.innerHTML = '';
|
||
|
this.error_holder.style.display = '';
|
||
|
$each(my_errors, function(i,error) {
|
||
|
self.error_holder.appendChild(self.theme.getErrorMessage(error.message));
|
||
|
});
|
||
|
}
|
||
|
// Hide error area
|
||
|
else {
|
||
|
this.error_holder.style.display = 'none';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Show error for the table row if this is inside a table
|
||
|
if(this.options.table_row) {
|
||
|
if(my_errors.length) {
|
||
|
this.theme.addTableRowError(this.container);
|
||
|
}
|
||
|
else {
|
||
|
this.theme.removeTableRowError(this.container);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Show errors for child editors
|
||
|
$each(this.editors, function(i,editor) {
|
||
|
editor.showValidationErrors(other_errors);
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.editors.array = JSONEditor.AbstractEditor.extend({
|
||
|
getDefault: function() {
|
||
|
return this.schema["default"] || [];
|
||
|
},
|
||
|
register: function() {
|
||
|
this._super();
|
||
|
if(this.rows) {
|
||
|
for(var i=0; i<this.rows.length; i++) {
|
||
|
this.rows[i].register();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
unregister: function() {
|
||
|
this._super();
|
||
|
if(this.rows) {
|
||
|
for(var i=0; i<this.rows.length; i++) {
|
||
|
this.rows[i].unregister();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
getNumColumns: function() {
|
||
|
var info = this.getItemInfo(0);
|
||
|
// Tabs require extra horizontal space
|
||
|
if(this.tabs_holder) {
|
||
|
return Math.max(Math.min(12,info.width+2),4);
|
||
|
}
|
||
|
else {
|
||
|
return info.width;
|
||
|
}
|
||
|
},
|
||
|
enable: function() {
|
||
|
if(this.add_row_button) this.add_row_button.disabled = false;
|
||
|
if(this.remove_all_rows_button) this.remove_all_rows_button.disabled = false;
|
||
|
if(this.delete_last_row_button) this.delete_last_row_button.disabled = false;
|
||
|
|
||
|
if(this.rows) {
|
||
|
for(var i=0; i<this.rows.length; i++) {
|
||
|
this.rows[i].enable();
|
||
|
|
||
|
if(this.rows[i].moveup_button) this.rows[i].moveup_button.disabled = false;
|
||
|
if(this.rows[i].movedown_button) this.rows[i].movedown_button.disabled = false;
|
||
|
if(this.rows[i].delete_button) this.rows[i].delete_button.disabled = false;
|
||
|
}
|
||
|
}
|
||
|
this._super();
|
||
|
},
|
||
|
disable: function() {
|
||
|
if(this.add_row_button) this.add_row_button.disabled = true;
|
||
|
if(this.remove_all_rows_button) this.remove_all_rows_button.disabled = true;
|
||
|
if(this.delete_last_row_button) this.delete_last_row_button.disabled = true;
|
||
|
|
||
|
if(this.rows) {
|
||
|
for(var i=0; i<this.rows.length; i++) {
|
||
|
this.rows[i].disable();
|
||
|
|
||
|
if(this.rows[i].moveup_button) this.rows[i].moveup_button.disabled = true;
|
||
|
if(this.rows[i].movedown_button) this.rows[i].movedown_button.disabled = true;
|
||
|
if(this.rows[i].delete_button) this.rows[i].delete_button.disabled = true;
|
||
|
}
|
||
|
}
|
||
|
this._super();
|
||
|
},
|
||
|
preBuild: function() {
|
||
|
this._super();
|
||
|
|
||
|
this.rows = [];
|
||
|
this.row_cache = [];
|
||
|
|
||
|
this.hide_delete_buttons = this.options.disable_array_delete || this.jsoneditor.options.disable_array_delete;
|
||
|
this.hide_move_buttons = this.options.disable_array_reorder || this.jsoneditor.options.disable_array_reorder;
|
||
|
this.hide_add_button = this.options.disable_array_add || this.jsoneditor.options.disable_array_add;
|
||
|
},
|
||
|
build: function() {
|
||
|
var self = this;
|
||
|
|
||
|
if(!this.options.compact) {
|
||
|
this.header = document.createElement('span');
|
||
|
this.header.textContent = this.getTitle();
|
||
|
this.title = this.theme.getHeader(this.header);
|
||
|
this.container.appendChild(this.title);
|
||
|
this.title_controls = this.theme.getHeaderButtonHolder();
|
||
|
this.title.appendChild(this.title_controls);
|
||
|
if(this.schema.description) {
|
||
|
this.description = this.theme.getDescription(this.schema.description);
|
||
|
this.container.appendChild(this.description);
|
||
|
}
|
||
|
this.error_holder = document.createElement('div');
|
||
|
this.container.appendChild(this.error_holder);
|
||
|
|
||
|
if(this.schema.format === 'tabs') {
|
||
|
this.controls = this.theme.getHeaderButtonHolder();
|
||
|
this.title.appendChild(this.controls);
|
||
|
this.tabs_holder = this.theme.getTabHolder();
|
||
|
this.container.appendChild(this.tabs_holder);
|
||
|
this.row_holder = this.theme.getTabContentHolder(this.tabs_holder);
|
||
|
|
||
|
this.active_tab = null;
|
||
|
}
|
||
|
else {
|
||
|
this.panel = this.theme.getIndentedPanel();
|
||
|
this.container.appendChild(this.panel);
|
||
|
this.row_holder = document.createElement('div');
|
||
|
this.panel.appendChild(this.row_holder);
|
||
|
this.controls = this.theme.getButtonHolder();
|
||
|
this.panel.appendChild(this.controls);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
this.panel = this.theme.getIndentedPanel();
|
||
|
this.container.appendChild(this.panel);
|
||
|
this.controls = this.theme.getButtonHolder();
|
||
|
this.panel.appendChild(this.controls);
|
||
|
this.row_holder = document.createElement('div');
|
||
|
this.panel.appendChild(this.row_holder);
|
||
|
}
|
||
|
|
||
|
// Add controls
|
||
|
this.addControls();
|
||
|
},
|
||
|
onChildEditorChange: function(editor) {
|
||
|
this.refreshValue();
|
||
|
this.refreshTabs(true);
|
||
|
this._super(editor);
|
||
|
},
|
||
|
getItemTitle: function() {
|
||
|
if(!this.item_title) {
|
||
|
if(this.schema.items && !Array.isArray(this.schema.items)) {
|
||
|
var tmp = this.jsoneditor.expandRefs(this.schema.items);
|
||
|
this.item_title = tmp.title || 'item';
|
||
|
}
|
||
|
else {
|
||
|
this.item_title = 'item';
|
||
|
}
|
||
|
}
|
||
|
return this.item_title;
|
||
|
},
|
||
|
getItemSchema: function(i) {
|
||
|
if(Array.isArray(this.schema.items)) {
|
||
|
if(i >= this.schema.items.length) {
|
||
|
if(this.schema.additionalItems===true) {
|
||
|
return {};
|
||
|
}
|
||
|
else if(this.schema.additionalItems) {
|
||
|
return $extend({},this.schema.additionalItems);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
return $extend({},this.schema.items[i]);
|
||
|
}
|
||
|
}
|
||
|
else if(this.schema.items) {
|
||
|
return $extend({},this.schema.items);
|
||
|
}
|
||
|
else {
|
||
|
return {};
|
||
|
}
|
||
|
},
|
||
|
getItemInfo: function(i) {
|
||
|
var schema = this.getItemSchema(i);
|
||
|
|
||
|
// Check if it's cached
|
||
|
this.item_info = this.item_info || {};
|
||
|
var stringified = JSON.stringify(schema);
|
||
|
if(typeof this.item_info[stringified] !== "undefined") return this.item_info[stringified];
|
||
|
|
||
|
// Get the schema for this item
|
||
|
schema = this.jsoneditor.expandRefs(schema);
|
||
|
|
||
|
this.item_info[stringified] = {
|
||
|
title: schema.title || "item",
|
||
|
'default': schema["default"],
|
||
|
width: 12,
|
||
|
child_editors: schema.properties || schema.items
|
||
|
};
|
||
|
|
||
|
return this.item_info[stringified];
|
||
|
},
|
||
|
getElementEditor: function(i) {
|
||
|
var item_info = this.getItemInfo(i);
|
||
|
var schema = this.getItemSchema(i);
|
||
|
schema = this.jsoneditor.expandRefs(schema);
|
||
|
schema.title = item_info.title+' '+(i+1);
|
||
|
|
||
|
var editor = this.jsoneditor.getEditorClass(schema);
|
||
|
|
||
|
var holder;
|
||
|
if(this.tabs_holder) {
|
||
|
holder = this.theme.getTabContent();
|
||
|
}
|
||
|
else if(item_info.child_editors) {
|
||
|
holder = this.theme.getChildEditorHolder();
|
||
|
}
|
||
|
else {
|
||
|
holder = this.theme.getIndentedPanel();
|
||
|
}
|
||
|
|
||
|
this.row_holder.appendChild(holder);
|
||
|
|
||
|
var ret = this.jsoneditor.createEditor(editor,{
|
||
|
jsoneditor: this.jsoneditor,
|
||
|
schema: schema,
|
||
|
container: holder,
|
||
|
path: this.path+'.'+i,
|
||
|
parent: this,
|
||
|
required: true
|
||
|
});
|
||
|
ret.preBuild();
|
||
|
ret.build();
|
||
|
ret.postBuild();
|
||
|
|
||
|
if(!ret.title_controls) {
|
||
|
ret.array_controls = this.theme.getButtonHolder();
|
||
|
holder.appendChild(ret.array_controls);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
},
|
||
|
destroy: function() {
|
||
|
this.empty(true);
|
||
|
if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
|
||
|
if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
|
||
|
if(this.row_holder && this.row_holder.parentNode) this.row_holder.parentNode.removeChild(this.row_holder);
|
||
|
if(this.controls && this.controls.parentNode) this.controls.parentNode.removeChild(this.controls);
|
||
|
if(this.panel && this.panel.parentNode) this.panel.parentNode.removeChild(this.panel);
|
||
|
|
||
|
this.rows = this.row_cache = this.title = this.description = this.row_holder = this.panel = this.controls = null;
|
||
|
|
||
|
this._super();
|
||
|
},
|
||
|
empty: function(hard) {
|
||
|
if(!this.rows) return;
|
||
|
var self = this;
|
||
|
$each(this.rows,function(i,row) {
|
||
|
if(hard) {
|
||
|
if(row.tab && row.tab.parentNode) row.tab.parentNode.removeChild(row.tab);
|
||
|
self.destroyRow(row,true);
|
||
|
self.row_cache[i] = null;
|
||
|
}
|
||
|
self.rows[i] = null;
|
||
|
});
|
||
|
self.rows = [];
|
||
|
if(hard) self.row_cache = [];
|
||
|
},
|
||
|
destroyRow: function(row,hard) {
|
||
|
var holder = row.container;
|
||
|
if(hard) {
|
||
|
row.destroy();
|
||
|
if(holder.parentNode) holder.parentNode.removeChild(holder);
|
||
|
if(row.tab && row.tab.parentNode) row.tab.parentNode.removeChild(row.tab);
|
||
|
}
|
||
|
else {
|
||
|
if(row.tab) row.tab.style.display = 'none';
|
||
|
holder.style.display = 'none';
|
||
|
row.unregister();
|
||
|
}
|
||
|
},
|
||
|
getMax: function() {
|
||
|
if((Array.isArray(this.schema.items)) && this.schema.additionalItems === false) {
|
||
|
return Math.min(this.schema.items.length,this.schema.maxItems || Infinity);
|
||
|
}
|
||
|
else {
|
||
|
return this.schema.maxItems || Infinity;
|
||
|
}
|
||
|
},
|
||
|
refreshTabs: function(refresh_headers) {
|
||
|
var self = this;
|
||
|
$each(this.rows, function(i,row) {
|
||
|
if(!row.tab) return;
|
||
|
|
||
|
if(refresh_headers) {
|
||
|
row.tab_text.textContent = row.getHeaderText();
|
||
|
}
|
||
|
else {
|
||
|
if(row.tab === self.active_tab) {
|
||
|
self.theme.markTabActive(row.tab);
|
||
|
row.container.style.display = '';
|
||
|
}
|
||
|
else {
|
||
|
self.theme.markTabInactive(row.tab);
|
||
|
row.container.style.display = 'none';
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
},
|
||
|
setValue: function(value, initial) {
|
||
|
// Update the array's value, adding/removing rows when necessary
|
||
|
value = value || [];
|
||
|
|
||
|
if(!(Array.isArray(value))) value = [value];
|
||
|
|
||
|
var serialized = JSON.stringify(value);
|
||
|
if(serialized === this.serialized) return;
|
||
|
|
||
|
// Make sure value has between minItems and maxItems items in it
|
||
|
if(this.schema.minItems) {
|
||
|
while(value.length < this.schema.minItems) {
|
||
|
value.push(this.getItemInfo(value.length)["default"]);
|
||
|
}
|
||
|
}
|
||
|
if(this.getMax() && value.length > this.getMax()) {
|
||
|
value = value.slice(0,this.getMax());
|
||
|
}
|
||
|
|
||
|
var self = this;
|
||
|
$each(value,function(i,val) {
|
||
|
if(self.rows[i]) {
|
||
|
// TODO: don't set the row's value if it hasn't changed
|
||
|
self.rows[i].setValue(val,initial);
|
||
|
}
|
||
|
else if(self.row_cache[i]) {
|
||
|
self.rows[i] = self.row_cache[i];
|
||
|
self.rows[i].setValue(val,initial);
|
||
|
self.rows[i].container.style.display = '';
|
||
|
if(self.rows[i].tab) self.rows[i].tab.style.display = '';
|
||
|
self.rows[i].register();
|
||
|
}
|
||
|
else {
|
||
|
self.addRow(val,initial);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
for(var j=value.length; j<self.rows.length; j++) {
|
||
|
self.destroyRow(self.rows[j]);
|
||
|
self.rows[j] = null;
|
||
|
}
|
||
|
self.rows = self.rows.slice(0,value.length);
|
||
|
|
||
|
// Set the active tab
|
||
|
var new_active_tab = null;
|
||
|
$each(self.rows, function(i,row) {
|
||
|
if(row.tab === self.active_tab) {
|
||
|
new_active_tab = row.tab;
|
||
|
return false;
|
||
|
}
|
||
|
});
|
||
|
if(!new_active_tab && self.rows.length) new_active_tab = self.rows[0].tab;
|
||
|
|
||
|
self.active_tab = new_active_tab;
|
||
|
|
||
|
self.refreshValue(initial);
|
||
|
self.refreshTabs(true);
|
||
|
self.refreshTabs();
|
||
|
|
||
|
self.onChange();
|
||
|
|
||
|
// TODO: sortable
|
||
|
},
|
||
|
refreshValue: function(force) {
|
||
|
var self = this;
|
||
|
var oldi = this.value? this.value.length : 0;
|
||
|
this.value = [];
|
||
|
|
||
|
$each(this.rows,function(i,editor) {
|
||
|
// Get the value for this editor
|
||
|
self.value[i] = editor.getValue();
|
||
|
});
|
||
|
|
||
|
if(oldi !== this.value.length || force) {
|
||
|
// If we currently have minItems items in the array
|
||
|
var minItems = this.schema.minItems && this.schema.minItems >= this.rows.length;
|
||
|
|
||
|
$each(this.rows,function(i,editor) {
|
||
|
// Hide the move down button for the last row
|
||
|
if(editor.movedown_button) {
|
||
|
if(i === self.rows.length - 1) {
|
||
|
editor.movedown_button.style.display = 'none';
|
||
|
}
|
||
|
else {
|
||
|
editor.movedown_button.style.display = '';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Hide the delete button if we have minItems items
|
||
|
if(editor.delete_button) {
|
||
|
if(minItems) {
|
||
|
editor.delete_button.style.display = 'none';
|
||
|
}
|
||
|
else {
|
||
|
editor.delete_button.style.display = '';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Get the value for this editor
|
||
|
self.value[i] = editor.getValue();
|
||
|
});
|
||
|
|
||
|
var controls_needed = false;
|
||
|
|
||
|
if(!this.value.length) {
|
||
|
this.delete_last_row_button.style.display = 'none';
|
||
|
this.remove_all_rows_button.style.display = 'none';
|
||
|
}
|
||
|
else if(this.value.length === 1) {
|
||
|
this.remove_all_rows_button.style.display = 'none';
|
||
|
|
||
|
// If there are minItems items in the array, hide the delete button beneath the rows
|
||
|
if(minItems || this.hide_delete_buttons) {
|
||
|
this.delete_last_row_button.style.display = 'none';
|
||
|
}
|
||
|
else {
|
||
|
this.delete_last_row_button.style.display = '';
|
||
|
controls_needed = true;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// If there are minItems items in the array, hide the delete button beneath the rows
|
||
|
if(minItems || this.hide_delete_buttons) {
|
||
|
this.delete_last_row_button.style.display = 'none';
|
||
|
this.remove_all_rows_button.style.display = 'none';
|
||
|
}
|
||
|
else {
|
||
|
this.delete_last_row_button.style.display = '';
|
||
|
this.remove_all_rows_button.style.display = '';
|
||
|
controls_needed = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If there are maxItems in the array, hide the add button beneath the rows
|
||
|
if((this.getMax() && this.getMax() <= this.rows.length) || this.hide_add_button){
|
||
|
this.add_row_button.style.display = 'none';
|
||
|
}
|
||
|
else {
|
||
|
this.add_row_button.style.display = '';
|
||
|
controls_needed = true;
|
||
|
}
|
||
|
|
||
|
if(!this.collapsed && controls_needed) {
|
||
|
this.controls.style.display = 'inline-block';
|
||
|
}
|
||
|
else {
|
||
|
this.controls.style.display = 'none';
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
addRow: function(value, initial) {
|
||
|
var self = this;
|
||
|
var i = this.rows.length;
|
||
|
|
||
|
self.rows[i] = this.getElementEditor(i);
|
||
|
self.row_cache[i] = self.rows[i];
|
||
|
|
||
|
if(self.tabs_holder) {
|
||
|
self.rows[i].tab_text = document.createElement('span');
|
||
|
self.rows[i].tab_text.textContent = self.rows[i].getHeaderText();
|
||
|
self.rows[i].tab = self.theme.getTab(self.rows[i].tab_text);
|
||
|
self.rows[i].tab.addEventListener('click', function(e) {
|
||
|
self.active_tab = self.rows[i].tab;
|
||
|
self.refreshTabs();
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
});
|
||
|
|
||
|
self.theme.addTab(self.tabs_holder, self.rows[i].tab);
|
||
|
}
|
||
|
|
||
|
var controls_holder = self.rows[i].title_controls || self.rows[i].array_controls;
|
||
|
|
||
|
// Buttons to delete row, move row up, and move row down
|
||
|
if(!self.hide_delete_buttons) {
|
||
|
self.rows[i].delete_button = this.getButton(self.getItemTitle(),'delete','Delete '+self.getItemTitle());
|
||
|
self.rows[i].delete_button.className += ' delete';
|
||
|
self.rows[i].delete_button.setAttribute('data-i',i);
|
||
|
self.rows[i].delete_button.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
var i = this.getAttribute('data-i')*1;
|
||
|
|
||
|
var value = self.getValue();
|
||
|
|
||
|
var newval = [];
|
||
|
var new_active_tab = null;
|
||
|
$each(value,function(j,row) {
|
||
|
if(j===i) {
|
||
|
// If the one we're deleting is the active tab
|
||
|
if(self.rows[j].tab === self.active_tab) {
|
||
|
// Make the next tab active if there is one
|
||
|
// Note: the next tab is going to be the current tab after deletion
|
||
|
if(self.rows[j+1]) new_active_tab = self.rows[j].tab;
|
||
|
// Otherwise, make the previous tab active if there is one
|
||
|
else if(j) new_active_tab = self.rows[j-1].tab;
|
||
|
}
|
||
|
|
||
|
return; // If this is the one we're deleting
|
||
|
}
|
||
|
newval.push(row);
|
||
|
});
|
||
|
self.setValue(newval);
|
||
|
if(new_active_tab) {
|
||
|
self.active_tab = new_active_tab;
|
||
|
self.refreshTabs();
|
||
|
}
|
||
|
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
|
||
|
if(controls_holder) {
|
||
|
controls_holder.appendChild(self.rows[i].delete_button);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(i && !self.hide_move_buttons) {
|
||
|
self.rows[i].moveup_button = this.getButton('','moveup','Move up');
|
||
|
self.rows[i].moveup_button.className += ' moveup';
|
||
|
self.rows[i].moveup_button.setAttribute('data-i',i);
|
||
|
self.rows[i].moveup_button.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
var i = this.getAttribute('data-i')*1;
|
||
|
|
||
|
if(i<=0) return;
|
||
|
var rows = self.getValue();
|
||
|
var tmp = rows[i-1];
|
||
|
rows[i-1] = rows[i];
|
||
|
rows[i] = tmp;
|
||
|
|
||
|
self.setValue(rows);
|
||
|
self.active_tab = self.rows[i-1].tab;
|
||
|
self.refreshTabs();
|
||
|
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
|
||
|
if(controls_holder) {
|
||
|
controls_holder.appendChild(self.rows[i].moveup_button);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(!self.hide_move_buttons) {
|
||
|
self.rows[i].movedown_button = this.getButton('','movedown','Move down');
|
||
|
self.rows[i].movedown_button.className += ' movedown';
|
||
|
self.rows[i].movedown_button.setAttribute('data-i',i);
|
||
|
self.rows[i].movedown_button.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
var i = this.getAttribute('data-i')*1;
|
||
|
|
||
|
var rows = self.getValue();
|
||
|
if(i>=rows.length-1) return;
|
||
|
var tmp = rows[i+1];
|
||
|
rows[i+1] = rows[i];
|
||
|
rows[i] = tmp;
|
||
|
|
||
|
self.setValue(rows);
|
||
|
self.active_tab = self.rows[i+1].tab;
|
||
|
self.refreshTabs();
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
|
||
|
if(controls_holder) {
|
||
|
controls_holder.appendChild(self.rows[i].movedown_button);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(value) self.rows[i].setValue(value, initial);
|
||
|
self.refreshTabs();
|
||
|
},
|
||
|
addControls: function() {
|
||
|
var self = this;
|
||
|
|
||
|
this.collapsed = false;
|
||
|
this.toggle_button = this.getButton('','collapse','Collapse');
|
||
|
this.title_controls.appendChild(this.toggle_button);
|
||
|
var row_holder_display = self.row_holder.style.display;
|
||
|
var controls_display = self.controls.style.display;
|
||
|
this.toggle_button.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
if(self.collapsed) {
|
||
|
self.collapsed = false;
|
||
|
if(self.panel) self.panel.style.display = '';
|
||
|
self.row_holder.style.display = row_holder_display;
|
||
|
if(self.tabs_holder) self.tabs_holder.style.display = '';
|
||
|
self.controls.style.display = controls_display;
|
||
|
self.setButtonText(this,'','collapse','Collapse');
|
||
|
}
|
||
|
else {
|
||
|
self.collapsed = true;
|
||
|
self.row_holder.style.display = 'none';
|
||
|
if(self.tabs_holder) self.tabs_holder.style.display = 'none';
|
||
|
self.controls.style.display = 'none';
|
||
|
if(self.panel) self.panel.style.display = 'none';
|
||
|
self.setButtonText(this,'','expand','Expand');
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// If it should start collapsed
|
||
|
if(this.options.collapsed) {
|
||
|
$trigger(this.toggle_button,'click');
|
||
|
}
|
||
|
|
||
|
// Collapse button disabled
|
||
|
if(this.schema.options && typeof this.schema.options.disable_collapse !== "undefined") {
|
||
|
if(this.schema.options.disable_collapse) this.toggle_button.style.display = 'none';
|
||
|
}
|
||
|
else if(this.jsoneditor.options.disable_collapse) {
|
||
|
this.toggle_button.style.display = 'none';
|
||
|
}
|
||
|
|
||
|
// Add "new row" and "delete last" buttons below editor
|
||
|
this.add_row_button = this.getButton(this.getItemTitle(),'add','Add '+this.getItemTitle());
|
||
|
|
||
|
this.add_row_button.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
var i = self.rows.length;
|
||
|
if(self.row_cache[i]) {
|
||
|
self.rows[i] = self.row_cache[i];
|
||
|
self.rows[i].setValue(self.rows[i].getDefault());
|
||
|
self.rows[i].container.style.display = '';
|
||
|
if(self.rows[i].tab) self.rows[i].tab.style.display = '';
|
||
|
self.rows[i].register();
|
||
|
}
|
||
|
else {
|
||
|
self.addRow();
|
||
|
}
|
||
|
self.active_tab = self.rows[i].tab;
|
||
|
self.refreshTabs();
|
||
|
self.refreshValue();
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
self.controls.appendChild(this.add_row_button);
|
||
|
|
||
|
this.delete_last_row_button = this.getButton('Last '+this.getItemTitle(),'delete','Delete Last '+this.getItemTitle());
|
||
|
this.delete_last_row_button.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
var rows = self.getValue();
|
||
|
|
||
|
var new_active_tab = null;
|
||
|
if(self.rows.length > 1 && self.rows[self.rows.length-1].tab === self.active_tab) new_active_tab = self.rows[self.rows.length-2].tab;
|
||
|
|
||
|
rows.pop();
|
||
|
self.setValue(rows);
|
||
|
if(new_active_tab) {
|
||
|
self.active_tab = new_active_tab;
|
||
|
self.refreshTabs();
|
||
|
}
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
self.controls.appendChild(this.delete_last_row_button);
|
||
|
|
||
|
this.remove_all_rows_button = this.getButton('All','delete','Delete All');
|
||
|
this.remove_all_rows_button.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
self.setValue([]);
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
self.controls.appendChild(this.remove_all_rows_button);
|
||
|
|
||
|
if(self.tabs) {
|
||
|
this.add_row_button.style.width = '100%';
|
||
|
this.add_row_button.style.textAlign = 'left';
|
||
|
this.add_row_button.style.marginBottom = '3px';
|
||
|
|
||
|
this.delete_last_row_button.style.width = '100%';
|
||
|
this.delete_last_row_button.style.textAlign = 'left';
|
||
|
this.delete_last_row_button.style.marginBottom = '3px';
|
||
|
|
||
|
this.remove_all_rows_button.style.width = '100%';
|
||
|
this.remove_all_rows_button.style.textAlign = 'left';
|
||
|
this.remove_all_rows_button.style.marginBottom = '3px';
|
||
|
}
|
||
|
},
|
||
|
showValidationErrors: function(errors) {
|
||
|
var self = this;
|
||
|
|
||
|
// Get all the errors that pertain to this editor
|
||
|
var my_errors = [];
|
||
|
var other_errors = [];
|
||
|
$each(errors, function(i,error) {
|
||
|
if(error.path === self.path) {
|
||
|
my_errors.push(error);
|
||
|
}
|
||
|
else {
|
||
|
other_errors.push(error);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Show errors for this editor
|
||
|
if(this.error_holder) {
|
||
|
if(my_errors.length) {
|
||
|
var message = [];
|
||
|
this.error_holder.innerHTML = '';
|
||
|
this.error_holder.style.display = '';
|
||
|
$each(my_errors, function(i,error) {
|
||
|
self.error_holder.appendChild(self.theme.getErrorMessage(error.message));
|
||
|
});
|
||
|
}
|
||
|
// Hide error area
|
||
|
else {
|
||
|
this.error_holder.style.display = 'none';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Show errors for child editors
|
||
|
$each(this.rows, function(i,row) {
|
||
|
row.showValidationErrors(other_errors);
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.editors.table = JSONEditor.defaults.editors.array.extend({
|
||
|
register: function() {
|
||
|
this._super();
|
||
|
if(this.rows) {
|
||
|
for(var i=0; i<this.rows.length; i++) {
|
||
|
this.rows[i].register();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
unregister: function() {
|
||
|
this._super();
|
||
|
if(this.rows) {
|
||
|
for(var i=0; i<this.rows.length; i++) {
|
||
|
this.rows[i].unregister();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
getNumColumns: function() {
|
||
|
return Math.max(Math.min(12,this.width),3);
|
||
|
},
|
||
|
preBuild: function() {
|
||
|
var item_schema = this.jsoneditor.expandRefs(this.schema.items || {});
|
||
|
|
||
|
this.item_title = item_schema.title || 'row';
|
||
|
this.item_default = item_schema["default"] || null;
|
||
|
this.item_has_child_editors = item_schema.properties || item_schema.items;
|
||
|
this.width = 12;
|
||
|
this._super();
|
||
|
},
|
||
|
build: function() {
|
||
|
var self = this;
|
||
|
this.table = this.theme.getTable();
|
||
|
this.container.appendChild(this.table);
|
||
|
this.thead = this.theme.getTableHead();
|
||
|
this.table.appendChild(this.thead);
|
||
|
this.header_row = this.theme.getTableRow();
|
||
|
this.thead.appendChild(this.header_row);
|
||
|
this.row_holder = this.theme.getTableBody();
|
||
|
this.table.appendChild(this.row_holder);
|
||
|
|
||
|
// Determine the default value of array element
|
||
|
var tmp = this.getElementEditor(0,true);
|
||
|
this.item_default = tmp.getDefault();
|
||
|
this.width = tmp.getNumColumns() + 2;
|
||
|
|
||
|
if(!this.options.compact) {
|
||
|
this.title = this.theme.getHeader(this.getTitle());
|
||
|
this.container.appendChild(this.title);
|
||
|
this.title_controls = this.theme.getHeaderButtonHolder();
|
||
|
this.title.appendChild(this.title_controls);
|
||
|
if(this.schema.description) {
|
||
|
this.description = this.theme.getDescription(this.schema.description);
|
||
|
this.container.appendChild(this.description);
|
||
|
}
|
||
|
this.panel = this.theme.getIndentedPanel();
|
||
|
this.container.appendChild(this.panel);
|
||
|
this.error_holder = document.createElement('div');
|
||
|
this.panel.appendChild(this.error_holder);
|
||
|
}
|
||
|
else {
|
||
|
this.panel = document.createElement('div');
|
||
|
this.container.appendChild(this.panel);
|
||
|
}
|
||
|
|
||
|
this.panel.appendChild(this.table);
|
||
|
this.controls = this.theme.getButtonHolder();
|
||
|
this.panel.appendChild(this.controls);
|
||
|
|
||
|
if(this.item_has_child_editors) {
|
||
|
var ce = tmp.getChildEditors();
|
||
|
var order = tmp.property_order || Object.keys(ce);
|
||
|
for(var i=0; i<order.length; i++) {
|
||
|
var th = self.theme.getTableHeaderCell(ce[order[i]].getTitle());
|
||
|
if(ce[order[i]].options.hidden) th.style.display = 'none';
|
||
|
self.header_row.appendChild(th);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
self.header_row.appendChild(self.theme.getTableHeaderCell(this.item_title));
|
||
|
}
|
||
|
|
||
|
tmp.destroy();
|
||
|
this.row_holder.innerHTML = '';
|
||
|
|
||
|
// Row Controls column
|
||
|
this.controls_header_cell = self.theme.getTableHeaderCell(" ");
|
||
|
self.header_row.appendChild(this.controls_header_cell);
|
||
|
|
||
|
// Add controls
|
||
|
this.addControls();
|
||
|
},
|
||
|
onChildEditorChange: function(editor) {
|
||
|
this.refreshValue();
|
||
|
this._super();
|
||
|
},
|
||
|
getItemDefault: function() {
|
||
|
return $extend({},{"default":this.item_default})["default"];
|
||
|
},
|
||
|
getItemTitle: function() {
|
||
|
return this.item_title;
|
||
|
},
|
||
|
getElementEditor: function(i,ignore) {
|
||
|
var schema_copy = $extend({},this.schema.items);
|
||
|
var editor = this.jsoneditor.getEditorClass(schema_copy, this.jsoneditor);
|
||
|
var row = this.row_holder.appendChild(this.theme.getTableRow());
|
||
|
var holder = row;
|
||
|
if(!this.item_has_child_editors) {
|
||
|
holder = this.theme.getTableCell();
|
||
|
row.appendChild(holder);
|
||
|
}
|
||
|
|
||
|
var ret = this.jsoneditor.createEditor(editor,{
|
||
|
jsoneditor: this.jsoneditor,
|
||
|
schema: schema_copy,
|
||
|
container: holder,
|
||
|
path: this.path+'.'+i,
|
||
|
parent: this,
|
||
|
compact: true,
|
||
|
table_row: true
|
||
|
});
|
||
|
|
||
|
ret.preBuild();
|
||
|
if(!ignore) {
|
||
|
ret.build();
|
||
|
ret.postBuild();
|
||
|
|
||
|
ret.controls_cell = row.appendChild(this.theme.getTableCell());
|
||
|
ret.row = row;
|
||
|
ret.table_controls = this.theme.getButtonHolder();
|
||
|
ret.controls_cell.appendChild(ret.table_controls);
|
||
|
ret.table_controls.style.margin = 0;
|
||
|
ret.table_controls.style.padding = 0;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
},
|
||
|
destroy: function() {
|
||
|
this.innerHTML = '';
|
||
|
if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
|
||
|
if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
|
||
|
if(this.row_holder && this.row_holder.parentNode) this.row_holder.parentNode.removeChild(this.row_holder);
|
||
|
if(this.table && this.table.parentNode) this.table.parentNode.removeChild(this.table);
|
||
|
if(this.panel && this.panel.parentNode) this.panel.parentNode.removeChild(this.panel);
|
||
|
|
||
|
this.rows = this.title = this.description = this.row_holder = this.table = this.panel = null;
|
||
|
|
||
|
this._super();
|
||
|
},
|
||
|
setValue: function(value, initial) {
|
||
|
// Update the array's value, adding/removing rows when necessary
|
||
|
value = value || [];
|
||
|
|
||
|
// Make sure value has between minItems and maxItems items in it
|
||
|
if(this.schema.minItems) {
|
||
|
while(value.length < this.schema.minItems) {
|
||
|
value.push(this.getItemDefault());
|
||
|
}
|
||
|
}
|
||
|
if(this.schema.maxItems && value.length > this.schema.maxItems) {
|
||
|
value = value.slice(0,this.schema.maxItems);
|
||
|
}
|
||
|
|
||
|
var serialized = JSON.stringify(value);
|
||
|
if(serialized === this.serialized) return;
|
||
|
|
||
|
var numrows_changed = false;
|
||
|
|
||
|
var self = this;
|
||
|
$each(value,function(i,val) {
|
||
|
if(self.rows[i]) {
|
||
|
// TODO: don't set the row's value if it hasn't changed
|
||
|
self.rows[i].setValue(val);
|
||
|
}
|
||
|
else {
|
||
|
self.addRow(val);
|
||
|
numrows_changed = true;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
for(var j=value.length; j<self.rows.length; j++) {
|
||
|
var holder = self.rows[j].container;
|
||
|
if(!self.item_has_child_editors) {
|
||
|
self.rows[j].row.parentNode.removeChild(self.rows[j].row);
|
||
|
}
|
||
|
self.rows[j].destroy();
|
||
|
if(holder.parentNode) holder.parentNode.removeChild(holder);
|
||
|
self.rows[j] = null;
|
||
|
numrows_changed = true;
|
||
|
}
|
||
|
self.rows = self.rows.slice(0,value.length);
|
||
|
|
||
|
self.refreshValue();
|
||
|
if(numrows_changed || initial) self.refreshRowButtons();
|
||
|
|
||
|
self.onChange();
|
||
|
|
||
|
// TODO: sortable
|
||
|
},
|
||
|
refreshRowButtons: function() {
|
||
|
var self = this;
|
||
|
|
||
|
// If we currently have minItems items in the array
|
||
|
var minItems = this.schema.minItems && this.schema.minItems >= this.rows.length;
|
||
|
|
||
|
var need_row_buttons = false;
|
||
|
$each(this.rows,function(i,editor) {
|
||
|
// Hide the move down button for the last row
|
||
|
if(editor.movedown_button) {
|
||
|
if(i === self.rows.length - 1) {
|
||
|
editor.movedown_button.style.display = 'none';
|
||
|
}
|
||
|
else {
|
||
|
need_row_buttons = true;
|
||
|
editor.movedown_button.style.display = '';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Hide the delete button if we have minItems items
|
||
|
if(editor.delete_button) {
|
||
|
if(minItems) {
|
||
|
editor.delete_button.style.display = 'none';
|
||
|
}
|
||
|
else {
|
||
|
need_row_buttons = true;
|
||
|
editor.delete_button.style.display = '';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(editor.moveup_button) {
|
||
|
need_row_buttons = true;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Show/hide controls column in table
|
||
|
$each(this.rows,function(i,editor) {
|
||
|
if(need_row_buttons) {
|
||
|
editor.controls_cell.style.display = '';
|
||
|
}
|
||
|
else {
|
||
|
editor.controls_cell.style.display = 'none';
|
||
|
}
|
||
|
});
|
||
|
if(need_row_buttons) {
|
||
|
this.controls_header_cell.style.display = '';
|
||
|
}
|
||
|
else {
|
||
|
this.controls_header_cell.style.display = 'none';
|
||
|
}
|
||
|
|
||
|
var controls_needed = false;
|
||
|
|
||
|
if(!this.value.length) {
|
||
|
this.delete_last_row_button.style.display = 'none';
|
||
|
this.remove_all_rows_button.style.display = 'none';
|
||
|
this.table.style.display = 'none';
|
||
|
}
|
||
|
else if(this.value.length === 1 || this.hide_delete_buttons) {
|
||
|
this.table.style.display = '';
|
||
|
this.remove_all_rows_button.style.display = 'none';
|
||
|
|
||
|
// If there are minItems items in the array, hide the delete button beneath the rows
|
||
|
if(minItems || this.hide_delete_buttons) {
|
||
|
this.delete_last_row_button.style.display = 'none';
|
||
|
}
|
||
|
else {
|
||
|
this.delete_last_row_button.style.display = '';
|
||
|
controls_needed = true;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
this.table.style.display = '';
|
||
|
// If there are minItems items in the array, hide the delete button beneath the rows
|
||
|
if(minItems || this.hide_delete_buttons) {
|
||
|
this.delete_last_row_button.style.display = 'none';
|
||
|
this.remove_all_rows_button.style.display = 'none';
|
||
|
}
|
||
|
else {
|
||
|
this.delete_last_row_button.style.display = '';
|
||
|
this.remove_all_rows_button.style.display = '';
|
||
|
controls_needed = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If there are maxItems in the array, hide the add button beneath the rows
|
||
|
if((this.schema.maxItems && this.schema.maxItems <= this.rows.length) || this.hide_add_button) {
|
||
|
this.add_row_button.style.display = 'none';
|
||
|
}
|
||
|
else {
|
||
|
this.add_row_button.style.display = '';
|
||
|
controls_needed = true;
|
||
|
}
|
||
|
|
||
|
if(!controls_needed) {
|
||
|
this.controls.style.display = 'none';
|
||
|
}
|
||
|
else {
|
||
|
this.controls.style.display = '';
|
||
|
}
|
||
|
},
|
||
|
refreshValue: function() {
|
||
|
var self = this;
|
||
|
this.value = [];
|
||
|
|
||
|
$each(this.rows,function(i,editor) {
|
||
|
// Get the value for this editor
|
||
|
self.value[i] = editor.getValue();
|
||
|
});
|
||
|
this.serialized = JSON.stringify(this.value);
|
||
|
},
|
||
|
addRow: function(value) {
|
||
|
var self = this;
|
||
|
var i = this.rows.length;
|
||
|
|
||
|
self.rows[i] = this.getElementEditor(i);
|
||
|
|
||
|
var controls_holder = self.rows[i].table_controls;
|
||
|
|
||
|
// Buttons to delete row, move row up, and move row down
|
||
|
if(!this.hide_delete_buttons) {
|
||
|
self.rows[i].delete_button = this.getButton('','delete','Delete');
|
||
|
self.rows[i].delete_button.className += ' delete';
|
||
|
self.rows[i].delete_button.setAttribute('data-i',i);
|
||
|
self.rows[i].delete_button.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
var i = this.getAttribute('data-i')*1;
|
||
|
|
||
|
var value = self.getValue();
|
||
|
|
||
|
var newval = [];
|
||
|
$each(value,function(j,row) {
|
||
|
if(j===i) return; // If this is the one we're deleting
|
||
|
newval.push(row);
|
||
|
});
|
||
|
self.setValue(newval);
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
controls_holder.appendChild(self.rows[i].delete_button);
|
||
|
}
|
||
|
|
||
|
|
||
|
if(i && !this.hide_move_buttons) {
|
||
|
self.rows[i].moveup_button = this.getButton('','moveup','Move up');
|
||
|
self.rows[i].moveup_button.className += ' moveup';
|
||
|
self.rows[i].moveup_button.setAttribute('data-i',i);
|
||
|
self.rows[i].moveup_button.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
var i = this.getAttribute('data-i')*1;
|
||
|
|
||
|
if(i<=0) return;
|
||
|
var rows = self.getValue();
|
||
|
var tmp = rows[i-1];
|
||
|
rows[i-1] = rows[i];
|
||
|
rows[i] = tmp;
|
||
|
|
||
|
self.setValue(rows);
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
controls_holder.appendChild(self.rows[i].moveup_button);
|
||
|
}
|
||
|
|
||
|
if(!this.hide_move_buttons) {
|
||
|
self.rows[i].movedown_button = this.getButton('','movedown','Move down');
|
||
|
self.rows[i].movedown_button.className += ' movedown';
|
||
|
self.rows[i].movedown_button.setAttribute('data-i',i);
|
||
|
self.rows[i].movedown_button.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
var i = this.getAttribute('data-i')*1;
|
||
|
var rows = self.getValue();
|
||
|
if(i>=rows.length-1) return;
|
||
|
var tmp = rows[i+1];
|
||
|
rows[i+1] = rows[i];
|
||
|
rows[i] = tmp;
|
||
|
|
||
|
self.setValue(rows);
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
controls_holder.appendChild(self.rows[i].movedown_button);
|
||
|
}
|
||
|
|
||
|
if(value) self.rows[i].setValue(value);
|
||
|
},
|
||
|
addControls: function() {
|
||
|
var self = this;
|
||
|
|
||
|
this.collapsed = false;
|
||
|
this.toggle_button = this.getButton('','collapse','Collapse');
|
||
|
if(this.title_controls) {
|
||
|
this.title_controls.appendChild(this.toggle_button);
|
||
|
this.toggle_button.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
if(self.collapsed) {
|
||
|
self.collapsed = false;
|
||
|
self.panel.style.display = '';
|
||
|
self.setButtonText(this,'','collapse','Collapse');
|
||
|
}
|
||
|
else {
|
||
|
self.collapsed = true;
|
||
|
self.panel.style.display = 'none';
|
||
|
self.setButtonText(this,'','expand','Expand');
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// If it should start collapsed
|
||
|
if(this.options.collapsed) {
|
||
|
$trigger(this.toggle_button,'click');
|
||
|
}
|
||
|
|
||
|
// Collapse button disabled
|
||
|
if(this.schema.options && typeof this.schema.options.disable_collapse !== "undefined") {
|
||
|
if(this.schema.options.disable_collapse) this.toggle_button.style.display = 'none';
|
||
|
}
|
||
|
else if(this.jsoneditor.options.disable_collapse) {
|
||
|
this.toggle_button.style.display = 'none';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Add "new row" and "delete last" buttons below editor
|
||
|
this.add_row_button = this.getButton(this.getItemTitle(),'add','Add '+this.getItemTitle());
|
||
|
this.add_row_button.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
self.addRow();
|
||
|
self.refreshValue();
|
||
|
self.refreshRowButtons();
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
self.controls.appendChild(this.add_row_button);
|
||
|
|
||
|
this.delete_last_row_button = this.getButton('Last '+this.getItemTitle(),'delete','Delete Last '+this.getItemTitle());
|
||
|
this.delete_last_row_button.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
var rows = self.getValue();
|
||
|
rows.pop();
|
||
|
self.setValue(rows);
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
self.controls.appendChild(this.delete_last_row_button);
|
||
|
|
||
|
this.remove_all_rows_button = this.getButton('All','delete','Delete All');
|
||
|
this.remove_all_rows_button.addEventListener('click',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
self.setValue([]);
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
self.controls.appendChild(this.remove_all_rows_button);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Multiple Editor (for when `type` is an array)
|
||
|
JSONEditor.defaults.editors.multiple = JSONEditor.AbstractEditor.extend({
|
||
|
register: function() {
|
||
|
if(this.editors) {
|
||
|
for(var i=0; i<this.editors.length; i++) {
|
||
|
if(!this.editors[i]) continue;
|
||
|
this.editors[i].unregister();
|
||
|
}
|
||
|
if(this.editors[this.type]) this.editors[this.type].register();
|
||
|
}
|
||
|
this._super();
|
||
|
},
|
||
|
unregister: function() {
|
||
|
this._super();
|
||
|
if(this.editors) {
|
||
|
for(var i=0; i<this.editors.length; i++) {
|
||
|
if(!this.editors[i]) continue;
|
||
|
this.editors[i].unregister();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
getNumColumns: function() {
|
||
|
if(!this.editors[this.type]) return 4;
|
||
|
return Math.max(this.editors[this.type].getNumColumns(),4);
|
||
|
},
|
||
|
enable: function() {
|
||
|
if(this.editors) {
|
||
|
for(var i=0; i<this.editors.length; i++) {
|
||
|
if(!this.editors[i]) continue;
|
||
|
this.editors[i].enable();
|
||
|
}
|
||
|
}
|
||
|
this.switcher.disabled = false;
|
||
|
this._super();
|
||
|
},
|
||
|
disable: function() {
|
||
|
if(this.editors) {
|
||
|
for(var i=0; i<this.editors.length; i++) {
|
||
|
if(!this.editors[i]) continue;
|
||
|
this.editors[i].disable();
|
||
|
}
|
||
|
}
|
||
|
this.switcher.disabled = true;
|
||
|
this._super();
|
||
|
},
|
||
|
switchEditor: function(i) {
|
||
|
var self = this;
|
||
|
|
||
|
if(!this.editors[i]) {
|
||
|
this.buildChildEditor(i);
|
||
|
}
|
||
|
|
||
|
self.type = i;
|
||
|
|
||
|
self.register();
|
||
|
|
||
|
var current_value = self.getValue();
|
||
|
|
||
|
$each(self.editors,function(type,editor) {
|
||
|
if(!editor) return;
|
||
|
if(self.type === type) {
|
||
|
if(self.keep_values) editor.setValue(current_value,true);
|
||
|
editor.container.style.display = '';
|
||
|
}
|
||
|
else editor.container.style.display = 'none';
|
||
|
});
|
||
|
self.refreshValue();
|
||
|
self.refreshHeaderText();
|
||
|
},
|
||
|
buildChildEditor: function(i) {
|
||
|
var self = this;
|
||
|
var type = this.types[i];
|
||
|
var holder = self.theme.getChildEditorHolder();
|
||
|
self.editor_holder.appendChild(holder);
|
||
|
|
||
|
var schema;
|
||
|
|
||
|
if(typeof type === "string") {
|
||
|
schema = $extend({},self.schema);
|
||
|
schema.type = type;
|
||
|
}
|
||
|
else {
|
||
|
schema = $extend({},self.schema,type);
|
||
|
schema = self.jsoneditor.expandRefs(schema);
|
||
|
|
||
|
// If we need to merge `required` arrays
|
||
|
if(type.required && Array.isArray(type.required) && self.schema.required && Array.isArray(self.schema.required)) {
|
||
|
schema.required = self.schema.required.concat(type.required);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var editor = self.jsoneditor.getEditorClass(schema);
|
||
|
|
||
|
self.editors[i] = self.jsoneditor.createEditor(editor,{
|
||
|
jsoneditor: self.jsoneditor,
|
||
|
schema: schema,
|
||
|
container: holder,
|
||
|
path: self.path,
|
||
|
parent: self,
|
||
|
required: true
|
||
|
});
|
||
|
self.editors[i].preBuild();
|
||
|
self.editors[i].build();
|
||
|
self.editors[i].postBuild();
|
||
|
|
||
|
if(self.editors[i].header) self.editors[i].header.style.display = 'none';
|
||
|
|
||
|
self.editors[i].option = self.switcher_options[i];
|
||
|
|
||
|
holder.addEventListener('change_header_text',function() {
|
||
|
self.refreshHeaderText();
|
||
|
});
|
||
|
|
||
|
if(i !== self.type) holder.style.display = 'none';
|
||
|
},
|
||
|
preBuild: function() {
|
||
|
var self = this;
|
||
|
|
||
|
this.types = [];
|
||
|
this.type = 0;
|
||
|
this.editors = [];
|
||
|
this.validators = [];
|
||
|
|
||
|
this.keep_values = true;
|
||
|
if(typeof this.jsoneditor.options.keep_oneof_values !== "undefined") this.keep_values = this.jsoneditor.options.keep_oneof_values;
|
||
|
if(typeof this.options.keep_oneof_values !== "undefined") this.keep_values = this.options.keep_oneof_values;
|
||
|
|
||
|
if(this.schema.oneOf) {
|
||
|
this.oneOf = true;
|
||
|
this.types = this.schema.oneOf;
|
||
|
$each(this.types,function(i,oneof) {
|
||
|
//self.types[i] = self.jsoneditor.expandSchema(oneof);
|
||
|
});
|
||
|
delete this.schema.oneOf;
|
||
|
}
|
||
|
else {
|
||
|
if(!this.schema.type || this.schema.type === "any") {
|
||
|
this.types = ['string','number','integer','boolean','object','array','null'];
|
||
|
|
||
|
// If any of these primitive types are disallowed
|
||
|
if(this.schema.disallow) {
|
||
|
var disallow = this.schema.disallow;
|
||
|
if(typeof disallow !== 'object' || !(Array.isArray(disallow))) {
|
||
|
disallow = [disallow];
|
||
|
}
|
||
|
var allowed_types = [];
|
||
|
$each(this.types,function(i,type) {
|
||
|
if(disallow.indexOf(type) === -1) allowed_types.push(type);
|
||
|
});
|
||
|
this.types = allowed_types;
|
||
|
}
|
||
|
}
|
||
|
else if(Array.isArray(this.schema.type)) {
|
||
|
this.types = this.schema.type;
|
||
|
}
|
||
|
else {
|
||
|
this.types = [this.schema.type];
|
||
|
}
|
||
|
delete this.schema.type;
|
||
|
}
|
||
|
|
||
|
this.display_text = this.getDisplayText(this.types);
|
||
|
},
|
||
|
build: function() {
|
||
|
var self = this;
|
||
|
var container = this.container;
|
||
|
|
||
|
this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
|
||
|
this.container.appendChild(this.header);
|
||
|
|
||
|
this.switcher = this.theme.getSwitcher(this.display_text);
|
||
|
container.appendChild(this.switcher);
|
||
|
this.switcher.addEventListener('change',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
self.switchEditor(self.display_text.indexOf(this.value));
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
|
||
|
this.editor_holder = document.createElement('div');
|
||
|
container.appendChild(this.editor_holder);
|
||
|
|
||
|
|
||
|
var validator_options = {};
|
||
|
if(self.jsoneditor.options.custom_validators) {
|
||
|
validator_options.custom_validators = self.jsoneditor.options.custom_validators;
|
||
|
}
|
||
|
|
||
|
this.switcher_options = this.theme.getSwitcherOptions(this.switcher);
|
||
|
$each(this.types,function(i,type) {
|
||
|
self.editors[i] = false;
|
||
|
|
||
|
var schema;
|
||
|
|
||
|
if(typeof type === "string") {
|
||
|
schema = $extend({},self.schema);
|
||
|
schema.type = type;
|
||
|
}
|
||
|
else {
|
||
|
schema = $extend({},self.schema,type);
|
||
|
|
||
|
// If we need to merge `required` arrays
|
||
|
if(type.required && Array.isArray(type.required) && self.schema.required && Array.isArray(self.schema.required)) {
|
||
|
schema.required = self.schema.required.concat(type.required);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
self.validators[i] = new JSONEditor.Validator(self.jsoneditor,schema,validator_options);
|
||
|
});
|
||
|
|
||
|
this.switchEditor(0);
|
||
|
},
|
||
|
onChildEditorChange: function(editor) {
|
||
|
if(this.editors[this.type]) {
|
||
|
this.refreshValue();
|
||
|
this.refreshHeaderText();
|
||
|
}
|
||
|
|
||
|
this._super();
|
||
|
},
|
||
|
refreshHeaderText: function() {
|
||
|
var display_text = this.getDisplayText(this.types);
|
||
|
$each(this.switcher_options, function(i,option) {
|
||
|
option.textContent = display_text[i];
|
||
|
});
|
||
|
},
|
||
|
refreshValue: function() {
|
||
|
this.value = this.editors[this.type].getValue();
|
||
|
},
|
||
|
setValue: function(val,initial) {
|
||
|
// Determine type by getting the first one that validates
|
||
|
var self = this;
|
||
|
$each(this.validators, function(i,validator) {
|
||
|
if(!validator.validate(val).length) {
|
||
|
self.type = i;
|
||
|
self.switcher.value = self.display_text[i];
|
||
|
return false;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
this.switchEditor(this.type);
|
||
|
|
||
|
this.editors[this.type].setValue(val,initial);
|
||
|
|
||
|
this.refreshValue();
|
||
|
self.onChange();
|
||
|
},
|
||
|
destroy: function() {
|
||
|
$each(this.editors, function(type,editor) {
|
||
|
if(editor) editor.destroy();
|
||
|
});
|
||
|
if(this.editor_holder && this.editor_holder.parentNode) this.editor_holder.parentNode.removeChild(this.editor_holder);
|
||
|
if(this.switcher && this.switcher.parentNode) this.switcher.parentNode.removeChild(this.switcher);
|
||
|
this._super();
|
||
|
},
|
||
|
showValidationErrors: function(errors) {
|
||
|
var self = this;
|
||
|
|
||
|
// oneOf error paths need to remove the oneOf[i] part before passing to child editors
|
||
|
if(this.oneOf) {
|
||
|
$each(this.editors,function(i,editor) {
|
||
|
if(!editor) return;
|
||
|
var check = self.path+'.oneOf['+i+']';
|
||
|
var new_errors = [];
|
||
|
$each(errors, function(j,error) {
|
||
|
if(error.path.substr(0,check.length)===check) {
|
||
|
var new_error = $extend({},error);
|
||
|
new_error.path = self.path+new_error.path.substr(check.length);
|
||
|
new_errors.push(new_error);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
editor.showValidationErrors(new_errors);
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
$each(this.editors,function(type,editor) {
|
||
|
if(!editor) return;
|
||
|
editor.showValidationErrors(errors);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Enum Editor (used for objects and arrays with enumerated values)
|
||
|
JSONEditor.defaults.editors["enum"] = JSONEditor.AbstractEditor.extend({
|
||
|
getNumColumns: function() {
|
||
|
return 4;
|
||
|
},
|
||
|
build: function() {
|
||
|
var container = this.container;
|
||
|
this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
|
||
|
this.container.appendChild(this.title);
|
||
|
|
||
|
this.options.enum_titles = this.options.enum_titles || [];
|
||
|
|
||
|
this["enum"] = this.schema["enum"];
|
||
|
this.selected = 0;
|
||
|
this.select_options = [];
|
||
|
this.html_values = [];
|
||
|
|
||
|
var self = this;
|
||
|
for(var i=0; i<this["enum"].length; i++) {
|
||
|
this.select_options[i] = this.options.enum_titles[i] || "Value "+(i+1);
|
||
|
this.html_values[i] = this.getHTML(this["enum"][i]);
|
||
|
}
|
||
|
|
||
|
// Switcher
|
||
|
this.switcher = this.theme.getSwitcher(this.select_options);
|
||
|
this.container.appendChild(this.switcher);
|
||
|
|
||
|
// Display area
|
||
|
this.display_area = this.theme.getIndentedPanel();
|
||
|
this.container.appendChild(this.display_area);
|
||
|
|
||
|
if(this.options.hide_display) this.display_area.style.display = "none";
|
||
|
|
||
|
this.switcher.addEventListener('change',function() {
|
||
|
self.selected = self.select_options.indexOf(this.value);
|
||
|
self.value = self["enum"][self.selected];
|
||
|
self.refreshValue();
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
this.value = this["enum"][0];
|
||
|
this.refreshValue();
|
||
|
|
||
|
if(this["enum"].length === 1) this.switcher.style.display = 'none';
|
||
|
},
|
||
|
refreshValue: function() {
|
||
|
var self = this;
|
||
|
self.selected = -1;
|
||
|
var stringified = JSON.stringify(this.value);
|
||
|
$each(this["enum"], function(i, el) {
|
||
|
if(stringified === JSON.stringify(el)) {
|
||
|
self.selected = i;
|
||
|
return false;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
if(self.selected<0) {
|
||
|
self.setValue(self["enum"][0]);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this.switcher.value = this.select_options[this.selected];
|
||
|
this.display_area.innerHTML = this.html_values[this.selected];
|
||
|
},
|
||
|
enable: function() {
|
||
|
if(!this.always_disabled) this.switcher.disabled = false;
|
||
|
this._super();
|
||
|
},
|
||
|
disable: function() {
|
||
|
this.switcher.disabled = true;
|
||
|
this._super();
|
||
|
},
|
||
|
getHTML: function(el) {
|
||
|
var self = this;
|
||
|
|
||
|
if(el === null) {
|
||
|
return '<em>null</em>';
|
||
|
}
|
||
|
// Array or Object
|
||
|
else if(typeof el === "object") {
|
||
|
// TODO: use theme
|
||
|
var ret = '';
|
||
|
|
||
|
$each(el,function(i,child) {
|
||
|
var html = self.getHTML(child);
|
||
|
|
||
|
// Add the keys to object children
|
||
|
if(!(Array.isArray(el))) {
|
||
|
// TODO: use theme
|
||
|
html = '<div><em>'+i+'</em>: '+html+'</div>';
|
||
|
}
|
||
|
|
||
|
// TODO: use theme
|
||
|
ret += '<li>'+html+'</li>';
|
||
|
});
|
||
|
|
||
|
if(Array.isArray(el)) ret = '<ol>'+ret+'</ol>';
|
||
|
else ret = "<ul style='margin-top:0;margin-bottom:0;padding-top:0;padding-bottom:0;'>"+ret+'</ul>';
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
// Boolean
|
||
|
else if(typeof el === "boolean") {
|
||
|
return el? 'true' : 'false';
|
||
|
}
|
||
|
// String
|
||
|
else if(typeof el === "string") {
|
||
|
return el.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||
|
}
|
||
|
// Number
|
||
|
else {
|
||
|
return el;
|
||
|
}
|
||
|
},
|
||
|
setValue: function(val) {
|
||
|
if(this.value !== val) {
|
||
|
this.value = val;
|
||
|
this.refreshValue();
|
||
|
this.onChange();
|
||
|
}
|
||
|
},
|
||
|
destroy: function() {
|
||
|
if(this.display_area && this.display_area.parentNode) this.display_area.parentNode.removeChild(this.display_area);
|
||
|
if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
|
||
|
if(this.switcher && this.switcher.parentNode) this.switcher.parentNode.removeChild(this.switcher);
|
||
|
|
||
|
this._super();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.editors.select = JSONEditor.AbstractEditor.extend({
|
||
|
setValue: function(value,initial) {
|
||
|
value = this.typecast(value||'');
|
||
|
|
||
|
// Sanitize value before setting it
|
||
|
var sanitized = value;
|
||
|
if(this.enum_values.indexOf(sanitized) < 0) {
|
||
|
sanitized = this.enum_values[0];
|
||
|
}
|
||
|
|
||
|
if(this.value === sanitized) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this.input.value = this.enum_options[this.enum_values.indexOf(sanitized)];
|
||
|
if(this.select2) this.select2.select2('val',this.input.value);
|
||
|
this.value = sanitized;
|
||
|
this.onChange();
|
||
|
},
|
||
|
register: function() {
|
||
|
this._super();
|
||
|
if(!this.input) return;
|
||
|
this.input.setAttribute('name',this.formname);
|
||
|
},
|
||
|
unregister: function() {
|
||
|
this._super();
|
||
|
if(!this.input) return;
|
||
|
this.input.removeAttribute('name');
|
||
|
},
|
||
|
getNumColumns: function() {
|
||
|
if(!this.enum_options) return 3;
|
||
|
var longest_text = this.getTitle().length;
|
||
|
for(var i=0; i<this.enum_options.length; i++) {
|
||
|
longest_text = Math.max(longest_text,this.enum_options[i].length+4);
|
||
|
}
|
||
|
return Math.min(12,Math.max(longest_text/7,2));
|
||
|
},
|
||
|
typecast: function(value) {
|
||
|
if(this.schema.type === "boolean") {
|
||
|
return !!value;
|
||
|
}
|
||
|
else if(this.schema.type === "number") {
|
||
|
return 1*value;
|
||
|
}
|
||
|
else if(this.schema.type === "integer") {
|
||
|
return Math.floor(value*1);
|
||
|
}
|
||
|
else {
|
||
|
return ""+value;
|
||
|
}
|
||
|
},
|
||
|
getValue: function() {
|
||
|
return this.value;
|
||
|
},
|
||
|
preBuild: function() {
|
||
|
var self = this;
|
||
|
this.input_type = 'select';
|
||
|
this.enum_options = [];
|
||
|
this.enum_values = [];
|
||
|
this.enum_display = [];
|
||
|
|
||
|
// Enum options enumerated
|
||
|
if(this.schema["enum"]) {
|
||
|
var display = this.schema.options && this.schema.options.enum_titles || [];
|
||
|
|
||
|
$each(this.schema["enum"],function(i,option) {
|
||
|
self.enum_options[i] = ""+option;
|
||
|
self.enum_display[i] = ""+(display[i] || option);
|
||
|
self.enum_values[i] = self.typecast(option);
|
||
|
});
|
||
|
|
||
|
if(!this.isRequired()){
|
||
|
self.enum_display.unshift(' ');
|
||
|
self.enum_options.unshift('undefined');
|
||
|
self.enum_values.unshift(undefined);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
// Boolean
|
||
|
else if(this.schema.type === "boolean") {
|
||
|
self.enum_display = this.schema.options && this.schema.options.enum_titles || ['true','false'];
|
||
|
self.enum_options = ['1',''];
|
||
|
self.enum_values = [true,false];
|
||
|
|
||
|
if(!this.isRequired()){
|
||
|
self.enum_display.unshift(' ');
|
||
|
self.enum_options.unshift('undefined');
|
||
|
self.enum_values.unshift(undefined);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
// Dynamic Enum
|
||
|
else if(this.schema.enumSource) {
|
||
|
this.enumSource = [];
|
||
|
this.enum_display = [];
|
||
|
this.enum_options = [];
|
||
|
this.enum_values = [];
|
||
|
|
||
|
// Shortcut declaration for using a single array
|
||
|
if(!(Array.isArray(this.schema.enumSource))) {
|
||
|
if(this.schema.enumValue) {
|
||
|
this.enumSource = [
|
||
|
{
|
||
|
source: this.schema.enumSource,
|
||
|
value: this.schema.enumValue
|
||
|
}
|
||
|
];
|
||
|
}
|
||
|
else {
|
||
|
this.enumSource = [
|
||
|
{
|
||
|
source: this.schema.enumSource
|
||
|
}
|
||
|
];
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
for(i=0; i<this.schema.enumSource.length; i++) {
|
||
|
// Shorthand for watched variable
|
||
|
if(typeof this.schema.enumSource[i] === "string") {
|
||
|
this.enumSource[i] = {
|
||
|
source: this.schema.enumSource[i]
|
||
|
};
|
||
|
}
|
||
|
// Make a copy of the schema
|
||
|
else if(!(Array.isArray(this.schema.enumSource[i]))) {
|
||
|
this.enumSource[i] = $extend({},this.schema.enumSource[i]);
|
||
|
}
|
||
|
else {
|
||
|
this.enumSource[i] = this.schema.enumSource[i];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Now, enumSource is an array of sources
|
||
|
// Walk through this array and fix up the values
|
||
|
for(i=0; i<this.enumSource.length; i++) {
|
||
|
if(this.enumSource[i].value) {
|
||
|
this.enumSource[i].value = this.jsoneditor.compileTemplate(this.enumSource[i].value, this.template_engine);
|
||
|
}
|
||
|
if(this.enumSource[i].title) {
|
||
|
this.enumSource[i].title = this.jsoneditor.compileTemplate(this.enumSource[i].title, this.template_engine);
|
||
|
}
|
||
|
if(this.enumSource[i].filter) {
|
||
|
this.enumSource[i].filter = this.jsoneditor.compileTemplate(this.enumSource[i].filter, this.template_engine);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Other, not supported
|
||
|
else {
|
||
|
throw "'select' editor requires the enum property to be set.";
|
||
|
}
|
||
|
},
|
||
|
build: function() {
|
||
|
var self = this;
|
||
|
if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
|
||
|
if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
|
||
|
|
||
|
if(this.options.compact) this.container.className += ' compact';
|
||
|
|
||
|
this.input = this.theme.getSelectInput(this.enum_options);
|
||
|
this.theme.setSelectOptions(this.input,this.enum_options,this.enum_display);
|
||
|
|
||
|
if(this.schema.readOnly || this.schema.readonly) {
|
||
|
this.always_disabled = true;
|
||
|
this.input.disabled = true;
|
||
|
}
|
||
|
|
||
|
this.input.addEventListener('change',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
self.onInputChange();
|
||
|
});
|
||
|
|
||
|
this.control = this.theme.getFormControl(this.label, this.input, this.description);
|
||
|
this.container.appendChild(this.control);
|
||
|
|
||
|
this.value = this.enum_values[0];
|
||
|
},
|
||
|
onInputChange: function() {
|
||
|
var val = this.input.value;
|
||
|
|
||
|
var new_val;
|
||
|
// Invalid option, use first option instead
|
||
|
if(this.enum_options.indexOf(val) === -1) {
|
||
|
new_val = this.enum_values[0];
|
||
|
}
|
||
|
else {
|
||
|
new_val = this.enum_values[this.enum_options.indexOf(val)];
|
||
|
}
|
||
|
|
||
|
// If valid hasn't changed
|
||
|
if(new_val === this.value) return;
|
||
|
|
||
|
// Store new value and propogate change event
|
||
|
this.value = new_val;
|
||
|
this.onChange(true);
|
||
|
},
|
||
|
setupSelect2: function() {
|
||
|
// If the Select2 library is loaded use it when we have lots of items
|
||
|
if(window.jQuery && window.jQuery.fn && window.jQuery.fn.select2 && (this.enum_options.length > 2 || (this.enum_options.length && this.enumSource))) {
|
||
|
var options = $extend({},JSONEditor.plugins.select2);
|
||
|
if(this.schema.options && this.schema.options.select2_options) options = $extend(options,this.schema.options.select2_options);
|
||
|
this.select2 = window.jQuery(this.input).select2(options);
|
||
|
var self = this;
|
||
|
this.select2.on('select2-blur',function() {
|
||
|
self.input.value = self.select2.select2('val');
|
||
|
self.onInputChange();
|
||
|
});
|
||
|
this.select2.on('change',function() {
|
||
|
self.input.value = self.select2.select2('val');
|
||
|
self.onInputChange();
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
this.select2 = null;
|
||
|
}
|
||
|
},
|
||
|
postBuild: function() {
|
||
|
this._super();
|
||
|
this.theme.afterInputReady(this.input);
|
||
|
this.setupSelect2();
|
||
|
},
|
||
|
onWatchedFieldChange: function() {
|
||
|
var self = this, vars, j;
|
||
|
|
||
|
// If this editor uses a dynamic select box
|
||
|
if(this.enumSource) {
|
||
|
vars = this.getWatchedFieldValues();
|
||
|
var select_options = [];
|
||
|
var select_titles = [];
|
||
|
|
||
|
for(var i=0; i<this.enumSource.length; i++) {
|
||
|
// Constant values
|
||
|
if(Array.isArray(this.enumSource[i])) {
|
||
|
select_options = select_options.concat(this.enumSource[i]);
|
||
|
select_titles = select_titles.concat(this.enumSource[i]);
|
||
|
}
|
||
|
else {
|
||
|
var items = [];
|
||
|
// Static list of items
|
||
|
if(Array.isArray(this.enumSource[i].source)) {
|
||
|
items = this.enumSource[i].source;
|
||
|
// A watched field
|
||
|
} else {
|
||
|
items = vars[this.enumSource[i].source];
|
||
|
}
|
||
|
|
||
|
if(items) {
|
||
|
// Only use a predefined part of the array
|
||
|
if(this.enumSource[i].slice) {
|
||
|
items = Array.prototype.slice.apply(items,this.enumSource[i].slice);
|
||
|
}
|
||
|
// Filter the items
|
||
|
if(this.enumSource[i].filter) {
|
||
|
var new_items = [];
|
||
|
for(j=0; j<items.length; j++) {
|
||
|
if(this.enumSource[i].filter({i:j,item:items[j],watched:vars})) new_items.push(items[j]);
|
||
|
}
|
||
|
items = new_items;
|
||
|
}
|
||
|
|
||
|
var item_titles = [];
|
||
|
var item_values = [];
|
||
|
for(j=0; j<items.length; j++) {
|
||
|
var item = items[j];
|
||
|
|
||
|
// Rendered value
|
||
|
if(this.enumSource[i].value) {
|
||
|
item_values[j] = this.enumSource[i].value({
|
||
|
i: j,
|
||
|
item: item
|
||
|
});
|
||
|
}
|
||
|
// Use value directly
|
||
|
else {
|
||
|
item_values[j] = items[j];
|
||
|
}
|
||
|
|
||
|
// Rendered title
|
||
|
if(this.enumSource[i].title) {
|
||
|
item_titles[j] = this.enumSource[i].title({
|
||
|
i: j,
|
||
|
item: item
|
||
|
});
|
||
|
}
|
||
|
// Use value as the title also
|
||
|
else {
|
||
|
item_titles[j] = item_values[j];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TODO: sort
|
||
|
|
||
|
select_options = select_options.concat(item_values);
|
||
|
select_titles = select_titles.concat(item_titles);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var prev_value = this.value;
|
||
|
|
||
|
this.theme.setSelectOptions(this.input, select_options, select_titles);
|
||
|
this.enum_options = select_options;
|
||
|
this.enum_display = select_titles;
|
||
|
this.enum_values = select_options;
|
||
|
|
||
|
if(this.select2) {
|
||
|
this.select2.select2('destroy');
|
||
|
}
|
||
|
|
||
|
// If the previous value is still in the new select options, stick with it
|
||
|
if(select_options.indexOf(prev_value) !== -1) {
|
||
|
this.input.value = prev_value;
|
||
|
this.value = prev_value;
|
||
|
}
|
||
|
// Otherwise, set the value to the first select option
|
||
|
else {
|
||
|
this.input.value = select_options[0];
|
||
|
this.value = select_options[0] || "";
|
||
|
if(this.parent) this.parent.onChildEditorChange(this);
|
||
|
else this.jsoneditor.onChange();
|
||
|
this.jsoneditor.notifyWatchers(this.path);
|
||
|
}
|
||
|
|
||
|
this.setupSelect2();
|
||
|
}
|
||
|
|
||
|
this._super();
|
||
|
},
|
||
|
enable: function() {
|
||
|
if(!this.always_disabled) {
|
||
|
this.input.disabled = false;
|
||
|
if(this.select2) this.select2.select2("enable",true);
|
||
|
}
|
||
|
this._super();
|
||
|
},
|
||
|
disable: function() {
|
||
|
this.input.disabled = true;
|
||
|
if(this.select2) this.select2.select2("enable",false);
|
||
|
this._super();
|
||
|
},
|
||
|
destroy: function() {
|
||
|
if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
|
||
|
if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
|
||
|
if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
|
||
|
if(this.select2) {
|
||
|
this.select2.select2('destroy');
|
||
|
this.select2 = null;
|
||
|
}
|
||
|
|
||
|
this._super();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.editors.selectize = JSONEditor.AbstractEditor.extend({
|
||
|
setValue: function(value,initial) {
|
||
|
value = this.typecast(value||'');
|
||
|
|
||
|
// Sanitize value before setting it
|
||
|
var sanitized = value;
|
||
|
if(this.enum_values.indexOf(sanitized) < 0) {
|
||
|
sanitized = this.enum_values[0];
|
||
|
}
|
||
|
|
||
|
if(this.value === sanitized) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this.input.value = this.enum_options[this.enum_values.indexOf(sanitized)];
|
||
|
|
||
|
if(this.selectize) {
|
||
|
this.selectize[0].selectize.addItem(sanitized);
|
||
|
}
|
||
|
|
||
|
this.value = sanitized;
|
||
|
this.onChange();
|
||
|
},
|
||
|
register: function() {
|
||
|
this._super();
|
||
|
if(!this.input) return;
|
||
|
this.input.setAttribute('name',this.formname);
|
||
|
},
|
||
|
unregister: function() {
|
||
|
this._super();
|
||
|
if(!this.input) return;
|
||
|
this.input.removeAttribute('name');
|
||
|
},
|
||
|
getNumColumns: function() {
|
||
|
if(!this.enum_options) return 3;
|
||
|
var longest_text = this.getTitle().length;
|
||
|
for(var i=0; i<this.enum_options.length; i++) {
|
||
|
longest_text = Math.max(longest_text,this.enum_options[i].length+4);
|
||
|
}
|
||
|
return Math.min(12,Math.max(longest_text/7,2));
|
||
|
},
|
||
|
typecast: function(value) {
|
||
|
if(this.schema.type === "boolean") {
|
||
|
return !!value;
|
||
|
}
|
||
|
else if(this.schema.type === "number") {
|
||
|
return 1*value;
|
||
|
}
|
||
|
else if(this.schema.type === "integer") {
|
||
|
return Math.floor(value*1);
|
||
|
}
|
||
|
else {
|
||
|
return ""+value;
|
||
|
}
|
||
|
},
|
||
|
getValue: function() {
|
||
|
return this.value;
|
||
|
},
|
||
|
preBuild: function() {
|
||
|
var self = this;
|
||
|
this.input_type = 'select';
|
||
|
this.enum_options = [];
|
||
|
this.enum_values = [];
|
||
|
this.enum_display = [];
|
||
|
|
||
|
// Enum options enumerated
|
||
|
if(this.schema.enum) {
|
||
|
var display = this.schema.options && this.schema.options.enum_titles || [];
|
||
|
|
||
|
$each(this.schema.enum,function(i,option) {
|
||
|
self.enum_options[i] = ""+option;
|
||
|
self.enum_display[i] = ""+(display[i] || option);
|
||
|
self.enum_values[i] = self.typecast(option);
|
||
|
});
|
||
|
}
|
||
|
// Boolean
|
||
|
else if(this.schema.type === "boolean") {
|
||
|
self.enum_display = this.schema.options && this.schema.options.enum_titles || ['true','false'];
|
||
|
self.enum_options = ['1','0'];
|
||
|
self.enum_values = [true,false];
|
||
|
}
|
||
|
// Dynamic Enum
|
||
|
else if(this.schema.enumSource) {
|
||
|
this.enumSource = [];
|
||
|
this.enum_display = [];
|
||
|
this.enum_options = [];
|
||
|
this.enum_values = [];
|
||
|
|
||
|
// Shortcut declaration for using a single array
|
||
|
if(!(Array.isArray(this.schema.enumSource))) {
|
||
|
if(this.schema.enumValue) {
|
||
|
this.enumSource = [
|
||
|
{
|
||
|
source: this.schema.enumSource,
|
||
|
value: this.schema.enumValue
|
||
|
}
|
||
|
];
|
||
|
}
|
||
|
else {
|
||
|
this.enumSource = [
|
||
|
{
|
||
|
source: this.schema.enumSource
|
||
|
}
|
||
|
];
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
for(i=0; i<this.schema.enumSource.length; i++) {
|
||
|
// Shorthand for watched variable
|
||
|
if(typeof this.schema.enumSource[i] === "string") {
|
||
|
this.enumSource[i] = {
|
||
|
source: this.schema.enumSource[i]
|
||
|
};
|
||
|
}
|
||
|
// Make a copy of the schema
|
||
|
else if(!(Array.isArray(this.schema.enumSource[i]))) {
|
||
|
this.enumSource[i] = $extend({},this.schema.enumSource[i]);
|
||
|
}
|
||
|
else {
|
||
|
this.enumSource[i] = this.schema.enumSource[i];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Now, enumSource is an array of sources
|
||
|
// Walk through this array and fix up the values
|
||
|
for(i=0; i<this.enumSource.length; i++) {
|
||
|
if(this.enumSource[i].value) {
|
||
|
this.enumSource[i].value = this.jsoneditor.compileTemplate(this.enumSource[i].value, this.template_engine);
|
||
|
}
|
||
|
if(this.enumSource[i].title) {
|
||
|
this.enumSource[i].title = this.jsoneditor.compileTemplate(this.enumSource[i].title, this.template_engine);
|
||
|
}
|
||
|
if(this.enumSource[i].filter) {
|
||
|
this.enumSource[i].filter = this.jsoneditor.compileTemplate(this.enumSource[i].filter, this.template_engine);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Other, not supported
|
||
|
else {
|
||
|
throw "'select' editor requires the enum property to be set.";
|
||
|
}
|
||
|
},
|
||
|
build: function() {
|
||
|
var self = this;
|
||
|
if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
|
||
|
if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
|
||
|
|
||
|
if(this.options.compact) this.container.className += ' compact';
|
||
|
|
||
|
this.input = this.theme.getSelectInput(this.enum_options);
|
||
|
this.theme.setSelectOptions(this.input,this.enum_options,this.enum_display);
|
||
|
|
||
|
if(this.schema.readOnly || this.schema.readonly) {
|
||
|
this.always_disabled = true;
|
||
|
this.input.disabled = true;
|
||
|
}
|
||
|
|
||
|
this.input.addEventListener('change',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
self.onInputChange();
|
||
|
});
|
||
|
|
||
|
this.control = this.theme.getFormControl(this.label, this.input, this.description);
|
||
|
this.container.appendChild(this.control);
|
||
|
|
||
|
this.value = this.enum_values[0];
|
||
|
},
|
||
|
onInputChange: function() {
|
||
|
var val = this.input.value;
|
||
|
|
||
|
var sanitized = val;
|
||
|
if(this.enum_options.indexOf(val) === -1) {
|
||
|
sanitized = this.enum_options[0];
|
||
|
}
|
||
|
|
||
|
this.value = this.enum_values[this.enum_options.indexOf(val)];
|
||
|
this.onChange(true);
|
||
|
},
|
||
|
setupSelectize: function() {
|
||
|
// If the Selectize library is loaded use it when we have lots of items
|
||
|
var self = this;
|
||
|
if(window.jQuery && window.jQuery.fn && window.jQuery.fn.selectize && (this.enum_options.length >= 2 || (this.enum_options.length && this.enumSource))) {
|
||
|
var options = $extend({},JSONEditor.plugins.selectize);
|
||
|
if(this.schema.options && this.schema.options.selectize_options) options = $extend(options,this.schema.options.selectize_options);
|
||
|
this.selectize = window.jQuery(this.input).selectize($extend(options,
|
||
|
{
|
||
|
create: true,
|
||
|
onChange : function() {
|
||
|
self.onInputChange();
|
||
|
}
|
||
|
}));
|
||
|
}
|
||
|
else {
|
||
|
this.selectize = null;
|
||
|
}
|
||
|
},
|
||
|
postBuild: function() {
|
||
|
this._super();
|
||
|
this.theme.afterInputReady(this.input);
|
||
|
this.setupSelectize();
|
||
|
},
|
||
|
onWatchedFieldChange: function() {
|
||
|
var self = this, vars, j;
|
||
|
|
||
|
// If this editor uses a dynamic select box
|
||
|
if(this.enumSource) {
|
||
|
vars = this.getWatchedFieldValues();
|
||
|
var select_options = [];
|
||
|
var select_titles = [];
|
||
|
|
||
|
for(var i=0; i<this.enumSource.length; i++) {
|
||
|
// Constant values
|
||
|
if(Array.isArray(this.enumSource[i])) {
|
||
|
select_options = select_options.concat(this.enumSource[i]);
|
||
|
select_titles = select_titles.concat(this.enumSource[i]);
|
||
|
}
|
||
|
// A watched field
|
||
|
else if(vars[this.enumSource[i].source]) {
|
||
|
var items = vars[this.enumSource[i].source];
|
||
|
|
||
|
// Only use a predefined part of the array
|
||
|
if(this.enumSource[i].slice) {
|
||
|
items = Array.prototype.slice.apply(items,this.enumSource[i].slice);
|
||
|
}
|
||
|
// Filter the items
|
||
|
if(this.enumSource[i].filter) {
|
||
|
var new_items = [];
|
||
|
for(j=0; j<items.length; j++) {
|
||
|
if(this.enumSource[i].filter({i:j,item:items[j]})) new_items.push(items[j]);
|
||
|
}
|
||
|
items = new_items;
|
||
|
}
|
||
|
|
||
|
var item_titles = [];
|
||
|
var item_values = [];
|
||
|
for(j=0; j<items.length; j++) {
|
||
|
var item = items[j];
|
||
|
|
||
|
// Rendered value
|
||
|
if(this.enumSource[i].value) {
|
||
|
item_values[j] = this.enumSource[i].value({
|
||
|
i: j,
|
||
|
item: item
|
||
|
});
|
||
|
}
|
||
|
// Use value directly
|
||
|
else {
|
||
|
item_values[j] = items[j];
|
||
|
}
|
||
|
|
||
|
// Rendered title
|
||
|
if(this.enumSource[i].title) {
|
||
|
item_titles[j] = this.enumSource[i].title({
|
||
|
i: j,
|
||
|
item: item
|
||
|
});
|
||
|
}
|
||
|
// Use value as the title also
|
||
|
else {
|
||
|
item_titles[j] = item_values[j];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TODO: sort
|
||
|
|
||
|
select_options = select_options.concat(item_values);
|
||
|
select_titles = select_titles.concat(item_titles);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var prev_value = this.value;
|
||
|
|
||
|
this.theme.setSelectOptions(this.input, select_options, select_titles);
|
||
|
this.enum_options = select_options;
|
||
|
this.enum_display = select_titles;
|
||
|
this.enum_values = select_options;
|
||
|
|
||
|
// If the previous value is still in the new select options, stick with it
|
||
|
if(select_options.indexOf(prev_value) !== -1) {
|
||
|
this.input.value = prev_value;
|
||
|
this.value = prev_value;
|
||
|
}
|
||
|
|
||
|
// Otherwise, set the value to the first select option
|
||
|
else {
|
||
|
this.input.value = select_options[0];
|
||
|
this.value = select_options[0] || "";
|
||
|
if(this.parent) this.parent.onChildEditorChange(this);
|
||
|
else this.jsoneditor.onChange();
|
||
|
this.jsoneditor.notifyWatchers(this.path);
|
||
|
}
|
||
|
|
||
|
if(this.selectize) {
|
||
|
// Update the Selectize options
|
||
|
this.updateSelectizeOptions(select_options);
|
||
|
}
|
||
|
else {
|
||
|
this.setupSelectize();
|
||
|
}
|
||
|
|
||
|
this._super();
|
||
|
}
|
||
|
},
|
||
|
updateSelectizeOptions: function(select_options) {
|
||
|
var selectized = this.selectize[0].selectize,
|
||
|
self = this;
|
||
|
|
||
|
selectized.off();
|
||
|
selectized.clearOptions();
|
||
|
for(var n in select_options) {
|
||
|
selectized.addOption({value:select_options[n],text:select_options[n]});
|
||
|
}
|
||
|
selectized.addItem(this.value);
|
||
|
selectized.on('change',function() {
|
||
|
self.onInputChange();
|
||
|
});
|
||
|
},
|
||
|
enable: function() {
|
||
|
if(!this.always_disabled) {
|
||
|
this.input.disabled = false;
|
||
|
if(this.selectize) {
|
||
|
this.selectize[0].selectize.unlock();
|
||
|
}
|
||
|
}
|
||
|
this._super();
|
||
|
},
|
||
|
disable: function() {
|
||
|
this.input.disabled = true;
|
||
|
if(this.selectize) {
|
||
|
this.selectize[0].selectize.lock();
|
||
|
}
|
||
|
this._super();
|
||
|
},
|
||
|
destroy: function() {
|
||
|
if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
|
||
|
if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
|
||
|
if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
|
||
|
if(this.selectize) {
|
||
|
this.selectize[0].selectize.destroy();
|
||
|
this.selectize = null;
|
||
|
}
|
||
|
this._super();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.editors.multiselect = JSONEditor.AbstractEditor.extend({
|
||
|
preBuild: function() {
|
||
|
this._super();
|
||
|
|
||
|
this.select_options = {};
|
||
|
this.select_values = {};
|
||
|
|
||
|
var items_schema = this.jsoneditor.expandRefs(this.schema.items || {});
|
||
|
|
||
|
var e = items_schema["enum"] || [];
|
||
|
this.option_keys = [];
|
||
|
for(i=0; i<e.length; i++) {
|
||
|
// If the sanitized value is different from the enum value, don't include it
|
||
|
if(this.sanitize(e[i]) !== e[i]) continue;
|
||
|
|
||
|
this.option_keys.push(e[i]+"");
|
||
|
this.select_values[e[i]+""] = e[i];
|
||
|
}
|
||
|
},
|
||
|
build: function() {
|
||
|
var self = this, i;
|
||
|
if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
|
||
|
if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
|
||
|
|
||
|
if((!this.schema.format && this.option_keys.length < 8) || this.schema.format === "checkbox") {
|
||
|
this.input_type = 'checkboxes';
|
||
|
|
||
|
this.inputs = {};
|
||
|
this.controls = {};
|
||
|
for(i=0; i<this.option_keys.length; i++) {
|
||
|
this.inputs[this.option_keys[i]] = this.theme.getCheckbox();
|
||
|
this.select_options[this.option_keys[i]] = this.inputs[this.option_keys[i]];
|
||
|
var label = this.theme.getCheckboxLabel(this.option_keys[i]);
|
||
|
this.controls[this.option_keys[i]] = this.theme.getFormControl(label, this.inputs[this.option_keys[i]]);
|
||
|
}
|
||
|
|
||
|
this.control = this.theme.getMultiCheckboxHolder(this.controls,this.label,this.description);
|
||
|
}
|
||
|
else {
|
||
|
this.input_type = 'select';
|
||
|
this.input = this.theme.getSelectInput(this.option_keys);
|
||
|
this.input.multiple = true;
|
||
|
this.input.size = Math.min(10,this.option_keys.length);
|
||
|
|
||
|
for(i=0; i<this.option_keys.length; i++) {
|
||
|
this.select_options[this.option_keys[i]] = this.input.children[i];
|
||
|
}
|
||
|
|
||
|
if(this.schema.readOnly || this.schema.readonly) {
|
||
|
this.always_disabled = true;
|
||
|
this.input.disabled = true;
|
||
|
}
|
||
|
|
||
|
this.control = this.theme.getFormControl(this.label, this.input, this.description);
|
||
|
}
|
||
|
|
||
|
this.container.appendChild(this.control);
|
||
|
this.control.addEventListener('change',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
var new_value = [];
|
||
|
for(i = 0; i<self.option_keys.length; i++) {
|
||
|
if(self.select_options[self.option_keys[i]].selected || self.select_options[self.option_keys[i]].checked) new_value.push(self.select_values[self.option_keys[i]]);
|
||
|
}
|
||
|
|
||
|
self.updateValue(new_value);
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
},
|
||
|
setValue: function(value, initial) {
|
||
|
var i;
|
||
|
value = value || [];
|
||
|
if(typeof value !== "object") value = [value];
|
||
|
else if(!(Array.isArray(value))) value = [];
|
||
|
|
||
|
// Make sure we are dealing with an array of strings so we can check for strict equality
|
||
|
for(i=0; i<value.length; i++) {
|
||
|
if(typeof value[i] !== "string") value[i] += "";
|
||
|
}
|
||
|
|
||
|
// Update selected status of options
|
||
|
for(i in this.select_options) {
|
||
|
if(!this.select_options.hasOwnProperty(i)) continue;
|
||
|
|
||
|
this.select_options[i][this.input_type === "select"? "selected" : "checked"] = (value.indexOf(i) !== -1);
|
||
|
}
|
||
|
|
||
|
this.updateValue(value);
|
||
|
this.onChange();
|
||
|
},
|
||
|
setupSelect2: function() {
|
||
|
if(window.jQuery && window.jQuery.fn && window.jQuery.fn.select2) {
|
||
|
var options = window.jQuery.extend({},JSONEditor.plugins.select2);
|
||
|
if(this.schema.options && this.schema.options.select2_options) options = $extend(options,this.schema.options.select2_options);
|
||
|
this.select2 = window.jQuery(this.input).select2(options);
|
||
|
var self = this;
|
||
|
this.select2.on('select2-blur',function() {
|
||
|
var val =self.select2.select2('val');
|
||
|
self.value = val;
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
this.select2 = null;
|
||
|
}
|
||
|
},
|
||
|
onInputChange: function() {
|
||
|
this.value = this.input.value;
|
||
|
this.onChange(true);
|
||
|
},
|
||
|
postBuild: function() {
|
||
|
this._super();
|
||
|
this.setupSelect2();
|
||
|
},
|
||
|
register: function() {
|
||
|
this._super();
|
||
|
if(!this.input) return;
|
||
|
this.input.setAttribute('name',this.formname);
|
||
|
},
|
||
|
unregister: function() {
|
||
|
this._super();
|
||
|
if(!this.input) return;
|
||
|
this.input.removeAttribute('name');
|
||
|
},
|
||
|
getNumColumns: function() {
|
||
|
var longest_text = this.getTitle().length;
|
||
|
for(var i in this.select_values) {
|
||
|
if(!this.select_values.hasOwnProperty(i)) continue;
|
||
|
longest_text = Math.max(longest_text,(this.select_values[i]+"").length+4);
|
||
|
}
|
||
|
|
||
|
return Math.min(12,Math.max(longest_text/7,2));
|
||
|
},
|
||
|
updateValue: function(value) {
|
||
|
var changed = false;
|
||
|
var new_value = [];
|
||
|
for(var i=0; i<value.length; i++) {
|
||
|
if(!this.select_options[value[i]+""]) {
|
||
|
changed = true;
|
||
|
continue;
|
||
|
}
|
||
|
var sanitized = this.sanitize(this.select_values[value[i]]);
|
||
|
new_value.push(sanitized);
|
||
|
if(sanitized !== value[i]) changed = true;
|
||
|
}
|
||
|
this.value = new_value;
|
||
|
if(this.select2) this.select2.select2('val',this.value);
|
||
|
return changed;
|
||
|
},
|
||
|
sanitize: function(value) {
|
||
|
if(this.schema.items.type === "number") {
|
||
|
return 1*value;
|
||
|
}
|
||
|
else if(this.schema.items.type === "integer") {
|
||
|
return Math.floor(value*1);
|
||
|
}
|
||
|
else {
|
||
|
return ""+value;
|
||
|
}
|
||
|
},
|
||
|
enable: function() {
|
||
|
if(!this.always_disabled) {
|
||
|
if(this.input) {
|
||
|
this.input.disabled = false;
|
||
|
}
|
||
|
else if(this.inputs) {
|
||
|
for(var i in this.inputs) {
|
||
|
if(!this.inputs.hasOwnProperty(i)) continue;
|
||
|
this.inputs[i].disabled = false;
|
||
|
}
|
||
|
}
|
||
|
if(this.select2) this.select2.select2("enable",true);
|
||
|
}
|
||
|
this._super();
|
||
|
},
|
||
|
disable: function() {
|
||
|
if(this.input) {
|
||
|
this.input.disabled = true;
|
||
|
}
|
||
|
else if(this.inputs) {
|
||
|
for(var i in this.inputs) {
|
||
|
if(!this.inputs.hasOwnProperty(i)) continue;
|
||
|
this.inputs[i].disabled = true;
|
||
|
}
|
||
|
}
|
||
|
if(this.select2) this.select2.select2("enable",false);
|
||
|
this._super();
|
||
|
},
|
||
|
destroy: function() {
|
||
|
if(this.select2) {
|
||
|
this.select2.select2('destroy');
|
||
|
this.select2 = null;
|
||
|
}
|
||
|
this._super();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.editors.base64 = JSONEditor.AbstractEditor.extend({
|
||
|
getNumColumns: function() {
|
||
|
return 4;
|
||
|
},
|
||
|
build: function() {
|
||
|
var self = this;
|
||
|
this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
|
||
|
|
||
|
// Input that holds the base64 string
|
||
|
this.input = this.theme.getFormInputField('hidden');
|
||
|
this.container.appendChild(this.input);
|
||
|
|
||
|
// Don't show uploader if this is readonly
|
||
|
if(!this.schema.readOnly && !this.schema.readonly) {
|
||
|
if(!window.FileReader) throw "FileReader required for base64 editor";
|
||
|
|
||
|
// File uploader
|
||
|
this.uploader = this.theme.getFormInputField('file');
|
||
|
|
||
|
this.uploader.addEventListener('change',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
if(this.files && this.files.length) {
|
||
|
var fr = new FileReader();
|
||
|
fr.onload = function(evt) {
|
||
|
self.value = evt.target.result;
|
||
|
self.refreshPreview();
|
||
|
self.onChange(true);
|
||
|
fr = null;
|
||
|
};
|
||
|
fr.readAsDataURL(this.files[0]);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
this.preview = this.theme.getFormInputDescription(this.schema.description);
|
||
|
this.container.appendChild(this.preview);
|
||
|
|
||
|
this.control = this.theme.getFormControl(this.label, this.uploader||this.input, this.preview);
|
||
|
this.container.appendChild(this.control);
|
||
|
},
|
||
|
refreshPreview: function() {
|
||
|
if(this.last_preview === this.value) return;
|
||
|
this.last_preview = this.value;
|
||
|
|
||
|
this.preview.innerHTML = '';
|
||
|
|
||
|
if(!this.value) return;
|
||
|
|
||
|
var mime = this.value.match(/^data:([^;,]+)[;,]/);
|
||
|
if(mime) mime = mime[1];
|
||
|
|
||
|
if(!mime) {
|
||
|
this.preview.innerHTML = '<em>Invalid data URI</em>';
|
||
|
}
|
||
|
else {
|
||
|
this.preview.innerHTML = '<strong>Type:</strong> '+mime+', <strong>Size:</strong> '+Math.floor((this.value.length-this.value.split(',')[0].length-1)/1.33333)+' bytes';
|
||
|
if(mime.substr(0,5)==="image") {
|
||
|
this.preview.innerHTML += '<br>';
|
||
|
var img = document.createElement('img');
|
||
|
img.style.maxWidth = '100%';
|
||
|
img.style.maxHeight = '100px';
|
||
|
img.src = this.value;
|
||
|
this.preview.appendChild(img);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
enable: function() {
|
||
|
if(this.uploader) this.uploader.disabled = false;
|
||
|
this._super();
|
||
|
},
|
||
|
disable: function() {
|
||
|
if(this.uploader) this.uploader.disabled = true;
|
||
|
this._super();
|
||
|
},
|
||
|
setValue: function(val) {
|
||
|
if(this.value !== val) {
|
||
|
this.value = val;
|
||
|
this.input.value = this.value;
|
||
|
this.refreshPreview();
|
||
|
this.onChange();
|
||
|
}
|
||
|
},
|
||
|
destroy: function() {
|
||
|
if(this.preview && this.preview.parentNode) this.preview.parentNode.removeChild(this.preview);
|
||
|
if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
|
||
|
if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
|
||
|
if(this.uploader && this.uploader.parentNode) this.uploader.parentNode.removeChild(this.uploader);
|
||
|
|
||
|
this._super();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.editors.upload = JSONEditor.AbstractEditor.extend({
|
||
|
getNumColumns: function() {
|
||
|
return 4;
|
||
|
},
|
||
|
build: function() {
|
||
|
var self = this;
|
||
|
this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
|
||
|
|
||
|
// Input that holds the base64 string
|
||
|
this.input = this.theme.getFormInputField('hidden');
|
||
|
this.container.appendChild(this.input);
|
||
|
|
||
|
// Don't show uploader if this is readonly
|
||
|
if(!this.schema.readOnly && !this.schema.readonly) {
|
||
|
|
||
|
if(!this.jsoneditor.options.upload) throw "Upload handler required for upload editor";
|
||
|
|
||
|
// File uploader
|
||
|
this.uploader = this.theme.getFormInputField('file');
|
||
|
|
||
|
this.uploader.addEventListener('change',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
if(this.files && this.files.length) {
|
||
|
var fr = new FileReader();
|
||
|
fr.onload = function(evt) {
|
||
|
self.preview_value = evt.target.result;
|
||
|
self.refreshPreview();
|
||
|
self.onChange(true);
|
||
|
fr = null;
|
||
|
};
|
||
|
fr.readAsDataURL(this.files[0]);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
var description = this.schema.description;
|
||
|
if (!description) description = '';
|
||
|
|
||
|
this.preview = this.theme.getFormInputDescription(description);
|
||
|
this.container.appendChild(this.preview);
|
||
|
|
||
|
this.control = this.theme.getFormControl(this.label, this.uploader||this.input, this.preview);
|
||
|
this.container.appendChild(this.control);
|
||
|
},
|
||
|
refreshPreview: function() {
|
||
|
if(this.last_preview === this.preview_value) return;
|
||
|
this.last_preview = this.preview_value;
|
||
|
|
||
|
this.preview.innerHTML = '';
|
||
|
|
||
|
if(!this.preview_value) return;
|
||
|
|
||
|
var self = this;
|
||
|
|
||
|
var mime = this.preview_value.match(/^data:([^;,]+)[;,]/);
|
||
|
if(mime) mime = mime[1];
|
||
|
if(!mime) mime = 'unknown';
|
||
|
|
||
|
var file = this.uploader.files[0];
|
||
|
|
||
|
this.preview.innerHTML = '<strong>Type:</strong> '+mime+', <strong>Size:</strong> '+file.size+' bytes';
|
||
|
if(mime.substr(0,5)==="image") {
|
||
|
this.preview.innerHTML += '<br>';
|
||
|
var img = document.createElement('img');
|
||
|
img.style.maxWidth = '100%';
|
||
|
img.style.maxHeight = '100px';
|
||
|
img.src = this.preview_value;
|
||
|
this.preview.appendChild(img);
|
||
|
}
|
||
|
|
||
|
this.preview.innerHTML += '<br>';
|
||
|
var uploadButton = this.getButton('Upload', 'upload', 'Upload');
|
||
|
this.preview.appendChild(uploadButton);
|
||
|
uploadButton.addEventListener('click',function(event) {
|
||
|
event.preventDefault();
|
||
|
|
||
|
uploadButton.setAttribute("disabled", "disabled");
|
||
|
self.theme.removeInputError(self.uploader);
|
||
|
|
||
|
if (self.theme.getProgressBar) {
|
||
|
self.progressBar = self.theme.getProgressBar();
|
||
|
self.preview.appendChild(self.progressBar);
|
||
|
}
|
||
|
|
||
|
self.jsoneditor.options.upload(self.path, file, {
|
||
|
success: function(url) {
|
||
|
self.setValue(url);
|
||
|
|
||
|
if(self.parent) self.parent.onChildEditorChange(self);
|
||
|
else self.jsoneditor.onChange();
|
||
|
|
||
|
if (self.progressBar) self.preview.removeChild(self.progressBar);
|
||
|
uploadButton.removeAttribute("disabled");
|
||
|
},
|
||
|
failure: function(error) {
|
||
|
self.theme.addInputError(self.uploader, error);
|
||
|
if (self.progressBar) self.preview.removeChild(self.progressBar);
|
||
|
uploadButton.removeAttribute("disabled");
|
||
|
},
|
||
|
updateProgress: function(progress) {
|
||
|
if (self.progressBar) {
|
||
|
if (progress) self.theme.updateProgressBar(self.progressBar, progress);
|
||
|
else self.theme.updateProgressBarUnknown(self.progressBar);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
},
|
||
|
enable: function() {
|
||
|
if(this.uploader) this.uploader.disabled = false;
|
||
|
this._super();
|
||
|
},
|
||
|
disable: function() {
|
||
|
if(this.uploader) this.uploader.disabled = true;
|
||
|
this._super();
|
||
|
},
|
||
|
setValue: function(val) {
|
||
|
if(this.value !== val) {
|
||
|
this.value = val;
|
||
|
this.input.value = this.value;
|
||
|
this.onChange();
|
||
|
}
|
||
|
},
|
||
|
destroy: function() {
|
||
|
if(this.preview && this.preview.parentNode) this.preview.parentNode.removeChild(this.preview);
|
||
|
if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
|
||
|
if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
|
||
|
if(this.uploader && this.uploader.parentNode) this.uploader.parentNode.removeChild(this.uploader);
|
||
|
|
||
|
this._super();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.editors.checkbox = JSONEditor.AbstractEditor.extend({
|
||
|
setValue: function(value,initial) {
|
||
|
this.value = !!value;
|
||
|
this.input.checked = this.value;
|
||
|
this.onChange();
|
||
|
},
|
||
|
register: function() {
|
||
|
this._super();
|
||
|
if(!this.input) return;
|
||
|
this.input.setAttribute('name',this.formname);
|
||
|
},
|
||
|
unregister: function() {
|
||
|
this._super();
|
||
|
if(!this.input) return;
|
||
|
this.input.removeAttribute('name');
|
||
|
},
|
||
|
getNumColumns: function() {
|
||
|
return Math.min(12,Math.max(this.getTitle().length/7,2));
|
||
|
},
|
||
|
build: function() {
|
||
|
var self = this;
|
||
|
if(!this.options.compact) {
|
||
|
this.label = this.header = this.theme.getCheckboxLabel(this.getTitle());
|
||
|
}
|
||
|
if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
|
||
|
if(this.options.compact) this.container.className += ' compact';
|
||
|
|
||
|
this.input = this.theme.getCheckbox();
|
||
|
this.control = this.theme.getFormControl(this.label, this.input, this.description);
|
||
|
|
||
|
if(this.schema.readOnly || this.schema.readonly) {
|
||
|
this.always_disabled = true;
|
||
|
this.input.disabled = true;
|
||
|
}
|
||
|
|
||
|
this.input.addEventListener('change',function(e) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
self.value = this.checked;
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
|
||
|
this.container.appendChild(this.control);
|
||
|
},
|
||
|
enable: function() {
|
||
|
if(!this.always_disabled) {
|
||
|
this.input.disabled = false;
|
||
|
}
|
||
|
this._super();
|
||
|
},
|
||
|
disable: function() {
|
||
|
this.input.disabled = true;
|
||
|
this._super();
|
||
|
},
|
||
|
destroy: function() {
|
||
|
if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
|
||
|
if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
|
||
|
if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
|
||
|
this._super();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.editors.arraySelectize = JSONEditor.AbstractEditor.extend({
|
||
|
build: function() {
|
||
|
this.title = this.theme.getFormInputLabel(this.getTitle());
|
||
|
|
||
|
this.title_controls = this.theme.getHeaderButtonHolder();
|
||
|
this.title.appendChild(this.title_controls);
|
||
|
this.error_holder = document.createElement('div');
|
||
|
|
||
|
if(this.schema.description) {
|
||
|
this.description = this.theme.getDescription(this.schema.description);
|
||
|
}
|
||
|
|
||
|
this.input = document.createElement('select');
|
||
|
this.input.setAttribute('multiple', 'multiple');
|
||
|
|
||
|
var group = this.theme.getFormControl(this.title, this.input, this.description);
|
||
|
|
||
|
this.container.appendChild(group);
|
||
|
this.container.appendChild(this.error_holder);
|
||
|
|
||
|
window.jQuery(this.input).selectize({
|
||
|
delimiter: false,
|
||
|
createOnBlur: true,
|
||
|
create: true
|
||
|
});
|
||
|
},
|
||
|
postBuild: function() {
|
||
|
var self = this;
|
||
|
this.input.selectize.on('change', function(event) {
|
||
|
self.refreshValue();
|
||
|
self.onChange(true);
|
||
|
});
|
||
|
},
|
||
|
destroy: function() {
|
||
|
this.empty(true);
|
||
|
if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
|
||
|
if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
|
||
|
if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
|
||
|
|
||
|
this._super();
|
||
|
},
|
||
|
empty: function(hard) {},
|
||
|
setValue: function(value, initial) {
|
||
|
var self = this;
|
||
|
// Update the array's value, adding/removing rows when necessary
|
||
|
value = value || [];
|
||
|
if(!(Array.isArray(value))) value = [value];
|
||
|
|
||
|
this.input.selectize.clearOptions();
|
||
|
this.input.selectize.clear(true);
|
||
|
|
||
|
value.forEach(function(item) {
|
||
|
self.input.selectize.addOption({text: item, value: item});
|
||
|
});
|
||
|
this.input.selectize.setValue(value);
|
||
|
|
||
|
this.refreshValue(initial);
|
||
|
},
|
||
|
refreshValue: function(force) {
|
||
|
this.value = this.input.selectize.getValue();
|
||
|
},
|
||
|
showValidationErrors: function(errors) {
|
||
|
var self = this;
|
||
|
|
||
|
// Get all the errors that pertain to this editor
|
||
|
var my_errors = [];
|
||
|
var other_errors = [];
|
||
|
$each(errors, function(i,error) {
|
||
|
if(error.path === self.path) {
|
||
|
my_errors.push(error);
|
||
|
}
|
||
|
else {
|
||
|
other_errors.push(error);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Show errors for this editor
|
||
|
if(this.error_holder) {
|
||
|
|
||
|
if(my_errors.length) {
|
||
|
var message = [];
|
||
|
this.error_holder.innerHTML = '';
|
||
|
this.error_holder.style.display = '';
|
||
|
$each(my_errors, function(i,error) {
|
||
|
self.error_holder.appendChild(self.theme.getErrorMessage(error.message));
|
||
|
});
|
||
|
}
|
||
|
// Hide error area
|
||
|
else {
|
||
|
this.error_holder.style.display = 'none';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
var matchKey = (function () {
|
||
|
var elem = document.documentElement;
|
||
|
|
||
|
if (elem.matches) return 'matches';
|
||
|
else if (elem.webkitMatchesSelector) return 'webkitMatchesSelector';
|
||
|
else if (elem.mozMatchesSelector) return 'mozMatchesSelector';
|
||
|
else if (elem.msMatchesSelector) return 'msMatchesSelector';
|
||
|
else if (elem.oMatchesSelector) return 'oMatchesSelector';
|
||
|
})();
|
||
|
|
||
|
JSONEditor.AbstractTheme = Class.extend({
|
||
|
getContainer: function() {
|
||
|
return document.createElement('div');
|
||
|
},
|
||
|
getFloatRightLinkHolder: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.style = el.style || {};
|
||
|
el.style.cssFloat = 'right';
|
||
|
el.style.marginLeft = '10px';
|
||
|
return el;
|
||
|
},
|
||
|
getModal: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.style.backgroundColor = 'white';
|
||
|
el.style.border = '1px solid black';
|
||
|
el.style.boxShadow = '3px 3px black';
|
||
|
el.style.position = 'absolute';
|
||
|
el.style.zIndex = '10';
|
||
|
el.style.display = 'none';
|
||
|
return el;
|
||
|
},
|
||
|
getGridContainer: function() {
|
||
|
var el = document.createElement('div');
|
||
|
return el;
|
||
|
},
|
||
|
getGridRow: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.className = 'row';
|
||
|
return el;
|
||
|
},
|
||
|
getGridColumn: function() {
|
||
|
var el = document.createElement('div');
|
||
|
return el;
|
||
|
},
|
||
|
setGridColumnSize: function(el,size) {
|
||
|
|
||
|
},
|
||
|
getLink: function(text) {
|
||
|
var el = document.createElement('a');
|
||
|
el.setAttribute('href','#');
|
||
|
el.appendChild(document.createTextNode(text));
|
||
|
return el;
|
||
|
},
|
||
|
disableHeader: function(header) {
|
||
|
header.style.color = '#ccc';
|
||
|
},
|
||
|
disableLabel: function(label) {
|
||
|
label.style.color = '#ccc';
|
||
|
},
|
||
|
enableHeader: function(header) {
|
||
|
header.style.color = '';
|
||
|
},
|
||
|
enableLabel: function(label) {
|
||
|
label.style.color = '';
|
||
|
},
|
||
|
getFormInputLabel: function(text) {
|
||
|
var el = document.createElement('label');
|
||
|
el.appendChild(document.createTextNode(text));
|
||
|
return el;
|
||
|
},
|
||
|
getCheckboxLabel: function(text) {
|
||
|
var el = this.getFormInputLabel(text);
|
||
|
el.style.fontWeight = 'normal';
|
||
|
return el;
|
||
|
},
|
||
|
getHeader: function(text) {
|
||
|
var el = document.createElement('h3');
|
||
|
if(typeof text === "string") {
|
||
|
el.textContent = text;
|
||
|
}
|
||
|
else {
|
||
|
el.appendChild(text);
|
||
|
}
|
||
|
|
||
|
return el;
|
||
|
},
|
||
|
getCheckbox: function() {
|
||
|
var el = this.getFormInputField('checkbox');
|
||
|
el.style.display = 'inline-block';
|
||
|
el.style.width = 'auto';
|
||
|
return el;
|
||
|
},
|
||
|
getMultiCheckboxHolder: function(controls,label,description) {
|
||
|
var el = document.createElement('div');
|
||
|
|
||
|
if(label) {
|
||
|
label.style.display = 'block';
|
||
|
el.appendChild(label);
|
||
|
}
|
||
|
|
||
|
for(var i in controls) {
|
||
|
if(!controls.hasOwnProperty(i)) continue;
|
||
|
controls[i].style.display = 'inline-block';
|
||
|
controls[i].style.marginRight = '20px';
|
||
|
el.appendChild(controls[i]);
|
||
|
}
|
||
|
|
||
|
if(description) el.appendChild(description);
|
||
|
|
||
|
return el;
|
||
|
},
|
||
|
getSelectInput: function(options) {
|
||
|
var select = document.createElement('select');
|
||
|
if(options) this.setSelectOptions(select, options);
|
||
|
return select;
|
||
|
},
|
||
|
getSwitcher: function(options) {
|
||
|
var switcher = this.getSelectInput(options);
|
||
|
switcher.style.backgroundColor = 'transparent';
|
||
|
switcher.style.display = 'inline-block';
|
||
|
switcher.style.fontStyle = 'italic';
|
||
|
switcher.style.fontWeight = 'normal';
|
||
|
switcher.style.height = 'auto';
|
||
|
switcher.style.marginBottom = 0;
|
||
|
switcher.style.marginLeft = '5px';
|
||
|
switcher.style.padding = '0 0 0 3px';
|
||
|
switcher.style.width = 'auto';
|
||
|
return switcher;
|
||
|
},
|
||
|
getSwitcherOptions: function(switcher) {
|
||
|
return switcher.getElementsByTagName('option');
|
||
|
},
|
||
|
setSwitcherOptions: function(switcher, options, titles) {
|
||
|
this.setSelectOptions(switcher, options, titles);
|
||
|
},
|
||
|
setSelectOptions: function(select, options, titles) {
|
||
|
titles = titles || [];
|
||
|
select.innerHTML = '';
|
||
|
for(var i=0; i<options.length; i++) {
|
||
|
var option = document.createElement('option');
|
||
|
option.setAttribute('value',options[i]);
|
||
|
option.textContent = titles[i] || options[i];
|
||
|
select.appendChild(option);
|
||
|
}
|
||
|
},
|
||
|
getTextareaInput: function() {
|
||
|
var el = document.createElement('textarea');
|
||
|
el.style = el.style || {};
|
||
|
el.style.width = '100%';
|
||
|
el.style.height = '300px';
|
||
|
el.style.boxSizing = 'border-box';
|
||
|
return el;
|
||
|
},
|
||
|
getRangeInput: function(min,max,step) {
|
||
|
var el = this.getFormInputField('range');
|
||
|
el.setAttribute('min',min);
|
||
|
el.setAttribute('max',max);
|
||
|
el.setAttribute('step',step);
|
||
|
return el;
|
||
|
},
|
||
|
getFormInputField: function(type) {
|
||
|
var el = document.createElement('input');
|
||
|
el.setAttribute('type',type);
|
||
|
return el;
|
||
|
},
|
||
|
afterInputReady: function(input) {
|
||
|
|
||
|
},
|
||
|
getFormControl: function(label, input, description) {
|
||
|
var el = document.createElement('div');
|
||
|
el.className = 'form-control';
|
||
|
if(label) el.appendChild(label);
|
||
|
if(input.type === 'checkbox') {
|
||
|
label.insertBefore(input,label.firstChild);
|
||
|
}
|
||
|
else {
|
||
|
el.appendChild(input);
|
||
|
}
|
||
|
|
||
|
if(description) el.appendChild(description);
|
||
|
return el;
|
||
|
},
|
||
|
getIndentedPanel: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.style = el.style || {};
|
||
|
el.style.paddingLeft = '10px';
|
||
|
el.style.marginLeft = '10px';
|
||
|
el.style.borderLeft = '1px solid #ccc';
|
||
|
return el;
|
||
|
},
|
||
|
getChildEditorHolder: function() {
|
||
|
return document.createElement('div');
|
||
|
},
|
||
|
getDescription: function(text) {
|
||
|
var el = document.createElement('p');
|
||
|
el.innerHTML = text;
|
||
|
return el;
|
||
|
},
|
||
|
getCheckboxDescription: function(text) {
|
||
|
return this.getDescription(text);
|
||
|
},
|
||
|
getFormInputDescription: function(text) {
|
||
|
return this.getDescription(text);
|
||
|
},
|
||
|
getHeaderButtonHolder: function() {
|
||
|
return this.getButtonHolder();
|
||
|
},
|
||
|
getButtonHolder: function() {
|
||
|
return document.createElement('div');
|
||
|
},
|
||
|
getButton: function(text, icon, title) {
|
||
|
var el = document.createElement('button');
|
||
|
el.type = 'button';
|
||
|
this.setButtonText(el,text,icon,title);
|
||
|
return el;
|
||
|
},
|
||
|
setButtonText: function(button, text, icon, title) {
|
||
|
button.innerHTML = '';
|
||
|
if(icon) {
|
||
|
button.appendChild(icon);
|
||
|
button.innerHTML += ' ';
|
||
|
}
|
||
|
button.appendChild(document.createTextNode(text));
|
||
|
if(title) button.setAttribute('title',title);
|
||
|
},
|
||
|
getTable: function() {
|
||
|
return document.createElement('table');
|
||
|
},
|
||
|
getTableRow: function() {
|
||
|
return document.createElement('tr');
|
||
|
},
|
||
|
getTableHead: function() {
|
||
|
return document.createElement('thead');
|
||
|
},
|
||
|
getTableBody: function() {
|
||
|
return document.createElement('tbody');
|
||
|
},
|
||
|
getTableHeaderCell: function(text) {
|
||
|
var el = document.createElement('th');
|
||
|
el.textContent = text;
|
||
|
return el;
|
||
|
},
|
||
|
getTableCell: function() {
|
||
|
var el = document.createElement('td');
|
||
|
return el;
|
||
|
},
|
||
|
getErrorMessage: function(text) {
|
||
|
var el = document.createElement('p');
|
||
|
el.style = el.style || {};
|
||
|
el.style.color = 'red';
|
||
|
el.appendChild(document.createTextNode(text));
|
||
|
return el;
|
||
|
},
|
||
|
addInputError: function(input, text) {
|
||
|
},
|
||
|
removeInputError: function(input) {
|
||
|
},
|
||
|
addTableRowError: function(row) {
|
||
|
},
|
||
|
removeTableRowError: function(row) {
|
||
|
},
|
||
|
getTabHolder: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.innerHTML = "<div style='float: left; width: 130px;' class='tabs'></div><div class='content' style='margin-left: 130px;'></div><div style='clear:both;'></div>";
|
||
|
return el;
|
||
|
},
|
||
|
applyStyles: function(el,styles) {
|
||
|
el.style = el.style || {};
|
||
|
for(var i in styles) {
|
||
|
if(!styles.hasOwnProperty(i)) continue;
|
||
|
el.style[i] = styles[i];
|
||
|
}
|
||
|
},
|
||
|
closest: function(elem, selector) {
|
||
|
while (elem && elem !== document) {
|
||
|
if (matchKey) {
|
||
|
if (elem[matchKey](selector)) {
|
||
|
return elem;
|
||
|
} else {
|
||
|
elem = elem.parentNode;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
},
|
||
|
getTab: function(span) {
|
||
|
var el = document.createElement('div');
|
||
|
el.appendChild(span);
|
||
|
el.style = el.style || {};
|
||
|
this.applyStyles(el,{
|
||
|
border: '1px solid #ccc',
|
||
|
borderWidth: '1px 0 1px 1px',
|
||
|
textAlign: 'center',
|
||
|
lineHeight: '30px',
|
||
|
borderRadius: '5px',
|
||
|
borderBottomRightRadius: 0,
|
||
|
borderTopRightRadius: 0,
|
||
|
fontWeight: 'bold',
|
||
|
cursor: 'pointer'
|
||
|
});
|
||
|
return el;
|
||
|
},
|
||
|
getTabContentHolder: function(tab_holder) {
|
||
|
return tab_holder.children[1];
|
||
|
},
|
||
|
getTabContent: function() {
|
||
|
return this.getIndentedPanel();
|
||
|
},
|
||
|
markTabActive: function(tab) {
|
||
|
this.applyStyles(tab,{
|
||
|
opacity: 1,
|
||
|
background: 'white'
|
||
|
});
|
||
|
},
|
||
|
markTabInactive: function(tab) {
|
||
|
this.applyStyles(tab,{
|
||
|
opacity:0.5,
|
||
|
background: ''
|
||
|
});
|
||
|
},
|
||
|
addTab: function(holder, tab) {
|
||
|
holder.children[0].appendChild(tab);
|
||
|
},
|
||
|
getBlockLink: function() {
|
||
|
var link = document.createElement('a');
|
||
|
link.style.display = 'block';
|
||
|
return link;
|
||
|
},
|
||
|
getBlockLinkHolder: function() {
|
||
|
var el = document.createElement('div');
|
||
|
return el;
|
||
|
},
|
||
|
getLinksHolder: function() {
|
||
|
var el = document.createElement('div');
|
||
|
return el;
|
||
|
},
|
||
|
createMediaLink: function(holder,link,media) {
|
||
|
holder.appendChild(link);
|
||
|
media.style.width='100%';
|
||
|
holder.appendChild(media);
|
||
|
},
|
||
|
createImageLink: function(holder,link,image) {
|
||
|
holder.appendChild(link);
|
||
|
link.appendChild(image);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.themes.bootstrap2 = JSONEditor.AbstractTheme.extend({
|
||
|
getRangeInput: function(min, max, step) {
|
||
|
// TODO: use bootstrap slider
|
||
|
return this._super(min, max, step);
|
||
|
},
|
||
|
getGridContainer: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.className = 'container-fluid';
|
||
|
return el;
|
||
|
},
|
||
|
getGridRow: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.className = 'row-fluid';
|
||
|
return el;
|
||
|
},
|
||
|
getFormInputLabel: function(text) {
|
||
|
var el = this._super(text);
|
||
|
el.style.display = 'inline-block';
|
||
|
el.style.fontWeight = 'bold';
|
||
|
return el;
|
||
|
},
|
||
|
setGridColumnSize: function(el,size) {
|
||
|
el.className = 'span'+size;
|
||
|
},
|
||
|
getSelectInput: function(options) {
|
||
|
var input = this._super(options);
|
||
|
input.style.width = 'auto';
|
||
|
input.style.maxWidth = '98%';
|
||
|
return input;
|
||
|
},
|
||
|
getFormInputField: function(type) {
|
||
|
var el = this._super(type);
|
||
|
el.style.width = '98%';
|
||
|
return el;
|
||
|
},
|
||
|
afterInputReady: function(input) {
|
||
|
if(input.controlgroup) return;
|
||
|
input.controlgroup = this.closest(input,'.control-group');
|
||
|
input.controls = this.closest(input,'.controls');
|
||
|
if(this.closest(input,'.compact')) {
|
||
|
input.controlgroup.className = input.controlgroup.className.replace(/control-group/g,'').replace(/[ ]{2,}/g,' ');
|
||
|
input.controls.className = input.controlgroup.className.replace(/controls/g,'').replace(/[ ]{2,}/g,' ');
|
||
|
input.style.marginBottom = 0;
|
||
|
}
|
||
|
|
||
|
// TODO: use bootstrap slider
|
||
|
},
|
||
|
getIndentedPanel: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.className = 'well well-small';
|
||
|
el.style.paddingBottom = 0;
|
||
|
return el;
|
||
|
},
|
||
|
getFormInputDescription: function(text) {
|
||
|
var el = document.createElement('p');
|
||
|
el.className = 'help-inline';
|
||
|
el.textContent = text;
|
||
|
return el;
|
||
|
},
|
||
|
getFormControl: function(label, input, description) {
|
||
|
var ret = document.createElement('div');
|
||
|
ret.className = 'control-group';
|
||
|
|
||
|
var controls = document.createElement('div');
|
||
|
controls.className = 'controls';
|
||
|
|
||
|
if(label && input.getAttribute('type') === 'checkbox') {
|
||
|
ret.appendChild(controls);
|
||
|
label.className += ' checkbox';
|
||
|
label.appendChild(input);
|
||
|
controls.appendChild(label);
|
||
|
controls.style.height = '30px';
|
||
|
}
|
||
|
else {
|
||
|
if(label) {
|
||
|
label.className += ' control-label';
|
||
|
ret.appendChild(label);
|
||
|
}
|
||
|
controls.appendChild(input);
|
||
|
ret.appendChild(controls);
|
||
|
}
|
||
|
|
||
|
if(description) controls.appendChild(description);
|
||
|
|
||
|
return ret;
|
||
|
},
|
||
|
getHeaderButtonHolder: function() {
|
||
|
var el = this.getButtonHolder();
|
||
|
el.style.marginLeft = '10px';
|
||
|
return el;
|
||
|
},
|
||
|
getButtonHolder: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.className = 'btn-group';
|
||
|
return el;
|
||
|
},
|
||
|
getButton: function(text, icon, title) {
|
||
|
var el = this._super(text, icon, title);
|
||
|
el.className += ' btn btn-default';
|
||
|
return el;
|
||
|
},
|
||
|
getTable: function() {
|
||
|
var el = document.createElement('table');
|
||
|
el.className = 'table table-bordered';
|
||
|
el.style.width = 'auto';
|
||
|
el.style.maxWidth = 'none';
|
||
|
return el;
|
||
|
},
|
||
|
addInputError: function(input,text) {
|
||
|
if(!input.controlgroup || !input.controls) return;
|
||
|
input.controlgroup.className += ' error';
|
||
|
if(!input.errmsg) {
|
||
|
input.errmsg = document.createElement('p');
|
||
|
input.errmsg.className = 'help-block errormsg';
|
||
|
input.controls.appendChild(input.errmsg);
|
||
|
}
|
||
|
else {
|
||
|
input.errmsg.style.display = '';
|
||
|
}
|
||
|
|
||
|
input.errmsg.textContent = text;
|
||
|
},
|
||
|
removeInputError: function(input) {
|
||
|
if(!input.errmsg) return;
|
||
|
input.errmsg.style.display = 'none';
|
||
|
input.controlgroup.className = input.controlgroup.className.replace(/\s?error/g,'');
|
||
|
},
|
||
|
getTabHolder: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.className = 'tabbable tabs-left';
|
||
|
el.innerHTML = "<ul class='nav nav-tabs span2' style='margin-right: 0;'></ul><div class='tab-content span10' style='overflow:visible;'></div>";
|
||
|
return el;
|
||
|
},
|
||
|
getTab: function(text) {
|
||
|
var el = document.createElement('li');
|
||
|
var a = document.createElement('a');
|
||
|
a.setAttribute('href','#');
|
||
|
a.appendChild(text);
|
||
|
el.appendChild(a);
|
||
|
return el;
|
||
|
},
|
||
|
getTabContentHolder: function(tab_holder) {
|
||
|
return tab_holder.children[1];
|
||
|
},
|
||
|
getTabContent: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.className = 'tab-pane active';
|
||
|
return el;
|
||
|
},
|
||
|
markTabActive: function(tab) {
|
||
|
tab.className += ' active';
|
||
|
},
|
||
|
markTabInactive: function(tab) {
|
||
|
tab.className = tab.className.replace(/\s?active/g,'');
|
||
|
},
|
||
|
addTab: function(holder, tab) {
|
||
|
holder.children[0].appendChild(tab);
|
||
|
},
|
||
|
getProgressBar: function() {
|
||
|
var container = document.createElement('div');
|
||
|
container.className = 'progress';
|
||
|
|
||
|
var bar = document.createElement('div');
|
||
|
bar.className = 'bar';
|
||
|
bar.style.width = '0%';
|
||
|
container.appendChild(bar);
|
||
|
|
||
|
return container;
|
||
|
},
|
||
|
updateProgressBar: function(progressBar, progress) {
|
||
|
if (!progressBar) return;
|
||
|
|
||
|
progressBar.firstChild.style.width = progress + "%";
|
||
|
},
|
||
|
updateProgressBarUnknown: function(progressBar) {
|
||
|
if (!progressBar) return;
|
||
|
|
||
|
progressBar.className = 'progress progress-striped active';
|
||
|
progressBar.firstChild.style.width = '100%';
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.themes.bootstrap3 = JSONEditor.AbstractTheme.extend({
|
||
|
getSelectInput: function(options) {
|
||
|
var el = this._super(options);
|
||
|
el.className += 'form-control';
|
||
|
//el.style.width = 'auto';
|
||
|
return el;
|
||
|
},
|
||
|
setGridColumnSize: function(el,size) {
|
||
|
el.className = 'col-md-'+size;
|
||
|
},
|
||
|
afterInputReady: function(input) {
|
||
|
if(input.controlgroup) return;
|
||
|
input.controlgroup = this.closest(input,'.form-group');
|
||
|
if(this.closest(input,'.compact')) {
|
||
|
input.controlgroup.style.marginBottom = 0;
|
||
|
}
|
||
|
|
||
|
// TODO: use bootstrap slider
|
||
|
},
|
||
|
getTextareaInput: function() {
|
||
|
var el = document.createElement('textarea');
|
||
|
el.className = 'form-control';
|
||
|
return el;
|
||
|
},
|
||
|
getRangeInput: function(min, max, step) {
|
||
|
// TODO: use better slider
|
||
|
return this._super(min, max, step);
|
||
|
},
|
||
|
getFormInputField: function(type) {
|
||
|
var el = this._super(type);
|
||
|
if(type !== 'checkbox') {
|
||
|
el.className += 'form-control';
|
||
|
}
|
||
|
return el;
|
||
|
},
|
||
|
getFormControl: function(label, input, description) {
|
||
|
var group = document.createElement('div');
|
||
|
|
||
|
if(label && input.type === 'checkbox') {
|
||
|
group.className += ' checkbox';
|
||
|
label.appendChild(input);
|
||
|
label.style.fontSize = '14px';
|
||
|
group.style.marginTop = '0';
|
||
|
group.appendChild(label);
|
||
|
input.style.position = 'relative';
|
||
|
input.style.cssFloat = 'left';
|
||
|
}
|
||
|
else {
|
||
|
group.className += ' form-group';
|
||
|
if(label) {
|
||
|
label.className += ' control-label';
|
||
|
group.appendChild(label);
|
||
|
}
|
||
|
group.appendChild(input);
|
||
|
}
|
||
|
|
||
|
if(description) group.appendChild(description);
|
||
|
|
||
|
return group;
|
||
|
},
|
||
|
getIndentedPanel: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.className = 'well well-sm';
|
||
|
el.style.paddingBottom = 0;
|
||
|
return el;
|
||
|
},
|
||
|
getFormInputDescription: function(text) {
|
||
|
var el = document.createElement('p');
|
||
|
el.className = 'help-block';
|
||
|
el.innerHTML = text;
|
||
|
return el;
|
||
|
},
|
||
|
getHeaderButtonHolder: function() {
|
||
|
var el = this.getButtonHolder();
|
||
|
el.style.marginLeft = '10px';
|
||
|
return el;
|
||
|
},
|
||
|
getButtonHolder: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.className = 'btn-group';
|
||
|
return el;
|
||
|
},
|
||
|
getButton: function(text, icon, title) {
|
||
|
var el = this._super(text, icon, title);
|
||
|
el.className += 'btn btn-default';
|
||
|
return el;
|
||
|
},
|
||
|
getTable: function() {
|
||
|
var el = document.createElement('table');
|
||
|
el.className = 'table table-bordered';
|
||
|
el.style.width = 'auto';
|
||
|
el.style.maxWidth = 'none';
|
||
|
return el;
|
||
|
},
|
||
|
|
||
|
addInputError: function(input,text) {
|
||
|
if(!input.controlgroup) return;
|
||
|
input.controlgroup.className += ' has-error';
|
||
|
if(!input.errmsg) {
|
||
|
input.errmsg = document.createElement('p');
|
||
|
input.errmsg.className = 'help-block errormsg';
|
||
|
input.controlgroup.appendChild(input.errmsg);
|
||
|
}
|
||
|
else {
|
||
|
input.errmsg.style.display = '';
|
||
|
}
|
||
|
|
||
|
input.errmsg.textContent = text;
|
||
|
},
|
||
|
removeInputError: function(input) {
|
||
|
if(!input.errmsg) return;
|
||
|
input.errmsg.style.display = 'none';
|
||
|
input.controlgroup.className = input.controlgroup.className.replace(/\s?has-error/g,'');
|
||
|
},
|
||
|
getTabHolder: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.innerHTML = "<div class='tabs list-group col-md-2'></div><div class='col-md-10'></div>";
|
||
|
el.className = 'rows';
|
||
|
return el;
|
||
|
},
|
||
|
getTab: function(text) {
|
||
|
var el = document.createElement('a');
|
||
|
el.className = 'list-group-item';
|
||
|
el.setAttribute('href','#');
|
||
|
el.appendChild(text);
|
||
|
return el;
|
||
|
},
|
||
|
markTabActive: function(tab) {
|
||
|
tab.className += ' active';
|
||
|
},
|
||
|
markTabInactive: function(tab) {
|
||
|
tab.className = tab.className.replace(/\s?active/g,'');
|
||
|
},
|
||
|
getProgressBar: function() {
|
||
|
var min = 0, max = 100, start = 0;
|
||
|
|
||
|
var container = document.createElement('div');
|
||
|
container.className = 'progress';
|
||
|
|
||
|
var bar = document.createElement('div');
|
||
|
bar.className = 'progress-bar';
|
||
|
bar.setAttribute('role', 'progressbar');
|
||
|
bar.setAttribute('aria-valuenow', start);
|
||
|
bar.setAttribute('aria-valuemin', min);
|
||
|
bar.setAttribute('aria-valuenax', max);
|
||
|
bar.innerHTML = start + "%";
|
||
|
container.appendChild(bar);
|
||
|
|
||
|
return container;
|
||
|
},
|
||
|
updateProgressBar: function(progressBar, progress) {
|
||
|
if (!progressBar) return;
|
||
|
|
||
|
var bar = progressBar.firstChild;
|
||
|
var percentage = progress + "%";
|
||
|
bar.setAttribute('aria-valuenow', progress);
|
||
|
bar.style.width = percentage;
|
||
|
bar.innerHTML = percentage;
|
||
|
},
|
||
|
updateProgressBarUnknown: function(progressBar) {
|
||
|
if (!progressBar) return;
|
||
|
|
||
|
var bar = progressBar.firstChild;
|
||
|
progressBar.className = 'progress progress-striped active';
|
||
|
bar.removeAttribute('aria-valuenow');
|
||
|
bar.style.width = '100%';
|
||
|
bar.innerHTML = '';
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Base Foundation theme
|
||
|
JSONEditor.defaults.themes.foundation = JSONEditor.AbstractTheme.extend({
|
||
|
getChildEditorHolder: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.style.marginBottom = '15px';
|
||
|
return el;
|
||
|
},
|
||
|
getSelectInput: function(options) {
|
||
|
var el = this._super(options);
|
||
|
el.style.minWidth = 'none';
|
||
|
el.style.padding = '5px';
|
||
|
el.style.marginTop = '3px';
|
||
|
return el;
|
||
|
},
|
||
|
getSwitcher: function(options) {
|
||
|
var el = this._super(options);
|
||
|
el.style.paddingRight = '8px';
|
||
|
return el;
|
||
|
},
|
||
|
afterInputReady: function(input) {
|
||
|
if(this.closest(input,'.compact')) {
|
||
|
input.style.marginBottom = 0;
|
||
|
}
|
||
|
input.group = this.closest(input,'.form-control');
|
||
|
},
|
||
|
getFormInputLabel: function(text) {
|
||
|
var el = this._super(text);
|
||
|
el.style.display = 'inline-block';
|
||
|
return el;
|
||
|
},
|
||
|
getFormInputField: function(type) {
|
||
|
var el = this._super(type);
|
||
|
el.style.width = '100%';
|
||
|
el.style.marginBottom = type==='checkbox'? '0' : '12px';
|
||
|
return el;
|
||
|
},
|
||
|
getFormInputDescription: function(text) {
|
||
|
var el = document.createElement('p');
|
||
|
el.textContent = text;
|
||
|
el.style.marginTop = '-10px';
|
||
|
el.style.fontStyle = 'italic';
|
||
|
return el;
|
||
|
},
|
||
|
getIndentedPanel: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.className = 'panel';
|
||
|
el.style.paddingBottom = 0;
|
||
|
return el;
|
||
|
},
|
||
|
getHeaderButtonHolder: function() {
|
||
|
var el = this.getButtonHolder();
|
||
|
el.style.display = 'inline-block';
|
||
|
el.style.marginLeft = '10px';
|
||
|
el.style.verticalAlign = 'middle';
|
||
|
return el;
|
||
|
},
|
||
|
getButtonHolder: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.className = 'button-group';
|
||
|
return el;
|
||
|
},
|
||
|
getButton: function(text, icon, title) {
|
||
|
var el = this._super(text, icon, title);
|
||
|
el.className += ' small button';
|
||
|
return el;
|
||
|
},
|
||
|
addInputError: function(input,text) {
|
||
|
if(!input.group) return;
|
||
|
input.group.className += ' error';
|
||
|
|
||
|
if(!input.errmsg) {
|
||
|
input.insertAdjacentHTML('afterend','<small class="error"></small>');
|
||
|
input.errmsg = input.parentNode.getElementsByClassName('error')[0];
|
||
|
}
|
||
|
else {
|
||
|
input.errmsg.style.display = '';
|
||
|
}
|
||
|
|
||
|
input.errmsg.textContent = text;
|
||
|
},
|
||
|
removeInputError: function(input) {
|
||
|
if(!input.errmsg) return;
|
||
|
input.group.className = input.group.className.replace(/ error/g,'');
|
||
|
input.errmsg.style.display = 'none';
|
||
|
},
|
||
|
getProgressBar: function() {
|
||
|
var progressBar = document.createElement('div');
|
||
|
progressBar.className = 'progress';
|
||
|
|
||
|
var meter = document.createElement('span');
|
||
|
meter.className = 'meter';
|
||
|
meter.style.width = '0%';
|
||
|
progressBar.appendChild(meter);
|
||
|
return progressBar;
|
||
|
},
|
||
|
updateProgressBar: function(progressBar, progress) {
|
||
|
if (!progressBar) return;
|
||
|
progressBar.firstChild.style.width = progress + '%';
|
||
|
},
|
||
|
updateProgressBarUnknown: function(progressBar) {
|
||
|
if (!progressBar) return;
|
||
|
progressBar.firstChild.style.width = '100%';
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Foundation 3 Specific Theme
|
||
|
JSONEditor.defaults.themes.foundation3 = JSONEditor.defaults.themes.foundation.extend({
|
||
|
getHeaderButtonHolder: function() {
|
||
|
var el = this._super();
|
||
|
el.style.fontSize = '.6em';
|
||
|
return el;
|
||
|
},
|
||
|
getFormInputLabel: function(text) {
|
||
|
var el = this._super(text);
|
||
|
el.style.fontWeight = 'bold';
|
||
|
return el;
|
||
|
},
|
||
|
getTabHolder: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.className = 'row';
|
||
|
el.innerHTML = "<dl class='tabs vertical two columns'></dl><div class='tabs-content ten columns'></div>";
|
||
|
return el;
|
||
|
},
|
||
|
setGridColumnSize: function(el,size) {
|
||
|
var sizes = ['zero','one','two','three','four','five','six','seven','eight','nine','ten','eleven','twelve'];
|
||
|
el.className = 'columns '+sizes[size];
|
||
|
},
|
||
|
getTab: function(text) {
|
||
|
var el = document.createElement('dd');
|
||
|
var a = document.createElement('a');
|
||
|
a.setAttribute('href','#');
|
||
|
a.appendChild(text);
|
||
|
el.appendChild(a);
|
||
|
return el;
|
||
|
},
|
||
|
getTabContentHolder: function(tab_holder) {
|
||
|
return tab_holder.children[1];
|
||
|
},
|
||
|
getTabContent: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.className = 'content active';
|
||
|
el.style.paddingLeft = '5px';
|
||
|
return el;
|
||
|
},
|
||
|
markTabActive: function(tab) {
|
||
|
tab.className += ' active';
|
||
|
},
|
||
|
markTabInactive: function(tab) {
|
||
|
tab.className = tab.className.replace(/\s*active/g,'');
|
||
|
},
|
||
|
addTab: function(holder, tab) {
|
||
|
holder.children[0].appendChild(tab);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Foundation 4 Specific Theme
|
||
|
JSONEditor.defaults.themes.foundation4 = JSONEditor.defaults.themes.foundation.extend({
|
||
|
getHeaderButtonHolder: function() {
|
||
|
var el = this._super();
|
||
|
el.style.fontSize = '.6em';
|
||
|
return el;
|
||
|
},
|
||
|
setGridColumnSize: function(el,size) {
|
||
|
el.className = 'columns large-'+size;
|
||
|
},
|
||
|
getFormInputDescription: function(text) {
|
||
|
var el = this._super(text);
|
||
|
el.style.fontSize = '.8rem';
|
||
|
return el;
|
||
|
},
|
||
|
getFormInputLabel: function(text) {
|
||
|
var el = this._super(text);
|
||
|
el.style.fontWeight = 'bold';
|
||
|
return el;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// Foundation 5 Specific Theme
|
||
|
JSONEditor.defaults.themes.foundation5 = JSONEditor.defaults.themes.foundation.extend({
|
||
|
getFormInputDescription: function(text) {
|
||
|
var el = this._super(text);
|
||
|
el.style.fontSize = '.8rem';
|
||
|
return el;
|
||
|
},
|
||
|
setGridColumnSize: function(el,size) {
|
||
|
el.className = 'columns medium-'+size;
|
||
|
},
|
||
|
getButton: function(text, icon, title) {
|
||
|
var el = this._super(text,icon,title);
|
||
|
el.className = el.className.replace(/\s*small/g,'') + ' tiny';
|
||
|
return el;
|
||
|
},
|
||
|
getTabHolder: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.innerHTML = "<dl class='tabs vertical'></dl><div class='tabs-content vertical'></div>";
|
||
|
return el;
|
||
|
},
|
||
|
getTab: function(text) {
|
||
|
var el = document.createElement('dd');
|
||
|
var a = document.createElement('a');
|
||
|
a.setAttribute('href','#');
|
||
|
a.appendChild(text);
|
||
|
el.appendChild(a);
|
||
|
return el;
|
||
|
},
|
||
|
getTabContentHolder: function(tab_holder) {
|
||
|
return tab_holder.children[1];
|
||
|
},
|
||
|
getTabContent: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.className = 'content active';
|
||
|
el.style.paddingLeft = '5px';
|
||
|
return el;
|
||
|
},
|
||
|
markTabActive: function(tab) {
|
||
|
tab.className += ' active';
|
||
|
},
|
||
|
markTabInactive: function(tab) {
|
||
|
tab.className = tab.className.replace(/\s*active/g,'');
|
||
|
},
|
||
|
addTab: function(holder, tab) {
|
||
|
holder.children[0].appendChild(tab);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.themes.html = JSONEditor.AbstractTheme.extend({
|
||
|
getFormInputLabel: function(text) {
|
||
|
var el = this._super(text);
|
||
|
el.style.display = 'block';
|
||
|
el.style.marginBottom = '3px';
|
||
|
el.style.fontWeight = 'bold';
|
||
|
return el;
|
||
|
},
|
||
|
getFormInputDescription: function(text) {
|
||
|
var el = this._super(text);
|
||
|
el.style.fontSize = '.8em';
|
||
|
el.style.margin = 0;
|
||
|
el.style.display = 'inline-block';
|
||
|
el.style.fontStyle = 'italic';
|
||
|
return el;
|
||
|
},
|
||
|
getIndentedPanel: function() {
|
||
|
var el = this._super();
|
||
|
el.style.border = '1px solid #ddd';
|
||
|
el.style.padding = '5px';
|
||
|
el.style.margin = '5px';
|
||
|
el.style.borderRadius = '3px';
|
||
|
return el;
|
||
|
},
|
||
|
getChildEditorHolder: function() {
|
||
|
var el = this._super();
|
||
|
el.style.marginBottom = '8px';
|
||
|
return el;
|
||
|
},
|
||
|
getHeaderButtonHolder: function() {
|
||
|
var el = this.getButtonHolder();
|
||
|
el.style.display = 'inline-block';
|
||
|
el.style.marginLeft = '10px';
|
||
|
el.style.fontSize = '.8em';
|
||
|
el.style.verticalAlign = 'middle';
|
||
|
return el;
|
||
|
},
|
||
|
getTable: function() {
|
||
|
var el = this._super();
|
||
|
el.style.borderBottom = '1px solid #ccc';
|
||
|
el.style.marginBottom = '5px';
|
||
|
return el;
|
||
|
},
|
||
|
addInputError: function(input, text) {
|
||
|
input.style.borderColor = 'red';
|
||
|
|
||
|
if(!input.errmsg) {
|
||
|
var group = this.closest(input,'.form-control');
|
||
|
input.errmsg = document.createElement('div');
|
||
|
input.errmsg.setAttribute('class','errmsg');
|
||
|
input.errmsg.style = input.errmsg.style || {};
|
||
|
input.errmsg.style.color = 'red';
|
||
|
group.appendChild(input.errmsg);
|
||
|
}
|
||
|
else {
|
||
|
input.errmsg.style.display = 'block';
|
||
|
}
|
||
|
|
||
|
input.errmsg.innerHTML = '';
|
||
|
input.errmsg.appendChild(document.createTextNode(text));
|
||
|
},
|
||
|
removeInputError: function(input) {
|
||
|
input.style.borderColor = '';
|
||
|
if(input.errmsg) input.errmsg.style.display = 'none';
|
||
|
},
|
||
|
getProgressBar: function() {
|
||
|
var max = 100, start = 0;
|
||
|
|
||
|
var progressBar = document.createElement('progress');
|
||
|
progressBar.setAttribute('max', max);
|
||
|
progressBar.setAttribute('value', start);
|
||
|
return progressBar;
|
||
|
},
|
||
|
updateProgressBar: function(progressBar, progress) {
|
||
|
if (!progressBar) return;
|
||
|
progressBar.setAttribute('value', progress);
|
||
|
},
|
||
|
updateProgressBarUnknown: function(progressBar) {
|
||
|
if (!progressBar) return;
|
||
|
progressBar.removeAttribute('value');
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.themes.jqueryui = JSONEditor.AbstractTheme.extend({
|
||
|
getTable: function() {
|
||
|
var el = this._super();
|
||
|
el.setAttribute('cellpadding',5);
|
||
|
el.setAttribute('cellspacing',0);
|
||
|
return el;
|
||
|
},
|
||
|
getTableHeaderCell: function(text) {
|
||
|
var el = this._super(text);
|
||
|
el.className = 'ui-state-active';
|
||
|
el.style.fontWeight = 'bold';
|
||
|
return el;
|
||
|
},
|
||
|
getTableCell: function() {
|
||
|
var el = this._super();
|
||
|
el.className = 'ui-widget-content';
|
||
|
return el;
|
||
|
},
|
||
|
getHeaderButtonHolder: function() {
|
||
|
var el = this.getButtonHolder();
|
||
|
el.style.marginLeft = '10px';
|
||
|
el.style.fontSize = '.6em';
|
||
|
el.style.display = 'inline-block';
|
||
|
return el;
|
||
|
},
|
||
|
getFormInputDescription: function(text) {
|
||
|
var el = this.getDescription(text);
|
||
|
el.style.marginLeft = '10px';
|
||
|
el.style.display = 'inline-block';
|
||
|
return el;
|
||
|
},
|
||
|
getFormControl: function(label, input, description) {
|
||
|
var el = this._super(label,input,description);
|
||
|
if(input.type === 'checkbox') {
|
||
|
el.style.lineHeight = '25px';
|
||
|
|
||
|
el.style.padding = '3px 0';
|
||
|
}
|
||
|
else {
|
||
|
el.style.padding = '4px 0 8px 0';
|
||
|
}
|
||
|
return el;
|
||
|
},
|
||
|
getDescription: function(text) {
|
||
|
var el = document.createElement('span');
|
||
|
el.style.fontSize = '.8em';
|
||
|
el.style.fontStyle = 'italic';
|
||
|
el.textContent = text;
|
||
|
return el;
|
||
|
},
|
||
|
getButtonHolder: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.className = 'ui-buttonset';
|
||
|
el.style.fontSize = '.7em';
|
||
|
return el;
|
||
|
},
|
||
|
getFormInputLabel: function(text) {
|
||
|
var el = document.createElement('label');
|
||
|
el.style.fontWeight = 'bold';
|
||
|
el.style.display = 'block';
|
||
|
el.textContent = text;
|
||
|
return el;
|
||
|
},
|
||
|
getButton: function(text, icon, title) {
|
||
|
var button = document.createElement("button");
|
||
|
button.className = 'ui-button ui-widget ui-state-default ui-corner-all';
|
||
|
|
||
|
// Icon only
|
||
|
if(icon && !text) {
|
||
|
button.className += ' ui-button-icon-only';
|
||
|
icon.className += ' ui-button-icon-primary ui-icon-primary';
|
||
|
button.appendChild(icon);
|
||
|
}
|
||
|
// Icon and Text
|
||
|
else if(icon) {
|
||
|
button.className += ' ui-button-text-icon-primary';
|
||
|
icon.className += ' ui-button-icon-primary ui-icon-primary';
|
||
|
button.appendChild(icon);
|
||
|
}
|
||
|
// Text only
|
||
|
else {
|
||
|
button.className += ' ui-button-text-only';
|
||
|
}
|
||
|
|
||
|
var el = document.createElement('span');
|
||
|
el.className = 'ui-button-text';
|
||
|
el.textContent = text||title||".";
|
||
|
button.appendChild(el);
|
||
|
|
||
|
button.setAttribute('title',title);
|
||
|
|
||
|
return button;
|
||
|
},
|
||
|
setButtonText: function(button,text, icon, title) {
|
||
|
button.innerHTML = '';
|
||
|
button.className = 'ui-button ui-widget ui-state-default ui-corner-all';
|
||
|
|
||
|
// Icon only
|
||
|
if(icon && !text) {
|
||
|
button.className += ' ui-button-icon-only';
|
||
|
icon.className += ' ui-button-icon-primary ui-icon-primary';
|
||
|
button.appendChild(icon);
|
||
|
}
|
||
|
// Icon and Text
|
||
|
else if(icon) {
|
||
|
button.className += ' ui-button-text-icon-primary';
|
||
|
icon.className += ' ui-button-icon-primary ui-icon-primary';
|
||
|
button.appendChild(icon);
|
||
|
}
|
||
|
// Text only
|
||
|
else {
|
||
|
button.className += ' ui-button-text-only';
|
||
|
}
|
||
|
|
||
|
var el = document.createElement('span');
|
||
|
el.className = 'ui-button-text';
|
||
|
el.textContent = text||title||".";
|
||
|
button.appendChild(el);
|
||
|
|
||
|
button.setAttribute('title',title);
|
||
|
},
|
||
|
getIndentedPanel: function() {
|
||
|
var el = document.createElement('div');
|
||
|
el.className = 'ui-widget-content ui-corner-all';
|
||
|
el.style.padding = '1em 1.4em';
|
||
|
el.style.marginBottom = '20px';
|
||
|
return el;
|
||
|
},
|
||
|
afterInputReady: function(input) {
|
||
|
if(input.controls) return;
|
||
|
input.controls = this.closest(input,'.form-control');
|
||
|
},
|
||
|
addInputError: function(input,text) {
|
||
|
if(!input.controls) return;
|
||
|
if(!input.errmsg) {
|
||
|
input.errmsg = document.createElement('div');
|
||
|
input.errmsg.className = 'ui-state-error';
|
||
|
input.controls.appendChild(input.errmsg);
|
||
|
}
|
||
|
else {
|
||
|
input.errmsg.style.display = '';
|
||
|
}
|
||
|
|
||
|
input.errmsg.textContent = text;
|
||
|
},
|
||
|
removeInputError: function(input) {
|
||
|
if(!input.errmsg) return;
|
||
|
input.errmsg.style.display = 'none';
|
||
|
},
|
||
|
markTabActive: function(tab) {
|
||
|
tab.className = tab.className.replace(/\s*ui-widget-header/g,'')+' ui-state-active';
|
||
|
},
|
||
|
markTabInactive: function(tab) {
|
||
|
tab.className = tab.className.replace(/\s*ui-state-active/g,'')+' ui-widget-header';
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.AbstractIconLib = Class.extend({
|
||
|
mapping: {
|
||
|
collapse: '',
|
||
|
expand: '',
|
||
|
"delete": '',
|
||
|
edit: '',
|
||
|
add: '',
|
||
|
cancel: '',
|
||
|
save: '',
|
||
|
moveup: '',
|
||
|
movedown: ''
|
||
|
},
|
||
|
icon_prefix: '',
|
||
|
getIconClass: function(key) {
|
||
|
if(this.mapping[key]) return this.icon_prefix+this.mapping[key];
|
||
|
else return null;
|
||
|
},
|
||
|
getIcon: function(key) {
|
||
|
var iconclass = this.getIconClass(key);
|
||
|
|
||
|
if(!iconclass) return null;
|
||
|
|
||
|
var i = document.createElement('i');
|
||
|
i.className = iconclass;
|
||
|
return i;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.iconlibs.bootstrap2 = JSONEditor.AbstractIconLib.extend({
|
||
|
mapping: {
|
||
|
collapse: 'chevron-down',
|
||
|
expand: 'chevron-up',
|
||
|
"delete": 'trash',
|
||
|
edit: 'pencil',
|
||
|
add: 'plus',
|
||
|
cancel: 'ban-circle',
|
||
|
save: 'ok',
|
||
|
moveup: 'arrow-up',
|
||
|
movedown: 'arrow-down'
|
||
|
},
|
||
|
icon_prefix: 'icon-'
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.iconlibs.bootstrap3 = JSONEditor.AbstractIconLib.extend({
|
||
|
mapping: {
|
||
|
collapse: 'chevron-down',
|
||
|
expand: 'chevron-right',
|
||
|
"delete": 'remove',
|
||
|
edit: 'pencil',
|
||
|
add: 'plus',
|
||
|
cancel: 'floppy-remove',
|
||
|
save: 'floppy-saved',
|
||
|
moveup: 'arrow-up',
|
||
|
movedown: 'arrow-down'
|
||
|
},
|
||
|
icon_prefix: 'glyphicon glyphicon-'
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.iconlibs.fontawesome3 = JSONEditor.AbstractIconLib.extend({
|
||
|
mapping: {
|
||
|
collapse: 'chevron-down',
|
||
|
expand: 'chevron-right',
|
||
|
"delete": 'remove',
|
||
|
edit: 'pencil',
|
||
|
add: 'plus',
|
||
|
cancel: 'ban-circle',
|
||
|
save: 'save',
|
||
|
moveup: 'arrow-up',
|
||
|
movedown: 'arrow-down'
|
||
|
},
|
||
|
icon_prefix: 'icon-'
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.iconlibs.fontawesome4 = JSONEditor.AbstractIconLib.extend({
|
||
|
mapping: {
|
||
|
collapse: 'caret-square-o-down',
|
||
|
expand: 'caret-square-o-right',
|
||
|
"delete": 'times',
|
||
|
edit: 'pencil',
|
||
|
add: 'plus',
|
||
|
cancel: 'ban',
|
||
|
save: 'save',
|
||
|
moveup: 'arrow-up',
|
||
|
movedown: 'arrow-down'
|
||
|
},
|
||
|
icon_prefix: 'fa fa-'
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.iconlibs.foundation2 = JSONEditor.AbstractIconLib.extend({
|
||
|
mapping: {
|
||
|
collapse: 'minus',
|
||
|
expand: 'plus',
|
||
|
"delete": 'remove',
|
||
|
edit: 'edit',
|
||
|
add: 'add-doc',
|
||
|
cancel: 'error',
|
||
|
save: 'checkmark',
|
||
|
moveup: 'up-arrow',
|
||
|
movedown: 'down-arrow'
|
||
|
},
|
||
|
icon_prefix: 'foundicon-'
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.iconlibs.foundation3 = JSONEditor.AbstractIconLib.extend({
|
||
|
mapping: {
|
||
|
collapse: 'minus',
|
||
|
expand: 'plus',
|
||
|
"delete": 'x',
|
||
|
edit: 'pencil',
|
||
|
add: 'page-add',
|
||
|
cancel: 'x-circle',
|
||
|
save: 'save',
|
||
|
moveup: 'arrow-up',
|
||
|
movedown: 'arrow-down'
|
||
|
},
|
||
|
icon_prefix: 'fi-'
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.iconlibs.jqueryui = JSONEditor.AbstractIconLib.extend({
|
||
|
mapping: {
|
||
|
collapse: 'triangle-1-s',
|
||
|
expand: 'triangle-1-e',
|
||
|
"delete": 'trash',
|
||
|
edit: 'pencil',
|
||
|
add: 'plusthick',
|
||
|
cancel: 'closethick',
|
||
|
save: 'disk',
|
||
|
moveup: 'arrowthick-1-n',
|
||
|
movedown: 'arrowthick-1-s'
|
||
|
},
|
||
|
icon_prefix: 'ui-icon ui-icon-'
|
||
|
});
|
||
|
|
||
|
JSONEditor.defaults.templates["default"] = function() {
|
||
|
return {
|
||
|
compile: function(template) {
|
||
|
var matches = template.match(/{{\s*([a-zA-Z0-9\-_ \.]+)\s*}}/g);
|
||
|
var l = matches && matches.length;
|
||
|
|
||
|
// Shortcut if the template contains no variables
|
||
|
if(!l) return function() { return template; };
|
||
|
|
||
|
// Pre-compute the search/replace functions
|
||
|
// This drastically speeds up template execution
|
||
|
var replacements = [];
|
||
|
var get_replacement = function(i) {
|
||
|
var p = matches[i].replace(/[{}]+/g,'').trim().split('.');
|
||
|
var n = p.length;
|
||
|
var func;
|
||
|
|
||
|
if(n > 1) {
|
||
|
var cur;
|
||
|
func = function(vars) {
|
||
|
cur = vars;
|
||
|
for(i=0; i<n; i++) {
|
||
|
cur = cur[p[i]];
|
||
|
if(!cur) break;
|
||
|
}
|
||
|
return cur;
|
||
|
};
|
||
|
}
|
||
|
else {
|
||
|
p = p[0];
|
||
|
func = function(vars) {
|
||
|
return vars[p];
|
||
|
};
|
||
|
}
|
||
|
|
||
|
replacements.push({
|
||
|
s: matches[i],
|
||
|
r: func
|
||
|
});
|
||
|
};
|
||
|
for(var i=0; i<l; i++) {
|
||
|
get_replacement(i);
|
||
|
}
|
||
|
|
||
|
// The compiled function
|
||
|
return function(vars) {
|
||
|
var ret = template+"";
|
||
|
var r;
|
||
|
for(i=0; i<l; i++) {
|
||
|
r = replacements[i];
|
||
|
ret = ret.replace(r.s, r.r(vars));
|
||
|
}
|
||
|
return ret;
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
};
|
||
|
|
||
|
JSONEditor.defaults.templates.ejs = function() {
|
||
|
if(!window.EJS) return false;
|
||
|
|
||
|
return {
|
||
|
compile: function(template) {
|
||
|
var compiled = new window.EJS({
|
||
|
text: template
|
||
|
});
|
||
|
|
||
|
return function(context) {
|
||
|
return compiled.render(context);
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
};
|
||
|
|
||
|
JSONEditor.defaults.templates.handlebars = function() {
|
||
|
return window.Handlebars;
|
||
|
};
|
||
|
|
||
|
JSONEditor.defaults.templates.hogan = function() {
|
||
|
if(!window.Hogan) return false;
|
||
|
|
||
|
return {
|
||
|
compile: function(template) {
|
||
|
var compiled = window.Hogan.compile(template);
|
||
|
return function(context) {
|
||
|
return compiled.render(context);
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
};
|
||
|
|
||
|
JSONEditor.defaults.templates.markup = function() {
|
||
|
if(!window.Mark || !window.Mark.up) return false;
|
||
|
|
||
|
return {
|
||
|
compile: function(template) {
|
||
|
return function(context) {
|
||
|
return window.Mark.up(template,context);
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
};
|
||
|
|
||
|
JSONEditor.defaults.templates.mustache = function() {
|
||
|
if(!window.Mustache) return false;
|
||
|
|
||
|
return {
|
||
|
compile: function(template) {
|
||
|
return function(view) {
|
||
|
return window.Mustache.render(template, view);
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
};
|
||
|
|
||
|
JSONEditor.defaults.templates.swig = function() {
|
||
|
return window.swig;
|
||
|
};
|
||
|
|
||
|
JSONEditor.defaults.templates.underscore = function() {
|
||
|
if(!window._) return false;
|
||
|
|
||
|
return {
|
||
|
compile: function(template) {
|
||
|
return function(context) {
|
||
|
return window._.template(template, context);
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
};
|
||
|
|
||
|
// Set the default theme
|
||
|
JSONEditor.defaults.theme = 'html';
|
||
|
|
||
|
// Set the default template engine
|
||
|
JSONEditor.defaults.template = 'default';
|
||
|
|
||
|
// Default options when initializing JSON Editor
|
||
|
JSONEditor.defaults.options = {};
|
||
|
|
||
|
// String translate function
|
||
|
JSONEditor.defaults.translate = function(key, variables) {
|
||
|
var lang = JSONEditor.defaults.languages[JSONEditor.defaults.language];
|
||
|
if(!lang) throw "Unknown language "+JSONEditor.defaults.language;
|
||
|
|
||
|
var string = lang[key] || JSONEditor.defaults.languages[JSONEditor.defaults.default_language][key];
|
||
|
|
||
|
if(typeof string === "undefined") throw "Unknown translate string "+key;
|
||
|
|
||
|
if(variables) {
|
||
|
for(var i=0; i<variables.length; i++) {
|
||
|
string = string.replace(new RegExp('\\{\\{'+i+'}}','g'),variables[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return string;
|
||
|
};
|
||
|
|
||
|
// Translation strings and default languages
|
||
|
JSONEditor.defaults.default_language = 'en';
|
||
|
JSONEditor.defaults.language = JSONEditor.defaults.default_language;
|
||
|
JSONEditor.defaults.languages.en = {
|
||
|
/**
|
||
|
* When a property is not set
|
||
|
*/
|
||
|
error_notset: "Property must be set",
|
||
|
/**
|
||
|
* When a string must not be empty
|
||
|
*/
|
||
|
error_notempty: "Value required",
|
||
|
/**
|
||
|
* When a value is not one of the enumerated values
|
||
|
*/
|
||
|
error_enum: "Value must be one of the enumerated values",
|
||
|
/**
|
||
|
* When a value doesn't validate any schema of a 'anyOf' combination
|
||
|
*/
|
||
|
error_anyOf: "Value must validate against at least one of the provided schemas",
|
||
|
/**
|
||
|
* When a value doesn't validate
|
||
|
* @variables This key takes one variable: The number of schemas the value does not validate
|
||
|
*/
|
||
|
error_oneOf: 'Value must validate against exactly one of the provided schemas. It currently validates against {{0}} of the schemas.',
|
||
|
/**
|
||
|
* When a value does not validate a 'not' schema
|
||
|
*/
|
||
|
error_not: "Value must not validate against the provided schema",
|
||
|
/**
|
||
|
* When a value does not match any of the provided types
|
||
|
*/
|
||
|
error_type_union: "Value must be one of the provided types",
|
||
|
/**
|
||
|
* When a value does not match the given type
|
||
|
* @variables This key takes one variable: The type the value should be of
|
||
|
*/
|
||
|
error_type: "Value must be of type {{0}}",
|
||
|
/**
|
||
|
* When the value validates one of the disallowed types
|
||
|
*/
|
||
|
error_disallow_union: "Value must not be one of the provided disallowed types",
|
||
|
/**
|
||
|
* When the value validates a disallowed type
|
||
|
* @variables This key takes one variable: The type the value should not be of
|
||
|
*/
|
||
|
error_disallow: "Value must not be of type {{0}}",
|
||
|
/**
|
||
|
* When a value is not a multiple of or divisible by a given number
|
||
|
* @variables This key takes one variable: The number mentioned above
|
||
|
*/
|
||
|
error_multipleOf: "Value must be a multiple of {{0}}",
|
||
|
/**
|
||
|
* When a value is greater than it's supposed to be (exclusive)
|
||
|
* @variables This key takes one variable: The maximum
|
||
|
*/
|
||
|
error_maximum_excl: "Value must be less than {{0}}",
|
||
|
/**
|
||
|
* When a value is greater than it's supposed to be (inclusive
|
||
|
* @variables This key takes one variable: The maximum
|
||
|
*/
|
||
|
error_maximum_incl: "Value must be at most {{0}}",
|
||
|
/**
|
||
|
* When a value is lesser than it's supposed to be (exclusive)
|
||
|
* @variables This key takes one variable: The minimum
|
||
|
*/
|
||
|
error_minimum_excl: "Value must be greater than {{0}}",
|
||
|
/**
|
||
|
* When a value is lesser than it's supposed to be (inclusive)
|
||
|
* @variables This key takes one variable: The minimum
|
||
|
*/
|
||
|
error_minimum_incl: "Value must be at least {{0}}",
|
||
|
/**
|
||
|
* When a value have too many characters
|
||
|
* @variables This key takes one variable: The maximum character count
|
||
|
*/
|
||
|
error_maxLength: "Value must be at most {{0}} characters long",
|
||
|
/**
|
||
|
* When a value does not have enough characters
|
||
|
* @variables This key takes one variable: The minimum character count
|
||
|
*/
|
||
|
error_minLength: "Value must be at least {{0}} characters long",
|
||
|
/**
|
||
|
* When a value does not match a given pattern
|
||
|
*/
|
||
|
error_pattern: "Value must match the provided pattern",
|
||
|
/**
|
||
|
* When an array has additional items whereas it is not supposed to
|
||
|
*/
|
||
|
error_additionalItems: "No additional items allowed in this array",
|
||
|
/**
|
||
|
* When there are to many items in an array
|
||
|
* @variables This key takes one variable: The maximum item count
|
||
|
*/
|
||
|
error_maxItems: "Value must have at most {{0}} items",
|
||
|
/**
|
||
|
* When there are not enough items in an array
|
||
|
* @variables This key takes one variable: The minimum item count
|
||
|
*/
|
||
|
error_minItems: "Value must have at least {{0}} items",
|
||
|
/**
|
||
|
* When an array is supposed to have unique items but has duplicates
|
||
|
*/
|
||
|
error_uniqueItems: "Array must have unique items",
|
||
|
/**
|
||
|
* When there are too many properties in an object
|
||
|
* @variables This key takes one variable: The maximum property count
|
||
|
*/
|
||
|
error_maxProperties: "Object must have at most {{0}} properties",
|
||
|
/**
|
||
|
* When there are not enough properties in an object
|
||
|
* @variables This key takes one variable: The minimum property count
|
||
|
*/
|
||
|
error_minProperties: "Object must have at least {{0}} properties",
|
||
|
/**
|
||
|
* When a required property is not defined
|
||
|
* @variables This key takes one variable: The name of the missing property
|
||
|
*/
|
||
|
error_required: "Object is missing the required property '{{0}}'",
|
||
|
/**
|
||
|
* When there is an additional property is set whereas there should be none
|
||
|
* @variables This key takes one variable: The name of the additional property
|
||
|
*/
|
||
|
error_additional_properties: "No additional properties allowed, but property {{0}} is set",
|
||
|
/**
|
||
|
* When a dependency is not resolved
|
||
|
* @variables This key takes one variable: The name of the missing property for the dependency
|
||
|
*/
|
||
|
error_dependency: "Must have property {{0}}"
|
||
|
};
|
||
|
|
||
|
// Miscellaneous Plugin Settings
|
||
|
JSONEditor.plugins = {
|
||
|
ace: {
|
||
|
theme: ''
|
||
|
},
|
||
|
epiceditor: {
|
||
|
|
||
|
},
|
||
|
sceditor: {
|
||
|
|
||
|
},
|
||
|
select2: {
|
||
|
|
||
|
},
|
||
|
selectize: {
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Default per-editor options
|
||
|
for(var i in JSONEditor.defaults.editors) {
|
||
|
if(!JSONEditor.defaults.editors.hasOwnProperty(i)) continue;
|
||
|
JSONEditor.defaults.editors[i].options = JSONEditor.defaults.editors.options || {};
|
||
|
}
|
||
|
|
||
|
// Set the default resolvers
|
||
|
// Use "multiple" as a fall back for everything
|
||
|
JSONEditor.defaults.resolvers.unshift(function(schema) {
|
||
|
if(typeof schema.type !== "string") return "multiple";
|
||
|
});
|
||
|
// If the type is not set but properties are defined, we can infer the type is actually object
|
||
|
JSONEditor.defaults.resolvers.unshift(function(schema) {
|
||
|
// If the schema is a simple type
|
||
|
if(!schema.type && schema.properties ) return "object";
|
||
|
});
|
||
|
// If the type is set and it's a basic type, use the primitive editor
|
||
|
JSONEditor.defaults.resolvers.unshift(function(schema) {
|
||
|
// If the schema is a simple type
|
||
|
if(typeof schema.type === "string") return schema.type;
|
||
|
});
|
||
|
// Boolean editors
|
||
|
JSONEditor.defaults.resolvers.unshift(function(schema) {
|
||
|
if(schema.type === 'boolean') {
|
||
|
// If explicitly set to 'checkbox', use that
|
||
|
if(schema.format === "checkbox" || (schema.options && schema.options.checkbox)) {
|
||
|
return "checkbox";
|
||
|
}
|
||
|
// Otherwise, default to select menu
|
||
|
return (JSONEditor.plugins.selectize.enable) ? 'selectize' : 'select';
|
||
|
}
|
||
|
});
|
||
|
// Use the multiple editor for schemas where the `type` is set to "any"
|
||
|
JSONEditor.defaults.resolvers.unshift(function(schema) {
|
||
|
// If the schema can be of any type
|
||
|
if(schema.type === "any") return "multiple";
|
||
|
});
|
||
|
// Editor for base64 encoded files
|
||
|
JSONEditor.defaults.resolvers.unshift(function(schema) {
|
||
|
// If the schema can be of any type
|
||
|
if(schema.type === "string" && schema.media && schema.media.binaryEncoding==="base64") {
|
||
|
return "base64";
|
||
|
}
|
||
|
});
|
||
|
// Editor for uploading files
|
||
|
JSONEditor.defaults.resolvers.unshift(function(schema) {
|
||
|
if(schema.type === "string" && schema.format === "url" && schema.options && schema.options.upload === true) {
|
||
|
if(window.FileReader) return "upload";
|
||
|
}
|
||
|
});
|
||
|
// Use the table editor for arrays with the format set to `table`
|
||
|
JSONEditor.defaults.resolvers.unshift(function(schema) {
|
||
|
// Type `array` with format set to `table`
|
||
|
if(schema.type == "array" && schema.format == "table") {
|
||
|
return "table";
|
||
|
}
|
||
|
});
|
||
|
// Use the `select` editor for dynamic enumSource enums
|
||
|
JSONEditor.defaults.resolvers.unshift(function(schema) {
|
||
|
if(schema.enumSource) return (JSONEditor.plugins.selectize.enable) ? 'selectize' : 'select';
|
||
|
});
|
||
|
// Use the `enum` or `select` editors for schemas with enumerated properties
|
||
|
JSONEditor.defaults.resolvers.unshift(function(schema) {
|
||
|
if(schema["enum"]) {
|
||
|
if(schema.type === "array" || schema.type === "object") {
|
||
|
return "enum";
|
||
|
}
|
||
|
else if(schema.type === "number" || schema.type === "integer" || schema.type === "string") {
|
||
|
return (JSONEditor.plugins.selectize.enable) ? 'selectize' : 'select';
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
// Specialized editors for arrays of strings
|
||
|
JSONEditor.defaults.resolvers.unshift(function(schema) {
|
||
|
if(schema.type === "array" && schema.items && !(Array.isArray(schema.items)) && schema.uniqueItems && ['string','number','integer'].indexOf(schema.items.type) >= 0) {
|
||
|
// For enumerated strings, number, or integers
|
||
|
if(schema.items.enum) {
|
||
|
return 'multiselect';
|
||
|
}
|
||
|
// For non-enumerated strings (tag editor)
|
||
|
else if(JSONEditor.plugins.selectize.enable && schema.items.type === "string") {
|
||
|
return 'arraySelectize';
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
// Use the multiple editor for schemas with `oneOf` set
|
||
|
JSONEditor.defaults.resolvers.unshift(function(schema) {
|
||
|
// If this schema uses `oneOf`
|
||
|
if(schema.oneOf) return "multiple";
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* This is a small wrapper for using JSON Editor like a typical jQuery plugin.
|
||
|
*/
|
||
|
(function() {
|
||
|
if(window.jQuery || window.Zepto) {
|
||
|
var $ = window.jQuery || window.Zepto;
|
||
|
$.jsoneditor = JSONEditor.defaults;
|
||
|
|
||
|
$.fn.jsoneditor = function(options) {
|
||
|
var self = this;
|
||
|
var editor = this.data('jsoneditor');
|
||
|
if(options === 'value') {
|
||
|
if(!editor) throw "Must initialize jsoneditor before getting/setting the value";
|
||
|
|
||
|
// Set value
|
||
|
if(arguments.length > 1) {
|
||
|
editor.setValue(arguments[1]);
|
||
|
}
|
||
|
// Get value
|
||
|
else {
|
||
|
return editor.getValue();
|
||
|
}
|
||
|
}
|
||
|
else if(options === 'validate') {
|
||
|
if(!editor) throw "Must initialize jsoneditor before validating";
|
||
|
|
||
|
// Validate a specific value
|
||
|
if(arguments.length > 1) {
|
||
|
return editor.validate(arguments[1]);
|
||
|
}
|
||
|
// Validate current value
|
||
|
else {
|
||
|
return editor.validate();
|
||
|
}
|
||
|
}
|
||
|
else if(options === 'destroy') {
|
||
|
if(editor) {
|
||
|
editor.destroy();
|
||
|
this.data('jsoneditor',null);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// Destroy first
|
||
|
if(editor) {
|
||
|
editor.destroy();
|
||
|
}
|
||
|
|
||
|
// Create editor
|
||
|
editor = new JSONEditor(this.get(0),options);
|
||
|
this.data('jsoneditor',editor);
|
||
|
|
||
|
// Setup event listeners
|
||
|
editor.on('change',function() {
|
||
|
self.trigger('change');
|
||
|
});
|
||
|
editor.on('ready',function() {
|
||
|
self.trigger('ready');
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
}
|
||
|
})();
|
||
|
|
||
|
window.JSONEditor = JSONEditor;
|
||
|
})();
|
||
|
|
||
|
//# sourceMappingURL=jsoneditor.js.map
|