├── .gitattributes ├── .gitignore ├── tests ├── string.json └── recursive.json ├── jsoneditor.png ├── src ├── templates │ ├── swig.js │ ├── handlebars.js │ ├── mustache.js │ ├── underscore.js │ ├── markup.js │ ├── hogan.js │ ├── ejs.js │ └── default.js ├── outro.js ├── editors │ ├── null.js │ ├── integer.js │ ├── number.js │ ├── checkbox.js │ ├── base64.js │ ├── enum.js │ ├── upload.js │ ├── multiselect.js │ ├── multiple.js │ ├── select.js │ ├── string.js │ └── table.js ├── intro.js ├── iconlibs │ ├── foundation3.js │ ├── foundation2.js │ ├── bootstrap2.js │ ├── fontawesome3.js │ ├── jqueryui.js │ ├── bootstrap3.js │ └── fontawesome4.js ├── iconlib.js ├── ie9.js ├── jquery.js ├── class.js ├── utilities.js ├── themes │ ├── html.js │ ├── jqueryui.js │ ├── bootstrap3.js │ ├── bootstrap2.js │ └── foundation.js ├── defaults.js ├── theme.js ├── editor.js └── validator.js ├── examples ├── basic_person.json ├── person.json ├── basic.html ├── wysiwyg.html ├── select2.html ├── upload.html ├── advanced.html ├── css_integration.html └── recursive.html ├── bower.json ├── package.json ├── LICENSE ├── CONTRIBUTING.md └── Gruntfile.js /.gitattributes: -------------------------------------------------------------------------------- 1 | dist/* merge=own 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /.idea/ 3 | -------------------------------------------------------------------------------- /tests/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "string" 3 | } 4 | -------------------------------------------------------------------------------- /jsoneditor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/englercj/json-editor/master/jsoneditor.png -------------------------------------------------------------------------------- /src/templates/swig.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.templates.swig = function() { 2 | return window.swig; 3 | }; 4 | -------------------------------------------------------------------------------- /src/templates/handlebars.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.templates.handlebars = function() { 2 | return window.Handlebars; 3 | }; 4 | -------------------------------------------------------------------------------- /tests/recursive.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "string": { 5 | "$ref": "http://localhost/json-editor/tests/string.json" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/outro.js: -------------------------------------------------------------------------------- 1 | /*global module*/ 2 | if (typeof module !== 'undefined' && module.exports) { 3 | module.exports = JSONEditor; 4 | } 5 | if (typeof window !== 'undefined') { 6 | window.JSONEditor = JSONEditor; 7 | } 8 | })(); 9 | -------------------------------------------------------------------------------- /src/editors/null.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.editors["null"] = JSONEditor.AbstractEditor.extend({ 2 | getValue: function() { 3 | return null; 4 | }, 5 | setValue: function() { 6 | this.onChange(); 7 | }, 8 | getNumColumns: function() { 9 | return 2; 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /src/editors/integer.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.editors.integer = JSONEditor.defaults.editors.number.extend({ 2 | sanitize: function(value) { 3 | value = value + ""; 4 | return value.replace(/[^0-9\-]/g,''); 5 | }, 6 | getNumColumns: function() { 7 | return 2; 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/intro.js: -------------------------------------------------------------------------------- 1 | /*! JSON Editor v0.7.21 - JSON Schema -> HTML Editor 2 | * By Jeremy Dorn - https://github.com/jdorn/json-editor/ 3 | * Released under the MIT license 4 | * 5 | * Date: 2015-07-03 6 | */ 7 | 8 | /** 9 | * See README.md for requirements and usage info 10 | */ 11 | 12 | (function() { 13 | -------------------------------------------------------------------------------- /src/templates/mustache.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.templates.mustache = function() { 2 | if(!window.Mustache) return false; 3 | 4 | return { 5 | compile: function(template) { 6 | return function(view) { 7 | return window.Mustache.render(template, view); 8 | }; 9 | } 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /src/templates/underscore.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.templates.underscore = function() { 2 | if(!window._) return false; 3 | 4 | return { 5 | compile: function(template) { 6 | return function(context) { 7 | return window._.template(template, context); 8 | }; 9 | } 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /src/templates/markup.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.templates.markup = function() { 2 | if(!window.Mark || !window.Mark.up) return false; 3 | 4 | return { 5 | compile: function(template) { 6 | return function(context) { 7 | return window.Mark.up(template,context); 8 | }; 9 | } 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /src/editors/number.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.editors.number = JSONEditor.defaults.editors.string.extend({ 2 | sanitize: function(value) { 3 | return (value+"").replace(/[^0-9\.\-eE]/g,''); 4 | }, 5 | getNumColumns: function() { 6 | return 2; 7 | }, 8 | getValue: function() { 9 | return this.value*1; 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /src/templates/hogan.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.templates.hogan = function() { 2 | if(!window.Hogan) return false; 3 | 4 | return { 5 | compile: function(template) { 6 | var compiled = window.Hogan.compile(template); 7 | return function(context) { 8 | return compiled.render(context); 9 | }; 10 | } 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /src/templates/ejs.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.templates.ejs = function() { 2 | if(!window.EJS) return false; 3 | 4 | return { 5 | compile: function(template) { 6 | var compiled = new window.EJS({ 7 | text: template 8 | }); 9 | 10 | return function(context) { 11 | return compiled.render(context); 12 | }; 13 | } 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /src/iconlibs/foundation3.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.iconlibs.foundation3 = JSONEditor.AbstractIconLib.extend({ 2 | mapping: { 3 | collapse: 'minus', 4 | expand: 'plus', 5 | "delete": 'x', 6 | edit: 'pencil', 7 | add: 'page-add', 8 | cancel: 'x-circle', 9 | save: 'save', 10 | moveup: 'arrow-up', 11 | movedown: 'arrow-down' 12 | }, 13 | icon_prefix: 'fi-' 14 | }); 15 | -------------------------------------------------------------------------------- /src/iconlibs/foundation2.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.iconlibs.foundation2 = JSONEditor.AbstractIconLib.extend({ 2 | mapping: { 3 | collapse: 'minus', 4 | expand: 'plus', 5 | "delete": 'remove', 6 | edit: 'edit', 7 | add: 'add-doc', 8 | cancel: 'error', 9 | save: 'checkmark', 10 | moveup: 'up-arrow', 11 | movedown: 'down-arrow' 12 | }, 13 | icon_prefix: 'foundicon-' 14 | }); 15 | -------------------------------------------------------------------------------- /src/iconlibs/bootstrap2.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.iconlibs.bootstrap2 = JSONEditor.AbstractIconLib.extend({ 2 | mapping: { 3 | collapse: 'chevron-down', 4 | expand: 'chevron-up', 5 | "delete": 'trash', 6 | edit: 'pencil', 7 | add: 'plus', 8 | cancel: 'ban-circle', 9 | save: 'ok', 10 | moveup: 'arrow-up', 11 | movedown: 'arrow-down' 12 | }, 13 | icon_prefix: 'icon-' 14 | }); 15 | -------------------------------------------------------------------------------- /src/iconlibs/fontawesome3.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.iconlibs.fontawesome3 = JSONEditor.AbstractIconLib.extend({ 2 | mapping: { 3 | collapse: 'chevron-down', 4 | expand: 'chevron-right', 5 | "delete": 'remove', 6 | edit: 'pencil', 7 | add: 'plus', 8 | cancel: 'ban-circle', 9 | save: 'save', 10 | moveup: 'arrow-up', 11 | movedown: 'arrow-down' 12 | }, 13 | icon_prefix: 'icon-' 14 | }); 15 | -------------------------------------------------------------------------------- /src/iconlibs/jqueryui.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.iconlibs.jqueryui = JSONEditor.AbstractIconLib.extend({ 2 | mapping: { 3 | collapse: 'triangle-1-s', 4 | expand: 'triangle-1-e', 5 | "delete": 'trash', 6 | edit: 'pencil', 7 | add: 'plusthick', 8 | cancel: 'closethick', 9 | save: 'disk', 10 | moveup: 'arrowthick-1-n', 11 | movedown: 'arrowthick-1-s' 12 | }, 13 | icon_prefix: 'ui-icon ui-icon-' 14 | }); 15 | -------------------------------------------------------------------------------- /src/iconlibs/bootstrap3.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.iconlibs.bootstrap3 = JSONEditor.AbstractIconLib.extend({ 2 | mapping: { 3 | collapse: 'chevron-down', 4 | expand: 'chevron-right', 5 | "delete": 'remove', 6 | edit: 'pencil', 7 | add: 'plus', 8 | cancel: 'floppy-remove', 9 | save: 'floppy-saved', 10 | moveup: 'arrow-up', 11 | movedown: 'arrow-down' 12 | }, 13 | icon_prefix: 'glyphicon glyphicon-' 14 | }); 15 | -------------------------------------------------------------------------------- /src/iconlibs/fontawesome4.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.iconlibs.fontawesome4 = JSONEditor.AbstractIconLib.extend({ 2 | mapping: { 3 | collapse: 'caret-square-o-down', 4 | expand: 'caret-square-o-right', 5 | "delete": 'times', 6 | edit: 'pencil', 7 | add: 'plus', 8 | cancel: 'ban', 9 | save: 'save', 10 | moveup: 'arrow-up', 11 | movedown: 'arrow-down', 12 | copy: 'files-o' 13 | }, 14 | icon_prefix: 'fa fa-' 15 | }); 16 | -------------------------------------------------------------------------------- /examples/basic_person.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Person", 3 | "type": "object", 4 | "id": "person", 5 | "properties": { 6 | "name": { 7 | "type": "string", 8 | "description": "First and Last name", 9 | "minLength": 4 10 | }, 11 | "age": { 12 | "type": "integer", 13 | "default": 21, 14 | "minimum": 18, 15 | "maximum": 99 16 | }, 17 | "gender": { 18 | "type": "string", 19 | "enum": [ 20 | "male", 21 | "female" 22 | ] 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-editor", 3 | "authors": [ 4 | "Jeremy Dorn " 5 | ], 6 | "description": "JSON Schema based editor", 7 | "main": "dist/jsoneditor.js", 8 | "keywords": [ 9 | "json", 10 | "schema", 11 | "jsonschema", 12 | "editor" 13 | ], 14 | "license": "MIT", 15 | "ignore": [ 16 | "**/.*", 17 | "*.md", 18 | "tests", 19 | "examples", 20 | "demo.html", 21 | "*.png", 22 | "package.json", 23 | "Gruntfile.js", 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/iconlib.js: -------------------------------------------------------------------------------- 1 | JSONEditor.AbstractIconLib = Class.extend({ 2 | mapping: { 3 | collapse: '', 4 | expand: '', 5 | "delete": '', 6 | edit: '', 7 | add: '', 8 | cancel: '', 9 | save: '', 10 | moveup: '', 11 | movedown: '' 12 | }, 13 | icon_prefix: '', 14 | getIconClass: function(key) { 15 | if(this.mapping[key]) return this.icon_prefix+this.mapping[key]; 16 | else return null; 17 | }, 18 | getIcon: function(key) { 19 | var iconclass = this.getIconClass(key); 20 | 21 | if(!iconclass) return null; 22 | 23 | var i = document.createElement('i'); 24 | i.className = iconclass; 25 | return i; 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-editor", 3 | "title": "JSONEditor", 4 | "description": "JSON Schema based editor", 5 | "version": "0.7.21", 6 | "main": "dist/jsoneditor.js", 7 | "author": { 8 | "name": "Jeremy Dorn", 9 | "email": "jeremy@jeremydorn.com", 10 | "url": "http://jeremydorn.com" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/jdorn/json-editor/issues" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/jdorn/json-editor.git" 18 | }, 19 | "keywords": [ 20 | "json", 21 | "schema", 22 | "jsonschema", 23 | "editor" 24 | ], 25 | "license": "MIT", 26 | "engines": { 27 | "node": ">= 0.8.0" 28 | }, 29 | "devDependencies": { 30 | "grunt": "~0.4.2", 31 | "grunt-concat-sourcemap": "^0.4.3", 32 | "grunt-contrib-jshint": "^0.10.0", 33 | "grunt-contrib-uglify": "~0.2.0", 34 | "grunt-contrib-watch": "~0.5.3" 35 | }, 36 | "scripts": { 37 | "build": "npm install && grunt", 38 | "start": "grunt watch", 39 | "test": "grunt" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Jeremy Dorn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /examples/person.json: -------------------------------------------------------------------------------- 1 | { 2 | "$ref": "basic_person.json", 3 | "properties": { 4 | "location": { 5 | "type": "object", 6 | "title": "Location", 7 | "properties": { 8 | "city": { 9 | "type": "string" 10 | }, 11 | "state": { 12 | "type": "string" 13 | }, 14 | "citystate": { 15 | "type": "string", 16 | "description": "This is generated automatically from the previous two fields", 17 | "template": "{{city}}, {{state}}", 18 | "watch": { 19 | "city": "person.location.city", 20 | "state": "person.location.state" 21 | } 22 | } 23 | } 24 | }, 25 | "pets": { 26 | "type": "array", 27 | "format": "table", 28 | "title": "Pets", 29 | "uniqueItems": true, 30 | "items": { 31 | "type": "object", 32 | "properties": { 33 | "type": { 34 | "type": "string", 35 | "enum": [ 36 | "cat", 37 | "dog", 38 | "bird", 39 | "reptile", 40 | "other" 41 | ], 42 | "default": "dog" 43 | }, 44 | "name": { 45 | "type": "string" 46 | }, 47 | "fixed": { 48 | "type": "boolean", 49 | "title": "spayed / neutered" 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/templates/default.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.templates["default"] = function() { 2 | return { 3 | compile: function(template) { 4 | var matches = template.match(/{{\s*([a-zA-Z0-9\-_\.]+)\s*}}/g); 5 | var l = matches.length; 6 | 7 | // Shortcut if the template contains no variables 8 | if(!l) return function() { return template; }; 9 | 10 | // Pre-compute the search/replace functions 11 | // This drastically speeds up template execution 12 | var replacements = []; 13 | var get_replacement = function(i) { 14 | var p = matches[i].replace(/[{}\s]+/g,'').split('.'); 15 | var n = p.length; 16 | var func; 17 | 18 | if(n > 1) { 19 | var cur; 20 | func = function(vars) { 21 | cur = vars; 22 | for(i=0; i 2 | 3 | 4 | 5 | Basic JSON Editor Example 6 | 7 | 8 | 9 |

Basic JSON Editor Example

10 | 11 |
12 | 13 | 14 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /examples/wysiwyg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSON Editor WYSIWYG Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

JSON Editor WYSIWYG Example

15 | 16 |

This example demonstrates JSONEditor's integration with SCEditor

17 | 18 |
19 | 20 | 21 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | =============== 3 | This document briefly lists the guidelines for contributing to JSON Editor. 4 | 5 | Reporting Bugs 6 | ---------------- 7 | When creating an issue in GitHub, try to include when feasible: 8 | * A brief description of the issue 9 | * An example JSON schema that causes the issue 10 | * Steps to reproduce 11 | 12 | If you can reproduce the issue on the demo page (http://jeremydorn.com/json-editor/), it's helpful to attach the "Direct Link" url (top right of page). Note: the direct link might not work for very large schemas or JSON values. 13 | 14 | 15 | Contributing Code 16 | -------------------------- 17 | One of the major goals of JSON Editor is to be easy to modify and hack. 18 | 19 | If you fix a bug or add a cool feature, please submit a pull request! 20 | 21 | 22 | ### Code Style 23 | 24 | * Use 2 spaces for indentation 25 | * Use comments whenever the code's meaning is not obvious 26 | * When in doubt, try to match the style in existing source files 27 | 28 | ###Grunt 29 | 30 | The easiest way to hack on JSON Editor is to run `grunt watch`, which 31 | re-builds `dist/jsoneditor.js` every time a source file changes. 32 | 33 | To do a full grunt build which includes jshint and minification, run `grunt`. 34 | 35 | ### Submitting Pull Requests 36 | Try to limit pull requests to a single narrow feature or bug fix. 37 | 38 | __Do not submit `dist/` files!__ 39 | 40 | The following is done when a pull request is accepted. There is no need to do any of these steps yourself. 41 | 42 | 1. Merge pull request into master 43 | 2. Increment version number in `src/intro.js` and `bower.json`. Set date in `src/intro.js`. 44 | 3. Build `dist/` files with grunt 45 | 4. Commit and push to github 46 | 5. Add a git tag and release for this version with a short changelog 47 | 48 | Sometimes, multiple pull requests will be merged before doing steps 2-5. 49 | -------------------------------------------------------------------------------- /src/ie9.js: -------------------------------------------------------------------------------- 1 | // CustomEvent constructor polyfill 2 | // From MDN 3 | (function () { 4 | function CustomEvent ( event, params ) { 5 | params = params || { bubbles: false, cancelable: false, detail: undefined }; 6 | var evt = document.createEvent( 'CustomEvent' ); 7 | evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail ); 8 | return evt; 9 | } 10 | 11 | CustomEvent.prototype = window.Event.prototype; 12 | 13 | window.CustomEvent = CustomEvent; 14 | })(); 15 | 16 | // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel 17 | // MIT license 18 | (function() { 19 | var lastTime = 0; 20 | var vendors = ['ms', 'moz', 'webkit', 'o']; 21 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 22 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 23 | window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || 24 | window[vendors[x]+'CancelRequestAnimationFrame']; 25 | } 26 | 27 | if (!window.requestAnimationFrame) 28 | window.requestAnimationFrame = function(callback, element) { 29 | var currTime = new Date().getTime(); 30 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 31 | var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 32 | timeToCall); 33 | lastTime = currTime + timeToCall; 34 | return id; 35 | }; 36 | 37 | if (!window.cancelAnimationFrame) 38 | window.cancelAnimationFrame = function(id) { 39 | clearTimeout(id); 40 | }; 41 | }()); 42 | 43 | // Array.isArray polyfill 44 | // From MDN 45 | (function() { 46 | if(!Array.isArray) { 47 | Array.isArray = function(arg) { 48 | return Object.prototype.toString.call(arg) === '[object Array]'; 49 | }; 50 | } 51 | }()); -------------------------------------------------------------------------------- /src/jquery.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a small wrapper for using JSON Editor like a typical jQuery plugin. 3 | */ 4 | (function() { 5 | if(window.jQuery || window.Zepto) { 6 | var $ = window.jQuery || window.Zepto; 7 | $.jsoneditor = JSONEditor.defaults; 8 | 9 | $.fn.jsoneditor = function(options) { 10 | var self = this; 11 | var editor = this.data('jsoneditor'); 12 | if(options === 'value') { 13 | if(!editor) throw "Must initialize jsoneditor before getting/setting the value"; 14 | 15 | // Set value 16 | if(arguments.length > 1) { 17 | editor.setValue(arguments[1]); 18 | } 19 | // Get value 20 | else { 21 | return editor.getValue(); 22 | } 23 | } 24 | else if(options === 'validate') { 25 | if(!editor) throw "Must initialize jsoneditor before validating"; 26 | 27 | // Validate a specific value 28 | if(arguments.length > 1) { 29 | return editor.validate(arguments[1]); 30 | } 31 | // Validate current value 32 | else { 33 | return editor.validate(); 34 | } 35 | } 36 | else if(options === 'destroy') { 37 | if(editor) { 38 | editor.destroy(); 39 | this.data('jsoneditor',null); 40 | } 41 | } 42 | else { 43 | // Destroy first 44 | if(editor) { 45 | editor.destroy(); 46 | } 47 | 48 | // Create editor 49 | editor = new JSONEditor(this.get(0),options); 50 | this.data('jsoneditor',editor); 51 | 52 | // Setup event listeners 53 | editor.on('change',function() { 54 | self.trigger('change'); 55 | }); 56 | editor.on('ready',function() { 57 | self.trigger('ready'); 58 | }); 59 | } 60 | 61 | return this; 62 | }; 63 | } 64 | })(); 65 | -------------------------------------------------------------------------------- /src/editors/checkbox.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.editors.checkbox = JSONEditor.AbstractEditor.extend({ 2 | setValue: function(value,initial) { 3 | this.value = !!value; 4 | this.input.checked = this.value; 5 | this.onChange(); 6 | }, 7 | register: function() { 8 | this._super(); 9 | if(!this.input) return; 10 | this.input.setAttribute('name',this.formname); 11 | }, 12 | unregister: function() { 13 | this._super(); 14 | if(!this.input) return; 15 | this.input.removeAttribute('name'); 16 | }, 17 | getNumColumns: function() { 18 | return Math.min(12,Math.max(this.getTitle().length/7,2)); 19 | }, 20 | build: function() { 21 | var self = this; 22 | if(!this.options.compact) { 23 | this.label = this.header = this.theme.getCheckboxLabel(this.getTitle()); 24 | } 25 | if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description); 26 | if(this.options.compact) this.container.className += ' compact'; 27 | 28 | this.input = this.theme.getCheckbox(); 29 | this.control = this.theme.getFormControl(this.label, this.input, this.description); 30 | 31 | if(this.schema.readOnly || this.schema.readonly) { 32 | this.always_disabled = true; 33 | this.input.disabled = true; 34 | } 35 | 36 | this.input.addEventListener('change',function(e) { 37 | e.preventDefault(); 38 | e.stopPropagation(); 39 | self.value = this.checked; 40 | self.onChange(true); 41 | }); 42 | 43 | this.container.appendChild(this.control); 44 | }, 45 | enable: function() { 46 | if(!this.always_disabled) { 47 | this.input.disabled = false; 48 | } 49 | this._super(); 50 | }, 51 | disable: function() { 52 | this.input.disabled = true; 53 | this._super(); 54 | }, 55 | destroy: function() { 56 | if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label); 57 | if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description); 58 | if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); 59 | this._super(); 60 | } 61 | }); 62 | -------------------------------------------------------------------------------- /src/class.js: -------------------------------------------------------------------------------- 1 | /*jshint loopfunc: true */ 2 | /* Simple JavaScript Inheritance 3 | * By John Resig http://ejohn.org/ 4 | * MIT Licensed. 5 | */ 6 | // Inspired by base2 and Prototype 7 | var Class; 8 | (function(){ 9 | var initializing = false, fnTest = /xyz/.test(function(){window.postMessage("xyz");}) ? /\b_super\b/ : /.*/; 10 | 11 | // The base Class implementation (does nothing) 12 | Class = function(){}; 13 | 14 | // Create a new Class that inherits from this class 15 | Class.extend = function(prop) { 16 | var _super = this.prototype; 17 | 18 | // Instantiate a base class (but only create the instance, 19 | // don't run the init constructor) 20 | initializing = true; 21 | var prototype = new this(); 22 | initializing = false; 23 | 24 | // Copy the properties over onto the new prototype 25 | for (var name in prop) { 26 | // Check if we're overwriting an existing function 27 | prototype[name] = typeof prop[name] == "function" && 28 | typeof _super[name] == "function" && fnTest.test(prop[name]) ? 29 | (function(name, fn){ 30 | return function() { 31 | var tmp = this._super; 32 | 33 | // Add a new ._super() method that is the same method 34 | // but on the super-class 35 | this._super = _super[name]; 36 | 37 | // The method only need to be bound temporarily, so we 38 | // remove it when we're done executing 39 | var ret = fn.apply(this, arguments); 40 | this._super = tmp; 41 | 42 | return ret; 43 | }; 44 | })(name, prop[name]) : 45 | prop[name]; 46 | } 47 | 48 | // The dummy class constructor 49 | function Class() { 50 | // All construction is actually done in the init method 51 | if ( !initializing && this.init ) 52 | this.init.apply(this, arguments); 53 | } 54 | 55 | // Populate our constructed prototype object 56 | Class.prototype = prototype; 57 | 58 | // Enforce the constructor to be what we expect 59 | Class.prototype.constructor = Class; 60 | 61 | // And make this class extendable 62 | Class.extend = arguments.callee; 63 | 64 | return Class; 65 | }; 66 | 67 | return Class; 68 | })(); 69 | -------------------------------------------------------------------------------- /src/utilities.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Taken from jQuery 2.1.3 3 | * 4 | * @param obj 5 | * @returns {boolean} 6 | */ 7 | var $isplainobject = function( obj ) { 8 | // Not plain objects: 9 | // - Any object or value whose internal [[Class]] property is not "[object Object]" 10 | // - DOM nodes 11 | // - window 12 | if (typeof obj !== "object" || obj.nodeType || (obj != null && obj === obj.window)) { 13 | return false; 14 | } 15 | 16 | if (obj.constructor && !Object.prototype.hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) { 17 | return false; 18 | } 19 | 20 | // If the function hasn't returned already, we're confident that 21 | // |obj| is a plain object, created by {} or constructed with new Object 22 | return true; 23 | }; 24 | 25 | var $extend = function(destination) { 26 | var source, i,property; 27 | for(i=1; i 0 && (obj.length - 1) in obj)) { 47 | for(i=0; i 2 | 3 | 4 | 5 | JSON Editor Select2 Integration Example 6 | 7 | 8 | 9 | 10 | 11 | 12 |

JSON Editor Select2 Integration Example

13 | 14 |

This example demonstrates JSONEditor's integration with Select2

15 | 16 |
17 | 18 | 19 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/editors/base64.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.editors.base64 = JSONEditor.AbstractEditor.extend({ 2 | getNumColumns: function() { 3 | return 4; 4 | }, 5 | build: function() { 6 | var self = this; 7 | this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle(),this.isRequired()); 8 | 9 | // Input that holds the base64 string 10 | this.input = this.theme.getFormInputField('hidden'); 11 | this.container.appendChild(this.input); 12 | 13 | // Don't show uploader if this is readonly 14 | if(!this.schema.readOnly && !this.schema.readonly) { 15 | if(!window.FileReader) throw "FileReader required for base64 editor"; 16 | 17 | // File uploader 18 | this.uploader = this.theme.getFormInputField('file'); 19 | 20 | this.uploader.addEventListener('change',function(e) { 21 | e.preventDefault(); 22 | e.stopPropagation(); 23 | 24 | if(this.files && this.files.length) { 25 | var fr = new FileReader(); 26 | fr.onload = function(evt) { 27 | self.value = evt.target.result; 28 | self.refreshPreview(); 29 | self.onChange(true); 30 | fr = null; 31 | }; 32 | fr.readAsDataURL(this.files[0]); 33 | } 34 | }); 35 | } 36 | 37 | this.preview = this.theme.getFormInputDescription(this.schema.description); 38 | this.container.appendChild(this.preview); 39 | 40 | this.control = this.theme.getFormControl(this.label, this.uploader||this.input, this.preview); 41 | this.container.appendChild(this.control); 42 | }, 43 | refreshPreview: function() { 44 | if(this.last_preview === this.value) return; 45 | this.last_preview = this.value; 46 | 47 | this.preview.innerHTML = ''; 48 | 49 | if(!this.value) return; 50 | 51 | var mime = this.value.match(/^data:([^;,]+)[;,]/); 52 | if(mime) mime = mime[1]; 53 | 54 | if(!mime) { 55 | this.preview.innerHTML = 'Invalid data URI'; 56 | } 57 | else { 58 | this.preview.innerHTML = 'Type: '+mime+', Size: '+Math.floor((this.value.length-this.value.split(',')[0].length-1)/1.33333)+' bytes'; 59 | if(mime.substr(0,5)==="image") { 60 | this.preview.innerHTML += '
'; 61 | var img = document.createElement('img'); 62 | img.style.maxWidth = '100%'; 63 | img.style.maxHeight = '100px'; 64 | img.src = this.value; 65 | this.preview.appendChild(img); 66 | } 67 | } 68 | }, 69 | enable: function() { 70 | if(this.uploader) this.uploader.disabled = false; 71 | this._super(); 72 | }, 73 | disable: function() { 74 | if(this.uploader) this.uploader.disabled = true; 75 | this._super(); 76 | }, 77 | setValue: function(val) { 78 | if(this.value !== val) { 79 | this.value = val; 80 | this.input.value = this.value; 81 | this.refreshPreview(); 82 | this.onChange(); 83 | } 84 | }, 85 | destroy: function() { 86 | if(this.preview && this.preview.parentNode) this.preview.parentNode.removeChild(this.preview); 87 | if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); 88 | if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); 89 | if(this.uploader && this.uploader.parentNode) this.uploader.parentNode.removeChild(this.uploader); 90 | 91 | this._super(); 92 | } 93 | }); 94 | -------------------------------------------------------------------------------- /examples/upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSON Editor Upload Example 6 | 7 | 8 | 9 |

JSON Editor Upload Example

10 | 11 |
12 | 13 | 14 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.initConfig({ 5 | concat_sourcemap: { 6 | options: { 7 | sourcesContent: true 8 | }, 9 | target: { 10 | files: { 11 | 'dist/jsoneditor.js': [ 12 | 13 | // License & version info, start the containing closure 14 | 'src/intro.js', 15 | 16 | // Simple inheritance 17 | 'src/class.js', 18 | // IE9 polyfills 19 | 'src/ie9.js', 20 | // Utils like extend, each, and trigger 21 | 'src/utilities.js', 22 | 23 | // The main JSONEditor class 24 | 'src/core.js', 25 | 26 | // JSON Schema validator 27 | 'src/validator.js', 28 | 29 | // All the editors 30 | 'src/editor.js', 31 | 'src/editors/null.js', 32 | 'src/editors/string.js', 33 | 'src/editors/number.js', 34 | 'src/editors/integer.js', 35 | 'src/editors/object.js', 36 | 'src/editors/array.js', 37 | 'src/editors/table.js', 38 | 'src/editors/multiple.js', 39 | 'src/editors/enum.js', 40 | 'src/editors/select.js', 41 | 'src/editors/multiselect.js', 42 | 'src/editors/base64.js', 43 | 'src/editors/upload.js', 44 | 'src/editors/checkbox.js', 45 | 46 | // All the themes and iconlibs 47 | 'src/theme.js', 48 | 'src/themes/*.js', 49 | 'src/iconlib.js', 50 | 'src/iconlibs/*.js', 51 | 52 | // The JS templating engines 53 | 'src/templates/*.js', 54 | 55 | // Set the defaults 56 | 'src/defaults.js', 57 | 58 | // Wrapper for $.fn style initialization 59 | 'src/jquery.js', 60 | 61 | // End the closure 62 | 'src/outro.js' 63 | ], 64 | } 65 | } 66 | }, 67 | uglify: { 68 | dist: { 69 | src: 'dist/jsoneditor.js', 70 | dest: 'dist/jsoneditor.min.js' 71 | }, 72 | options: { 73 | preserveComments: 'some' 74 | } 75 | }, 76 | watch: { 77 | scripts: { 78 | files: ["src/**/*.js"], 79 | tasks: ["concat_sourcemap"] 80 | } 81 | }, 82 | jshint: { 83 | options: { 84 | browser: true, 85 | indent: 2, 86 | nonbsp: true, 87 | nonew: true, 88 | immed: true, 89 | latedef: true 90 | }, 91 | beforeconcat: [ 92 | 'src/class.js', 93 | 'src/ie9.js', 94 | 95 | // Utils like extend, each, and trigger 96 | 'src/utilities.js', 97 | 98 | // The main JSONEditor class 99 | 'src/core.js', 100 | 101 | // JSON Schema validator 102 | 'src/validator.js', 103 | 104 | // All the editors 105 | 'src/editor.js', 106 | 'src/editors/*.js', 107 | 108 | // All the themes and iconlibs 109 | 'src/theme.js', 110 | 'src/themes/*.js', 111 | 'src/iconlib.js', 112 | 'src/iconlibs/*.js', 113 | 114 | // The JS templating engines 115 | 'src/templates/*.js', 116 | 117 | // Set the defaults 118 | 'src/defaults.js', 119 | 120 | // Wrapper for $.fn style initialization 121 | 'src/jquery.js' 122 | ], 123 | afterconcat: { 124 | options: { 125 | undef: true 126 | }, 127 | files: { 128 | src: ['dist/jsoneditor.js'] 129 | } 130 | } 131 | } 132 | }); 133 | 134 | // These plugins provide necessary tasks. 135 | grunt.loadNpmTasks('grunt-contrib-uglify'); 136 | grunt.loadNpmTasks('grunt-contrib-watch'); 137 | grunt.loadNpmTasks('grunt-contrib-jshint'); 138 | grunt.loadNpmTasks('grunt-concat-sourcemap'); 139 | 140 | // Default task. 141 | grunt.registerTask('default', ['jshint:beforeconcat','concat_sourcemap','jshint:afterconcat','uglify']); 142 | 143 | }; 144 | -------------------------------------------------------------------------------- /src/editors/enum.js: -------------------------------------------------------------------------------- 1 | // Enum Editor (used for objects and arrays with enumerated values) 2 | JSONEditor.defaults.editors["enum"] = JSONEditor.AbstractEditor.extend({ 3 | getNumColumns: function() { 4 | return 4; 5 | }, 6 | build: function() { 7 | var container = this.container; 8 | this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle(),this.isRequired()); 9 | this.container.appendChild(this.title); 10 | 11 | this.options.enum_titles = this.options.enum_titles || []; 12 | 13 | this["enum"] = this.schema["enum"]; 14 | this.selected = 0; 15 | this.select_options = []; 16 | this.html_values = []; 17 | 18 | var self = this; 19 | for(var i=0; inull'; 83 | } 84 | // Array or Object 85 | else if(typeof el === "object") { 86 | // TODO: use theme 87 | var ret = ''; 88 | 89 | $each(el,function(i,child) { 90 | var html = self.getHTML(child); 91 | 92 | // Add the keys to object children 93 | if(!(Array.isArray(el))) { 94 | // TODO: use theme 95 | html = '
'+i+': '+html+'
'; 96 | } 97 | 98 | // TODO: use theme 99 | ret += '
  • '+html+'
  • '; 100 | }); 101 | 102 | if(Array.isArray(el)) ret = '
      '+ret+'
    '; 103 | else ret = "
      "+ret+'
    '; 104 | 105 | return ret; 106 | } 107 | // Boolean 108 | else if(typeof el === "boolean") { 109 | return el? 'true' : 'false'; 110 | } 111 | // String 112 | else if(typeof el === "string") { 113 | return el.replace(/&/g,'&').replace(//g,'>'); 114 | } 115 | // Number 116 | else { 117 | return el; 118 | } 119 | }, 120 | setValue: function(val) { 121 | if(this.value !== val) { 122 | this.value = val; 123 | this.refreshValue(); 124 | this.onChange(); 125 | } 126 | }, 127 | destroy: function() { 128 | if(this.display_area && this.display_area.parentNode) this.display_area.parentNode.removeChild(this.display_area); 129 | if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); 130 | if(this.switcher && this.switcher.parentNode) this.switcher.parentNode.removeChild(this.switcher); 131 | 132 | this._super(); 133 | } 134 | }); 135 | -------------------------------------------------------------------------------- /examples/advanced.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Advanced JSON Editor Example 6 | 7 | 8 | 9 | 10 |

    Advanced JSON Editor Example

    11 | 12 |

    This example demonstrates the following:

    13 |
      14 |
    • Loading external schemas via ajax (using $ref)
    • 15 |
    • Setting the editor's value from javascript (try the Restore to Default button)
    • 16 |
    • Validating the editor's contents (try setting name to an empty string)
    • 17 |
    • Macro templates (try changing the city or state fields and watch the citystate field update automatically)
    • 18 |
    • Enabling and disabling editor fields
    • 19 |
    20 | 21 | 22 | 23 | 24 | 25 | 26 |
    27 | 28 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /examples/css_integration.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CSS Integration JSON Editor Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 |
    22 |
    23 |

    CSS Integration JSON Editor Example

    24 |
    25 |
    26 |
    27 |
    28 |

    JSON Editor supports these popular CSS frameworks:

    29 |
      30 |
    • Bootstrap 2
    • 31 |
    • Bootstrap 3
    • 32 |
    • Foundation 3
    • 33 |
    • Foundation 4
    • 34 |
    • Foundation 5 (shown here)
    • 35 |
    • jQuery UI
    • 36 |
    37 |
    38 |
    39 |

    JSON Editor supports these popular icon libraries:

    40 |
      41 |
    • Bootstrap 2 Glyphicons
    • 42 |
    • Bootstrap 3 Glyphicons
    • 43 |
    • Foundicons 2
    • 44 |
    • Foundicons 3
    • 45 |
    • jQueryUI
    • 46 |
    • Font Awesome 3
    • 47 |
    • Font Awesome 4 (shown here)
    • 48 |
    49 |
    50 |
    51 |
    52 |
    53 | 54 | 55 | 56 |
    57 |
    58 |
    59 |
    60 |
    61 | 62 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /src/editors/upload.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.editors.upload = JSONEditor.AbstractEditor.extend({ 2 | getNumColumns: function() { 3 | return 4; 4 | }, 5 | build: function() { 6 | var self = this; 7 | this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle(),this.isRequired()); 8 | 9 | // Input that holds the base64 string 10 | this.input = this.theme.getFormInputField('hidden'); 11 | this.container.appendChild(this.input); 12 | 13 | // Don't show uploader if this is readonly 14 | if(!this.schema.readOnly && !this.schema.readonly) { 15 | 16 | if(!this.jsoneditor.options.upload) throw "Upload handler required for upload editor"; 17 | 18 | // File uploader 19 | this.uploader = this.theme.getFormInputField('file'); 20 | 21 | this.uploader.addEventListener('change',function(e) { 22 | e.preventDefault(); 23 | e.stopPropagation(); 24 | 25 | if(this.files && this.files.length) { 26 | var fr = new FileReader(); 27 | fr.onload = function(evt) { 28 | self.preview_value = evt.target.result; 29 | self.refreshPreview(); 30 | self.onChange(true); 31 | fr = null; 32 | }; 33 | fr.readAsDataURL(this.files[0]); 34 | } 35 | }); 36 | } 37 | 38 | var description = this.schema.description; 39 | if (!description) description = ''; 40 | 41 | this.preview = this.theme.getFormInputDescription(description); 42 | this.container.appendChild(this.preview); 43 | 44 | this.control = this.theme.getFormControl(this.label, this.uploader||this.input, this.preview); 45 | this.container.appendChild(this.control); 46 | }, 47 | refreshPreview: function() { 48 | if(this.last_preview === this.preview_value) return; 49 | this.last_preview = this.preview_value; 50 | 51 | this.preview.innerHTML = ''; 52 | 53 | if(!this.preview_value) return; 54 | 55 | var self = this; 56 | 57 | var mime = this.preview_value.match(/^data:([^;,]+)[;,]/); 58 | if(mime) mime = mime[1]; 59 | if(!mime) mime = 'unknown'; 60 | 61 | var file = this.uploader.files[0]; 62 | 63 | this.preview.innerHTML = 'Type: '+mime+', Size: '+file.size+' bytes'; 64 | if(mime.substr(0,5)==="image") { 65 | this.preview.innerHTML += '
    '; 66 | var img = document.createElement('img'); 67 | img.style.maxWidth = '100%'; 68 | img.style.maxHeight = '100px'; 69 | img.src = this.preview_value; 70 | this.preview.appendChild(img); 71 | } 72 | 73 | this.preview.innerHTML += '
    '; 74 | var uploadButton = this.getButton('Upload', 'upload', 'Upload'); 75 | this.preview.appendChild(uploadButton); 76 | uploadButton.addEventListener('click',function(event) { 77 | event.preventDefault(); 78 | 79 | uploadButton.setAttribute("disabled", "disabled"); 80 | self.theme.removeInputError(self.uploader); 81 | 82 | if (self.theme.getProgressBar) { 83 | self.progressBar = self.theme.getProgressBar(); 84 | self.preview.appendChild(self.progressBar); 85 | } 86 | 87 | self.jsoneditor.options.upload(self.path, file, { 88 | success: function(url) { 89 | self.setValue(url); 90 | 91 | if(self.parent) self.parent.onChildEditorChange(self); 92 | else self.jsoneditor.onChange(); 93 | 94 | if (self.progressBar) self.preview.removeChild(self.progressBar); 95 | uploadButton.removeAttribute("disabled"); 96 | }, 97 | failure: function(error) { 98 | self.theme.addInputError(self.uploader, error); 99 | if (self.progressBar) self.preview.removeChild(self.progressBar); 100 | uploadButton.removeAttribute("disabled"); 101 | }, 102 | updateProgress: function(progress) { 103 | if (self.progressBar) { 104 | if (progress) self.theme.updateProgressBar(self.progressBar, progress); 105 | else self.theme.updateProgressBarUnknown(self.progressBar); 106 | } 107 | } 108 | }); 109 | }); 110 | }, 111 | enable: function() { 112 | if(this.uploader) this.uploader.disabled = false; 113 | this._super(); 114 | }, 115 | disable: function() { 116 | if(this.uploader) this.uploader.disabled = true; 117 | this._super(); 118 | }, 119 | setValue: function(val) { 120 | if(this.value !== val) { 121 | this.value = val; 122 | this.input.value = this.value; 123 | this.onChange(); 124 | } 125 | }, 126 | destroy: function() { 127 | if(this.preview && this.preview.parentNode) this.preview.parentNode.removeChild(this.preview); 128 | if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); 129 | if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); 130 | if(this.uploader && this.uploader.parentNode) this.uploader.parentNode.removeChild(this.uploader); 131 | 132 | this._super(); 133 | } 134 | }); 135 | -------------------------------------------------------------------------------- /src/themes/jqueryui.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.themes.jqueryui = JSONEditor.AbstractTheme.extend({ 2 | getTable: function() { 3 | var el = this._super(); 4 | el.setAttribute('cellpadding',5); 5 | el.setAttribute('cellspacing',0); 6 | return el; 7 | }, 8 | getTableHeaderCell: function(text) { 9 | var el = this._super(text); 10 | el.className = 'ui-state-active'; 11 | el.style.fontWeight = 'bold'; 12 | return el; 13 | }, 14 | getTableCell: function() { 15 | var el = this._super(); 16 | el.className = 'ui-widget-content'; 17 | return el; 18 | }, 19 | getHeaderButtonHolder: function() { 20 | var el = this.getButtonHolder(); 21 | el.style.marginLeft = '10px'; 22 | el.style.fontSize = '.6em'; 23 | el.style.display = 'inline-block'; 24 | return el; 25 | }, 26 | getFormInputDescription: function(text) { 27 | var el = this.getDescription(text); 28 | el.style.marginLeft = '10px'; 29 | el.style.display = 'inline-block'; 30 | return el; 31 | }, 32 | getFormControl: function(label, input, description) { 33 | var el = this._super(label,input,description); 34 | if(input.type === 'checkbox') { 35 | el.style.lineHeight = '25px'; 36 | 37 | el.style.padding = '3px 0'; 38 | } 39 | else { 40 | el.style.padding = '4px 0 8px 0'; 41 | } 42 | return el; 43 | }, 44 | getDescription: function(text) { 45 | var el = document.createElement('span'); 46 | el.style.fontSize = '.8em'; 47 | el.style.fontStyle = 'italic'; 48 | el.textContent = text; 49 | return el; 50 | }, 51 | getButtonHolder: function() { 52 | var el = document.createElement('div'); 53 | el.className = 'ui-buttonset'; 54 | el.style.fontSize = '.7em'; 55 | return el; 56 | }, 57 | getFormInputLabel: function(text, required) { 58 | var el = document.createElement('label'); 59 | el.style.fontWeight = 'bold'; 60 | el.style.display = 'block'; 61 | if(required)el.className += " required"; 62 | el.textContent = text; 63 | return el; 64 | }, 65 | getButton: function(text, icon, title) { 66 | var button = document.createElement("button"); 67 | button.className = 'ui-button ui-widget ui-state-default ui-corner-all'; 68 | 69 | // Icon only 70 | if(icon && !text) { 71 | button.className += ' ui-button-icon-only'; 72 | icon.className += ' ui-button-icon-primary ui-icon-primary'; 73 | button.appendChild(icon); 74 | } 75 | // Icon and Text 76 | else if(icon) { 77 | button.className += ' ui-button-text-icon-primary'; 78 | icon.className += ' ui-button-icon-primary ui-icon-primary'; 79 | button.appendChild(icon); 80 | } 81 | // Text only 82 | else { 83 | button.className += ' ui-button-text-only'; 84 | } 85 | 86 | var el = document.createElement('span'); 87 | el.className = 'ui-button-text'; 88 | el.textContent = text||title||"."; 89 | button.appendChild(el); 90 | 91 | button.setAttribute('title',title); 92 | 93 | return button; 94 | }, 95 | setButtonText: function(button,text, icon, title) { 96 | button.innerHTML = ''; 97 | button.className = 'ui-button ui-widget ui-state-default ui-corner-all'; 98 | 99 | // Icon only 100 | if(icon && !text) { 101 | button.className += ' ui-button-icon-only'; 102 | icon.className += ' ui-button-icon-primary ui-icon-primary'; 103 | button.appendChild(icon); 104 | } 105 | // Icon and Text 106 | else if(icon) { 107 | button.className += ' ui-button-text-icon-primary'; 108 | icon.className += ' ui-button-icon-primary ui-icon-primary'; 109 | button.appendChild(icon); 110 | } 111 | // Text only 112 | else { 113 | button.className += ' ui-button-text-only'; 114 | } 115 | 116 | var el = document.createElement('span'); 117 | el.className = 'ui-button-text'; 118 | el.textContent = text||title||"."; 119 | button.appendChild(el); 120 | 121 | button.setAttribute('title',title); 122 | }, 123 | getIndentedPanel: function() { 124 | var el = document.createElement('div'); 125 | el.className = 'ui-widget-content ui-corner-all'; 126 | el.style.padding = '1em 1.4em'; 127 | el.style.marginBottom = '20px'; 128 | return el; 129 | }, 130 | afterInputReady: function(input) { 131 | if(input.controls) return; 132 | input.controls = this.closest(input,'.form-control'); 133 | if (this.queuedInputErrorText) { 134 | var text = this.queuedInputErrorText; 135 | delete this.queuedInputErrorText; 136 | this.addInputError(input,text); 137 | } 138 | }, 139 | addInputError: function(input,text) { 140 | if(!input.controls) { 141 | this.queuedInputErrorText = text; 142 | return; 143 | } 144 | if(!input.errmsg) { 145 | input.errmsg = document.createElement('div'); 146 | input.errmsg.className = 'ui-state-error'; 147 | input.controls.appendChild(input.errmsg); 148 | } 149 | else { 150 | input.errmsg.style.display = ''; 151 | } 152 | 153 | input.errmsg.textContent = text; 154 | }, 155 | removeInputError: function(input) { 156 | if(!input.controls) { 157 | delete this.queuedInputErrorText; 158 | } 159 | if(!input.errmsg) return; 160 | input.errmsg.style.display = 'none'; 161 | }, 162 | markTabActive: function(tab) { 163 | tab.className = tab.className.replace(/\s*ui-widget-header/g,'')+' ui-state-active'; 164 | }, 165 | markTabInactive: function(tab) { 166 | tab.className = tab.className.replace(/\s*ui-state-active/g,'')+' ui-widget-header'; 167 | } 168 | }); 169 | -------------------------------------------------------------------------------- /src/themes/bootstrap3.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.themes.bootstrap3 = JSONEditor.AbstractTheme.extend({ 2 | getSelectInput: function(options) { 3 | var el = this._super(options); 4 | el.className += 'form-control'; 5 | //el.style.width = 'auto'; 6 | return el; 7 | }, 8 | setGridColumnSize: function(el,size) { 9 | el.className = 'col-md-'+size; 10 | }, 11 | afterInputReady: function(input) { 12 | if(input.controlgroup) return; 13 | input.controlgroup = this.closest(input,'.form-group'); 14 | if(this.closest(input,'.compact')) { 15 | input.controlgroup.style.marginBottom = 0; 16 | } 17 | if (this.queuedInputErrorText) { 18 | var text = this.queuedInputErrorText; 19 | delete this.queuedInputErrorText; 20 | this.addInputError(input,text); 21 | } 22 | 23 | // TODO: use bootstrap slider 24 | }, 25 | getTextareaInput: function() { 26 | var el = document.createElement('textarea'); 27 | el.className = 'form-control'; 28 | return el; 29 | }, 30 | getRangeInput: function(min, max, step) { 31 | // TODO: use better slider 32 | return this._super(min, max, step); 33 | }, 34 | getFormInputField: function(type) { 35 | var el = this._super(type); 36 | if(type !== 'checkbox') { 37 | el.className += 'form-control'; 38 | } 39 | return el; 40 | }, 41 | getFormControl: function(label, input, description) { 42 | var group = document.createElement('div'); 43 | 44 | if(label && input.type === 'checkbox') { 45 | group.className += ' checkbox'; 46 | label.appendChild(input); 47 | label.style.fontSize = '14px'; 48 | group.style.marginTop = '0'; 49 | group.appendChild(label); 50 | input.style.position = 'relative'; 51 | input.style.cssFloat = 'left'; 52 | } 53 | else { 54 | group.className += ' form-group'; 55 | if(label) { 56 | label.className += ' control-label'; 57 | group.appendChild(label); 58 | } 59 | group.appendChild(input); 60 | } 61 | 62 | if(description) group.appendChild(description); 63 | 64 | return group; 65 | }, 66 | getIndentedPanel: function() { 67 | var el = document.createElement('div'); 68 | el.className = 'well well-sm'; 69 | return el; 70 | }, 71 | getFormInputDescription: function(text) { 72 | var el = document.createElement('p'); 73 | el.className = 'help-block'; 74 | el.innerHTML = text; 75 | return el; 76 | }, 77 | getHeaderButtonHolder: function() { 78 | var el = this.getButtonHolder(); 79 | el.style.marginLeft = '10px'; 80 | return el; 81 | }, 82 | getButtonHolder: function() { 83 | var el = document.createElement('div'); 84 | el.className = 'btn-group'; 85 | return el; 86 | }, 87 | getButton: function(text, icon, title) { 88 | var el = this._super(text, icon, title); 89 | el.className += 'btn btn-default'; 90 | return el; 91 | }, 92 | getTable: function() { 93 | var el = document.createElement('table'); 94 | el.className = 'table table-bordered'; 95 | el.style.width = 'auto'; 96 | el.style.maxWidth = 'none'; 97 | return el; 98 | }, 99 | 100 | addInputError: function(input,text) { 101 | if(!input.controlgroup) { 102 | this.queuedInputErrorText = text; 103 | return; 104 | } 105 | input.controlgroup.className += ' has-error'; 106 | if(!input.errmsg) { 107 | input.errmsg = document.createElement('p'); 108 | input.errmsg.className = 'help-block errormsg'; 109 | input.controlgroup.appendChild(input.errmsg); 110 | } 111 | else { 112 | input.errmsg.style.display = ''; 113 | } 114 | 115 | input.errmsg.textContent = text; 116 | }, 117 | removeInputError: function(input) { 118 | if(!input.controlgroup) { 119 | delete this.queuedInputErrorText; 120 | } 121 | if(!input.errmsg) return; 122 | input.errmsg.style.display = 'none'; 123 | input.controlgroup.className = input.controlgroup.className.replace(/\s?has-error/g,''); 124 | }, 125 | getTabHolder: function() { 126 | var el = document.createElement('div'); 127 | el.innerHTML = "
    "; 128 | el.className = 'rows'; 129 | return el; 130 | }, 131 | getTab: function(text) { 132 | var el = document.createElement('a'); 133 | el.className = 'list-group-item'; 134 | el.setAttribute('href','#'); 135 | el.appendChild(text); 136 | return el; 137 | }, 138 | markTabActive: function(tab) { 139 | tab.className += ' active'; 140 | }, 141 | markTabInactive: function(tab) { 142 | tab.className = tab.className.replace(/\s?active/g,''); 143 | }, 144 | getProgressBar: function() { 145 | var min = 0, max = 100, start = 0; 146 | 147 | var container = document.createElement('div'); 148 | container.className = 'progress'; 149 | 150 | var bar = document.createElement('div'); 151 | bar.className = 'progress-bar'; 152 | bar.setAttribute('role', 'progressbar'); 153 | bar.setAttribute('aria-valuenow', start); 154 | bar.setAttribute('aria-valuemin', min); 155 | bar.setAttribute('aria-valuenax', max); 156 | bar.innerHTML = start + "%"; 157 | container.appendChild(bar); 158 | 159 | return container; 160 | }, 161 | updateProgressBar: function(progressBar, progress) { 162 | if (!progressBar) return; 163 | 164 | var bar = progressBar.firstChild; 165 | var percentage = progress + "%"; 166 | bar.setAttribute('aria-valuenow', progress); 167 | bar.style.width = percentage; 168 | bar.innerHTML = percentage; 169 | }, 170 | updateProgressBarUnknown: function(progressBar) { 171 | if (!progressBar) return; 172 | 173 | var bar = progressBar.firstChild; 174 | progressBar.className = 'progress progress-striped active'; 175 | bar.removeAttribute('aria-valuenow'); 176 | bar.style.width = '100%'; 177 | bar.innerHTML = ''; 178 | } 179 | }); 180 | -------------------------------------------------------------------------------- /src/themes/bootstrap2.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.themes.bootstrap2 = JSONEditor.AbstractTheme.extend({ 2 | getRangeInput: function(min, max, step) { 3 | // TODO: use bootstrap slider 4 | return this._super(min, max, step); 5 | }, 6 | getGridContainer: function() { 7 | var el = document.createElement('div'); 8 | el.className = 'container-fluid'; 9 | return el; 10 | }, 11 | getGridRow: function() { 12 | var el = document.createElement('div'); 13 | el.className = 'row-fluid'; 14 | return el; 15 | }, 16 | getFormInputLabel: function(text, required) { 17 | var el = this._super(text); 18 | el.style.display = 'inline-block'; 19 | el.style.fontWeight = 'bold'; 20 | if(required)el.className += " required"; 21 | return el; 22 | }, 23 | setGridColumnSize: function(el,size) { 24 | el.className = 'span'+size; 25 | }, 26 | getSelectInput: function(options) { 27 | var input = this._super(options); 28 | input.style.width = 'auto'; 29 | input.style.maxWidth = '98%'; 30 | return input; 31 | }, 32 | getFormInputField: function(type) { 33 | var el = this._super(type); 34 | el.style.width = '98%'; 35 | return el; 36 | }, 37 | afterInputReady: function(input) { 38 | if(input.controlgroup) return; 39 | input.controlgroup = this.closest(input,'.control-group'); 40 | input.controls = this.closest(input,'.controls'); 41 | if(this.closest(input,'.compact')) { 42 | input.controlgroup.className = input.controlgroup.className.replace(/control-group/g,'').replace(/[ ]{2,}/g,' '); 43 | input.controls.className = input.controlgroup.className.replace(/controls/g,'').replace(/[ ]{2,}/g,' '); 44 | input.style.marginBottom = 0; 45 | } 46 | if (this.queuedInputErrorText) { 47 | var text = this.queuedInputErrorText; 48 | delete this.queuedInputErrorText; 49 | this.addInputError(input,text); 50 | } 51 | 52 | // TODO: use bootstrap slider 53 | }, 54 | getIndentedPanel: function() { 55 | var el = document.createElement('div'); 56 | el.className = 'well well-small'; 57 | return el; 58 | }, 59 | getFormInputDescription: function(text) { 60 | var el = document.createElement('p'); 61 | el.className = 'help-inline'; 62 | el.textContent = text; 63 | return el; 64 | }, 65 | getFormControl: function(label, input, description) { 66 | var ret = document.createElement('div'); 67 | ret.className = 'control-group'; 68 | 69 | var controls = document.createElement('div'); 70 | controls.className = 'controls'; 71 | 72 | if(label && input.getAttribute('type') === 'checkbox') { 73 | ret.appendChild(controls); 74 | label.className += ' checkbox'; 75 | label.appendChild(input); 76 | controls.appendChild(label); 77 | controls.style.height = '30px'; 78 | } 79 | else { 80 | if(label) { 81 | label.className += ' control-label'; 82 | ret.appendChild(label); 83 | } 84 | controls.appendChild(input); 85 | ret.appendChild(controls); 86 | } 87 | 88 | if(description) controls.appendChild(description); 89 | 90 | return ret; 91 | }, 92 | getHeaderButtonHolder: function() { 93 | var el = this.getButtonHolder(); 94 | el.style.marginLeft = '10px'; 95 | return el; 96 | }, 97 | getButtonHolder: function() { 98 | var el = document.createElement('div'); 99 | el.className = 'btn-group'; 100 | return el; 101 | }, 102 | getButton: function(text, icon, title) { 103 | var el = this._super(text, icon, title); 104 | el.className += ' btn btn-default'; 105 | return el; 106 | }, 107 | getTable: function() { 108 | var el = document.createElement('table'); 109 | el.className = 'table table-bordered'; 110 | el.style.width = 'auto'; 111 | el.style.maxWidth = 'none'; 112 | return el; 113 | }, 114 | addInputError: function(input,text) { 115 | if(!input.controlgroup) { 116 | this.queuedInputErrorText = text; 117 | return; 118 | } 119 | if(!input.controlgroup || !input.controls) return; 120 | input.controlgroup.className += ' error'; 121 | if(!input.errmsg) { 122 | input.errmsg = document.createElement('p'); 123 | input.errmsg.className = 'help-block errormsg'; 124 | input.controls.appendChild(input.errmsg); 125 | } 126 | else { 127 | input.errmsg.style.display = ''; 128 | } 129 | 130 | input.errmsg.textContent = text; 131 | }, 132 | removeInputError: function(input) { 133 | if(!input.controlgroup) { 134 | delete this.queuedInputErrorText; 135 | } 136 | if(!input.errmsg) return; 137 | input.errmsg.style.display = 'none'; 138 | input.controlgroup.className = input.controlgroup.className.replace(/\s?error/g,''); 139 | }, 140 | getTabHolder: function() { 141 | var el = document.createElement('div'); 142 | el.className = 'tabbable tabs-left'; 143 | el.innerHTML = "
    "; 144 | return el; 145 | }, 146 | getTab: function(text) { 147 | var el = document.createElement('li'); 148 | var a = document.createElement('a'); 149 | a.setAttribute('href','#'); 150 | a.appendChild(text); 151 | el.appendChild(a); 152 | return el; 153 | }, 154 | getTabContentHolder: function(tab_holder) { 155 | return tab_holder.children[1]; 156 | }, 157 | getTabContent: function() { 158 | var el = document.createElement('div'); 159 | el.className = 'tab-pane active'; 160 | return el; 161 | }, 162 | markTabActive: function(tab) { 163 | tab.className += ' active'; 164 | }, 165 | markTabInactive: function(tab) { 166 | tab.className = tab.className.replace(/\s?active/g,''); 167 | }, 168 | addTab: function(holder, tab) { 169 | holder.children[0].appendChild(tab); 170 | }, 171 | getProgressBar: function() { 172 | var container = document.createElement('div'); 173 | container.className = 'progress'; 174 | 175 | var bar = document.createElement('div'); 176 | bar.className = 'bar'; 177 | bar.style.width = '0%'; 178 | container.appendChild(bar); 179 | 180 | return container; 181 | }, 182 | updateProgressBar: function(progressBar, progress) { 183 | if (!progressBar) return; 184 | 185 | progressBar.firstChild.style.width = progress + "%"; 186 | }, 187 | updateProgressBarUnknown: function(progressBar) { 188 | if (!progressBar) return; 189 | 190 | progressBar.className = 'progress progress-striped active'; 191 | progressBar.firstChild.style.width = '100%'; 192 | } 193 | }); 194 | -------------------------------------------------------------------------------- /src/editors/multiselect.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.editors.multiselect = JSONEditor.AbstractEditor.extend({ 2 | preBuild: function() { 3 | this._super(); 4 | 5 | this.select_options = {}; 6 | this.select_values = {}; 7 | 8 | var items_schema = this.jsoneditor.expandRefs(this.schema.items || {}); 9 | 10 | var e = items_schema["enum"] || []; 11 | this.option_keys = []; 12 | for(i=0; i 2 | 3 | 4 | 5 | Recursive JSON Editor Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
    22 |
    23 |
    24 |

    Recursive JSON Editor Example

    25 | 26 |

    27 | This example demonstrates the many ways you can use recursive schemas (aka self-referential or circular schemas). 28 |

    29 |
      30 |
    • Within array items as long as minLength is 0. See "coworkers" below.
    • 31 |
    • In non-default properties. See "mother" below (click the "object properties" button and check "mother")
    • 32 |
    • In oneOf as long as it's not the 1st choice. See "bestFriend" below.
    • 33 |
    • In patternProperties. Try adding the property "cousin_1" using the "object properties" button.
    • 34 |
    35 |
    36 |
    37 |
    38 |
    39 | 40 | 41 | 42 | 43 |
    44 |
    45 |
    46 |
    47 |
    48 |
    49 |
    50 |
    51 | 52 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /src/themes/foundation.js: -------------------------------------------------------------------------------- 1 | // Base Foundation theme 2 | JSONEditor.defaults.themes.foundation = JSONEditor.AbstractTheme.extend({ 3 | getChildEditorHolder: function() { 4 | var el = document.createElement('div'); 5 | el.style.marginBottom = '15px'; 6 | return el; 7 | }, 8 | getSelectInput: function(options) { 9 | var el = this._super(options); 10 | el.style.minWidth = 'none'; 11 | el.style.padding = '5px'; 12 | el.style.marginTop = '3px'; 13 | return el; 14 | }, 15 | getSwitcher: function(options) { 16 | var el = this._super(options); 17 | el.style.paddingRight = '8px'; 18 | return el; 19 | }, 20 | afterInputReady: function(input) { 21 | if(input.group) return; 22 | if(this.closest(input,'.compact')) { 23 | input.style.marginBottom = 0; 24 | } 25 | input.group = this.closest(input,'.form-control'); 26 | if (this.queuedInputErrorText) { 27 | var text = this.queuedInputErrorText; 28 | delete this.queuedInputErrorText; 29 | this.addInputError(input,text); 30 | } 31 | }, 32 | getFormInputLabel: function(text, required) { 33 | var el = this._super(text); 34 | el.style.display = 'inline-block'; 35 | if(required)el.className += " required"; 36 | return el; 37 | }, 38 | getFormInputField: function(type) { 39 | var el = this._super(type); 40 | el.style.width = '100%'; 41 | el.style.marginBottom = type==='checkbox'? '0' : '12px'; 42 | return el; 43 | }, 44 | getFormInputDescription: function(text) { 45 | var el = document.createElement('p'); 46 | el.textContent = text; 47 | el.style.marginTop = '-10px'; 48 | el.style.fontStyle = 'italic'; 49 | return el; 50 | }, 51 | getIndentedPanel: function() { 52 | var el = document.createElement('div'); 53 | el.className = 'panel'; 54 | return el; 55 | }, 56 | getHeaderButtonHolder: function() { 57 | var el = this.getButtonHolder(); 58 | el.style.display = 'inline-block'; 59 | el.style.marginLeft = '10px'; 60 | el.style.verticalAlign = 'middle'; 61 | return el; 62 | }, 63 | getButtonHolder: function() { 64 | var el = document.createElement('div'); 65 | el.className = 'button-group'; 66 | return el; 67 | }, 68 | getButton: function(text, icon, title) { 69 | var el = this._super(text, icon, title); 70 | el.className += ' small button'; 71 | return el; 72 | }, 73 | addInputError: function(input,text) { 74 | if(!input.group) { 75 | this.queuedInputErrorText = text; 76 | return; 77 | } 78 | input.group.className += ' error'; 79 | 80 | if(!input.errmsg) { 81 | input.insertAdjacentHTML('afterend',''); 82 | input.errmsg = input.parentNode.getElementsByClassName('error')[0]; 83 | } 84 | else { 85 | input.errmsg.style.display = ''; 86 | } 87 | 88 | input.errmsg.textContent = text; 89 | }, 90 | removeInputError: function(input) { 91 | if(!input.group) { 92 | delete this.queuedInputErrorText; 93 | } 94 | if(!input.errmsg) return; 95 | input.group.className = input.group.className.replace(/ error/g,''); 96 | input.errmsg.style.display = 'none'; 97 | }, 98 | getProgressBar: function() { 99 | var progressBar = document.createElement('div'); 100 | progressBar.className = 'progress'; 101 | 102 | var meter = document.createElement('span'); 103 | meter.className = 'meter'; 104 | meter.style.width = '0%'; 105 | progressBar.appendChild(meter); 106 | return progressBar; 107 | }, 108 | updateProgressBar: function(progressBar, progress) { 109 | if (!progressBar) return; 110 | progressBar.firstChild.style.width = progress + '%'; 111 | }, 112 | updateProgressBarUnknown: function(progressBar) { 113 | if (!progressBar) return; 114 | progressBar.firstChild.style.width = '100%'; 115 | } 116 | }); 117 | 118 | // Foundation 3 Specific Theme 119 | JSONEditor.defaults.themes.foundation3 = JSONEditor.defaults.themes.foundation.extend({ 120 | getHeaderButtonHolder: function() { 121 | var el = this._super(); 122 | el.style.fontSize = '.6em'; 123 | return el; 124 | }, 125 | getFormInputLabel: function(text, required) { 126 | var el = this._super(text); 127 | el.style.fontWeight = 'bold'; 128 | if(required)el.className += " required"; 129 | return el; 130 | }, 131 | getTabHolder: function() { 132 | var el = document.createElement('div'); 133 | el.className = 'row'; 134 | el.innerHTML = "
    "; 135 | return el; 136 | }, 137 | setGridColumnSize: function(el,size) { 138 | var sizes = ['zero','one','two','three','four','five','six','seven','eight','nine','ten','eleven','twelve']; 139 | el.className = 'columns '+sizes[size]; 140 | }, 141 | getTab: function(text) { 142 | var el = document.createElement('dd'); 143 | var a = document.createElement('a'); 144 | a.setAttribute('href','#'); 145 | a.appendChild(text); 146 | el.appendChild(a); 147 | return el; 148 | }, 149 | getTabContentHolder: function(tab_holder) { 150 | return tab_holder.children[1]; 151 | }, 152 | getTabContent: function() { 153 | var el = document.createElement('div'); 154 | el.className = 'content active'; 155 | el.style.paddingLeft = '5px'; 156 | return el; 157 | }, 158 | markTabActive: function(tab) { 159 | tab.className += ' active'; 160 | }, 161 | markTabInactive: function(tab) { 162 | tab.className = tab.className.replace(/\s*active/g,''); 163 | }, 164 | addTab: function(holder, tab) { 165 | holder.children[0].appendChild(tab); 166 | } 167 | }); 168 | 169 | // Foundation 4 Specific Theme 170 | JSONEditor.defaults.themes.foundation4 = JSONEditor.defaults.themes.foundation.extend({ 171 | getHeaderButtonHolder: function() { 172 | var el = this._super(); 173 | el.style.fontSize = '.6em'; 174 | return el; 175 | }, 176 | setGridColumnSize: function(el,size) { 177 | el.className = 'columns large-'+size; 178 | }, 179 | getFormInputDescription: function(text) { 180 | var el = this._super(text); 181 | el.style.fontSize = '.8rem'; 182 | return el; 183 | }, 184 | getFormInputLabel: function(text, required) { 185 | var el = this._super(text); 186 | el.style.fontWeight = 'bold'; 187 | if(required)el.className += " required"; 188 | return el; 189 | } 190 | }); 191 | 192 | // Foundation 5 Specific Theme 193 | JSONEditor.defaults.themes.foundation5 = JSONEditor.defaults.themes.foundation.extend({ 194 | getFormInputDescription: function(text) { 195 | var el = this._super(text); 196 | el.style.fontSize = '.8rem'; 197 | return el; 198 | }, 199 | setGridColumnSize: function(el,size) { 200 | el.className = 'columns medium-'+size; 201 | }, 202 | getButton: function(text, icon, title) { 203 | var el = this._super(text,icon,title); 204 | el.className = el.className.replace(/\s*small/g,'') + ' tiny'; 205 | return el; 206 | }, 207 | getTabHolder: function() { 208 | var el = document.createElement('div'); 209 | el.innerHTML = "
    "; 210 | return el; 211 | }, 212 | getTab: function(text) { 213 | var el = document.createElement('dd'); 214 | var a = document.createElement('a'); 215 | a.setAttribute('href','#'); 216 | a.appendChild(text); 217 | el.appendChild(a); 218 | return el; 219 | }, 220 | getTabContentHolder: function(tab_holder) { 221 | return tab_holder.children[1]; 222 | }, 223 | getTabContent: function() { 224 | var el = document.createElement('div'); 225 | el.className = 'content active'; 226 | el.style.paddingLeft = '5px'; 227 | return el; 228 | }, 229 | markTabActive: function(tab) { 230 | tab.className += ' active'; 231 | }, 232 | markTabInactive: function(tab) { 233 | tab.className = tab.className.replace(/\s*active/g,''); 234 | }, 235 | addTab: function(holder, tab) { 236 | holder.children[0].appendChild(tab); 237 | } 238 | }); 239 | -------------------------------------------------------------------------------- /src/editors/multiple.js: -------------------------------------------------------------------------------- 1 | // Multiple Editor (for when `type` is an array, also when `oneOf` is present) 2 | JSONEditor.defaults.editors.multiple = JSONEditor.AbstractEditor.extend({ 3 | register: function() { 4 | if(this.editors) { 5 | for(var i=0; i= 0) { 245 | return 'multiselect'; 246 | } 247 | }); 248 | // Use the multiple editor for schemas with `oneOf` set 249 | JSONEditor.defaults.resolvers.unshift(function(schema) { 250 | // If this schema uses `oneOf` 251 | if(schema.oneOf) return "multiple"; 252 | }); 253 | -------------------------------------------------------------------------------- /src/theme.js: -------------------------------------------------------------------------------- 1 | var matchKey = (function () { 2 | var elem = document.documentElement; 3 | 4 | if (elem.matches) return 'matches'; 5 | else if (elem.webkitMatchesSelector) return 'webkitMatchesSelector'; 6 | else if (elem.mozMatchesSelector) return 'mozMatchesSelector'; 7 | else if (elem.msMatchesSelector) return 'msMatchesSelector'; 8 | else if (elem.oMatchesSelector) return 'oMatchesSelector'; 9 | })(); 10 | 11 | JSONEditor.AbstractTheme = Class.extend({ 12 | getContainer: function() { 13 | return document.createElement('div'); 14 | }, 15 | getFloatRightLinkHolder: function() { 16 | var el = document.createElement('div'); 17 | el.style = el.style || {}; 18 | el.style.cssFloat = 'right'; 19 | el.style.marginLeft = '10px'; 20 | return el; 21 | }, 22 | getModal: function() { 23 | var el = document.createElement('div'); 24 | el.style.backgroundColor = 'white'; 25 | el.style.border = '1px solid black'; 26 | el.style.boxShadow = '3px 3px black'; 27 | el.style.position = 'absolute'; 28 | el.style.zIndex = '10'; 29 | el.style.display = 'none'; 30 | return el; 31 | }, 32 | getGridContainer: function() { 33 | var el = document.createElement('div'); 34 | return el; 35 | }, 36 | getGridRow: function() { 37 | var el = document.createElement('div'); 38 | el.className = 'row'; 39 | return el; 40 | }, 41 | getGridColumn: function() { 42 | var el = document.createElement('div'); 43 | return el; 44 | }, 45 | setGridColumnSize: function(el,size) { 46 | 47 | }, 48 | getLink: function(text) { 49 | var el = document.createElement('a'); 50 | el.setAttribute('href','#'); 51 | el.appendChild(document.createTextNode(text)); 52 | return el; 53 | }, 54 | disableHeader: function(header) { 55 | header.style.color = '#ccc'; 56 | }, 57 | disableLabel: function(label) { 58 | label.style.color = '#ccc'; 59 | }, 60 | enableHeader: function(header) { 61 | header.style.color = ''; 62 | }, 63 | enableLabel: function(label) { 64 | label.style.color = ''; 65 | }, 66 | getFormInputLabel: function(text) { 67 | var el = document.createElement('label'); 68 | el.appendChild(document.createTextNode(text)); 69 | return el; 70 | }, 71 | getCheckboxLabel: function(text) { 72 | var el = this.getFormInputLabel(text); 73 | el.style.fontWeight = 'normal'; 74 | return el; 75 | }, 76 | getHeader: function(text, required) { 77 | var el = document.createElement('h3'); 78 | if(typeof text === "string") { 79 | el.textContent = text; 80 | } 81 | else { 82 | el.appendChild(text); 83 | } 84 | 85 | if(required)el.className += " required"; 86 | 87 | return el; 88 | }, 89 | getCheckbox: function() { 90 | var el = this.getFormInputField('checkbox'); 91 | el.style.display = 'inline-block'; 92 | el.style.width = 'auto'; 93 | return el; 94 | }, 95 | getMultiCheckboxHolder: function(controls,label,description) { 96 | var el = document.createElement('div'); 97 | 98 | if(label) { 99 | label.style.display = 'block'; 100 | el.appendChild(label); 101 | } 102 | 103 | for(var i in controls) { 104 | if(!controls.hasOwnProperty(i)) continue; 105 | controls[i].style.display = 'inline-block'; 106 | controls[i].style.marginRight = '20px'; 107 | el.appendChild(controls[i]); 108 | } 109 | 110 | if(description) el.appendChild(description); 111 | 112 | return el; 113 | }, 114 | getSelectInput: function(options) { 115 | var select = document.createElement('select'); 116 | if(options) this.setSelectOptions(select, options); 117 | return select; 118 | }, 119 | getSwitcher: function(options) { 120 | var switcher = this.getSelectInput(options); 121 | switcher.style.backgroundColor = 'transparent'; 122 | switcher.style.height = 'auto'; 123 | switcher.style.fontStyle = 'italic'; 124 | switcher.style.fontWeight = 'normal'; 125 | switcher.style.padding = '0 0 0 3px'; 126 | return switcher; 127 | }, 128 | getSwitcherOptions: function(switcher) { 129 | return switcher.getElementsByTagName('option'); 130 | }, 131 | setSwitcherOptions: function(switcher, options, titles) { 132 | this.setSelectOptions(switcher, options, titles); 133 | }, 134 | setSelectOptions: function(select, options, titles) { 135 | titles = titles || []; 136 | select.innerHTML = ''; 137 | for(var i=0; i 2 || (this.enum_options.length && this.enumSource))) { 195 | var options = $extend({},JSONEditor.plugins.select2); 196 | if(this.schema.options && this.schema.options.select2_options) options = $extend(options,this.schema.options.select2_options); 197 | this.select2 = window.jQuery(this.input).select2(options); 198 | var self = this; 199 | this.select2.on('select2-blur',function() { 200 | self.input.value = self.select2.select2('val'); 201 | self.onInputChange(); 202 | }); 203 | } 204 | else { 205 | this.select2 = null; 206 | } 207 | }, 208 | postBuild: function() { 209 | this._super(); 210 | this.theme.afterInputReady(this.input); 211 | this.setupSelect2(); 212 | }, 213 | onWatchedFieldChange: function() { 214 | var self = this, vars, j; 215 | 216 | // If this editor uses a dynamic select box 217 | if(this.enumSource) { 218 | vars = this.getWatchedFieldValues(); 219 | var select_options = []; 220 | var select_titles = []; 221 | 222 | for(var i=0; i=0) { 198 | holder = this.theme.getBlockLinkHolder(); 199 | 200 | link = this.theme.getBlockLink(); 201 | link.setAttribute('target','_blank'); 202 | 203 | var media = document.createElement(type); 204 | media.setAttribute('controls','controls'); 205 | 206 | this.theme.createMediaLink(holder,link,media); 207 | 208 | // When a watched field changes, update the url 209 | this.link_watchers.push(function(vars) { 210 | var url = href(vars); 211 | link.setAttribute('href',url); 212 | link.textContent = data.rel || url; 213 | media.setAttribute('src',url); 214 | }); 215 | } 216 | // Text links 217 | else { 218 | holder = this.theme.getBlockLink(); 219 | holder.setAttribute('target','_blank'); 220 | holder.textContent = data.rel; 221 | 222 | // When a watched field changes, update the url 223 | this.link_watchers.push(function(vars) { 224 | var url = href(vars); 225 | holder.setAttribute('href',url); 226 | holder.textContent = data.rel || url; 227 | }); 228 | } 229 | 230 | return holder; 231 | }, 232 | refreshWatchedFieldValues: function() { 233 | if(!this.watched_values) return; 234 | var watched = {}; 235 | var changed = false; 236 | var self = this; 237 | 238 | if(this.watched) { 239 | var val,editor; 240 | for(var name in this.watched) { 241 | if(!this.watched.hasOwnProperty(name)) continue; 242 | editor = self.jsoneditor.getEditor(this.watched[name]); 243 | val = editor? editor.getValue() : null; 244 | if(self.watched_values[name] !== val) changed = true; 245 | watched[name] = val; 246 | } 247 | } 248 | 249 | watched.self = this.getValue(); 250 | if(this.watched_values.self !== watched.self) changed = true; 251 | 252 | this.watched_values = watched; 253 | 254 | return changed; 255 | }, 256 | getWatchedFieldValues: function() { 257 | return this.watched_values; 258 | }, 259 | updateHeaderText: function() { 260 | if(this.header) { 261 | // If the header has children, only update the text node's value 262 | if(this.header.children.length) { 263 | for(var i=0; i -1; 374 | else if(this.jsoneditor.options.required_by_default) return true; 375 | else return false; 376 | }, 377 | getDisplayText: function(arr) { 378 | var disp = []; 379 | var used = {}; 380 | 381 | // Determine how many times each attribute name is used. 382 | // This helps us pick the most distinct display text for the schemas. 383 | $each(arr,function(i,el) { 384 | if(el.title) { 385 | used[el.title] = used[el.title] || 0; 386 | used[el.title]++; 387 | } 388 | if(el.description) { 389 | used[el.description] = used[el.description] || 0; 390 | used[el.description]++; 391 | } 392 | if(el.format) { 393 | used[el.format] = used[el.format] || 0; 394 | used[el.format]++; 395 | } 396 | if(el.type) { 397 | used[el.type] = used[el.type] || 0; 398 | used[el.type]++; 399 | } 400 | }); 401 | 402 | // Determine display text for each element of the array 403 | $each(arr,function(i,el) { 404 | var name; 405 | 406 | // If it's a simple string 407 | if(typeof el === "string") name = el; 408 | // Object 409 | else if(el.title && used[el.title]<=1) name = el.title; 410 | else if(el.format && used[el.format]<=1) name = el.format; 411 | else if(el.type && used[el.type]<=1) name = el.type; 412 | else if(el.description && used[el.description]<=1) name = el.descripton; 413 | else if(el.title) name = el.title; 414 | else if(el.format) name = el.format; 415 | else if(el.type) name = el.type; 416 | else if(el.description) name = el.description; 417 | else if(JSON.stringify(el).length < 50) name = JSON.stringify(el); 418 | else name = "type"; 419 | 420 | disp.push(name); 421 | }); 422 | 423 | // Replace identical display text with "text 1", "text 2", etc. 424 | var inc = {}; 425 | $each(disp,function(i,name) { 426 | inc[name] = inc[name] || 0; 427 | inc[name]++; 428 | 429 | if(used[name] > 1) disp[i] = name + " " + inc[name]; 430 | }); 431 | 432 | return disp; 433 | }, 434 | getOption: function(key) { 435 | try { 436 | throw "getOption is deprecated"; 437 | } 438 | catch(e) { 439 | window.console.error(e); 440 | } 441 | 442 | return this.options[key]; 443 | }, 444 | showValidationErrors: function(errors) { 445 | 446 | } 447 | }); 448 | -------------------------------------------------------------------------------- /src/editors/string.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.editors.string = JSONEditor.AbstractEditor.extend({ 2 | register: function() { 3 | this._super(); 4 | if(!this.input) return; 5 | this.input.setAttribute('name',this.formname); 6 | }, 7 | unregister: function() { 8 | this._super(); 9 | if(!this.input) return; 10 | this.input.removeAttribute('name'); 11 | }, 12 | setValue: function(value,initial,from_template) { 13 | var self = this; 14 | 15 | if(this.template && !from_template) { 16 | return; 17 | } 18 | 19 | if(value === null) value = ""; 20 | else if(typeof value === "object") value = JSON.stringify(value); 21 | else if(typeof value !== "string") value = ""+value; 22 | 23 | if(value === this.serialized) return; 24 | 25 | // Sanitize value before setting it 26 | var sanitized = this.sanitize(value); 27 | 28 | if(this.input.value === sanitized) { 29 | return; 30 | } 31 | 32 | this.input.value = sanitized; 33 | 34 | // If using SCEditor, update the WYSIWYG 35 | if(this.sceditor_instance) { 36 | this.sceditor_instance.val(sanitized); 37 | } 38 | else if(this.epiceditor) { 39 | this.epiceditor.importFile(null,sanitized); 40 | } 41 | else if(this.ace_editor) { 42 | this.ace_editor.setValue(sanitized); 43 | } 44 | 45 | var changed = from_template || this.getValue() !== value; 46 | 47 | this.refreshValue(); 48 | 49 | if(initial) this.is_dirty = false; 50 | else if(this.jsoneditor.options.show_errors === "change") this.is_dirty = true; 51 | 52 | if(this.adjust_height) this.adjust_height(this.input); 53 | 54 | // Bubble this setValue to parents if the value changed 55 | this.onChange(changed); 56 | }, 57 | getNumColumns: function() { 58 | var min = Math.ceil(Math.max(this.getTitle().length,this.schema.maxLength||0,this.schema.minLength||0)/5); 59 | var num; 60 | 61 | if(this.input_type === 'textarea') num = 6; 62 | else if(['text','email'].indexOf(this.input_type) >= 0) num = 4; 63 | else num = 2; 64 | 65 | return Math.min(12,Math.max(min,num)); 66 | }, 67 | build: function() { 68 | var self = this, i; 69 | if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle(),this.isRequired()); 70 | if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description); 71 | 72 | this.format = this.schema.format; 73 | if(!this.format && this.schema.media && this.schema.media.type) { 74 | this.format = this.schema.media.type.replace(/(^(application|text)\/(x-)?(script\.)?)|(-source$)/g,''); 75 | } 76 | if(!this.format && this.options.default_format) { 77 | this.format = this.options.default_format; 78 | } 79 | if(this.options.format) { 80 | this.format = this.options.format; 81 | } 82 | 83 | // Specific format 84 | if(this.format) { 85 | // Text Area 86 | if(this.format === 'textarea') { 87 | this.input_type = 'textarea'; 88 | this.input = this.theme.getTextareaInput(); 89 | } 90 | // Range Input 91 | else if(this.format === 'range') { 92 | this.input_type = 'range'; 93 | var min = this.schema.minimum || 0; 94 | var max = this.schema.maximum || Math.max(100,min+1); 95 | var step = 1; 96 | if(this.schema.multipleOf) { 97 | if(min%this.schema.multipleOf) min = Math.ceil(min/this.schema.multipleOf)*this.schema.multipleOf; 98 | if(max%this.schema.multipleOf) max = Math.floor(max/this.schema.multipleOf)*this.schema.multipleOf; 99 | step = this.schema.multipleOf; 100 | } 101 | 102 | this.input = this.theme.getRangeInput(min,max,step); 103 | } 104 | // Source Code 105 | else if([ 106 | 'actionscript', 107 | 'batchfile', 108 | 'bbcode', 109 | 'c', 110 | 'c++', 111 | 'cpp', 112 | 'coffee', 113 | 'csharp', 114 | 'css', 115 | 'dart', 116 | 'django', 117 | 'ejs', 118 | 'erlang', 119 | 'golang', 120 | 'handlebars', 121 | 'haskell', 122 | 'haxe', 123 | 'html', 124 | 'ini', 125 | 'jade', 126 | 'java', 127 | 'javascript', 128 | 'json', 129 | 'less', 130 | 'lisp', 131 | 'lua', 132 | 'makefile', 133 | 'markdown', 134 | 'matlab', 135 | 'mysql', 136 | 'objectivec', 137 | 'pascal', 138 | 'perl', 139 | 'pgsql', 140 | 'php', 141 | 'python', 142 | 'r', 143 | 'ruby', 144 | 'sass', 145 | 'scala', 146 | 'scss', 147 | 'smarty', 148 | 'sql', 149 | 'stylus', 150 | 'svg', 151 | 'twig', 152 | 'vbscript', 153 | 'xml', 154 | 'yaml' 155 | ].indexOf(this.format) >= 0 156 | ) { 157 | this.input_type = this.format; 158 | this.source_code = true; 159 | 160 | this.input = this.theme.getTextareaInput(); 161 | } 162 | // HTML5 Input type 163 | else { 164 | this.input_type = this.format; 165 | this.input = this.theme.getFormInputField(this.input_type); 166 | } 167 | } 168 | // Normal text input 169 | else { 170 | this.input_type = 'text'; 171 | this.input = this.theme.getFormInputField(this.input_type); 172 | } 173 | 174 | // minLength, maxLength, and pattern 175 | if(typeof this.schema.maxLength !== "undefined") this.input.setAttribute('maxlength',this.schema.maxLength); 176 | if(typeof this.schema.pattern !== "undefined") this.input.setAttribute('pattern',this.schema.pattern); 177 | else if(typeof this.schema.minLength !== "undefined") this.input.setAttribute('pattern','.{'+this.schema.minLength+',}'); 178 | 179 | if(this.options.compact) { 180 | this.container.className += ' compact'; 181 | } 182 | else { 183 | if(this.options.input_width) this.input.style.width = this.options.input_width; 184 | } 185 | 186 | if(this.schema.readOnly || this.schema.readonly || this.schema.template) { 187 | this.always_disabled = true; 188 | this.input.disabled = true; 189 | } 190 | 191 | this.input 192 | .addEventListener('change',function(e) { 193 | e.preventDefault(); 194 | e.stopPropagation(); 195 | 196 | // Don't allow changing if this field is a template 197 | if(self.schema.template) { 198 | this.value = self.value; 199 | return; 200 | } 201 | 202 | var val = this.value; 203 | 204 | // sanitize value 205 | var sanitized = self.sanitize(val); 206 | if(val !== sanitized) { 207 | this.value = sanitized; 208 | } 209 | 210 | self.is_dirty = true; 211 | 212 | self.refreshValue(); 213 | self.onChange(true); 214 | }); 215 | 216 | if(this.options.input_height) this.input.style.height = this.options.input_height; 217 | if(this.options.expand_height) { 218 | this.adjust_height = function(el) { 219 | if(!el) return; 220 | var i, ch=el.offsetHeight; 221 | // Input too short 222 | if(el.offsetHeight < el.scrollHeight) { 223 | i=0; 224 | while(el.offsetHeight < el.scrollHeight+3) { 225 | if(i>100) break; 226 | i++; 227 | ch++; 228 | el.style.height = ch+'px'; 229 | } 230 | } 231 | else { 232 | i=0; 233 | while(el.offsetHeight >= el.scrollHeight+3) { 234 | if(i>100) break; 235 | i++; 236 | ch--; 237 | el.style.height = ch+'px'; 238 | } 239 | el.style.height = (ch+1)+'px'; 240 | } 241 | }; 242 | 243 | this.input.addEventListener('keyup',function(e) { 244 | self.adjust_height(this); 245 | }); 246 | this.input.addEventListener('change',function(e) { 247 | self.adjust_height(this); 248 | }); 249 | this.adjust_height(); 250 | } 251 | 252 | if(this.format) this.input.setAttribute('data-schemaformat',this.format); 253 | 254 | this.control = this.theme.getFormControl(this.label, this.input, this.description); 255 | this.container.appendChild(this.control); 256 | 257 | // Any special formatting that needs to happen after the input is added to the dom 258 | window.requestAnimationFrame(function() { 259 | // Skip in case the input is only a temporary editor, 260 | // otherwise, in the case of an ace_editor creation, 261 | // it will generate an error trying to append it to the missing parentNode 262 | if(self.input.parentNode) self.afterInputReady(); 263 | if(self.adjust_height) self.adjust_height(self.input); 264 | }); 265 | 266 | // Compile and store the template 267 | if(this.schema.template) { 268 | this.template = this.jsoneditor.compileTemplate(this.schema.template, this.template_engine); 269 | this.refreshValue(); 270 | } 271 | else { 272 | this.refreshValue(); 273 | } 274 | }, 275 | enable: function() { 276 | if(!this.always_disabled) { 277 | this.input.disabled = false; 278 | // TODO: WYSIWYG and Markdown editors 279 | } 280 | this._super(); 281 | }, 282 | disable: function() { 283 | this.input.disabled = true; 284 | // TODO: WYSIWYG and Markdown editors 285 | this._super(); 286 | }, 287 | afterInputReady: function() { 288 | var self = this, options; 289 | 290 | // Code editor 291 | if(this.source_code) { 292 | // WYSIWYG html and bbcode editor 293 | if(this.options.wysiwyg && 294 | ['html','bbcode'].indexOf(this.input_type) >= 0 && 295 | window.jQuery && window.jQuery.fn && window.jQuery.fn.sceditor 296 | ) { 297 | options = $extend({},{ 298 | plugins: self.input_type==='html'? 'xhtml' : 'bbcode', 299 | emoticonsEnabled: false, 300 | width: '100%', 301 | height: 300 302 | },JSONEditor.plugins.sceditor,self.options.sceditor_options||{}); 303 | 304 | window.jQuery(self.input).sceditor(options); 305 | 306 | self.sceditor_instance = window.jQuery(self.input).sceditor('instance'); 307 | 308 | self.sceditor_instance.blur(function() { 309 | // Get editor's value 310 | var val = window.jQuery("
    "+self.sceditor_instance.val()+"
    "); 311 | // Remove sceditor spans/divs 312 | window.jQuery('#sceditor-start-marker,#sceditor-end-marker,.sceditor-nlf',val).remove(); 313 | // Set the value and update 314 | self.input.value = val.html(); 315 | self.value = self.input.value; 316 | self.is_dirty = true; 317 | self.onChange(true); 318 | }); 319 | } 320 | // EpicEditor for markdown (if it's loaded) 321 | else if (this.input_type === 'markdown' && window.EpicEditor) { 322 | this.epiceditor_container = document.createElement('div'); 323 | this.input.parentNode.insertBefore(this.epiceditor_container,this.input); 324 | this.input.style.display = 'none'; 325 | 326 | options = $extend({},JSONEditor.plugins.epiceditor,{ 327 | container: this.epiceditor_container, 328 | clientSideStorage: false 329 | }); 330 | 331 | this.epiceditor = new window.EpicEditor(options).load(); 332 | 333 | this.epiceditor.importFile(null,this.getValue()); 334 | 335 | this.epiceditor.on('update',function() { 336 | var val = self.epiceditor.exportFile(); 337 | self.input.value = val; 338 | self.value = val; 339 | self.is_dirty = true; 340 | self.onChange(true); 341 | }); 342 | } 343 | // ACE editor for everything else 344 | else if(window.ace) { 345 | var mode = this.input_type; 346 | // aliases for c/cpp 347 | if(mode === 'cpp' || mode === 'c++' || mode === 'c') { 348 | mode = 'c_cpp'; 349 | } 350 | 351 | this.ace_container = document.createElement('div'); 352 | this.ace_container.style.width = '100%'; 353 | this.ace_container.style.position = 'relative'; 354 | this.ace_container.style.height = '400px'; 355 | this.input.parentNode.insertBefore(this.ace_container,this.input); 356 | this.input.style.display = 'none'; 357 | this.ace_editor = window.ace.edit(this.ace_container); 358 | 359 | this.ace_editor.setValue(this.getValue()); 360 | 361 | // The theme 362 | if(JSONEditor.plugins.ace.theme) this.ace_editor.setTheme('ace/theme/'+JSONEditor.plugins.ace.theme); 363 | // The mode 364 | mode = window.ace.require("ace/mode/"+mode); 365 | if(mode) this.ace_editor.getSession().setMode(new mode.Mode()); 366 | 367 | // Listen for changes 368 | this.ace_editor.on('change',function() { 369 | var val = self.ace_editor.getValue(); 370 | self.input.value = val; 371 | self.refreshValue(); 372 | self.is_dirty = true; 373 | self.onChange(true); 374 | }); 375 | } 376 | } 377 | 378 | self.theme.afterInputReady(self.input); 379 | }, 380 | refreshValue: function() { 381 | this.value = this.input.value; 382 | if(typeof this.value !== "string") this.value = ''; 383 | this.serialized = this.value; 384 | }, 385 | destroy: function() { 386 | // If using SCEditor, destroy the editor instance 387 | if(this.sceditor_instance) { 388 | this.sceditor_instance.destroy(); 389 | } 390 | else if(this.epiceditor) { 391 | this.epiceditor.unload(); 392 | } 393 | else if(this.ace_editor) { 394 | this.ace_editor.destroy(); 395 | } 396 | 397 | 398 | this.template = null; 399 | if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); 400 | if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label); 401 | if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description); 402 | 403 | this._super(); 404 | }, 405 | /** 406 | * This is overridden in derivative editors 407 | */ 408 | sanitize: function(value) { 409 | return value; 410 | }, 411 | /** 412 | * Re-calculates the value if needed 413 | */ 414 | onWatchedFieldChange: function() { 415 | var self = this, vars, j; 416 | 417 | // If this editor needs to be rendered by a macro template 418 | if(this.template) { 419 | vars = this.getWatchedFieldValues(); 420 | this.setValue(this.template(vars),false,true); 421 | } 422 | 423 | this._super(); 424 | }, 425 | showValidationErrors: function(errors) { 426 | var self = this; 427 | 428 | if(this.jsoneditor.options.show_errors === "always") {} 429 | else if(!this.is_dirty && this.previous_error_setting===this.jsoneditor.options.show_errors) return; 430 | 431 | this.previous_error_setting = this.jsoneditor.options.show_errors; 432 | 433 | var messages = []; 434 | $each(errors,function(i,error) { 435 | if(error.path === self.path) { 436 | messages.push(error.message); 437 | } 438 | }); 439 | 440 | if(messages.length) { 441 | this.theme.addInputError(this.input, messages.join('. ')+'.'); 442 | } 443 | else { 444 | this.theme.removeInputError(this.input); 445 | } 446 | } 447 | }); 448 | -------------------------------------------------------------------------------- /src/editors/table.js: -------------------------------------------------------------------------------- 1 | JSONEditor.defaults.editors.table = JSONEditor.defaults.editors.array.extend({ 2 | register: function() { 3 | this._super(); 4 | if(this.rows) { 5 | for(var i=0; i this.schema.maxItems) { 160 | value = value.slice(0,this.schema.maxItems); 161 | } 162 | 163 | var serialized = JSON.stringify(value); 164 | if(serialized === this.serialized) return; 165 | 166 | var numrows_changed = false; 167 | 168 | var self = this; 169 | $each(value,function(i,val) { 170 | if(self.rows[i]) { 171 | // TODO: don't set the row's value if it hasn't changed 172 | self.rows[i].setValue(val); 173 | } 174 | else { 175 | self.addRow(val); 176 | numrows_changed = true; 177 | } 178 | }); 179 | 180 | for(var j=value.length; j= this.rows.length; 204 | 205 | var need_row_buttons = false; 206 | $each(this.rows,function(i,editor) { 207 | // Hide the move down button for the last row 208 | if(editor.movedown_button) { 209 | if(i === self.rows.length - 1) { 210 | editor.movedown_button.style.display = 'none'; 211 | } 212 | else { 213 | need_row_buttons = true; 214 | editor.movedown_button.style.display = ''; 215 | } 216 | } 217 | 218 | // Hide the delete button if we have minItems items 219 | if(editor.delete_button) { 220 | if(minItems) { 221 | editor.delete_button.style.display = 'none'; 222 | } 223 | else { 224 | need_row_buttons = true; 225 | editor.delete_button.style.display = ''; 226 | } 227 | } 228 | 229 | if(editor.moveup_button) { 230 | need_row_buttons = true; 231 | } 232 | }); 233 | 234 | // Show/hide controls column in table 235 | $each(this.rows,function(i,editor) { 236 | if(need_row_buttons) { 237 | editor.controls_cell.style.display = ''; 238 | } 239 | else { 240 | editor.controls_cell.style.display = 'none'; 241 | } 242 | }); 243 | if(need_row_buttons) { 244 | this.controls_header_cell.style.display = ''; 245 | } 246 | else { 247 | this.controls_header_cell.style.display = 'none'; 248 | } 249 | 250 | var controls_needed = false; 251 | 252 | if(!this.value.length) { 253 | this.delete_last_row_button.style.display = 'none'; 254 | this.remove_all_rows_button.style.display = 'none'; 255 | this.table.style.display = 'none'; 256 | } 257 | else if(this.value.length === 1 || this.hide_delete_buttons) { 258 | this.table.style.display = ''; 259 | this.remove_all_rows_button.style.display = 'none'; 260 | 261 | // If there are minItems items in the array, hide the delete button beneath the rows 262 | if(minItems || this.hide_delete_buttons) { 263 | this.delete_last_row_button.style.display = 'none'; 264 | } 265 | else { 266 | this.delete_last_row_button.style.display = ''; 267 | controls_needed = true; 268 | } 269 | } 270 | else { 271 | this.table.style.display = ''; 272 | // If there are minItems items in the array, hide the delete button beneath the rows 273 | if(minItems || this.hide_delete_buttons) { 274 | this.delete_last_row_button.style.display = 'none'; 275 | this.remove_all_rows_button.style.display = 'none'; 276 | } 277 | else { 278 | this.delete_last_row_button.style.display = ''; 279 | this.remove_all_rows_button.style.display = ''; 280 | controls_needed = true; 281 | } 282 | } 283 | 284 | // If there are maxItems in the array, hide the add button beneath the rows 285 | if((this.schema.maxItems && this.schema.maxItems <= this.rows.length) || this.hide_add_button) { 286 | this.add_row_button.style.display = 'none'; 287 | } 288 | else { 289 | this.add_row_button.style.display = ''; 290 | controls_needed = true; 291 | } 292 | 293 | if(!controls_needed) { 294 | this.controls.style.display = 'none'; 295 | } 296 | else { 297 | this.controls.style.display = ''; 298 | } 299 | }, 300 | refreshValue: function() { 301 | var self = this; 302 | this.value = []; 303 | 304 | $each(this.rows,function(i,editor) { 305 | // Get the value for this editor 306 | self.value[i] = editor.getValue(); 307 | }); 308 | this.serialized = JSON.stringify(this.value); 309 | }, 310 | addRow: function(value) { 311 | var self = this; 312 | var i = this.rows.length; 313 | 314 | self.rows[i] = this.getElementEditor(i); 315 | 316 | var controls_holder = self.rows[i].table_controls; 317 | 318 | // Buttons to delete row, move row up, and move row down 319 | if(!this.hide_delete_buttons) { 320 | self.rows[i].delete_button = this.getButton('','delete','Delete'); 321 | self.rows[i].delete_button.className += ' delete'; 322 | self.rows[i].delete_button.setAttribute('data-i',i); 323 | self.rows[i].delete_button.addEventListener('click',function(e) { 324 | e.preventDefault(); 325 | e.stopPropagation(); 326 | var i = this.getAttribute('data-i')*1; 327 | 328 | var value = self.getValue(); 329 | 330 | var newval = []; 331 | $each(value,function(j,row) { 332 | if(j===i) return; // If this is the one we're deleting 333 | newval.push(row); 334 | }); 335 | self.setValue(newval); 336 | self.onChange(true); 337 | }); 338 | controls_holder.appendChild(self.rows[i].delete_button); 339 | } 340 | 341 | 342 | if(i && !this.hide_move_buttons) { 343 | self.rows[i].moveup_button = this.getButton('','moveup','Move up'); 344 | self.rows[i].moveup_button.className += ' moveup'; 345 | self.rows[i].moveup_button.setAttribute('data-i',i); 346 | self.rows[i].moveup_button.addEventListener('click',function(e) { 347 | e.preventDefault(); 348 | e.stopPropagation(); 349 | var i = this.getAttribute('data-i')*1; 350 | 351 | if(i<=0) return; 352 | var rows = self.getValue(); 353 | var tmp = rows[i-1]; 354 | rows[i-1] = rows[i]; 355 | rows[i] = tmp; 356 | 357 | self.setValue(rows); 358 | self.onChange(true); 359 | }); 360 | controls_holder.appendChild(self.rows[i].moveup_button); 361 | } 362 | 363 | if(!this.hide_move_buttons) { 364 | self.rows[i].movedown_button = this.getButton('','movedown','Move down'); 365 | self.rows[i].movedown_button.className += ' movedown'; 366 | self.rows[i].movedown_button.setAttribute('data-i',i); 367 | self.rows[i].movedown_button.addEventListener('click',function(e) { 368 | e.preventDefault(); 369 | e.stopPropagation(); 370 | var i = this.getAttribute('data-i')*1; 371 | var rows = self.getValue(); 372 | if(i>=rows.length-1) return; 373 | var tmp = rows[i+1]; 374 | rows[i+1] = rows[i]; 375 | rows[i] = tmp; 376 | 377 | self.setValue(rows); 378 | self.onChange(true); 379 | }); 380 | controls_holder.appendChild(self.rows[i].movedown_button); 381 | } 382 | 383 | if(value) self.rows[i].setValue(value); 384 | }, 385 | addControls: function() { 386 | var self = this; 387 | 388 | this.collapsed = false; 389 | this.toggle_button = this.getButton('','collapse','Collapse'); 390 | if(this.title_controls) { 391 | this.title_controls.appendChild(this.toggle_button); 392 | this.toggle_button.addEventListener('click',function(e) { 393 | e.preventDefault(); 394 | e.stopPropagation(); 395 | 396 | if(self.collapsed) { 397 | self.collapsed = false; 398 | self.panel.style.display = ''; 399 | self.setButtonText(this,'','collapse','Collapse'); 400 | } 401 | else { 402 | self.collapsed = true; 403 | self.panel.style.display = 'none'; 404 | self.setButtonText(this,'','expand','Expand'); 405 | } 406 | }); 407 | 408 | // If it should start collapsed 409 | if(this.options.collapsed) { 410 | $trigger(this.toggle_button,'click'); 411 | } 412 | 413 | // Collapse button disabled 414 | if(this.schema.options && typeof this.schema.options.disable_collapse !== "undefined") { 415 | if(this.schema.options.disable_collapse) this.toggle_button.style.display = 'none'; 416 | } 417 | else if(this.jsoneditor.options.disable_collapse) { 418 | this.toggle_button.style.display = 'none'; 419 | } 420 | } 421 | 422 | // Add "new row" and "delete last" buttons below editor 423 | this.add_row_button = this.getButton(this.getItemTitle(),'add','Add '+this.getItemTitle()); 424 | this.add_row_button.addEventListener('click',function(e) { 425 | e.preventDefault(); 426 | e.stopPropagation(); 427 | 428 | self.addRow(); 429 | self.refreshValue(); 430 | self.refreshRowButtons(); 431 | self.onChange(true); 432 | }); 433 | self.controls.appendChild(this.add_row_button); 434 | 435 | this.delete_last_row_button = this.getButton('Last '+this.getItemTitle(),'delete','Delete Last '+this.getItemTitle()); 436 | this.delete_last_row_button.addEventListener('click',function(e) { 437 | e.preventDefault(); 438 | e.stopPropagation(); 439 | 440 | var rows = self.getValue(); 441 | rows.pop(); 442 | self.setValue(rows); 443 | self.onChange(true); 444 | }); 445 | self.controls.appendChild(this.delete_last_row_button); 446 | 447 | this.remove_all_rows_button = this.getButton('All','delete','Delete All'); 448 | this.remove_all_rows_button.addEventListener('click',function(e) { 449 | e.preventDefault(); 450 | e.stopPropagation(); 451 | 452 | self.setValue([]); 453 | self.onChange(true); 454 | }); 455 | self.controls.appendChild(this.remove_all_rows_button); 456 | } 457 | }); 458 | -------------------------------------------------------------------------------- /src/validator.js: -------------------------------------------------------------------------------- 1 | JSONEditor.Validator = Class.extend({ 2 | init: function(jsoneditor,schema) { 3 | this.jsoneditor = jsoneditor; 4 | this.schema = schema || this.jsoneditor.subschema; 5 | this.options = {}; 6 | this.translate = this.jsoneditor.translate || JSONEditor.defaults.translate; 7 | }, 8 | validate: function(value) { 9 | return this._validateSchema(this.schema, value); 10 | }, 11 | _validateSchema: function(schema,value,path) { 12 | var self = this; 13 | var errors = []; 14 | var valid, i, j; 15 | var stringified = JSON.stringify(value); 16 | 17 | path = path || 'root'; 18 | 19 | // Work on a copy of the schema 20 | schema = $extend({},this.jsoneditor.expandRefs(schema)); 21 | 22 | /* 23 | * Type Agnostic Validation 24 | */ 25 | 26 | // Version 3 `required` 27 | if(schema.required && schema.required === true) { 28 | if(typeof value === "undefined") { 29 | errors.push({ 30 | path: path, 31 | property: 'required', 32 | message: this.translate("error_notset") 33 | }); 34 | 35 | // Can't do any more validation at this point 36 | return errors; 37 | } 38 | } 39 | // Value not defined 40 | else if(typeof value === "undefined") { 41 | // If required_by_default is set, all fields are required 42 | if(this.jsoneditor.options.required_by_default) { 43 | errors.push({ 44 | path: path, 45 | property: 'required', 46 | message: this.translate("error_notset") 47 | }); 48 | } 49 | // Not required, no further validation needed 50 | else { 51 | return errors; 52 | } 53 | } 54 | 55 | // `enum` 56 | if(schema["enum"]) { 57 | valid = false; 58 | for(i=0; i= schema.maximum) { 224 | errors.push({ 225 | path: path, 226 | property: 'maximum', 227 | message: this.translate('error_maximum_excl', [schema.maximum]) 228 | }); 229 | } 230 | else if(!schema.exclusiveMaximum && value > schema.maximum) { 231 | errors.push({ 232 | path: path, 233 | property: 'maximum', 234 | message: this.translate('error_maximum_incl', [schema.maximum]) 235 | }); 236 | } 237 | } 238 | 239 | // `minimum` 240 | if(schema.hasOwnProperty('minimum')) { 241 | if(schema.exclusiveMinimum && value <= schema.minimum) { 242 | errors.push({ 243 | path: path, 244 | property: 'minimum', 245 | message: this.translate('error_minimum_excl', [schema.minimum]) 246 | }); 247 | } 248 | else if(!schema.exclusiveMinimum && value < schema.minimum) { 249 | errors.push({ 250 | path: path, 251 | property: 'minimum', 252 | message: this.translate('error_minimum_incl', [schema.minimum]) 253 | }); 254 | } 255 | } 256 | } 257 | // String specific validation 258 | else if(typeof value === "string") { 259 | // `maxLength` 260 | if(schema.maxLength) { 261 | if((value+"").length > schema.maxLength) { 262 | errors.push({ 263 | path: path, 264 | property: 'maxLength', 265 | message: this.translate('error_maxLength', [schema.maxLength]) 266 | }); 267 | } 268 | } 269 | 270 | // `minLength` 271 | if(schema.minLength) { 272 | if((value+"").length < schema.minLength) { 273 | errors.push({ 274 | path: path, 275 | property: 'minLength', 276 | message: this.translate((schema.minLength===1?'error_notempty':'error_minLength'), [schema.minLength]) 277 | }); 278 | } 279 | } 280 | 281 | // `pattern` 282 | if(schema.pattern) { 283 | if(!(new RegExp(schema.pattern)).test(value)) { 284 | errors.push({ 285 | path: path, 286 | property: 'pattern', 287 | message: this.translate('error_pattern') 288 | }); 289 | } 290 | } 291 | } 292 | // Array specific validation 293 | else if(typeof value === "object" && value !== null && Array.isArray(value)) { 294 | // `items` and `additionalItems` 295 | if(schema.items) { 296 | // `items` is an array 297 | if(Array.isArray(schema.items)) { 298 | for(i=0; i schema.maxItems) { 340 | errors.push({ 341 | path: path, 342 | property: 'maxItems', 343 | message: this.translate('error_maxItems', [schema.maxItems]) 344 | }); 345 | } 346 | } 347 | 348 | // `minItems` 349 | if(schema.minItems) { 350 | if(value.length < schema.minItems) { 351 | errors.push({ 352 | path: path, 353 | property: 'minItems', 354 | message: this.translate('error_minItems', [schema.minItems]) 355 | }); 356 | } 357 | } 358 | 359 | // `uniqueItems` 360 | if(schema.uniqueItems) { 361 | var seen = {}; 362 | for(i=0; i schema.maxProperties) { 386 | errors.push({ 387 | path: path, 388 | property: 'maxProperties', 389 | message: this.translate('error_maxProperties', [schema.maxProperties]) 390 | }); 391 | } 392 | } 393 | 394 | // `minProperties` 395 | if(schema.minProperties) { 396 | valid = 0; 397 | for(i in value) { 398 | if(!value.hasOwnProperty(i)) continue; 399 | valid++; 400 | } 401 | if(valid < schema.minProperties) { 402 | errors.push({ 403 | path: path, 404 | property: 'minProperties', 405 | message: this.translate('error_minProperties', [schema.minProperties]) 406 | }); 407 | } 408 | } 409 | 410 | // Version 4 `required` 411 | if(schema.required && Array.isArray(schema.required)) { 412 | for(i=0; i