mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2023-10-10 13:36:59 +02:00
0f2d72fc0a
- versionBranch was renamed to watchedVersionBranch (including all variables and strings. even config) - Enhancements for JSON Editor. Detection of advanced was not so good. Now you can see advaced if advanced or expert is selected. expert is the highest and should also make advanced visible. - The levels default, advanced, expert are now also respected in the information tabs. Same logic as for the forms itself. Expert is highest and will also show all advanced settings.
7288 lines
219 KiB
JavaScript
Executable File
7288 lines
219 KiB
JavaScript
Executable File
/*! JSON Editor v0.7.28 - JSON Schema -> HTML Editor
|
|
* By Jeremy Dorn - https://github.com/jdorn/json-editor/
|
|
* Released under the MIT license
|
|
*
|
|
* Date: 2016-08-07
|
|
*/
|
|
|
|
/**
|
|
* 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 extend(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 = extend;
|
|
|
|
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.access = this.options.access;
|
|
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);
|
|
|
|
// 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.access = 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 and defaultProperties arrays should be unioned together
|
|
if((prop === 'required'||prop === 'defaultProperties') && typeof val === "object" && Array.isArray(val)) {
|
|
// Union arrays and unique
|
|
extended[prop] = 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];
|
|
|
|
// If type is only defined in the first schema, keep it
|
|
if(!obj2.type || !obj2.type.length) {
|
|
extended.type = val;
|
|
}
|
|
// If type is defined in both schemas, do an intersect
|
|
else {
|
|
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];
|
|
}
|
|
// Remove the type property if it's empty
|
|
else if(extended.type.length === 0) {
|
|
delete extended.type;
|
|
}
|
|
}
|
|
// 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("edt_msg_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("edt_msg_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("edt_msg_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('edt_msg_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('edt_msg_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('edt_msg_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('edt_msg_error_type_union')
|
|
});
|
|
}
|
|
}
|
|
// Simple type
|
|
else {
|
|
if(!this._checkType(schema.type, value)) {
|
|
errors.push({
|
|
path: path,
|
|
property: 'type',
|
|
message: this.translate('edt_msg_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('edt_msg_error_disallow_union')
|
|
});
|
|
}
|
|
}
|
|
// Simple type
|
|
else {
|
|
if(this._checkType(schema.disallow, value)) {
|
|
errors.push({
|
|
path: path,
|
|
property: 'disallow',
|
|
message: this.translate('edt_msg_error_disallow', [schema.disallow])
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Type Specific Validation
|
|
*/
|
|
|
|
// Number Specific Validation
|
|
if(typeof value === "number") {
|
|
// `multipleOf` and `divisibleBy`
|
|
if(schema.multipleOf || schema.divisibleBy) {
|
|
var divisor = schema.multipleOf || schema.divisibleBy;
|
|
// Vanilla JS, prone to floating point rounding errors (e.g. 1.14 / .01 == 113.99999)
|
|
valid = (value/divisor === Math.floor(value/divisor));
|
|
|
|
// Use math.js is available
|
|
if(window.math) {
|
|
valid = window.math.mod(window.math.bignumber(value), window.math.bignumber(divisor)).equals(0);
|
|
}
|
|
// Use decimal.js is available
|
|
else if(window.Decimal) {
|
|
valid = (new window.Decimal(value)).mod(new window.Decimal(divisor)).equals(0);
|
|
}
|
|
|
|
if(!valid) {
|
|
errors.push({
|
|
path: path,
|
|
property: schema.multipleOf? 'multipleOf' : 'divisibleBy',
|
|
message: this.translate('edt_msg_error_multipleOf', [divisor])
|
|
});
|
|
}
|
|
}
|
|
|
|
// `maximum`
|
|
if(schema.hasOwnProperty('maximum')) {
|
|
// Vanilla JS, prone to floating point rounding errors (e.g. .999999999999999 == 1)
|
|
valid = schema.exclusiveMaximum? (value < schema.maximum) : (value <= schema.maximum);
|
|
|
|
// Use math.js is available
|
|
if(window.math) {
|
|
valid = window.math[schema.exclusiveMaximum?'smaller':'smallerEq'](
|
|
window.math.bignumber(value),
|
|
window.math.bignumber(schema.maximum)
|
|
);
|
|
}
|
|
// Use Decimal.js if available
|
|
else if(window.Decimal) {
|
|
valid = (new window.Decimal(value))[schema.exclusiveMaximum?'lt':'lte'](new window.Decimal(schema.maximum));
|
|
}
|
|
|
|
if(!valid) {
|
|
errors.push({
|
|
path: path,
|
|
property: 'maximum',
|
|
message: this.translate(
|
|
(schema.exclusiveMaximum?'edt_msg_error_maximum_excl':'edt_msg_error_maximum_incl'),
|
|
[schema.maximum]
|
|
)
|
|
});
|
|
}
|
|
}
|
|
|
|
// `minimum`
|
|
if(schema.hasOwnProperty('minimum')) {
|
|
// Vanilla JS, prone to floating point rounding errors (e.g. .999999999999999 == 1)
|
|
valid = schema.exclusiveMinimum? (value > schema.minimum) : (value >= schema.minimum);
|
|
|
|
// Use math.js is available
|
|
if(window.math) {
|
|
valid = window.math[schema.exclusiveMinimum?'larger':'largerEq'](
|
|
window.math.bignumber(value),
|
|
window.math.bignumber(schema.minimum)
|
|
);
|
|
}
|
|
// Use Decimal.js if available
|
|
else if(window.Decimal) {
|
|
valid = (new window.Decimal(value))[schema.exclusiveMinimum?'gt':'gte'](new window.Decimal(schema.minimum));
|
|
}
|
|
|
|
if(!valid) {
|
|
errors.push({
|
|
path: path,
|
|
property: 'minimum',
|
|
message: this.translate(
|
|
(schema.exclusiveMinimum?'edt_msg_error_minimum_excl':'edt_msg_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('edt_msg_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?'edt_msg_error_notempty':'edt_msg_error_minLength'), [schema.minLength])
|
|
});
|
|
}
|
|
}
|
|
|
|
// `pattern`
|
|
if(schema.pattern) {
|
|
if(!(new RegExp(schema.pattern)).test(value)) {
|
|
errors.push({
|
|
path: path,
|
|
property: 'pattern',
|
|
message: this.translate('edt_msg_error_pattern', [schema.pattern])
|
|
});
|
|
}
|
|
}
|
|
}
|
|
// Array specific validation
|
|
else if(typeof value === "object" && value !== null && Array.isArray(value)) {
|
|
// `items` and `additionalItems`
|
|
if(schema.items) {
|
|
// `items` is an array
|
|
if(Array.isArray(schema.items)) {
|
|
for(i=0; i<value.length; i++) {
|
|
// If this item has a specific schema tied to it
|
|
// Validate against it
|
|
if(schema.items[i]) {
|
|
errors = errors.concat(this._validateSchema(schema.items[i],value[i],path+'.'+i));
|
|
}
|
|
// If all additional items are allowed
|
|
else if(schema.additionalItems === true) {
|
|
break;
|
|
}
|
|
// If additional items is a schema
|
|
// TODO: Incompatibility between version 3 and 4 of the spec
|
|
else if(schema.additionalItems) {
|
|
errors = errors.concat(this._validateSchema(schema.additionalItems,value[i],path+'.'+i));
|
|
}
|
|
// If no additional items are allowed
|
|
else if(schema.additionalItems === false) {
|
|
errors.push({
|
|
path: path,
|
|
property: 'additionalItems',
|
|
message: this.translate('edt_msg_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('edt_msg_error_maxItems', [schema.maxItems])
|
|
});
|
|
}
|
|
}
|
|
|
|
// `minItems`
|
|
if(schema.minItems) {
|
|
if(value.length < schema.minItems) {
|
|
errors.push({
|
|
path: path,
|
|
property: 'minItems',
|
|
message: this.translate('edt_msg_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('edt_msg_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('edt_msg_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('edt_msg_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('edt_msg_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('edt_msg_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('edt_msg_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.access = this.jsoneditor.access;
|
|
|
|
this.translate = this.jsoneditor.translate || JSONEditor.defaults.translate;
|
|
|
|
this.original_schema = options.schema;
|
|
this.schema = this.jsoneditor.expandSchema(this.original_schema);
|
|
|
|
this.options = $extend({}, (this.options || {}), (options.schema.options || {}), options);
|
|
|
|
if(!options.path && !this.schema.id) this.schema.id = 'root';
|
|
this.path = options.path || 'root';
|
|
this.formname = options.formname || this.path.replace(/\.([^.]+)/g,'_$1');
|
|
if(this.jsoneditor.options.form_name_root) this.formname = this.formname.replace(/^root\[/,this.jsoneditor.options.form_name_root+'[');
|
|
this.key = this.path.split('.').pop();
|
|
this.parent = options.parent;
|
|
|
|
this.link_watchers = [];
|
|
|
|
if(options.container) this.setContainer(options.container);
|
|
this.registerDependencies();
|
|
},
|
|
registerDependencies: function() {
|
|
this.dependenciesFulfilled = true;
|
|
var deps = this.options.dependencies;
|
|
if (!deps) {
|
|
return;
|
|
}
|
|
|
|
var self = this;
|
|
Object.keys(deps).forEach(function(dependency) {
|
|
var path = self.path.split('.');
|
|
path[path.length - 1] = dependency;
|
|
path = path.join('.');
|
|
var choices = deps[dependency];
|
|
self.jsoneditor.watch(path, function() {
|
|
self.checkDependency(path, choices);
|
|
});
|
|
});
|
|
},
|
|
checkDependency: function(path, choices) {
|
|
var wrapper = this.control || this.container;
|
|
if (this.path === path || !wrapper) {
|
|
return;
|
|
}
|
|
|
|
var self = this;
|
|
var editor = this.jsoneditor.getEditor(path);
|
|
var value = editor ? editor.getValue() : undefined;
|
|
var previousStatus = this.dependenciesFulfilled;
|
|
this.dependenciesFulfilled = false;
|
|
|
|
if (!editor || !editor.dependenciesFulfilled) {
|
|
this.dependenciesFulfilled = false;
|
|
} else if (Array.isArray(choices)) {
|
|
choices.some(function(choice) {
|
|
if (value === choice) {
|
|
self.dependenciesFulfilled = true;
|
|
return true;
|
|
}
|
|
});
|
|
} else if (typeof choices === 'object') {
|
|
if (typeof value !== 'object') {
|
|
this.dependenciesFulfilled = choices === value;
|
|
} else {
|
|
Object.keys(choices).some(function(key) {
|
|
if (!choices.hasOwnProperty(key)) {
|
|
return false;
|
|
}
|
|
if (!value.hasOwnProperty(key) || choices[key] !== value[key]) {
|
|
self.dependenciesFulfilled = false;
|
|
return true;
|
|
}
|
|
self.dependenciesFulfilled = true;
|
|
});
|
|
}
|
|
} else if (typeof choices === 'string' || typeof choices === 'number') {
|
|
this.dependenciesFulfilled = value === choices;
|
|
} else if (typeof choices === 'boolean') {
|
|
if (choices) {
|
|
this.dependenciesFulfilled = value;
|
|
} else {
|
|
this.dependenciesFulfilled = !value;
|
|
}
|
|
}
|
|
|
|
if (this.dependenciesFulfilled !== previousStatus) {
|
|
this.notify();
|
|
}
|
|
|
|
if (this.dependenciesFulfilled) {
|
|
wrapper.style.display = 'block';
|
|
} else {
|
|
wrapper.style.display = 'none';
|
|
}
|
|
},
|
|
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();
|
|
|
|
//hide input fields, if they didn't match the current access level
|
|
var storedAccess = this.access
|
|
if(this.schema.access){
|
|
if(this.schema.access == 'system')
|
|
this.container.style.display = "none";
|
|
else if(this.schema.access == 'advanced' && storedAccess == 'default')
|
|
{
|
|
this.container.style.display = "none";
|
|
}
|
|
else if(this.schema.access == 'expert' && storedAccess != 'expert')
|
|
{
|
|
this.container.style.display = "none";
|
|
//this.disable();
|
|
}
|
|
}
|
|
},
|
|
|
|
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)) {
|
|
if(path.length<2) continue;
|
|
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);
|
|
|
|
// Template to generate the link's download attribute
|
|
var download = null;
|
|
if(data.download) download = data.download;
|
|
|
|
if(download && download !== true) {
|
|
download = this.jsoneditor.compileTemplate(download, 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 {
|
|
link = 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;
|
|
});
|
|
}
|
|
|
|
if(download && link) {
|
|
if(download === true) {
|
|
link.setAttribute('download','');
|
|
}
|
|
else {
|
|
this.link_watchers.push(function(vars) {
|
|
link.setAttribute('download',download(vars));
|
|
});
|
|
}
|
|
}
|
|
|
|
if(data.class) link.className = link.className + ' ' + data.class;
|
|
|
|
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() {
|
|
if (this.schema.title == null)
|
|
return this.key;
|
|
else
|
|
return $.i18n(this.schema.title);
|
|
},
|
|
getAppend: function() {
|
|
return $.i18n(this.schema.append);
|
|
},
|
|
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 true;
|
|
},
|
|
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('id',this.formname);
|
|
},
|
|
unregister: function() {
|
|
this._super();
|
|
if(!this.input) return;
|
|
this.input.removeAttribute('id');
|
|
},
|
|
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);
|
|
if(this.schema.append) this.append = this.theme.getFormInputAppend(this.getAppend());
|
|
|
|
this.placeholder = this.schema.default;
|
|
|
|
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',
|
|
'groovy',
|
|
'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);
|
|
}
|
|
}
|
|
// Number or integer adds html5 tag 'number'
|
|
else if (this.schema.type == "number" || this.schema.type == "integer"){
|
|
|
|
var min = this.schema.minimum
|
|
var max = this.schema.maximum
|
|
var step = this.schema.step
|
|
|
|
this.input = this.theme.getRangeInput(min,max,step);
|
|
}
|
|
// 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);
|
|
if(this.defaultValue) this.input.setAttribute('data-schemaformat',this.format);
|
|
if(this.formname && this.label)this.label.setAttribute('for',this.formname);
|
|
|
|
this.control = this.theme.getFormControl(this.label, this.input, this.description, this.append, this.placeholder);
|
|
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 {
|
|
if(!this.schema.defaultProperties) {
|
|
if(this.jsoneditor.options.display_required_only || this.options.display_required_only) {
|
|
this.schema.defaultProperties = [];
|
|
$each(this.schema.properties, function(k,s) {
|
|
if(self.isRequired({key: k, schema: s})) {
|
|
self.schema.defaultProperties.push(k);
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
self.schema.defaultProperties = Object.keys(self.schema.properties);
|
|
}
|
|
}
|
|
|
|
// Increase the grid width to account for padding
|
|
self.maxwidth += 1;
|
|
|
|
$each(this.schema.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',this.translate('edt_msg_button_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',self.translate('edt_msg_button_collapse'));
|
|
}
|
|
else {
|
|
self.editor_holder.style.display = 'none';
|
|
self.collapsed = true;
|
|
self.setButtonText(self.toggle_button,'','expand',self.translate('edt_msg_button_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_delete_all_rows_buttons = this.hide_delete_buttons || this.options.disable_array_delete_all_rows || this.jsoneditor.options.disable_array_delete_all_rows;
|
|
this.hide_delete_last_row_buttons = this.hide_delete_buttons || this.options.disable_array_delete_last_row || this.jsoneditor.options.disable_array_delete_last_row;
|
|
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);
|
|
}
|
|
if(this.schema.append) {
|
|
this.append = this.theme.getAppend(this.schema.append);
|
|
this.container.appendChild(this.append);
|
|
}
|
|
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);
|
|
if (typeof tmp.title == 'undefined')
|
|
this.item_title = 'item';
|
|
else
|
|
this.item_title = $.i18n(tmp.title);
|
|
}
|
|
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 = $.i18n(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, or configured to hide the delete_last_row button, hide the delete button beneath the rows
|
|
if(minItems || this.hide_delete_last_row_buttons) {
|
|
this.delete_last_row_button.style.display = 'none';
|
|
}
|
|
else {
|
|
this.delete_last_row_button.style.display = '';
|
|
controls_needed = true;
|
|
}
|
|
}
|
|
else {
|
|
if(minItems || this.hide_delete_last_row_buttons) {
|
|
this.delete_last_row_button.style.display = 'none';
|
|
}
|
|
else {
|
|
this.delete_last_row_button.style.display = '';
|
|
controls_needed = true;
|
|
}
|
|
|
|
if(minItems || this.hide_delete_all_rows_buttons) {
|
|
this.remove_all_rows_button.style.display = 'none';
|
|
}
|
|
else {
|
|
this.remove_all_rows_button.style.display = '';
|
|
controls_needed = true;
|
|
}
|
|
}
|
|
|
|
// If there are maxItems in the array, hide the add button beneath the rows
|
|
if((this.getMax() && this.getMax() <= this.rows.length) || this.hide_add_button){
|
|
this.add_row_button.style.display = 'none';
|
|
}
|
|
else {
|
|
this.add_row_button.style.display = '';
|
|
controls_needed = true;
|
|
}
|
|
|
|
if(!this.collapsed && controls_needed) {
|
|
this.controls.style.display = 'inline-block';
|
|
}
|
|
else {
|
|
this.controls.style.display = 'none';
|
|
}
|
|
}
|
|
},
|
|
addRow: function(value, initial) {
|
|
var self = this;
|
|
var i = this.rows.length;
|
|
|
|
self.rows[i] = this.getElementEditor(i);
|
|
self.row_cache[i] = self.rows[i];
|
|
|
|
if(self.tabs_holder) {
|
|
self.rows[i].tab_text = document.createElement('span');
|
|
self.rows[i].tab_text.textContent = self.rows[i].getHeaderText();
|
|
self.rows[i].tab = self.theme.getTab(self.rows[i].tab_text);
|
|
self.rows[i].tab.addEventListener('click', function(e) {
|
|
self.active_tab = self.rows[i].tab;
|
|
self.refreshTabs();
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
});
|
|
|
|
self.theme.addTab(self.tabs_holder, self.rows[i].tab);
|
|
}
|
|
|
|
var controls_holder = self.rows[i].title_controls || self.rows[i].array_controls;
|
|
|
|
// Buttons to delete row, move row up, and move row down
|
|
if(!self.hide_delete_buttons) {
|
|
self.rows[i].delete_button = this.getButton(self.getItemTitle(),'delete',this.translate('edt_msg_button_delete_row_title',[self.getItemTitle()]));
|
|
self.rows[i].delete_button.className += ' delete';
|
|
self.rows[i].delete_button.setAttribute('data-i',i);
|
|
self.rows[i].delete_button.addEventListener('click',function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var i = this.getAttribute('data-i')*1;
|
|
|
|
var value = self.getValue();
|
|
|
|
var newval = [];
|
|
var new_active_tab = null;
|
|
$each(value,function(j,row) {
|
|
if(j===i) {
|
|
// If the one we're deleting is the active tab
|
|
if(self.rows[j].tab === self.active_tab) {
|
|
// Make the next tab active if there is one
|
|
// Note: the next tab is going to be the current tab after deletion
|
|
if(self.rows[j+1]) new_active_tab = self.rows[j].tab;
|
|
// Otherwise, make the previous tab active if there is one
|
|
else if(j) new_active_tab = self.rows[j-1].tab;
|
|
}
|
|
|
|
return; // If this is the one we're deleting
|
|
}
|
|
newval.push(row);
|
|
});
|
|
self.setValue(newval);
|
|
if(new_active_tab) {
|
|
self.active_tab = new_active_tab;
|
|
self.refreshTabs();
|
|
}
|
|
|
|
self.onChange(true);
|
|
});
|
|
|
|
if(controls_holder) {
|
|
controls_holder.appendChild(self.rows[i].delete_button);
|
|
}
|
|
}
|
|
|
|
if(i && !self.hide_move_buttons) {
|
|
self.rows[i].moveup_button = this.getButton('','moveup',this.translate('edt_msg_button_move_up_title'));
|
|
self.rows[i].moveup_button.className += ' moveup';
|
|
self.rows[i].moveup_button.setAttribute('data-i',i);
|
|
self.rows[i].moveup_button.addEventListener('click',function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var i = this.getAttribute('data-i')*1;
|
|
|
|
if(i<=0) return;
|
|
var rows = self.getValue();
|
|
var tmp = rows[i-1];
|
|
rows[i-1] = rows[i];
|
|
rows[i] = tmp;
|
|
|
|
self.setValue(rows);
|
|
self.active_tab = self.rows[i-1].tab;
|
|
self.refreshTabs();
|
|
|
|
self.onChange(true);
|
|
});
|
|
|
|
if(controls_holder) {
|
|
controls_holder.appendChild(self.rows[i].moveup_button);
|
|
}
|
|
}
|
|
|
|
if(!self.hide_move_buttons) {
|
|
self.rows[i].movedown_button = this.getButton('','movedown',this.translate('edt_msg_button_move_down_title'));
|
|
self.rows[i].movedown_button.className += ' movedown';
|
|
self.rows[i].movedown_button.setAttribute('data-i',i);
|
|
self.rows[i].movedown_button.addEventListener('click',function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var i = this.getAttribute('data-i')*1;
|
|
|
|
var rows = self.getValue();
|
|
if(i>=rows.length-1) return;
|
|
var tmp = rows[i+1];
|
|
rows[i+1] = rows[i];
|
|
rows[i] = tmp;
|
|
|
|
self.setValue(rows);
|
|
self.active_tab = self.rows[i+1].tab;
|
|
self.refreshTabs();
|
|
self.onChange(true);
|
|
});
|
|
|
|
if(controls_holder) {
|
|
controls_holder.appendChild(self.rows[i].movedown_button);
|
|
}
|
|
}
|
|
|
|
if(value) self.rows[i].setValue(value, initial);
|
|
self.refreshTabs();
|
|
},
|
|
addControls: function() {
|
|
var self = this;
|
|
|
|
this.collapsed = false;
|
|
this.toggle_button = this.getButton('','collapse',this.translate('edt_msg_button_collapse'));
|
|
this.title_controls.appendChild(this.toggle_button);
|
|
var row_holder_display = self.row_holder.style.display;
|
|
var controls_display = self.controls.style.display;
|
|
this.toggle_button.addEventListener('click',function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
if(self.collapsed) {
|
|
self.collapsed = false;
|
|
if(self.panel) self.panel.style.display = '';
|
|
self.row_holder.style.display = row_holder_display;
|
|
if(self.tabs_holder) self.tabs_holder.style.display = '';
|
|
self.controls.style.display = controls_display;
|
|
self.setButtonText(this,'','collapse',self.translate('edt_msg_button_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',self.translate('edt_msg_button_expand'));
|
|
}
|
|
});
|
|
|
|
// If it should start collapsed
|
|
if(this.options.collapsed) {
|
|
$trigger(this.toggle_button,'click');
|
|
}
|
|
|
|
// Collapse button disabled
|
|
if(this.schema.options && typeof this.schema.options.disable_collapse !== "undefined") {
|
|
if(this.schema.options.disable_collapse) this.toggle_button.style.display = 'none';
|
|
}
|
|
else if(this.jsoneditor.options.disable_collapse) {
|
|
this.toggle_button.style.display = 'none';
|
|
}
|
|
|
|
// Add "new row" and "delete last" buttons below editor
|
|
this.add_row_button = this.getButton(this.getItemTitle(),'add',this.translate('edt_msg_button_add_row_title',[this.getItemTitle()]));
|
|
|
|
this.add_row_button.addEventListener('click',function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var i = self.rows.length;
|
|
if(self.row_cache[i]) {
|
|
self.rows[i] = self.row_cache[i];
|
|
self.rows[i].setValue(self.rows[i].getDefault(), true);
|
|
self.rows[i].container.style.display = '';
|
|
if(self.rows[i].tab) self.rows[i].tab.style.display = '';
|
|
self.rows[i].register();
|
|
}
|
|
else {
|
|
self.addRow();
|
|
}
|
|
self.active_tab = self.rows[i].tab;
|
|
self.refreshTabs();
|
|
self.refreshValue();
|
|
self.onChange(true);
|
|
});
|
|
self.controls.appendChild(this.add_row_button);
|
|
|
|
this.delete_last_row_button = this.getButton(this.translate('edt_msg_button_delete_last',[this.getItemTitle()]),'delete',this.translate('edt_msg_button_delete_last_title',[this.getItemTitle()]));
|
|
this.delete_last_row_button.addEventListener('click',function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var rows = self.getValue();
|
|
|
|
var new_active_tab = null;
|
|
if(self.rows.length > 1 && self.rows[self.rows.length-1].tab === self.active_tab) new_active_tab = self.rows[self.rows.length-2].tab;
|
|
|
|
rows.pop();
|
|
self.setValue(rows);
|
|
if(new_active_tab) {
|
|
self.active_tab = new_active_tab;
|
|
self.refreshTabs();
|
|
}
|
|
self.onChange(true);
|
|
});
|
|
self.controls.appendChild(this.delete_last_row_button);
|
|
|
|
this.remove_all_rows_button = this.getButton(this.translate('edt_msg_button_delete_all'),'delete',this.translate('edt_msg_button_delete_all_title'));
|
|
this.remove_all_rows_button.addEventListener('click',function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
self.setValue([]);
|
|
self.onChange(true);
|
|
});
|
|
self.controls.appendChild(this.remove_all_rows_button);
|
|
|
|
if(self.tabs) {
|
|
this.add_row_button.style.width = '100%';
|
|
this.add_row_button.style.textAlign = 'left';
|
|
this.add_row_button.style.marginBottom = '3px';
|
|
|
|
this.delete_last_row_button.style.width = '100%';
|
|
this.delete_last_row_button.style.textAlign = 'left';
|
|
this.delete_last_row_button.style.marginBottom = '3px';
|
|
|
|
this.remove_all_rows_button.style.width = '100%';
|
|
this.remove_all_rows_button.style.textAlign = 'left';
|
|
this.remove_all_rows_button.style.marginBottom = '3px';
|
|
}
|
|
},
|
|
showValidationErrors: function(errors) {
|
|
var self = this;
|
|
|
|
// Get all the errors that pertain to this editor
|
|
var my_errors = [];
|
|
var other_errors = [];
|
|
$each(errors, function(i,error) {
|
|
if(error.path === self.path) {
|
|
my_errors.push(error);
|
|
}
|
|
else {
|
|
other_errors.push(error);
|
|
}
|
|
});
|
|
|
|
// Show errors for this editor
|
|
if(this.error_holder) {
|
|
if(my_errors.length) {
|
|
var message = [];
|
|
this.error_holder.innerHTML = '';
|
|
this.error_holder.style.display = '';
|
|
$each(my_errors, function(i,error) {
|
|
self.error_holder.appendChild(self.theme.getErrorMessage(error.message));
|
|
});
|
|
}
|
|
// Hide error area
|
|
else {
|
|
this.error_holder.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
// Show errors for child editors
|
|
$each(this.rows, function(i,row) {
|
|
row.showValidationErrors(other_errors);
|
|
});
|
|
}
|
|
});
|
|
|
|
JSONEditor.defaults.editors.table = JSONEditor.defaults.editors.array.extend({
|
|
register: function() {
|
|
this._super();
|
|
if(this.rows) {
|
|
for(var i=0; i<this.rows.length; i++) {
|
|
this.rows[i].register();
|
|
}
|
|
}
|
|
},
|
|
unregister: function() {
|
|
this._super();
|
|
if(this.rows) {
|
|
for(var i=0; i<this.rows.length; i++) {
|
|
this.rows[i].unregister();
|
|
}
|
|
}
|
|
},
|
|
getNumColumns: function() {
|
|
return Math.max(Math.min(12,this.width),3);
|
|
},
|
|
preBuild: function() {
|
|
var item_schema = this.jsoneditor.expandRefs(this.schema.items || {});
|
|
|
|
this.item_title = item_schema.title || 'row';
|
|
this.item_default = item_schema["default"] || null;
|
|
this.item_has_child_editors = item_schema.properties || item_schema.items;
|
|
this.width = 12;
|
|
this._super();
|
|
},
|
|
build: function() {
|
|
var self = this;
|
|
this.table = this.theme.getTable();
|
|
this.container.appendChild(this.table);
|
|
this.thead = this.theme.getTableHead();
|
|
this.table.appendChild(this.thead);
|
|
this.header_row = this.theme.getTableRow();
|
|
this.thead.appendChild(this.header_row);
|
|
this.row_holder = this.theme.getTableBody();
|
|
this.table.appendChild(this.row_holder);
|
|
|
|
// Determine the default value of array element
|
|
var tmp = this.getElementEditor(0,true);
|
|
this.item_default = tmp.getDefault();
|
|
this.width = tmp.getNumColumns() + 2;
|
|
|
|
if(!this.options.compact) {
|
|
this.title = this.theme.getHeader(this.getTitle());
|
|
this.container.appendChild(this.title);
|
|
this.title_controls = this.theme.getHeaderButtonHolder();
|
|
this.title.appendChild(this.title_controls);
|
|
if(this.schema.description) {
|
|
this.description = this.theme.getDescription(this.schema.description);
|
|
this.container.appendChild(this.description);
|
|
}
|
|
if(this.schema.append) {
|
|
this.append = this.theme.getAppend(this.getAppend());
|
|
this.container.appendChild(this.append);
|
|
}
|
|
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.table.style.display = '';
|
|
this.remove_all_rows_button.style.display = 'none';
|
|
|
|
// If there are minItems items in the array, or configured to hide the delete_last_row button, hide the delete button beneath the rows
|
|
if(minItems || this.hide_delete_last_row_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(minItems || this.hide_delete_last_row_buttons) {
|
|
this.delete_last_row_button.style.display = 'none';
|
|
}
|
|
else {
|
|
this.delete_last_row_button.style.display = '';
|
|
controls_needed = true;
|
|
}
|
|
|
|
if(minItems || this.hide_delete_all_rows_buttons) {
|
|
this.remove_all_rows_button.style.display = 'none';
|
|
}
|
|
else {
|
|
this.remove_all_rows_button.style.display = '';
|
|
controls_needed = true;
|
|
}
|
|
}
|
|
|
|
// If there are maxItems in the array, hide the add button beneath the rows
|
|
if((this.schema.maxItems && this.schema.maxItems <= this.rows.length) || this.hide_add_button) {
|
|
this.add_row_button.style.display = 'none';
|
|
}
|
|
else {
|
|
this.add_row_button.style.display = '';
|
|
controls_needed = true;
|
|
}
|
|
|
|
if(!controls_needed) {
|
|
this.controls.style.display = 'none';
|
|
}
|
|
else {
|
|
this.controls.style.display = '';
|
|
}
|
|
},
|
|
refreshValue: function() {
|
|
var self = this;
|
|
this.value = [];
|
|
|
|
$each(this.rows,function(i,editor) {
|
|
// Get the value for this editor
|
|
self.value[i] = editor.getValue();
|
|
});
|
|
this.serialized = JSON.stringify(this.value);
|
|
},
|
|
addRow: function(value) {
|
|
var self = this;
|
|
var i = this.rows.length;
|
|
|
|
self.rows[i] = this.getElementEditor(i);
|
|
|
|
var controls_holder = self.rows[i].table_controls;
|
|
|
|
// Buttons to delete row, move row up, and move row down
|
|
if(!this.hide_delete_buttons) {
|
|
self.rows[i].delete_button = this.getButton('','delete',this.translate('edt_msg_button_delete_row_title_short'));
|
|
self.rows[i].delete_button.className += ' delete';
|
|
self.rows[i].delete_button.setAttribute('data-i',i);
|
|
self.rows[i].delete_button.addEventListener('click',function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var i = this.getAttribute('data-i')*1;
|
|
|
|
var value = self.getValue();
|
|
|
|
var newval = [];
|
|
$each(value,function(j,row) {
|
|
if(j===i) return; // If this is the one we're deleting
|
|
newval.push(row);
|
|
});
|
|
self.setValue(newval);
|
|
self.onChange(true);
|
|
});
|
|
controls_holder.appendChild(self.rows[i].delete_button);
|
|
}
|
|
|
|
|
|
if(i && !this.hide_move_buttons) {
|
|
self.rows[i].moveup_button = this.getButton('','moveup',this.translate('edt_msg_button_move_up_title'));
|
|
self.rows[i].moveup_button.className += ' moveup';
|
|
self.rows[i].moveup_button.setAttribute('data-i',i);
|
|
self.rows[i].moveup_button.addEventListener('click',function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var i = this.getAttribute('data-i')*1;
|
|
|
|
if(i<=0) return;
|
|
var rows = self.getValue();
|
|
var tmp = rows[i-1];
|
|
rows[i-1] = rows[i];
|
|
rows[i] = tmp;
|
|
|
|
self.setValue(rows);
|
|
self.onChange(true);
|
|
});
|
|
controls_holder.appendChild(self.rows[i].moveup_button);
|
|
}
|
|
|
|
if(!this.hide_move_buttons) {
|
|
self.rows[i].movedown_button = this.getButton('','movedown',this.translate('edt_msg_button_move_down_title'));
|
|
self.rows[i].movedown_button.className += ' movedown';
|
|
self.rows[i].movedown_button.setAttribute('data-i',i);
|
|
self.rows[i].movedown_button.addEventListener('click',function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var i = this.getAttribute('data-i')*1;
|
|
var rows = self.getValue();
|
|
if(i>=rows.length-1) return;
|
|
var tmp = rows[i+1];
|
|
rows[i+1] = rows[i];
|
|
rows[i] = tmp;
|
|
|
|
self.setValue(rows);
|
|
self.onChange(true);
|
|
});
|
|
controls_holder.appendChild(self.rows[i].movedown_button);
|
|
}
|
|
|
|
if(value) self.rows[i].setValue(value);
|
|
},
|
|
addControls: function() {
|
|
var self = this;
|
|
|
|
this.collapsed = false;
|
|
this.toggle_button = this.getButton('','collapse',this.translate('edt_msg_button_collapse'));
|
|
if(this.title_controls) {
|
|
this.title_controls.appendChild(this.toggle_button);
|
|
this.toggle_button.addEventListener('click',function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
if(self.collapsed) {
|
|
self.collapsed = false;
|
|
self.panel.style.display = '';
|
|
self.setButtonText(this,'','collapse',self.translate('edt_msg_button_collapse'));
|
|
}
|
|
else {
|
|
self.collapsed = true;
|
|
self.panel.style.display = 'none';
|
|
self.setButtonText(this,'','expand',self.translate('edt_msg_button_expand'));
|
|
}
|
|
});
|
|
|
|
// If it should start collapsed
|
|
if(this.options.collapsed) {
|
|
$trigger(this.toggle_button,'click');
|
|
}
|
|
|
|
// Collapse button disabled
|
|
if(this.schema.options && typeof this.schema.options.disable_collapse !== "undefined") {
|
|
if(this.schema.options.disable_collapse) this.toggle_button.style.display = 'none';
|
|
}
|
|
else if(this.jsoneditor.options.disable_collapse) {
|
|
this.toggle_button.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
// Add "new row" and "delete last" buttons below editor
|
|
this.add_row_button = this.getButton(this.getItemTitle(),'add',this.translate('edt_msg_button_add_row_title',[this.getItemTitle()]));
|
|
this.add_row_button.addEventListener('click',function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
self.addRow();
|
|
self.refreshValue();
|
|
self.refreshRowButtons();
|
|
self.onChange(true);
|
|
});
|
|
self.controls.appendChild(this.add_row_button);
|
|
|
|
this.delete_last_row_button = this.getButton(this.translate('edt_msg_button_delete_last',[this.getItemTitle()]),'delete',this.translate('edt_msg_button_delete_last_title',[this.getItemTitle()]));
|
|
this.delete_last_row_button.addEventListener('click',function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
var rows = self.getValue();
|
|
rows.pop();
|
|
self.setValue(rows);
|
|
self.onChange(true);
|
|
});
|
|
self.controls.appendChild(this.delete_last_row_button);
|
|
|
|
this.remove_all_rows_button = this.getButton(this.translate('edt_msg_button_delete_all'),'delete',this.translate('edt_msg_button_delete_all_title'));
|
|
this.remove_all_rows_button.addEventListener('click',function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
self.setValue([]);
|
|
self.onChange(true);
|
|
});
|
|
self.controls.appendChild(this.remove_all_rows_button);
|
|
}
|
|
});
|
|
|
|
// Multiple Editor (for when `type` is an array)
|
|
JSONEditor.defaults.editors.multiple = JSONEditor.AbstractEditor.extend({
|
|
register: function() {
|
|
if(this.editors) {
|
|
for(var i=0; i<this.editors.length; i++) {
|
|
if(!this.editors[i]) continue;
|
|
this.editors[i].unregister();
|
|
}
|
|
if(this.editors[this.type]) this.editors[this.type].register();
|
|
}
|
|
this._super();
|
|
},
|
|
unregister: function() {
|
|
this._super();
|
|
if(this.editors) {
|
|
for(var i=0; i<this.editors.length; i++) {
|
|
if(!this.editors[i]) continue;
|
|
this.editors[i].unregister();
|
|
}
|
|
}
|
|
},
|
|
getNumColumns: function() {
|
|
if(!this.editors[this.type]) return 4;
|
|
return Math.max(this.editors[this.type].getNumColumns(),4);
|
|
},
|
|
enable: function() {
|
|
if(this.editors) {
|
|
for(var i=0; i<this.editors.length; i++) {
|
|
if(!this.editors[i]) continue;
|
|
this.editors[i].enable();
|
|
}
|
|
}
|
|
this.switcher.disabled = false;
|
|
this._super();
|
|
},
|
|
disable: function() {
|
|
if(this.editors) {
|
|
for(var i=0; i<this.editors.length; i++) {
|
|
if(!this.editors[i]) continue;
|
|
this.editors[i].disable();
|
|
}
|
|
}
|
|
this.switcher.disabled = true;
|
|
this._super();
|
|
},
|
|
switchEditor: function(i) {
|
|
var self = this;
|
|
|
|
if(!this.editors[i]) {
|
|
this.buildChildEditor(i);
|
|
}
|
|
|
|
var current_value = self.getValue();
|
|
|
|
self.type = i;
|
|
|
|
self.register();
|
|
|
|
$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;
|
|
delete this.schema.oneOf;
|
|
}
|
|
else if(this.schema.anyOf) {
|
|
this.anyOf = true;
|
|
this.types = this.schema.anyOf;
|
|
delete this.schema.anyOf;
|
|
}
|
|
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 and anyOf error paths need to remove the oneOf[i] part before passing to child editors
|
|
if(this.oneOf || this.anyOf) {
|
|
var check_part = this.oneOf? 'oneOf' : 'anyOf';
|
|
$each(this.editors,function(i,editor) {
|
|
if(!editor) return;
|
|
var check = self.path+'.'+check_part+'['+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('id',this.formname);
|
|
},
|
|
unregister: function() {
|
|
this._super();
|
|
if(!this.input) return;
|
|
this.input.removeAttribute('id');
|
|
},
|
|
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 = [];
|
|
var i;
|
|
|
|
// 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.schema.append) this.append = this.theme.getFormInputAppend(this.getAppend());
|
|
|
|
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();
|
|
});
|
|
|
|
if(this.formname)this.label.setAttribute('for',this.formname);
|
|
|
|
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;
|
|
|
|
// Store new value and propogate change event
|
|
this.value = 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 = [];
|
|
var i;
|
|
|
|
// 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.schema.append) this.append = this.theme.getFormInputAppend(this.getAppend());
|
|
|
|
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();
|
|
var i;
|
|
|
|
this.select_options = {};
|
|
this.select_values = {};
|
|
|
|
var items_schema = this.jsoneditor.expandRefs(this.schema.items || {});
|
|
|
|
var e = items_schema["enum"] || [];
|
|
var t = items_schema.options? items_schema.options.enum_titles || [] : [];
|
|
this.option_keys = [];
|
|
this.option_titles = [];
|
|
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.option_titles.push((t[i]||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.append) this.append = this.theme.getFormInputAppend(this.getAppend());
|
|
|
|
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_titles[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.theme.setSelectOptions(this.input,this.option_keys,this.option_titles);
|
|
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 file = this.uploader.files[0];
|
|
|
|
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);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
if(this.jsoneditor.options.auto_upload || this.schema.options.auto_upload) {
|
|
uploadButton.dispatchEvent(new MouseEvent('click'));
|
|
this.preview.removeChild(uploadButton);
|
|
}
|
|
},
|
|
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();
|
|
if(this.formname)this.label.setAttribute('for',this.formname);
|
|
if(this.formname)this.input.setAttribute('id',this.formname);
|
|
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);
|
|
if (this.input.id.endsWith('_enable'))
|
|
this.container.appendChild(document.createElement('hr'));
|
|
},
|
|
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);
|
|
}
|
|
if(this.schema.append) {
|
|
this.append = this.theme.getAppend(this.getAppend());
|
|
}
|
|
|
|
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';
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// colorpicker creation and handling, build on top of strings editor
|
|
JSONEditor.defaults.editors.colorPicker = JSONEditor.defaults.editors.string.extend({
|
|
getValue: function() {
|
|
if ($(this.input).data("colorpicker") !== undefined) {
|
|
var color = $(this.input).data('colorpicker').color.toRGB();
|
|
return [color.r,color.g, color.b];
|
|
}
|
|
else {
|
|
return [0,0,0];
|
|
}
|
|
},
|
|
|
|
setValue: function(val) {
|
|
function rgb2hex(rgb)
|
|
{
|
|
return "#" +
|
|
("0" + parseInt(rgb[0],10).toString(16)).slice(-2) +
|
|
("0" + parseInt(rgb[1],10).toString(16)).slice(-2) +
|
|
("0" + parseInt(rgb[2],10).toString(16)).slice(-2);
|
|
}
|
|
|
|
$(this.input).colorpicker('updateInput', 'rgb('+val+')');
|
|
$(this.input).colorpicker('updateData', val);
|
|
$(this.input).colorpicker('updatePicker', rgb2hex(val));
|
|
$(this.input).colorpicker('updateComponent', 'rgb('+val+')');
|
|
},
|
|
|
|
|
|
|
|
build: function() {
|
|
this._super();
|
|
var myinput = this;
|
|
$(myinput.input).parent().attr("class", $(myinput.input).parent().attr('class') + " colorpicker-element input-group");
|
|
$(myinput.input).append("<span class='input-group-addon' id='event_catcher'><i></i></span>");
|
|
$(myinput.input).colorpicker({
|
|
format: 'rgb',
|
|
customClass: 'colorpicker-2x',
|
|
sliders: {
|
|
saturation: {
|
|
maxLeft: 200,
|
|
maxTop: 200
|
|
},
|
|
hue: {
|
|
maxTop: 200
|
|
},
|
|
},
|
|
})
|
|
|
|
$("#event_catcher").detach().insertAfter(myinput.input);
|
|
$("#event_catcher").attr("id", "selector");
|
|
|
|
$(this.input).colorpicker().on('changeColor', function(e) {
|
|
$(myinput).val(e.color.toRGB()).change();
|
|
});
|
|
},
|
|
|
|
destroy: function() {
|
|
$(this.input).colorpicker('destroy');
|
|
}
|
|
});
|
|
|
|
// colorpickerRGBA creation and handling, build on top of strings editor
|
|
JSONEditor.defaults.editors.colorPickerRGBA = JSONEditor.defaults.editors.string.extend({
|
|
getValue: function() {
|
|
if ($(this.input).data("colorpicker") !== undefined) {
|
|
var color = $(this.input).data('colorpicker').color.toRGB();
|
|
return [color.r,color.g, color.b, color.a];
|
|
}
|
|
else {
|
|
return [0,0,0,1];
|
|
}
|
|
},
|
|
|
|
setValue: function(val) {
|
|
$(this.input).colorpicker('updateInput', 'rgba('+val+')');
|
|
$(this.input).colorpicker('updateData', val);
|
|
// $(this.input).colorpicker('updatePicker', rgb2hex(val));
|
|
$(this.input).colorpicker('updateComponent', 'rgba('+val+')');
|
|
},
|
|
|
|
|
|
|
|
build: function() {
|
|
this._super();
|
|
var myinput = this;
|
|
$(myinput.input).parent().attr("class", $(myinput.input).parent().attr('class') + " colorpicker-element input-group");
|
|
$(myinput.input).append("<span class='input-group-addon' id='event_catcher'><i></i></span>");
|
|
$(myinput.input).colorpicker({
|
|
format: 'rgba',
|
|
customClass: 'colorpicker-2x',
|
|
sliders: {
|
|
saturation: {
|
|
maxLeft: 200,
|
|
maxTop: 200
|
|
},
|
|
hue: {
|
|
maxTop: 200
|
|
},
|
|
alpha: {
|
|
maxTop: 200
|
|
}
|
|
},
|
|
})
|
|
|
|
$("#event_catcher").detach().insertAfter(myinput.input);
|
|
$("#event_catcher").attr("id", "selector");
|
|
|
|
$(this.input).colorpicker().on('changeColor', function(e) {
|
|
$(myinput).val(e.color.toRGB()).change();
|
|
});
|
|
},
|
|
|
|
destroy: function() {
|
|
$(this.input).colorpicker('destroy');
|
|
}
|
|
});
|
|
|
|
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 = 'bold';
|
|
return el;
|
|
},
|
|
getHeader: function(text) {
|
|
var el = document.createElement('h4');
|
|
if(text.innerHTML == ''){
|
|
text.style.display = 'none';
|
|
return text;
|
|
}
|
|
else 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) {
|
|
if (typeof options != "undefined")
|
|
{
|
|
titles = titles || [];
|
|
select.innerHTML = '';
|
|
for(var i=0; i<options.length; i++) {
|
|
var option = document.createElement('option');
|
|
option.setAttribute('value',options[i]);
|
|
if (typeof titles[i] != 'undefined' && titles[i].startsWith('edt_'))
|
|
option.textContent = $.i18n(titles[i]);
|
|
else
|
|
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) {
|
|
if (typeof step == "undefined") step = 1;
|
|
|
|
var el = this.getFormInputField('number');
|
|
if (typeof min != "undefined") el.setAttribute('min',min);
|
|
if (typeof max != "undefined") 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;
|
|
},
|
|
getAppend: 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 (elem[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.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, append, placeholder) {
|
|
var group = document.createElement('div');
|
|
var subgroup = document.createElement('div');
|
|
|
|
if(placeholder)
|
|
input.setAttribute('placeholder',placeholder);
|
|
|
|
if (input.type === 'checkbox'){
|
|
var helplabel = document.createElement("label")
|
|
|
|
group.className += ' form-group';
|
|
group.style.minHeight = "30px";
|
|
label.className += ' col-form-label col-sm-5 col-md-3 col-lg-5 col-xxl-4';
|
|
label.style.fontWeight = "bold";
|
|
group.appendChild(label);
|
|
group.appendChild(subgroup);
|
|
subgroup.className += 'checkbox col-sm-7 col-md-9 col-lg-7 col-xxl-8';
|
|
subgroup.style.marginTop = "0px";
|
|
subgroup.appendChild(input);
|
|
subgroup.appendChild(helplabel);
|
|
|
|
if (input.id.endsWith('_enable'))
|
|
subgroup.className += ' checkbox-success';
|
|
}
|
|
else if (append){
|
|
group.className += ' form-group';
|
|
if(label) {
|
|
label.className += ' col-form-label col-sm-5 col-md-3 col-lg-5 col-xxl-4';
|
|
group.appendChild(label);
|
|
}
|
|
group.appendChild(subgroup);
|
|
subgroup.className += ' col-sm-7 col-md-9 col-lg-7 input-group col-xxl-8';
|
|
subgroup.appendChild(input);
|
|
subgroup.appendChild(append);
|
|
}
|
|
else {
|
|
group.className += ' form-group';
|
|
if(label) {
|
|
label.className += ' col-form-label col-sm-5 col-md-3 col-lg-5 col-xxl-4';
|
|
group.appendChild(label);
|
|
}
|
|
group.appendChild(subgroup);
|
|
subgroup.className += ' input-group col-sm-7 col-md-9 col-lg-7 col-xxl-8';
|
|
subgroup.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;
|
|
},
|
|
getFormInputAppend: function(text) {
|
|
var el = document.createElement('div');
|
|
el.className = 'input-group-addon';
|
|
el.textContent = 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 = '';
|
|
}
|
|
});
|
|
|
|
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.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.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) {
|
|
|
|
return $.i18n(key, variables);
|
|
|
|
};
|
|
|
|
// Miscellaneous Plugin Settings
|
|
JSONEditor.plugins = {
|
|
ace: {
|
|
theme: ''
|
|
},
|
|
epiceditor: {
|
|
|
|
},
|
|
sceditor: {
|
|
|
|
},
|
|
select2: {
|
|
|
|
},
|
|
selectize: {
|
|
}
|
|
};
|
|
|
|
// Default per-editor options
|
|
$each(JSONEditor.defaults.editors, function(i,editor) {
|
|
JSONEditor.defaults.editors[i].options = editor.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') {
|
|
// convert all boolean to checkbox
|
|
return "checkbox";
|
|
}
|
|
});
|
|
// 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';
|
|
}
|
|
}
|
|
});
|
|
// Use the `select` editor if a string has format "select"
|
|
JSONEditor.defaults.resolvers.unshift(function(schema) {
|
|
if(schema.type === "string" && schema.format === "select")
|
|
return "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` or `anyOf`
|
|
if(schema.oneOf || schema.anyOf) return "multiple";
|
|
});
|
|
// colorpicker extend for strings
|
|
JSONEditor.defaults.resolvers.unshift(function(schema) {
|
|
if(schema.type === "array" && schema.format === "colorpicker") {
|
|
return "colorPicker";
|
|
}
|
|
});
|
|
// colorpickerRGBA extend for strings
|
|
JSONEditor.defaults.resolvers.unshift(function(schema) {
|
|
if(schema.type === "array" && schema.format === "colorpickerRGBA") {
|
|
return "colorPickerRGBA";
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 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
|