From 605f22789743808c1f152ad9f844a61d2de3113b Mon Sep 17 00:00:00 2001 From: Moe Date: Sat, 18 Apr 2020 22:00:50 -0700 Subject: [PATCH] Base framework for JSON Editor on Configuration in Super panel --- web/libs/css/super.configEditor.css | 51 + web/libs/js/jsonEditor.js | 7219 ++++++++++++++++++++++++ web/libs/js/super.configEditor.js | 284 + web/pages/blocks/superConfigEditor.ejs | 26 + web/pages/super.ejs | 110 +- 5 files changed, 7586 insertions(+), 104 deletions(-) create mode 100644 web/libs/css/super.configEditor.css create mode 100644 web/libs/js/jsonEditor.js create mode 100644 web/libs/js/super.configEditor.js create mode 100644 web/pages/blocks/superConfigEditor.ejs diff --git a/web/libs/css/super.configEditor.css b/web/libs/css/super.configEditor.css new file mode 100644 index 00000000..03279023 --- /dev/null +++ b/web/libs/css/super.configEditor.css @@ -0,0 +1,51 @@ +.better-json-editor h3 { + font-size: initial; + margin: 0 10px 10px 0; +} +.better-json-editor .well .well { + margin-left:20px; +} +.better-json-editor label, .better-json-editor .control-label { + color: #bd4147; + background-color: #f8f9fa; + padding: 7px 10px; + border-radius: .25rem; + font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + margin-bottom:5px; +} +.better-json-editor .floating-json { + padding: 10px; + border-radius: 5px; + border: 1px solid #E3E3E3; +} +.better-json-editor .form-group:nth-last-child(2) { + margin-bottom:0; +} +.better-json-editor table { + width: 100%!important; + margin-bottom: 10px; +} +.better-json-editor .floating-json textarea.form-control { + padding: 20px; + border-radius: 5px; + border: 1px solid #E3E3E3; + margin-bottom: 10px; +} +.better-json-editor [class*="json-editor-btn"].badge { + margin: 0; + margin-right: 5px; +} +.better-json-editor .badge { + cursor: pointer; + outline: none; +} +.better-json-editor .row { + margin: 0; +} +.better-json-editor .row > div { + border: 1px solid #eee; + border-radius: 5px; + padding-top: 15px; + padding-bottom: 15px; + margin-bottom: 10px; +} diff --git a/web/libs/js/jsonEditor.js b/web/libs/js/jsonEditor.js new file mode 100644 index 00000000..ad22053a --- /dev/null +++ b/web/libs/js/jsonEditor.js @@ -0,0 +1,7219 @@ +/*! 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 0 && (obj.length - 1) in obj)) { + for(i=0; i= 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.minimum) : (value >= schema.minimum); + + // Use math.js is available + if(window.math) { + valid = window.math[schema.exclusiveMinimum?'larger':'largerEq']( + window.math.bignumber(value), + window.math.bignumber(schema.minimum) + ); + } + // Use Decimal.js if available + else if(window.Decimal) { + valid = (new window.Decimal(value))[schema.exclusiveMinimum?'gt':'gte'](new window.Decimal(schema.minimum)); + } + + if(!valid) { + errors.push({ + path: path, + property: 'minimum', + message: this.translate( + (schema.exclusiveMinimum?'error_minimum_excl':'error_minimum_incl'), + [schema.minimum] + ) + }); + } + } + } + // String specific validation + else if(typeof value === "string") { + // `maxLength` + if(schema.maxLength) { + if((value+"").length > schema.maxLength) { + errors.push({ + path: path, + property: 'maxLength', + message: this.translate('error_maxLength', [schema.maxLength]) + }); + } + } + + // `minLength` + if(schema.minLength) { + if((value+"").length < schema.minLength) { + errors.push({ + path: path, + property: 'minLength', + message: this.translate((schema.minLength===1?'error_notempty':'error_minLength'), [schema.minLength]) + }); + } + } + + // `pattern` + if(schema.pattern) { + if(!(new RegExp(schema.pattern)).test(value)) { + errors.push({ + path: path, + property: 'pattern', + message: this.translate('error_pattern', [schema.pattern]) + }); + } + } + } + // Array specific validation + else if(typeof value === "object" && value !== null && Array.isArray(value)) { + // `items` and `additionalItems` + if(schema.items) { + // `items` is an array + if(Array.isArray(schema.items)) { + for(i=0; i schema.maxItems) { + errors.push({ + path: path, + property: 'maxItems', + message: this.translate('error_maxItems', [schema.maxItems]) + }); + } + } + + // `minItems` + if(schema.minItems) { + if(value.length < schema.minItems) { + errors.push({ + path: path, + property: 'minItems', + message: this.translate('error_minItems', [schema.minItems]) + }); + } + } + + // `uniqueItems` + if(schema.uniqueItems) { + var seen = {}; + for(i=0; i schema.maxProperties) { + errors.push({ + path: path, + property: 'maxProperties', + message: this.translate('error_maxProperties', [schema.maxProperties]) + }); + } + } + + // `minProperties` + if(schema.minProperties) { + valid = 0; + for(i in value) { + if(!value.hasOwnProperty(i)) continue; + valid++; + } + if(valid < schema.minProperties) { + errors.push({ + path: path, + property: 'minProperties', + message: this.translate('error_minProperties', [schema.minProperties]) + }); + } + } + + // Version 4 `required` + if(schema.required && Array.isArray(schema.required)) { + for(i=0; i=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 -1; + else if(this.jsoneditor.options.required_by_default) return true; + else return false; + }, + getDisplayText: function(arr) { + var disp = []; + var used = {}; + + // Determine how many times each attribute name is used. + // This helps us pick the most distinct display text for the schemas. + $each(arr,function(i,el) { + if(el.title) { + used[el.title] = used[el.title] || 0; + used[el.title]++; + } + if(el.description) { + used[el.description] = used[el.description] || 0; + used[el.description]++; + } + if(el.format) { + used[el.format] = used[el.format] || 0; + used[el.format]++; + } + if(el.type) { + used[el.type] = used[el.type] || 0; + used[el.type]++; + } + }); + + // Determine display text for each element of the array + $each(arr,function(i,el) { + var name; + + // If it's a simple string + if(typeof el === "string") name = el; + // Object + else if(el.title && used[el.title]<=1) name = el.title; + else if(el.format && used[el.format]<=1) name = el.format; + else if(el.type && used[el.type]<=1) name = el.type; + else if(el.description && used[el.description]<=1) name = el.descripton; + else if(el.title) name = el.title; + else if(el.format) name = el.format; + else if(el.type) name = el.type; + else if(el.description) name = el.description; + else if(JSON.stringify(el).length < 50) name = JSON.stringify(el); + else name = "type"; + + disp.push(name); + }); + + // Replace identical display text with "text 1", "text 2", etc. + var inc = {}; + $each(disp,function(i,name) { + inc[name] = inc[name] || 0; + inc[name]++; + + if(used[name] > 1) disp[i] = name + " " + inc[name]; + }); + + return disp; + }, + getOption: function(key) { + try { + throw "getOption is deprecated"; + } + catch(e) { + window.console.error(e); + } + + return this.options[key]; + }, + showValidationErrors: function(errors) { + + } +}); + +JSONEditor.defaults.editors["null"] = JSONEditor.AbstractEditor.extend({ + getValue: function() { + return null; + }, + setValue: function() { + this.onChange(); + }, + getNumColumns: function() { + return 2; + } +}); + +JSONEditor.defaults.editors.string = JSONEditor.AbstractEditor.extend({ + register: function() { + this._super(); + if(!this.input) return; + this.input.setAttribute('name',this.formname); + }, + unregister: function() { + this._super(); + if(!this.input) return; + this.input.removeAttribute('name'); + }, + setValue: function(value,initial,from_template) { + var self = this; + + if(this.template && !from_template) { + return; + } + + if(value === null || typeof value === 'undefined') value = ""; + else if(typeof value === "object") value = JSON.stringify(value); + else if(typeof value !== "string") value = ""+value; + + if(value === this.serialized) return; + + // Sanitize value before setting it + var sanitized = this.sanitize(value); + + if(this.input.value === sanitized) { + return; + } + + this.input.value = sanitized; + + // If using SCEditor, update the WYSIWYG + if(this.sceditor_instance) { + this.sceditor_instance.val(sanitized); + } + else if(this.epiceditor) { + this.epiceditor.importFile(null,sanitized); + } + else if(this.ace_editor) { + this.ace_editor.setValue(sanitized); + } + + var changed = from_template || this.getValue() !== value; + + this.refreshValue(); + + if(initial) this.is_dirty = false; + else if(this.jsoneditor.options.show_errors === "change") this.is_dirty = true; + + if(this.adjust_height) this.adjust_height(this.input); + + // Bubble this setValue to parents if the value changed + this.onChange(changed); + }, + getNumColumns: function() { + var min = Math.ceil(Math.max(this.getTitle().length,this.schema.maxLength||0,this.schema.minLength||0)/5); + var num; + + if(this.input_type === 'textarea') num = 6; + else if(['text','email'].indexOf(this.input_type) >= 0) num = 4; + else num = 2; + + return Math.min(12,Math.max(min,num)); + }, + build: function() { + var self = this, i; + if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle()); + if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description); + + this.format = this.schema.format; + if(!this.format && this.schema.media && this.schema.media.type) { + this.format = this.schema.media.type.replace(/(^(application|text)\/(x-)?(script\.)?)|(-source$)/g,''); + } + if(!this.format && this.options.default_format) { + this.format = this.options.default_format; + } + if(this.options.format) { + this.format = this.options.format; + } + + // Specific format + if(this.format) { + // Text Area + if(this.format === 'textarea') { + this.input_type = 'textarea'; + this.input = this.theme.getTextareaInput(); + } + // Range Input + else if(this.format === 'range') { + this.input_type = 'range'; + var min = this.schema.minimum || 0; + var max = this.schema.maximum || Math.max(100,min+1); + var step = 1; + if(this.schema.multipleOf) { + if(min%this.schema.multipleOf) min = Math.ceil(min/this.schema.multipleOf)*this.schema.multipleOf; + if(max%this.schema.multipleOf) max = Math.floor(max/this.schema.multipleOf)*this.schema.multipleOf; + step = this.schema.multipleOf; + } + + this.input = this.theme.getRangeInput(min,max,step); + } + // Source Code + else if([ + 'actionscript', + 'batchfile', + 'bbcode', + 'c', + 'c++', + 'cpp', + 'coffee', + 'csharp', + 'css', + 'dart', + 'django', + 'ejs', + 'erlang', + 'golang', + '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); + } + } + // Normal text input + else { + this.input_type = 'text'; + this.input = this.theme.getFormInputField(this.input_type); + } + + // minLength, maxLength, and pattern + if(typeof this.schema.maxLength !== "undefined") this.input.setAttribute('maxlength',this.schema.maxLength); + if(typeof this.schema.pattern !== "undefined") this.input.setAttribute('pattern',this.schema.pattern); + else if(typeof this.schema.minLength !== "undefined") this.input.setAttribute('pattern','.{'+this.schema.minLength+',}'); + + if(this.options.compact) { + this.container.className += ' compact'; + } + else { + if(this.options.input_width) this.input.style.width = this.options.input_width; + } + + if(this.schema.readOnly || this.schema.readonly || this.schema.template) { + this.always_disabled = true; + this.input.disabled = true; + } + + this.input + .addEventListener('change',function(e) { + e.preventDefault(); + e.stopPropagation(); + + // Don't allow changing if this field is a template + if(self.schema.template) { + this.value = self.value; + return; + } + + var val = this.value; + + // sanitize value + var sanitized = self.sanitize(val); + if(val !== sanitized) { + this.value = sanitized; + } + + self.is_dirty = true; + + self.refreshValue(); + self.onChange(true); + }); + + if(this.options.input_height) this.input.style.height = this.options.input_height; + if(this.options.expand_height) { + this.adjust_height = function(el) { + if(!el) return; + var i, ch=el.offsetHeight; + // Input too short + if(el.offsetHeight < el.scrollHeight) { + i=0; + while(el.offsetHeight < el.scrollHeight+3) { + if(i>100) break; + i++; + ch++; + el.style.height = ch+'px'; + } + } + else { + i=0; + while(el.offsetHeight >= el.scrollHeight+3) { + if(i>100) break; + i++; + ch--; + el.style.height = ch+'px'; + } + el.style.height = (ch+1)+'px'; + } + }; + + this.input.addEventListener('keyup',function(e) { + self.adjust_height(this); + }); + this.input.addEventListener('change',function(e) { + self.adjust_height(this); + }); + this.adjust_height(); + } + + if(this.format) this.input.setAttribute('data-schemaformat',this.format); + + this.control = this.theme.getFormControl(this.label, this.input, this.description); + this.container.appendChild(this.control); + + // Any special formatting that needs to happen after the input is added to the dom + window.requestAnimationFrame(function() { + // Skip in case the input is only a temporary editor, + // otherwise, in the case of an ace_editor creation, + // it will generate an error trying to append it to the missing parentNode + if(self.input.parentNode) self.afterInputReady(); + if(self.adjust_height) self.adjust_height(self.input); + }); + + // Compile and store the template + if(this.schema.template) { + this.template = this.jsoneditor.compileTemplate(this.schema.template, this.template_engine); + this.refreshValue(); + } + else { + this.refreshValue(); + } + }, + enable: function() { + if(!this.always_disabled) { + this.input.disabled = false; + // TODO: WYSIWYG and Markdown editors + } + this._super(); + }, + disable: function() { + this.input.disabled = true; + // TODO: WYSIWYG and Markdown editors + this._super(); + }, + afterInputReady: function() { + var self = this, options; + + // Code editor + if(this.source_code) { + // WYSIWYG html and bbcode editor + if(this.options.wysiwyg && + ['html','bbcode'].indexOf(this.input_type) >= 0 && + window.jQuery && window.jQuery.fn && window.jQuery.fn.sceditor + ) { + options = $extend({},{ + plugins: self.input_type==='html'? 'xhtml' : 'bbcode', + emoticonsEnabled: false, + width: '100%', + height: 300 + },JSONEditor.plugins.sceditor,self.options.sceditor_options||{}); + + window.jQuery(self.input).sceditor(options); + + self.sceditor_instance = window.jQuery(self.input).sceditor('instance'); + + self.sceditor_instance.blur(function() { + // Get editor's value + var val = window.jQuery("
"+self.sceditor_instance.val()+"
"); + // 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 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[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= 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.schema.items.length) { + if(this.schema.additionalItems===true) { + return {}; + } + else if(this.schema.additionalItems) { + return $extend({},this.schema.additionalItems); + } + } + else { + return $extend({},this.schema.items[i]); + } + } + else if(this.schema.items) { + return $extend({},this.schema.items); + } + else { + return {}; + } + }, + getItemInfo: function(i) { + var schema = this.getItemSchema(i); + + // Check if it's cached + this.item_info = this.item_info || {}; + var stringified = JSON.stringify(schema); + if(typeof this.item_info[stringified] !== "undefined") return this.item_info[stringified]; + + // Get the schema for this item + schema = this.jsoneditor.expandRefs(schema); + + this.item_info[stringified] = { + title: schema.title || "item", + 'default': schema["default"], + width: 12, + child_editors: schema.properties || schema.items + }; + + return this.item_info[stringified]; + }, + getElementEditor: function(i) { + var item_info = this.getItemInfo(i); + var schema = this.getItemSchema(i); + schema = this.jsoneditor.expandRefs(schema); + schema.title = item_info.title+' '+(i+1); + + var editor = this.jsoneditor.getEditorClass(schema); + + var holder; + if(this.tabs_holder) { + holder = this.theme.getTabContent(); + } + else if(item_info.child_editors) { + holder = this.theme.getChildEditorHolder(); + } + else { + holder = this.theme.getIndentedPanel(); + } + + this.row_holder.appendChild(holder); + + var ret = this.jsoneditor.createEditor(editor,{ + jsoneditor: this.jsoneditor, + schema: schema, + container: holder, + path: this.path+'.'+i, + parent: this, + required: true + }); + ret.preBuild(); + ret.build(); + ret.postBuild(); + + if(!ret.title_controls) { + ret.array_controls = this.theme.getButtonHolder(); + holder.appendChild(ret.array_controls); + } + + return ret; + }, + destroy: function() { + this.empty(true); + if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); + if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description); + if(this.row_holder && this.row_holder.parentNode) this.row_holder.parentNode.removeChild(this.row_holder); + if(this.controls && this.controls.parentNode) this.controls.parentNode.removeChild(this.controls); + if(this.panel && this.panel.parentNode) this.panel.parentNode.removeChild(this.panel); + + this.rows = this.row_cache = this.title = this.description = this.row_holder = this.panel = this.controls = null; + + this._super(); + }, + empty: function(hard) { + if(!this.rows) return; + var self = this; + $each(this.rows,function(i,row) { + if(hard) { + if(row.tab && row.tab.parentNode) row.tab.parentNode.removeChild(row.tab); + self.destroyRow(row,true); + self.row_cache[i] = null; + } + self.rows[i] = null; + }); + self.rows = []; + if(hard) self.row_cache = []; + }, + destroyRow: function(row,hard) { + var holder = row.container; + if(hard) { + row.destroy(); + if(holder.parentNode) holder.parentNode.removeChild(holder); + if(row.tab && row.tab.parentNode) row.tab.parentNode.removeChild(row.tab); + } + else { + if(row.tab) row.tab.style.display = 'none'; + holder.style.display = 'none'; + row.unregister(); + } + }, + getMax: function() { + if((Array.isArray(this.schema.items)) && this.schema.additionalItems === false) { + return Math.min(this.schema.items.length,this.schema.maxItems || Infinity); + } + else { + return this.schema.maxItems || Infinity; + } + }, + refreshTabs: function(refresh_headers) { + var self = this; + $each(this.rows, function(i,row) { + if(!row.tab) return; + + if(refresh_headers) { + row.tab_text.textContent = row.getHeaderText(); + } + else { + if(row.tab === self.active_tab) { + self.theme.markTabActive(row.tab); + row.container.style.display = ''; + } + else { + self.theme.markTabInactive(row.tab); + row.container.style.display = 'none'; + } + } + }); + }, + setValue: function(value, initial) { + // Update the array's value, adding/removing rows when necessary + value = value || []; + + if(!(Array.isArray(value))) value = [value]; + + var serialized = JSON.stringify(value); + if(serialized === this.serialized) return; + + // Make sure value has between minItems and maxItems items in it + if(this.schema.minItems) { + while(value.length < this.schema.minItems) { + value.push(this.getItemInfo(value.length)["default"]); + } + } + if(this.getMax() && value.length > this.getMax()) { + value = value.slice(0,this.getMax()); + } + + var self = this; + $each(value,function(i,val) { + if(self.rows[i]) { + // TODO: don't set the row's value if it hasn't changed + self.rows[i].setValue(val,initial); + } + else if(self.row_cache[i]) { + self.rows[i] = self.row_cache[i]; + self.rows[i].setValue(val,initial); + self.rows[i].container.style.display = ''; + if(self.rows[i].tab) self.rows[i].tab.style.display = ''; + self.rows[i].register(); + } + else { + self.addRow(val,initial); + } + }); + + for(var j=value.length; j= 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('button_delete_row_title',[self.getItemTitle()])); + self.rows[i].delete_button.className += ' delete'; + self.rows[i].delete_button.setAttribute('data-i',i); + self.rows[i].delete_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + var i = this.getAttribute('data-i')*1; + + var value = self.getValue(); + + var newval = []; + var new_active_tab = null; + $each(value,function(j,row) { + if(j===i) { + // If the one we're deleting is the active tab + if(self.rows[j].tab === self.active_tab) { + // Make the next tab active if there is one + // Note: the next tab is going to be the current tab after deletion + if(self.rows[j+1]) new_active_tab = self.rows[j].tab; + // Otherwise, make the previous tab active if there is one + else if(j) new_active_tab = self.rows[j-1].tab; + } + + return; // If this is the one we're deleting + } + newval.push(row); + }); + self.setValue(newval); + if(new_active_tab) { + self.active_tab = new_active_tab; + self.refreshTabs(); + } + + self.onChange(true); + }); + + if(controls_holder) { + controls_holder.appendChild(self.rows[i].delete_button); + } + } + + if(i && !self.hide_move_buttons) { + self.rows[i].moveup_button = this.getButton('','moveup',this.translate('button_move_up_title')); + self.rows[i].moveup_button.className += ' moveup'; + self.rows[i].moveup_button.setAttribute('data-i',i); + self.rows[i].moveup_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + var i = this.getAttribute('data-i')*1; + + if(i<=0) return; + var rows = self.getValue(); + var tmp = rows[i-1]; + rows[i-1] = rows[i]; + rows[i] = tmp; + + self.setValue(rows); + self.active_tab = self.rows[i-1].tab; + self.refreshTabs(); + + self.onChange(true); + }); + + if(controls_holder) { + controls_holder.appendChild(self.rows[i].moveup_button); + } + } + + if(!self.hide_move_buttons) { + self.rows[i].movedown_button = this.getButton('','movedown',this.translate('button_move_down_title')); + self.rows[i].movedown_button.className += ' movedown'; + self.rows[i].movedown_button.setAttribute('data-i',i); + self.rows[i].movedown_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + var i = this.getAttribute('data-i')*1; + + var rows = self.getValue(); + if(i>=rows.length-1) return; + var tmp = rows[i+1]; + rows[i+1] = rows[i]; + rows[i] = tmp; + + self.setValue(rows); + self.active_tab = self.rows[i+1].tab; + self.refreshTabs(); + self.onChange(true); + }); + + if(controls_holder) { + controls_holder.appendChild(self.rows[i].movedown_button); + } + } + + if(value) self.rows[i].setValue(value, initial); + self.refreshTabs(); + }, + addControls: function() { + var self = this; + + this.collapsed = false; + this.toggle_button = this.getButton('','collapse',this.translate('button_collapse')); + this.title_controls.appendChild(this.toggle_button); + var row_holder_display = self.row_holder.style.display; + var controls_display = self.controls.style.display; + this.toggle_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + if(self.collapsed) { + self.collapsed = false; + if(self.panel) self.panel.style.display = ''; + self.row_holder.style.display = row_holder_display; + if(self.tabs_holder) self.tabs_holder.style.display = ''; + self.controls.style.display = controls_display; + self.setButtonText(this,'','collapse',self.translate('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('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('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('button_delete_last',[this.getItemTitle()]),'delete',this.translate('button_delete_last_title',[this.getItemTitle()])); + this.delete_last_row_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + var rows = self.getValue(); + + var new_active_tab = null; + if(self.rows.length > 1 && self.rows[self.rows.length-1].tab === self.active_tab) new_active_tab = self.rows[self.rows.length-2].tab; + + rows.pop(); + self.setValue(rows); + if(new_active_tab) { + self.active_tab = new_active_tab; + self.refreshTabs(); + } + self.onChange(true); + }); + self.controls.appendChild(this.delete_last_row_button); + + this.remove_all_rows_button = this.getButton(this.translate('button_delete_all'),'delete',this.translate('button_delete_all_title')); + this.remove_all_rows_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + self.setValue([]); + self.onChange(true); + }); + self.controls.appendChild(this.remove_all_rows_button); + + if(self.tabs) { + this.add_row_button.style.width = '100%'; + this.add_row_button.style.textAlign = 'left'; + this.add_row_button.style.marginBottom = '3px'; + + this.delete_last_row_button.style.width = '100%'; + this.delete_last_row_button.style.textAlign = 'left'; + this.delete_last_row_button.style.marginBottom = '3px'; + + this.remove_all_rows_button.style.width = '100%'; + this.remove_all_rows_button.style.textAlign = 'left'; + this.remove_all_rows_button.style.marginBottom = '3px'; + } + }, + showValidationErrors: function(errors) { + var self = this; + + // Get all the errors that pertain to this editor + var my_errors = []; + var other_errors = []; + $each(errors, function(i,error) { + if(error.path === self.path) { + my_errors.push(error); + } + else { + other_errors.push(error); + } + }); + + // Show errors for this editor + if(this.error_holder) { + if(my_errors.length) { + var message = []; + this.error_holder.innerHTML = ''; + this.error_holder.style.display = ''; + $each(my_errors, function(i,error) { + self.error_holder.appendChild(self.theme.getErrorMessage(error.message)); + }); + } + // Hide error area + else { + this.error_holder.style.display = 'none'; + } + } + + // Show errors for child editors + $each(this.rows, function(i,row) { + row.showValidationErrors(other_errors); + }); + } +}); + +JSONEditor.defaults.editors.table = JSONEditor.defaults.editors.array.extend({ + register: function() { + this._super(); + if(this.rows) { + for(var i=0; i this.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= 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('button_delete_row_title_short')); + self.rows[i].delete_button.className += ' delete'; + self.rows[i].delete_button.setAttribute('data-i',i); + self.rows[i].delete_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + var i = this.getAttribute('data-i')*1; + + var value = self.getValue(); + + var newval = []; + $each(value,function(j,row) { + if(j===i) return; // If this is the one we're deleting + newval.push(row); + }); + self.setValue(newval); + self.onChange(true); + }); + controls_holder.appendChild(self.rows[i].delete_button); + } + + + if(i && !this.hide_move_buttons) { + self.rows[i].moveup_button = this.getButton('','moveup',this.translate('button_move_up_title')); + self.rows[i].moveup_button.className += ' moveup'; + self.rows[i].moveup_button.setAttribute('data-i',i); + self.rows[i].moveup_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + var i = this.getAttribute('data-i')*1; + + if(i<=0) return; + var rows = self.getValue(); + var tmp = rows[i-1]; + rows[i-1] = rows[i]; + rows[i] = tmp; + + self.setValue(rows); + self.onChange(true); + }); + controls_holder.appendChild(self.rows[i].moveup_button); + } + + if(!this.hide_move_buttons) { + self.rows[i].movedown_button = this.getButton('','movedown',this.translate('button_move_down_title')); + self.rows[i].movedown_button.className += ' movedown'; + self.rows[i].movedown_button.setAttribute('data-i',i); + self.rows[i].movedown_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + var i = this.getAttribute('data-i')*1; + var rows = self.getValue(); + if(i>=rows.length-1) return; + var tmp = rows[i+1]; + rows[i+1] = rows[i]; + rows[i] = tmp; + + self.setValue(rows); + self.onChange(true); + }); + controls_holder.appendChild(self.rows[i].movedown_button); + } + + if(value) self.rows[i].setValue(value); + }, + addControls: function() { + var self = this; + + this.collapsed = false; + this.toggle_button = this.getButton('','collapse',this.translate('button_collapse')); + if(this.title_controls) { + this.title_controls.appendChild(this.toggle_button); + this.toggle_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + + if(self.collapsed) { + self.collapsed = false; + self.panel.style.display = ''; + self.setButtonText(this,'','collapse',self.translate('button_collapse')); + } + else { + self.collapsed = true; + self.panel.style.display = 'none'; + self.setButtonText(this,'','expand',self.translate('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('button_add_row_title',[this.getItemTitle()])); + this.add_row_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + + self.addRow(); + self.refreshValue(); + self.refreshRowButtons(); + self.onChange(true); + }); + self.controls.appendChild(this.add_row_button); + + this.delete_last_row_button = this.getButton(this.translate('button_delete_last',[this.getItemTitle()]),'delete',this.translate('button_delete_last_title',[this.getItemTitle()])); + this.delete_last_row_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + + var rows = self.getValue(); + rows.pop(); + self.setValue(rows); + self.onChange(true); + }); + self.controls.appendChild(this.delete_last_row_button); + + this.remove_all_rows_button = this.getButton(this.translate('button_delete_all'),'delete',this.translate('button_delete_all_title')); + this.remove_all_rows_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + + self.setValue([]); + self.onChange(true); + }); + self.controls.appendChild(this.remove_all_rows_button); + } +}); + +// Multiple Editor (for when `type` is an array) +JSONEditor.defaults.editors.multiple = JSONEditor.AbstractEditor.extend({ + register: function() { + if(this.editors) { + for(var i=0; inull'; + } + // 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 = '
'+i+': '+html+'
'; + } + + // TODO: use theme + ret += '
  • '+html+'
  • '; + }); + + if(Array.isArray(el)) ret = '
      '+ret+'
    '; + else ret = "
      "+ret+'
    '; + + return ret; + } + // Boolean + else if(typeof el === "boolean") { + return el? 'true' : 'false'; + } + // String + else if(typeof el === "string") { + return el.replace(/&/g,'&').replace(//g,'>'); + } + // Number + else { + return el; + } + }, + setValue: function(val) { + if(this.value !== val) { + this.value = val; + this.refreshValue(); + this.onChange(); + } + }, + destroy: function() { + if(this.display_area && this.display_area.parentNode) this.display_area.parentNode.removeChild(this.display_area); + if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); + if(this.switcher && this.switcher.parentNode) this.switcher.parentNode.removeChild(this.switcher); + + this._super(); + } +}); + +JSONEditor.defaults.editors.select = JSONEditor.AbstractEditor.extend({ + setValue: function(value,initial) { + value = this.typecast(value||''); + + // Sanitize value before setting it + var sanitized = value; + if(this.enum_values.indexOf(sanitized) < 0) { + sanitized = this.enum_values[0]; + } + + if(this.value === sanitized) { + return; + } + + this.input.value = this.enum_options[this.enum_values.indexOf(sanitized)]; + if(this.select2) this.select2.select2('val',this.input.value); + this.value = sanitized; + this.onChange(); + }, + register: function() { + this._super(); + if(!this.input) return; + this.input.setAttribute('name',this.formname); + }, + unregister: function() { + this._super(); + if(!this.input) return; + this.input.removeAttribute('name'); + }, + getNumColumns: function() { + if(!this.enum_options) return 3; + var longest_text = this.getTitle().length; + for(var i=0; i 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= 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; iSize: '+Math.floor((this.value.length-this.value.split(',')[0].length-1)/1.33333)+' bytes'; + if(mime.substr(0,5)==="image") { + this.preview.innerHTML += '
    '; + var img = document.createElement('img'); + img.style.maxWidth = '100%'; + img.style.maxHeight = '100px'; + img.src = this.value; + this.preview.appendChild(img); + } + } + }, + enable: function() { + if(this.uploader) this.uploader.disabled = false; + this._super(); + }, + disable: function() { + if(this.uploader) this.uploader.disabled = true; + this._super(); + }, + setValue: function(val) { + if(this.value !== val) { + this.value = val; + this.input.value = this.value; + this.refreshPreview(); + this.onChange(); + } + }, + destroy: function() { + if(this.preview && this.preview.parentNode) this.preview.parentNode.removeChild(this.preview); + if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); + if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); + if(this.uploader && this.uploader.parentNode) this.uploader.parentNode.removeChild(this.uploader); + + this._super(); + } +}); + +JSONEditor.defaults.editors.upload = JSONEditor.AbstractEditor.extend({ + getNumColumns: function() { + return 4; + }, + build: function() { + var self = this; + this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle()); + + // Input that holds the base64 string + this.input = this.theme.getFormInputField('hidden'); + this.container.appendChild(this.input); + + // Don't show uploader if this is readonly + if(!this.schema.readOnly && !this.schema.readonly) { + + if(!this.jsoneditor.options.upload) throw "Upload handler required for upload editor"; + + // File uploader + this.uploader = this.theme.getFormInputField('file'); + + this.uploader.addEventListener('change',function(e) { + e.preventDefault(); + e.stopPropagation(); + + if(this.files && this.files.length) { + var fr = new FileReader(); + fr.onload = function(evt) { + self.preview_value = evt.target.result; + self.refreshPreview(); + self.onChange(true); + fr = null; + }; + fr.readAsDataURL(this.files[0]); + } + }); + } + + var description = this.schema.description; + if (!description) description = ''; + + this.preview = this.theme.getFormInputDescription(description); + this.container.appendChild(this.preview); + + this.control = this.theme.getFormControl(this.label, this.uploader||this.input, this.preview); + this.container.appendChild(this.control); + }, + refreshPreview: function() { + if(this.last_preview === this.preview_value) return; + this.last_preview = this.preview_value; + + this.preview.innerHTML = ''; + + if(!this.preview_value) return; + + var self = this; + + var mime = this.preview_value.match(/^data:([^;,]+)[;,]/); + if(mime) mime = mime[1]; + if(!mime) mime = 'unknown'; + + var file = this.uploader.files[0]; + + this.preview.innerHTML = 'Type: '+mime+', Size: '+file.size+' bytes'; + if(mime.substr(0,5)==="image") { + this.preview.innerHTML += '
    '; + var img = document.createElement('img'); + img.style.maxWidth = '100%'; + img.style.maxHeight = '100px'; + img.src = this.preview_value; + this.preview.appendChild(img); + } + + this.preview.innerHTML += '
    '; + var uploadButton = this.getButton('Upload', 'upload', 'Upload'); + this.preview.appendChild(uploadButton); + uploadButton.addEventListener('click',function(event) { + event.preventDefault(); + + uploadButton.setAttribute("disabled", "disabled"); + self.theme.removeInputError(self.uploader); + + if (self.theme.getProgressBar) { + self.progressBar = self.theme.getProgressBar(); + self.preview.appendChild(self.progressBar); + } + + self.jsoneditor.options.upload(self.path, file, { + success: function(url) { + self.setValue(url); + + if(self.parent) self.parent.onChildEditorChange(self); + else self.jsoneditor.onChange(); + + if (self.progressBar) self.preview.removeChild(self.progressBar); + uploadButton.removeAttribute("disabled"); + }, + failure: function(error) { + self.theme.addInputError(self.uploader, error); + if (self.progressBar) self.preview.removeChild(self.progressBar); + uploadButton.removeAttribute("disabled"); + }, + updateProgress: function(progress) { + if (self.progressBar) { + if (progress) self.theme.updateProgressBar(self.progressBar, progress); + else self.theme.updateProgressBarUnknown(self.progressBar); + } + } + }); + }); + }, + enable: function() { + if(this.uploader) this.uploader.disabled = false; + this._super(); + }, + disable: function() { + if(this.uploader) this.uploader.disabled = true; + this._super(); + }, + setValue: function(val) { + if(this.value !== val) { + this.value = val; + this.input.value = this.value; + this.onChange(); + } + }, + destroy: function() { + if(this.preview && this.preview.parentNode) this.preview.parentNode.removeChild(this.preview); + if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); + if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); + if(this.uploader && this.uploader.parentNode) this.uploader.parentNode.removeChild(this.uploader); + + this._super(); + } +}); + +JSONEditor.defaults.editors.checkbox = JSONEditor.AbstractEditor.extend({ + setValue: function(value,initial) { + this.value = !!value; + this.input.checked = this.value; + this.onChange(); + }, + register: function() { + this._super(); + if(!this.input) return; + this.input.setAttribute('name',this.formname); + }, + unregister: function() { + this._super(); + if(!this.input) return; + this.input.removeAttribute('name'); + }, + getNumColumns: function() { + return Math.min(12,Math.max(this.getTitle().length/7,2)); + }, + build: function() { + var self = this; + if(!this.options.compact) { + this.label = this.header = this.theme.getCheckboxLabel(this.getTitle()); + } + if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description); + if(this.options.compact) this.container.className += ' compact'; + + this.input = this.theme.getCheckbox(); + this.control = this.theme.getFormControl(this.label, this.input, this.description); + + if(this.schema.readOnly || this.schema.readonly) { + this.always_disabled = true; + this.input.disabled = true; + } + + this.input.addEventListener('change',function(e) { + e.preventDefault(); + e.stopPropagation(); + self.value = this.checked; + self.onChange(true); + }); + + this.container.appendChild(this.control); + }, + enable: function() { + if(!this.always_disabled) { + this.input.disabled = false; + } + this._super(); + }, + disable: function() { + this.input.disabled = true; + this._super(); + }, + destroy: function() { + if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label); + if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description); + if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); + this._super(); + } +}); + +JSONEditor.defaults.editors.arraySelectize = JSONEditor.AbstractEditor.extend({ + build: function() { + this.title = this.theme.getFormInputLabel(this.getTitle()); + + this.title_controls = this.theme.getHeaderButtonHolder(); + this.title.appendChild(this.title_controls); + this.error_holder = document.createElement('div'); + + if(this.schema.description) { + this.description = this.theme.getDescription(this.schema.description); + } + + this.input = document.createElement('select'); + this.input.setAttribute('multiple', 'multiple'); + + var group = this.theme.getFormControl(this.title, this.input, this.description); + + this.container.appendChild(group); + this.container.appendChild(this.error_holder); + + window.jQuery(this.input).selectize({ + delimiter: false, + createOnBlur: true, + create: true + }); + }, + postBuild: function() { + var self = this; + this.input.selectize.on('change', function(event) { + self.refreshValue(); + self.onChange(true); + }); + }, + destroy: function() { + this.empty(true); + if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); + if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description); + if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); + + this._super(); + }, + empty: function(hard) {}, + setValue: function(value, initial) { + var self = this; + // Update the array's value, adding/removing rows when necessary + value = value || []; + if(!(Array.isArray(value))) value = [value]; + + this.input.selectize.clearOptions(); + this.input.selectize.clear(true); + + value.forEach(function(item) { + self.input.selectize.addOption({text: item, value: item}); + }); + this.input.selectize.setValue(value); + + this.refreshValue(initial); + }, + refreshValue: function(force) { + this.value = this.input.selectize.getValue(); + }, + showValidationErrors: function(errors) { + var self = this; + + // Get all the errors that pertain to this editor + var my_errors = []; + var other_errors = []; + $each(errors, function(i,error) { + if(error.path === self.path) { + my_errors.push(error); + } + else { + other_errors.push(error); + } + }); + + // Show errors for this editor + if(this.error_holder) { + + if(my_errors.length) { + var message = []; + this.error_holder.innerHTML = ''; + this.error_holder.style.display = ''; + $each(my_errors, function(i,error) { + self.error_holder.appendChild(self.theme.getErrorMessage(error.message)); + }); + } + // Hide error area + else { + this.error_holder.style.display = 'none'; + } + } + } +}); + +var matchKey = (function () { + var elem = document.documentElement; + + if (elem.matches) return 'matches'; + else if (elem.webkitMatchesSelector) return 'webkitMatchesSelector'; + else if (elem.mozMatchesSelector) return 'mozMatchesSelector'; + else if (elem.msMatchesSelector) return 'msMatchesSelector'; + else if (elem.oMatchesSelector) return 'oMatchesSelector'; +})(); + +JSONEditor.AbstractTheme = Class.extend({ + getContainer: function() { + return document.createElement('div'); + }, + getFloatRightLinkHolder: function() { + var el = document.createElement('div'); + el.style = el.style || {}; + el.style.cssFloat = 'right'; + el.style.marginLeft = '10px'; + return el; + }, + getModal: function() { + var el = document.createElement('div'); + el.className = 'floating-json'; + el.style.backgroundColor = 'white'; + el.style.border = '1px solid eee'; + el.style.boxShadow = '3px 3px 15px #eee'; + el.style.borderRadius = '5px;'; + el.style.position = 'absolute'; + el.style.zIndex = '10'; + el.style.display = 'none'; + return el; + }, + getGridContainer: function() { + var el = document.createElement('div'); + return el; + }, + getGridRow: function() { + var el = document.createElement('div'); + el.className = 'row'; + return el; + }, + getGridColumn: function() { + var el = document.createElement('div'); + return el; + }, + setGridColumnSize: function(el,size) { + + }, + getLink: function(text) { + var el = document.createElement('a'); + el.setAttribute('href','#'); + el.appendChild(document.createTextNode(text)); + return el; + }, + disableHeader: function(header) { + header.style.color = '#ccc'; + }, + disableLabel: function(label) { + label.style.color = '#ccc'; + }, + enableHeader: function(header) { + header.style.color = ''; + }, + enableLabel: function(label) { + label.style.color = ''; + }, + getFormInputLabel: function(text) { + var el = document.createElement('label'); + + el.appendChild(document.createTextNode(text)); + return el; + }, + getCheckboxLabel: function(text) { + var el = this.getFormInputLabel(text); + el.style.fontWeight = 'normal'; + return el; + }, + getHeader: function(text) { + var el = document.createElement('h3'); + if(typeof text === "string") { + el.textContent = text; + } + else { + el.appendChild(text); + } + + return el; + }, + getCheckbox: function() { + var el = this.getFormInputField('checkbox'); + el.style.display = 'inline-block'; + el.style.width = 'auto'; + return el; + }, + getMultiCheckboxHolder: function(controls,label,description) { + var el = document.createElement('div'); + + if(label) { + label.style.display = 'block'; + el.appendChild(label); + } + + for(var i in controls) { + if(!controls.hasOwnProperty(i)) continue; + controls[i].style.display = 'inline-block'; + controls[i].style.marginRight = '20px'; + el.appendChild(controls[i]); + } + + if(description) el.appendChild(description); + + return el; + }, + getSelectInput: function(options) { + var select = document.createElement('select'); + if(options) this.setSelectOptions(select, options); + return select; + }, + getSwitcher: function(options) { + var switcher = this.getSelectInput(options); + switcher.style.backgroundColor = 'transparent'; + switcher.style.display = 'inline-block'; + switcher.style.fontStyle = 'italic'; + switcher.style.fontWeight = 'normal'; + switcher.style.height = 'auto'; + switcher.style.marginBottom = 0; + switcher.style.marginLeft = '5px'; + switcher.style.padding = '0 0 0 3px'; + switcher.style.width = 'auto'; + return switcher; + }, + getSwitcherOptions: function(switcher) { + return switcher.getElementsByTagName('option'); + }, + setSwitcherOptions: function(switcher, options, titles) { + this.setSelectOptions(switcher, options, titles); + }, + setSelectOptions: function(select, options, titles) { + titles = titles || []; + select.innerHTML = ''; + for(var i=0; i 1) { + var cur; + func = function(vars) { + cur = vars; + for(i=0; i= 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"; +}); + +/** + * 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; +})(); diff --git a/web/libs/js/super.configEditor.js b/web/libs/js/super.configEditor.js new file mode 100644 index 00000000..aef66fb5 --- /dev/null +++ b/web/libs/js/super.configEditor.js @@ -0,0 +1,284 @@ + +var schema = { + "title": "Shinobi Configuration", + "type": "object", + "properties": { + "subscriptionId": { + "type": "string", + }, + "port": { + "type": "integer", + "default": 8080 + }, + "passwordType": { + "type": "string", + "enum": [ + "sha256", + "sha512", + "md5" + ], + "default": "sha256" + }, + "addStorage": { + "type": "array", + "format": "table", + "title": "Additional Storage", + "description": "Separate storage locations that can be set for different monitors.", + "uniqueItems": true, + "items": { + "type": "object", + "title": "Storage Array", + "properties": { + "name": { + "type": "string", + }, + "path": { + "type": "string", + "default": "__DIR__/videos2" + } + } + }, + "default": [ + { + "name": "second", + "path": "__DIR__/videos2" + } + ] + }, + "plugins": { + "type": "array", + "format": "table", + "title": "Plugins", + "descripton": "Elaborate Plugin connection settings.", + "uniqueItems": true, + "items": { + "type": "object", + "title": "Plugin", + "properties": { + "plug": { + "type": "string", + "default": "pluginName" + }, + "key": { + "type": "string" + }, + "mode": { + "type": "string", + "enum": [ + "host", + "client" + ], + "default": "client" + }, + "https": { + "type": "boolean", + "descripton": "Only for Host mode.", + "default": false + }, + "host": { + "type": "string", + "descripton": "Only for Host mode.", + "default": "localhost" + }, + "port": { + "type": "integer", + "descripton": "Only for Host mode.", + "default": 8082 + }, + "type": { + "type": "string", + "default": "detector" + } + } + }, + "default": [ + { + "name": "second", + "path": "__DIR__/videos2" + } + ] + }, + "pluginKeys": { + "type": "object", + "format": "table", + "title": "Plugin Keys", + "description": "Quick client connection setup for plugins. Just add the plugin key to make it ready for incoming connections.", + "uniqueItems": true, + "items": { + "type": "object", + "title": "Plugin Key", + "properties": {} + } + }, + "db": { + "type": "object", + "format": "table", + "title": "Database Options", + "description": "Credentials to connect to where detailed information is stored.", + "properties": { + "host": { + "type": "string", + "default": "127.0.0.1" + }, + "user": { + "type": "string", + "default": "majesticflame" + }, + "password": { + "type": "string", + "default": "" + }, + "database": { + "type": "string", + "default": "ccio" + }, + "port": { + "type": "integer", + "default": 3306 + } + }, + "default": { + "host": "127.0.0.1", + "user": "majesticflame", + "password": "", + "database": "ccio", + "port":3306 + } + }, + "cron": { + "type": "object", + "format": "table", + "title": "CRON Options", + "properties": { + "key": { + "type": "string", + }, + "deleteOld": { + "type": "boolean", + "description": "cron will delete videos older than Max Number of Days per account.", + "default": true + }, + "deleteNoVideo": { + "type": "boolean", + "description": "cron will delete SQL rows that it thinks have no video files.", + "default": true + }, + "deleteOverMax": { + "type": "boolean", + "description": "cron will delete files that are over the set maximum storage per account.", + "default": true + }, + } + }, + "mail": { + "type": "object", + "format": "table", + "title": "Email Options", + "properties": { + "service": { + "type": "string", + }, + "host": { + "type": "string", + }, + "auth": { + "type": "object", + "properties": { + "user": { + "type": "string", + }, + "pass": { + "type": "string", + }, + }, + }, + "secure": { + "type": "boolean", + "default": false + }, + "ignoreTLS": { + "type": "boolean", + }, + "requireTLS": { + "type": "boolean", + }, + "port": { + "type": "integer", + } + } + }, + "detectorMergePamRegionTriggers": { + "type": "boolean", + "default": true + }, + "doSnapshot": { + "type": "boolean", + "default": true + }, + "discordBot": { + "type": "boolean", + "default": false + }, + "dropInEventServer": { + "type": "boolean", + "default": false + }, + "ftpServer": { + "type": "boolean", + "default": false + }, + "oldPowerVideo": { + "type": "boolean", + "default": false + }, + "defaultMjpeg": { + "type": "string", + }, + "streamDir": { + "type": "string", + }, + "videosDir": { + "type": "string", + }, + "windowsTempDir": { + "type": "string", + } + } +}; + +// Set default options +JSONEditor.defaults.options.theme = 'bootstrap3'; +JSONEditor.defaults.options.iconlib = 'fontawesome4'; + +// Initialize the editor +var editor = new JSONEditor(document.getElementById("configForHumans"),{ + theme: 'bootstrap3', + schema: schema, + //schema: { + // type: "object", + // properties: { + // name: { "type": "string" } + // } + //} +}); + +// Set the value +editor.setValue(shinobiConfig); +//editor.setValue({ +// name: "John Smith" +//}); + +// Get the value +var data = editor.getValue(); +console.log(data); // "John Smith" + +// Validate +var errors = editor.validate(); +if(errors.length) { + // Not valid +} + +// Listen for changes +editor.on("change", function() { + // Do something... +}); diff --git a/web/pages/blocks/superConfigEditor.ejs b/web/pages/blocks/superConfigEditor.ejs new file mode 100644 index 00000000..c3bd404c --- /dev/null +++ b/web/pages/blocks/superConfigEditor.ejs @@ -0,0 +1,26 @@ + + + +
    + +
    + +
    +
    + +
    +
    + + diff --git a/web/pages/super.ejs b/web/pages/super.ejs index b5f60c8b..d79e0106 100644 --- a/web/pages/super.ejs +++ b/web/pages/super.ejs @@ -32,6 +32,10 @@ .list-group li .form-group {margin:0} a {cursor:pointer} + <% customAutoLoad.superLibsCss.forEach(function(lib){ %> <% }) %> @@ -103,28 +107,8 @@
    -
    - - -
    - -
    -
    - -
    -
    +
    + <% include blocks/superConfigEditor.ejs %>
    - <% include blocks/confirm.ejs %> <% customAutoLoad.superPageBlocks.forEach(function(block){ %> <%- include(block) %> @@ -290,28 +271,6 @@ $.ccio.tm=function(x,d,z,k){ if(!d.time){d.time=$.ccio.init('t')} tmp+='
    '+d.time+'
    '+d.mid+'
    '+$.ccio.init('jsontoblock',JSON.parse(d.info))+'
    ' break; - case 5://config element - if(d.value instanceof Object){ - tmp += '
    \ -
    \ -
      ' - ++d.layer - $.each(d.value,function(key,value){ - tmp += '
    • ' - tmp += $.ccio.tm(5,{key:key,value:value,layer:d.layer}) - tmp += '
    • ' - }) - tmp += '
    ' - tmp += '
    ' - }else{ - tmp += '
    \ - \ -
    ' - } - break; } if(z){ $(z).prepend(tmp) @@ -362,17 +321,8 @@ $.logs={e:$('#logs')} var config = <%- JSON.stringify(plainConfig) || [] %> $.conf={e:$('#config')}; $.conf.f = $.conf.e.find('form') -$.conf.configForHumans=$('#configForHumans') $.conf.draw=$.conf.e.find('[name="json"]') $.conf.valid=1; -$.conf.jsonToFields = function(){ -// var tmp = '' -// $.each(config,function(key,value){ -// var layer = 0 -// tmp += $.ccio.tm(5,{key:key,value:value,layer:layer}) -// }) -// $.conf.configForHumans.html(tmp) -} $.conf.draw.val(JSON.stringify(<%-stringedConfig%>,null,3)) $.conf.draw.keyup(function(){ var msg='' @@ -382,7 +332,6 @@ $.conf.draw.keyup(function(){ msg = 'Valid JSON' color = 'success' $.conf.valid = 1 - $.conf.jsonToFields() }catch(er){ msg = 'Not a valid JSON' color = 'danger' @@ -414,53 +363,6 @@ $.conf.f.submit(function(e){ } return false; }) -$.conf.e.on('keyup','.config-row input',function(){ - var newConfig = {} - var checkRow = function(v,object){ - var _this = $(v) - var key = _this.find('.key:first').val() - var layer = parseInt(_this.attr('layer')) - var list = _this.find('ul').first() - var isObject = list.length > 0 - if(isObject){ - if(!object[key]){ - if(isNaN(key) === true){ - object[key] = {} - }else{ - object[key] = [] - } - } - list.find('.config-row[layer="'+(layer+1)+'"]').each(function(n,v){ - checkRow(v,object[key]) - }) - }else{ - var value = _this.find('.value').val() - switch(value){ - case'true': - value = true - break; - case'false': - value = false - break; - default: - if(isNaN(value) === false){ - value = parseFloat(value) - } - break; - } - object[key] = value - } - - } - $.conf.configForHumans.children('.config-row').each(function(n,v){ - checkRow(v,newConfig) - }) - $.conf.draw.val(JSON.stringify(newConfig,null,3)) - console.log(newConfig) -}) -$.conf.f.ready(function(){ - $.conf.jsonToFields() -}) //sys controls $.system={e:$('#system')} $.system.e.find('[system]').click(function(e){