Licitator 1.0
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

8221 lines
248 KiB

5 years ago
  1. /*! JSON Editor v0.7.28 - JSON Schema -> HTML Editor
  2. * By Jeremy Dorn - https://github.com/jdorn/json-editor/
  3. * Released under the MIT license
  4. *
  5. * Date: 2016-08-07
  6. */
  7. /**
  8. * See README.md for requirements and usage info
  9. */
  10. (function() {
  11. /*jshint loopfunc: true */
  12. /* Simple JavaScript Inheritance
  13. * By John Resig http://ejohn.org/
  14. * MIT Licensed.
  15. */
  16. // Inspired by base2 and Prototype
  17. var Class;
  18. (function(){
  19. var initializing = false, fnTest = /xyz/.test(function(){window.postMessage("xyz");}) ? /\b_super\b/ : /.*/;
  20. // The base Class implementation (does nothing)
  21. Class = function(){};
  22. // Create a new Class that inherits from this class
  23. Class.extend = function extend(prop) {
  24. var _super = this.prototype;
  25. // Instantiate a base class (but only create the instance,
  26. // don't run the init constructor)
  27. initializing = true;
  28. var prototype = new this();
  29. initializing = false;
  30. // Copy the properties over onto the new prototype
  31. for (var name in prop) {
  32. // Check if we're overwriting an existing function
  33. prototype[name] = typeof prop[name] == "function" &&
  34. typeof _super[name] == "function" && fnTest.test(prop[name]) ?
  35. (function(name, fn){
  36. return function() {
  37. var tmp = this._super;
  38. // Add a new ._super() method that is the same method
  39. // but on the super-class
  40. this._super = _super[name];
  41. // The method only need to be bound temporarily, so we
  42. // remove it when we're done executing
  43. var ret = fn.apply(this, arguments);
  44. this._super = tmp;
  45. return ret;
  46. };
  47. })(name, prop[name]) :
  48. prop[name];
  49. }
  50. // The dummy class constructor
  51. function Class() {
  52. // All construction is actually done in the init method
  53. if ( !initializing && this.init )
  54. this.init.apply(this, arguments);
  55. }
  56. // Populate our constructed prototype object
  57. Class.prototype = prototype;
  58. // Enforce the constructor to be what we expect
  59. Class.prototype.constructor = Class;
  60. // And make this class extendable
  61. Class.extend = extend;
  62. return Class;
  63. };
  64. return Class;
  65. })();
  66. // CustomEvent constructor polyfill
  67. // From MDN
  68. (function () {
  69. function CustomEvent ( event, params ) {
  70. params = params || { bubbles: false, cancelable: false, detail: undefined };
  71. var evt = document.createEvent( 'CustomEvent' );
  72. evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
  73. return evt;
  74. }
  75. CustomEvent.prototype = window.Event.prototype;
  76. window.CustomEvent = CustomEvent;
  77. })();
  78. // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
  79. // MIT license
  80. (function() {
  81. var lastTime = 0;
  82. var vendors = ['ms', 'moz', 'webkit', 'o'];
  83. for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
  84. window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
  85. window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] ||
  86. window[vendors[x]+'CancelRequestAnimationFrame'];
  87. }
  88. if (!window.requestAnimationFrame)
  89. window.requestAnimationFrame = function(callback, element) {
  90. var currTime = new Date().getTime();
  91. var timeToCall = Math.max(0, 16 - (currTime - lastTime));
  92. var id = window.setTimeout(function() { callback(currTime + timeToCall); },
  93. timeToCall);
  94. lastTime = currTime + timeToCall;
  95. return id;
  96. };
  97. if (!window.cancelAnimationFrame)
  98. window.cancelAnimationFrame = function(id) {
  99. clearTimeout(id);
  100. };
  101. }());
  102. // Array.isArray polyfill
  103. // From MDN
  104. (function() {
  105. if(!Array.isArray) {
  106. Array.isArray = function(arg) {
  107. return Object.prototype.toString.call(arg) === '[object Array]';
  108. };
  109. }
  110. }());
  111. /**
  112. * Taken from jQuery 2.1.3
  113. *
  114. * @param obj
  115. * @returns {boolean}
  116. */
  117. var $isplainobject = function( obj ) {
  118. // Not plain objects:
  119. // - Any object or value whose internal [[Class]] property is not "[object Object]"
  120. // - DOM nodes
  121. // - window
  122. if (typeof obj !== "object" || obj.nodeType || (obj !== null && obj === obj.window)) {
  123. return false;
  124. }
  125. if (obj.constructor && !Object.prototype.hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) {
  126. return false;
  127. }
  128. // If the function hasn't returned already, we're confident that
  129. // |obj| is a plain object, created by {} or constructed with new Object
  130. return true;
  131. };
  132. var $extend = function(destination) {
  133. var source, i,property;
  134. for(i=1; i<arguments.length; i++) {
  135. source = arguments[i];
  136. for (property in source) {
  137. if(!source.hasOwnProperty(property)) continue;
  138. if(source[property] && $isplainobject(source[property])) {
  139. if(!destination.hasOwnProperty(property)) destination[property] = {};
  140. $extend(destination[property], source[property]);
  141. }
  142. else {
  143. destination[property] = source[property];
  144. }
  145. }
  146. }
  147. return destination;
  148. };
  149. var $each = function(obj,callback) {
  150. if(!obj || typeof obj !== "object") return;
  151. var i;
  152. if(Array.isArray(obj) || (typeof obj.length === 'number' && obj.length > 0 && (obj.length - 1) in obj)) {
  153. for(i=0; i<obj.length; i++) {
  154. if(callback(i,obj[i])===false) return;
  155. }
  156. }
  157. else {
  158. if (Object.keys) {
  159. var keys = Object.keys(obj);
  160. for(i=0; i<keys.length; i++) {
  161. if(callback(keys[i],obj[keys[i]])===false) return;
  162. }
  163. }
  164. else {
  165. for(i in obj) {
  166. if(!obj.hasOwnProperty(i)) continue;
  167. if(callback(i,obj[i])===false) return;
  168. }
  169. }
  170. }
  171. };
  172. var $trigger = function(el,event) {
  173. var e = document.createEvent('HTMLEvents');
  174. e.initEvent(event, true, true);
  175. el.dispatchEvent(e);
  176. };
  177. var $triggerc = function(el,event) {
  178. var e = new CustomEvent(event,{
  179. bubbles: true,
  180. cancelable: true
  181. });
  182. el.dispatchEvent(e);
  183. };
  184. var JSONEditor = function(element,options) {
  185. if (!(element instanceof Element)) {
  186. throw new Error('element should be an instance of Element');
  187. }
  188. options = $extend({},JSONEditor.defaults.options,options||{});
  189. this.element = element;
  190. this.options = options;
  191. this.init();
  192. };
  193. JSONEditor.prototype = {
  194. // necessary since we remove the ctor property by doing a literal assignment. Without this
  195. // the $isplainobject function will think that this is a plain object.
  196. constructor: JSONEditor,
  197. init: function() {
  198. var self = this;
  199. this.ready = false;
  200. var theme_class = JSONEditor.defaults.themes[this.options.theme || JSONEditor.defaults.theme];
  201. if(!theme_class) throw "Unknown theme " + (this.options.theme || JSONEditor.defaults.theme);
  202. this.schema = this.options.schema;
  203. this.theme = new theme_class();
  204. this.template = this.options.template;
  205. this.refs = this.options.refs || {};
  206. this.uuid = 0;
  207. this.__data = {};
  208. var icon_class = JSONEditor.defaults.iconlibs[this.options.iconlib || JSONEditor.defaults.iconlib];
  209. if(icon_class) this.iconlib = new icon_class();
  210. this.root_container = this.theme.getContainer();
  211. this.element.appendChild(this.root_container);
  212. this.translate = this.options.translate || JSONEditor.defaults.translate;
  213. // Fetch all external refs via ajax
  214. this._loadExternalRefs(this.schema, function() {
  215. self._getDefinitions(self.schema);
  216. // Validator options
  217. var validator_options = {};
  218. if(self.options.custom_validators) {
  219. validator_options.custom_validators = self.options.custom_validators;
  220. }
  221. self.validator = new JSONEditor.Validator(self,null,validator_options);
  222. // Create the root editor
  223. var editor_class = self.getEditorClass(self.schema);
  224. self.root = self.createEditor(editor_class, {
  225. jsoneditor: self,
  226. schema: self.schema,
  227. required: true,
  228. container: self.root_container
  229. });
  230. self.root.preBuild();
  231. self.root.build();
  232. self.root.postBuild();
  233. // Starting data
  234. if(self.options.startval) self.root.setValue(self.options.startval);
  235. self.validation_results = self.validator.validate(self.root.getValue());
  236. self.root.showValidationErrors(self.validation_results);
  237. self.ready = true;
  238. // Fire ready event asynchronously
  239. window.requestAnimationFrame(function() {
  240. if(!self.ready) return;
  241. self.validation_results = self.validator.validate(self.root.getValue());
  242. self.root.showValidationErrors(self.validation_results);
  243. self.trigger('ready');
  244. self.trigger('change');
  245. });
  246. });
  247. },
  248. getValue: function() {
  249. if(!this.ready) throw "JSON Editor not ready yet. Listen for 'ready' event before getting the value";
  250. return this.root.getValue();
  251. },
  252. setValue: function(value) {
  253. if(!this.ready) throw "JSON Editor not ready yet. Listen for 'ready' event before setting the value";
  254. this.root.setValue(value);
  255. return this;
  256. },
  257. validate: function(value) {
  258. if(!this.ready) throw "JSON Editor not ready yet. Listen for 'ready' event before validating";
  259. // Custom value
  260. if(arguments.length === 1) {
  261. return this.validator.validate(value);
  262. }
  263. // Current value (use cached result)
  264. else {
  265. return this.validation_results;
  266. }
  267. },
  268. destroy: function() {
  269. if(this.destroyed) return;
  270. if(!this.ready) return;
  271. this.schema = null;
  272. this.options = null;
  273. this.root.destroy();
  274. this.root = null;
  275. this.root_container = null;
  276. this.validator = null;
  277. this.validation_results = null;
  278. this.theme = null;
  279. this.iconlib = null;
  280. this.template = null;
  281. this.__data = null;
  282. this.ready = false;
  283. this.element.innerHTML = '';
  284. this.destroyed = true;
  285. },
  286. on: function(event, callback) {
  287. this.callbacks = this.callbacks || {};
  288. this.callbacks[event] = this.callbacks[event] || [];
  289. this.callbacks[event].push(callback);
  290. return this;
  291. },
  292. off: function(event, callback) {
  293. // Specific callback
  294. if(event && callback) {
  295. this.callbacks = this.callbacks || {};
  296. this.callbacks[event] = this.callbacks[event] || [];
  297. var newcallbacks = [];
  298. for(var i=0; i<this.callbacks[event].length; i++) {
  299. if(this.callbacks[event][i]===callback) continue;
  300. newcallbacks.push(this.callbacks[event][i]);
  301. }
  302. this.callbacks[event] = newcallbacks;
  303. }
  304. // All callbacks for a specific event
  305. else if(event) {
  306. this.callbacks = this.callbacks || {};
  307. this.callbacks[event] = [];
  308. }
  309. // All callbacks for all events
  310. else {
  311. this.callbacks = {};
  312. }
  313. return this;
  314. },
  315. trigger: function(event) {
  316. if(this.callbacks && this.callbacks[event] && this.callbacks[event].length) {
  317. for(var i=0; i<this.callbacks[event].length; i++) {
  318. this.callbacks[event][i]();
  319. }
  320. }
  321. return this;
  322. },
  323. setOption: function(option, value) {
  324. if(option === "show_errors") {
  325. this.options.show_errors = value;
  326. this.onChange();
  327. }
  328. // Only the `show_errors` option is supported for now
  329. else {
  330. throw "Option "+option+" must be set during instantiation and cannot be changed later";
  331. }
  332. return this;
  333. },
  334. getEditorClass: function(schema) {
  335. var classname;
  336. schema = this.expandSchema(schema);
  337. $each(JSONEditor.defaults.resolvers,function(i,resolver) {
  338. var tmp = resolver(schema);
  339. if(tmp) {
  340. if(JSONEditor.defaults.editors[tmp]) {
  341. classname = tmp;
  342. return false;
  343. }
  344. }
  345. });
  346. if(!classname) throw "Unknown editor for schema "+JSON.stringify(schema);
  347. if(!JSONEditor.defaults.editors[classname]) throw "Unknown editor "+classname;
  348. return JSONEditor.defaults.editors[classname];
  349. },
  350. createEditor: function(editor_class, options) {
  351. options = $extend({},editor_class.options||{},options);
  352. return new editor_class(options);
  353. },
  354. onChange: function() {
  355. if(!this.ready) return;
  356. if(this.firing_change) return;
  357. this.firing_change = true;
  358. var self = this;
  359. window.requestAnimationFrame(function() {
  360. self.firing_change = false;
  361. if(!self.ready) return;
  362. // Validate and cache results
  363. self.validation_results = self.validator.validate(self.root.getValue());
  364. if(self.options.show_errors !== "never") {
  365. self.root.showValidationErrors(self.validation_results);
  366. }
  367. else {
  368. self.root.showValidationErrors([]);
  369. }
  370. // Fire change event
  371. self.trigger('change');
  372. });
  373. return this;
  374. },
  375. compileTemplate: function(template, name) {
  376. name = name || JSONEditor.defaults.template;
  377. var engine;
  378. // Specifying a preset engine
  379. if(typeof name === 'string') {
  380. if(!JSONEditor.defaults.templates[name]) throw "Unknown template engine "+name;
  381. engine = JSONEditor.defaults.templates[name]();
  382. if(!engine) throw "Template engine "+name+" missing required library.";
  383. }
  384. // Specifying a custom engine
  385. else {
  386. engine = name;
  387. }
  388. if(!engine) throw "No template engine set";
  389. if(!engine.compile) throw "Invalid template engine set";
  390. return engine.compile(template);
  391. },
  392. _data: function(el,key,value) {
  393. // Setting data
  394. if(arguments.length === 3) {
  395. var uuid;
  396. if(el.hasAttribute('data-jsoneditor-'+key)) {
  397. uuid = el.getAttribute('data-jsoneditor-'+key);
  398. }
  399. else {
  400. uuid = this.uuid++;
  401. el.setAttribute('data-jsoneditor-'+key,uuid);
  402. }
  403. this.__data[uuid] = value;
  404. }
  405. // Getting data
  406. else {
  407. // No data stored
  408. if(!el.hasAttribute('data-jsoneditor-'+key)) return null;
  409. return this.__data[el.getAttribute('data-jsoneditor-'+key)];
  410. }
  411. },
  412. registerEditor: function(editor) {
  413. this.editors = this.editors || {};
  414. this.editors[editor.path] = editor;
  415. return this;
  416. },
  417. unregisterEditor: function(editor) {
  418. this.editors = this.editors || {};
  419. this.editors[editor.path] = null;
  420. return this;
  421. },
  422. getEditor: function(path) {
  423. if(!this.editors) return;
  424. return this.editors[path];
  425. },
  426. watch: function(path,callback) {
  427. this.watchlist = this.watchlist || {};
  428. this.watchlist[path] = this.watchlist[path] || [];
  429. this.watchlist[path].push(callback);
  430. return this;
  431. },
  432. unwatch: function(path,callback) {
  433. if(!this.watchlist || !this.watchlist[path]) return this;
  434. // If removing all callbacks for a path
  435. if(!callback) {
  436. this.watchlist[path] = null;
  437. return this;
  438. }
  439. var newlist = [];
  440. for(var i=0; i<this.watchlist[path].length; i++) {
  441. if(this.watchlist[path][i] === callback) continue;
  442. else newlist.push(this.watchlist[path][i]);
  443. }
  444. this.watchlist[path] = newlist.length? newlist : null;
  445. return this;
  446. },
  447. notifyWatchers: function(path) {
  448. if(!this.watchlist || !this.watchlist[path]) return this;
  449. for(var i=0; i<this.watchlist[path].length; i++) {
  450. this.watchlist[path][i]();
  451. }
  452. },
  453. isEnabled: function() {
  454. return !this.root || this.root.isEnabled();
  455. },
  456. enable: function() {
  457. this.root.enable();
  458. },
  459. disable: function() {
  460. this.root.disable();
  461. },
  462. _getDefinitions: function(schema,path) {
  463. path = path || '#/definitions/';
  464. if(schema.definitions) {
  465. for(var i in schema.definitions) {
  466. if(!schema.definitions.hasOwnProperty(i)) continue;
  467. this.refs[path+i] = schema.definitions[i];
  468. if(schema.definitions[i].definitions) {
  469. this._getDefinitions(schema.definitions[i],path+i+'/definitions/');
  470. }
  471. }
  472. }
  473. },
  474. _getExternalRefs: function(schema) {
  475. var refs = {};
  476. var merge_refs = function(newrefs) {
  477. for(var i in newrefs) {
  478. if(newrefs.hasOwnProperty(i)) {
  479. refs[i] = true;
  480. }
  481. }
  482. };
  483. if(schema.$ref && typeof schema.$ref !== "object" && schema.$ref.substr(0,1) !== "#" && !this.refs[schema.$ref]) {
  484. refs[schema.$ref] = true;
  485. }
  486. for(var i in schema) {
  487. if(!schema.hasOwnProperty(i)) continue;
  488. if(schema[i] && typeof schema[i] === "object" && Array.isArray(schema[i])) {
  489. for(var j=0; j<schema[i].length; j++) {
  490. if(typeof schema[i][j]==="object") {
  491. merge_refs(this._getExternalRefs(schema[i][j]));
  492. }
  493. }
  494. }
  495. else if(schema[i] && typeof schema[i] === "object") {
  496. merge_refs(this._getExternalRefs(schema[i]));
  497. }
  498. }
  499. return refs;
  500. },
  501. _loadExternalRefs: function(schema, callback) {
  502. var self = this;
  503. var refs = this._getExternalRefs(schema);
  504. var done = 0, waiting = 0, callback_fired = false;
  505. $each(refs,function(url) {
  506. if(self.refs[url]) return;
  507. if(!self.options.ajax) throw "Must set ajax option to true to load external ref "+url;
  508. self.refs[url] = 'loading';
  509. waiting++;
  510. var r = new XMLHttpRequest();
  511. r.open("GET", url, true);
  512. r.onreadystatechange = function () {
  513. if (r.readyState != 4) return;
  514. // Request succeeded
  515. if(r.status === 200) {
  516. var response;
  517. try {
  518. response = JSON.parse(r.responseText);
  519. }
  520. catch(e) {
  521. window.console.log(e);
  522. throw "Failed to parse external ref "+url;
  523. }
  524. if(!response || typeof response !== "object") throw "External ref does not contain a valid schema - "+url;
  525. self.refs[url] = response;
  526. self._loadExternalRefs(response,function() {
  527. done++;
  528. if(done >= waiting && !callback_fired) {
  529. callback_fired = true;
  530. callback();
  531. }
  532. });
  533. }
  534. // Request failed
  535. else {
  536. window.console.log(r);
  537. throw "Failed to fetch ref via ajax- "+url;
  538. }
  539. };
  540. r.send();
  541. });
  542. if(!waiting) {
  543. callback();
  544. }
  545. },
  546. expandRefs: function(schema) {
  547. schema = $extend({},schema);
  548. while (schema.$ref) {
  549. var ref = schema.$ref;
  550. delete schema.$ref;
  551. if(!this.refs[ref]) ref = decodeURIComponent(ref);
  552. schema = this.extendSchemas(schema,this.refs[ref]);
  553. }
  554. return schema;
  555. },
  556. expandSchema: function(schema) {
  557. var self = this;
  558. var extended = $extend({},schema);
  559. var i;
  560. // Version 3 `type`
  561. if(typeof schema.type === 'object') {
  562. // Array of types
  563. if(Array.isArray(schema.type)) {
  564. $each(schema.type, function(key,value) {
  565. // Schema
  566. if(typeof value === 'object') {
  567. schema.type[key] = self.expandSchema(value);
  568. }
  569. });
  570. }
  571. // Schema
  572. else {
  573. schema.type = self.expandSchema(schema.type);
  574. }
  575. }
  576. // Version 3 `disallow`
  577. if(typeof schema.disallow === 'object') {
  578. // Array of types
  579. if(Array.isArray(schema.disallow)) {
  580. $each(schema.disallow, function(key,value) {
  581. // Schema
  582. if(typeof value === 'object') {
  583. schema.disallow[key] = self.expandSchema(value);
  584. }
  585. });
  586. }
  587. // Schema
  588. else {
  589. schema.disallow = self.expandSchema(schema.disallow);
  590. }
  591. }
  592. // Version 4 `anyOf`
  593. if(schema.anyOf) {
  594. $each(schema.anyOf, function(key,value) {
  595. schema.anyOf[key] = self.expandSchema(value);
  596. });
  597. }
  598. // Version 4 `dependencies` (schema dependencies)
  599. if(schema.dependencies) {
  600. $each(schema.dependencies,function(key,value) {
  601. if(typeof value === "object" && !(Array.isArray(value))) {
  602. schema.dependencies[key] = self.expandSchema(value);
  603. }
  604. });
  605. }
  606. // Version 4 `not`
  607. if(schema.not) {
  608. schema.not = this.expandSchema(schema.not);
  609. }
  610. // allOf schemas should be merged into the parent
  611. if(schema.allOf) {
  612. for(i=0; i<schema.allOf.length; i++) {
  613. extended = this.extendSchemas(extended,this.expandSchema(schema.allOf[i]));
  614. }
  615. delete extended.allOf;
  616. }
  617. // extends schemas should be merged into parent
  618. if(schema["extends"]) {
  619. // If extends is a schema
  620. if(!(Array.isArray(schema["extends"]))) {
  621. extended = this.extendSchemas(extended,this.expandSchema(schema["extends"]));
  622. }
  623. // If extends is an array of schemas
  624. else {
  625. for(i=0; i<schema["extends"].length; i++) {
  626. extended = this.extendSchemas(extended,this.expandSchema(schema["extends"][i]));
  627. }
  628. }
  629. delete extended["extends"];
  630. }
  631. // parent should be merged into oneOf schemas
  632. if(schema.oneOf) {
  633. var tmp = $extend({},extended);
  634. delete tmp.oneOf;
  635. for(i=0; i<schema.oneOf.length; i++) {
  636. extended.oneOf[i] = this.extendSchemas(this.expandSchema(schema.oneOf[i]),tmp);
  637. }
  638. }
  639. return this.expandRefs(extended);
  640. },
  641. extendSchemas: function(obj1, obj2) {
  642. obj1 = $extend({},obj1);
  643. obj2 = $extend({},obj2);
  644. var self = this;
  645. var extended = {};
  646. $each(obj1, function(prop,val) {
  647. // If this key is also defined in obj2, merge them
  648. if(typeof obj2[prop] !== "undefined") {
  649. // Required and defaultProperties arrays should be unioned together
  650. if((prop === 'required'||prop === 'defaultProperties') && typeof val === "object" && Array.isArray(val)) {
  651. // Union arrays and unique
  652. extended[prop] = val.concat(obj2[prop]).reduce(function(p, c) {
  653. if (p.indexOf(c) < 0) p.push(c);
  654. return p;
  655. }, []);
  656. }
  657. // Type should be intersected and is either an array or string
  658. else if(prop === 'type' && (typeof val === "string" || Array.isArray(val))) {
  659. // Make sure we're dealing with arrays
  660. if(typeof val === "string") val = [val];
  661. if(typeof obj2.type === "string") obj2.type = [obj2.type];
  662. // If type is only defined in the first schema, keep it
  663. if(!obj2.type || !obj2.type.length) {
  664. extended.type = val;
  665. }
  666. // If type is defined in both schemas, do an intersect
  667. else {
  668. extended.type = val.filter(function(n) {
  669. return obj2.type.indexOf(n) !== -1;
  670. });
  671. }
  672. // If there's only 1 type and it's a primitive, use a string instead of array
  673. if(extended.type.length === 1 && typeof extended.type[0] === "string") {
  674. extended.type = extended.type[0];
  675. }
  676. // Remove the type property if it's empty
  677. else if(extended.type.length === 0) {
  678. delete extended.type;
  679. }
  680. }
  681. // All other arrays should be intersected (enum, etc.)
  682. else if(typeof val === "object" && Array.isArray(val)){
  683. extended[prop] = val.filter(function(n) {
  684. return obj2[prop].indexOf(n) !== -1;
  685. });
  686. }
  687. // Objects should be recursively merged
  688. else if(typeof val === "object" && val !== null) {
  689. extended[prop] = self.extendSchemas(val,obj2[prop]);
  690. }
  691. // Otherwise, use the first value
  692. else {
  693. extended[prop] = val;
  694. }
  695. }
  696. // Otherwise, just use the one in obj1
  697. else {
  698. extended[prop] = val;
  699. }
  700. });
  701. // Properties in obj2 that aren't in obj1
  702. $each(obj2, function(prop,val) {
  703. if(typeof obj1[prop] === "undefined") {
  704. extended[prop] = val;
  705. }
  706. });
  707. return extended;
  708. }
  709. };
  710. JSONEditor.defaults = {
  711. themes: {},
  712. templates: {},
  713. iconlibs: {},
  714. editors: {},
  715. languages: {},
  716. resolvers: [],
  717. custom_validators: []
  718. };
  719. JSONEditor.Validator = Class.extend({
  720. init: function(jsoneditor,schema,options) {
  721. this.jsoneditor = jsoneditor;
  722. this.schema = schema || this.jsoneditor.schema;
  723. this.options = options || {};
  724. this.translate = this.jsoneditor.translate || JSONEditor.defaults.translate;
  725. },
  726. validate: function(value) {
  727. return this._validateSchema(this.schema, value);
  728. },
  729. _validateSchema: function(schema,value,path) {
  730. var self = this;
  731. var errors = [];
  732. var valid, i, j;
  733. var stringified = JSON.stringify(value);
  734. path = path || 'root';
  735. // Work on a copy of the schema
  736. schema = $extend({},this.jsoneditor.expandRefs(schema));
  737. /*
  738. * Type Agnostic Validation
  739. */
  740. // Version 3 `required`
  741. if(schema.required && schema.required === true) {
  742. if(typeof value === "undefined") {
  743. errors.push({
  744. path: path,
  745. property: 'required',
  746. message: this.translate("error_notset")
  747. });
  748. // Can't do any more validation at this point
  749. return errors;
  750. }
  751. }
  752. // Value not defined
  753. else if(typeof value === "undefined") {
  754. // If required_by_default is set, all fields are required
  755. if(this.jsoneditor.options.required_by_default) {
  756. errors.push({
  757. path: path,
  758. property: 'required',
  759. message: this.translate("error_notset")
  760. });
  761. }
  762. // Not required, no further validation needed
  763. else {
  764. return errors;
  765. }
  766. }
  767. // `enum`
  768. if(schema["enum"]) {
  769. valid = false;
  770. for(i=0; i<schema["enum"].length; i++) {
  771. if(stringified === JSON.stringify(schema["enum"][i])) valid = true;
  772. }
  773. if(!valid) {
  774. errors.push({
  775. path: path,
  776. property: 'enum',
  777. message: this.translate("error_enum")
  778. });
  779. }
  780. }
  781. // `extends` (version 3)
  782. if(schema["extends"]) {
  783. for(i=0; i<schema["extends"].length; i++) {
  784. errors = errors.concat(this._validateSchema(schema["extends"][i],value,path));
  785. }
  786. }
  787. // `allOf`
  788. if(schema.allOf) {
  789. for(i=0; i<schema.allOf.length; i++) {
  790. errors = errors.concat(this._validateSchema(schema.allOf[i],value,path));
  791. }
  792. }
  793. // `anyOf`
  794. if(schema.anyOf) {
  795. valid = false;
  796. for(i=0; i<schema.anyOf.length; i++) {
  797. if(!this._validateSchema(schema.anyOf[i],value,path).length) {
  798. valid = true;
  799. break;
  800. }
  801. }
  802. if(!valid) {
  803. errors.push({
  804. path: path,
  805. property: 'anyOf',
  806. message: this.translate('error_anyOf')
  807. });
  808. }
  809. }
  810. // `oneOf`
  811. if(schema.oneOf) {
  812. valid = 0;
  813. var oneof_errors = [];
  814. for(i=0; i<schema.oneOf.length; i++) {
  815. // Set the error paths to be path.oneOf[i].rest.of.path
  816. var tmp = this._validateSchema(schema.oneOf[i],value,path);
  817. if(!tmp.length) {
  818. valid++;
  819. }
  820. for(j=0; j<tmp.length; j++) {
  821. tmp[j].path = path+'.oneOf['+i+']'+tmp[j].path.substr(path.length);
  822. }
  823. oneof_errors = oneof_errors.concat(tmp);
  824. }
  825. if(valid !== 1) {
  826. errors.push({
  827. path: path,
  828. property: 'oneOf',
  829. message: this.translate('error_oneOf', [valid])
  830. });
  831. errors = errors.concat(oneof_errors);
  832. }
  833. }
  834. // `not`
  835. if(schema.not) {
  836. if(!this._validateSchema(schema.not,value,path).length) {
  837. errors.push({
  838. path: path,
  839. property: 'not',
  840. message: this.translate('error_not')
  841. });
  842. }
  843. }
  844. // `type` (both Version 3 and Version 4 support)
  845. if(schema.type) {
  846. // Union type
  847. if(Array.isArray(schema.type)) {
  848. valid = false;
  849. for(i=0;i<schema.type.length;i++) {
  850. if(this._checkType(schema.type[i], value)) {
  851. valid = true;
  852. break;
  853. }
  854. }
  855. if(!valid) {
  856. errors.push({
  857. path: path,
  858. property: 'type',
  859. message: this.translate('error_type_union')
  860. });
  861. }
  862. }
  863. // Simple type
  864. else {
  865. if(!this._checkType(schema.type, value)) {
  866. errors.push({
  867. path: path,
  868. property: 'type',
  869. message: this.translate('error_type', [schema.type])
  870. });
  871. }
  872. }
  873. }
  874. // `disallow` (version 3)
  875. if(schema.disallow) {
  876. // Union type
  877. if(Array.isArray(schema.disallow)) {
  878. valid = true;
  879. for(i=0;i<schema.disallow.length;i++) {
  880. if(this._checkType(schema.disallow[i], value)) {
  881. valid = false;
  882. break;
  883. }
  884. }
  885. if(!valid) {
  886. errors.push({
  887. path: path,
  888. property: 'disallow',
  889. message: this.translate('error_disallow_union')
  890. });
  891. }
  892. }
  893. // Simple type
  894. else {
  895. if(this._checkType(schema.disallow, value)) {
  896. errors.push({
  897. path: path,
  898. property: 'disallow',
  899. message: this.translate('error_disallow', [schema.disallow])
  900. });
  901. }
  902. }
  903. }
  904. /*
  905. * Type Specific Validation
  906. */
  907. // Number Specific Validation
  908. if(typeof value === "number") {
  909. // `multipleOf` and `divisibleBy`
  910. if(schema.multipleOf || schema.divisibleBy) {
  911. var divisor = schema.multipleOf || schema.divisibleBy;
  912. // Vanilla JS, prone to floating point rounding errors (e.g. 1.14 / .01 == 113.99999)
  913. valid = (value/divisor === Math.floor(value/divisor));
  914. // Use math.js is available
  915. if(window.math) {
  916. valid = window.math.mod(window.math.bignumber(value), window.math.bignumber(divisor)).equals(0);
  917. }
  918. // Use decimal.js is available
  919. else if(window.Decimal) {
  920. valid = (new window.Decimal(value)).mod(new window.Decimal(divisor)).equals(0);
  921. }
  922. if(!valid) {
  923. errors.push({
  924. path: path,
  925. property: schema.multipleOf? 'multipleOf' : 'divisibleBy',
  926. message: this.translate('error_multipleOf', [divisor])
  927. });
  928. }
  929. }
  930. // `maximum`
  931. if(schema.hasOwnProperty('maximum')) {
  932. // Vanilla JS, prone to floating point rounding errors (e.g. .999999999999999 == 1)
  933. valid = schema.exclusiveMaximum? (value < schema.maximum) : (value <= schema.maximum);
  934. // Use math.js is available
  935. if(window.math) {
  936. valid = window.math[schema.exclusiveMaximum?'smaller':'smallerEq'](
  937. window.math.bignumber(value),
  938. window.math.bignumber(schema.maximum)
  939. );
  940. }
  941. // Use Decimal.js if available
  942. else if(window.Decimal) {
  943. valid = (new window.Decimal(value))[schema.exclusiveMaximum?'lt':'lte'](new window.Decimal(schema.maximum));
  944. }
  945. if(!valid) {
  946. errors.push({
  947. path: path,
  948. property: 'maximum',
  949. message: this.translate(
  950. (schema.exclusiveMaximum?'error_maximum_excl':'error_maximum_incl'),
  951. [schema.maximum]
  952. )
  953. });
  954. }
  955. }
  956. // `minimum`
  957. if(schema.hasOwnProperty('minimum')) {
  958. // Vanilla JS, prone to floating point rounding errors (e.g. .999999999999999 == 1)
  959. valid = schema.exclusiveMinimum? (value > schema.minimum) : (value >= schema.minimum);
  960. // Use math.js is available
  961. if(window.math) {
  962. valid = window.math[schema.exclusiveMinimum?'larger':'largerEq'](
  963. window.math.bignumber(value),
  964. window.math.bignumber(schema.minimum)
  965. );
  966. }
  967. // Use Decimal.js if available
  968. else if(window.Decimal) {
  969. valid = (new window.Decimal(value))[schema.exclusiveMinimum?'gt':'gte'](new window.Decimal(schema.minimum));
  970. }
  971. if(!valid) {
  972. errors.push({
  973. path: path,
  974. property: 'minimum',
  975. message: this.translate(
  976. (schema.exclusiveMinimum?'error_minimum_excl':'error_minimum_incl'),
  977. [schema.minimum]
  978. )
  979. });
  980. }
  981. }
  982. }
  983. // String specific validation
  984. else if(typeof value === "string") {
  985. // `maxLength`
  986. if(schema.maxLength) {
  987. if((value+"").length > schema.maxLength) {
  988. errors.push({
  989. path: path,
  990. property: 'maxLength',
  991. message: this.translate('error_maxLength', [schema.maxLength])
  992. });
  993. }
  994. }
  995. // `minLength`
  996. if(schema.minLength) {
  997. if((value+"").length < schema.minLength) {
  998. errors.push({
  999. path: path,
  1000. property: 'minLength',
  1001. message: this.translate((schema.minLength===1?'error_notempty':'error_minLength'), [schema.minLength])
  1002. });
  1003. }
  1004. }
  1005. // `pattern`
  1006. if(schema.pattern) {
  1007. if(!(new RegExp(schema.pattern)).test(value)) {
  1008. errors.push({
  1009. path: path,
  1010. property: 'pattern',
  1011. message: this.translate('error_pattern', [schema.pattern])
  1012. });
  1013. }
  1014. }
  1015. }
  1016. // Array specific validation
  1017. else if(typeof value === "object" && value !== null && Array.isArray(value)) {
  1018. // `items` and `additionalItems`
  1019. if(schema.items) {
  1020. // `items` is an array
  1021. if(Array.isArray(schema.items)) {
  1022. for(i=0; i<value.length; i++) {
  1023. // If this item has a specific schema tied to it
  1024. // Validate against it
  1025. if(schema.items[i]) {
  1026. errors = errors.concat(this._validateSchema(schema.items[i],value[i],path+'.'+i));
  1027. }
  1028. // If all additional items are allowed
  1029. else if(schema.additionalItems === true) {
  1030. break;
  1031. }
  1032. // If additional items is a schema
  1033. // TODO: Incompatibility between version 3 and 4 of the spec
  1034. else if(schema.additionalItems) {
  1035. errors = errors.concat(this._validateSchema(schema.additionalItems,value[i],path+'.'+i));
  1036. }
  1037. // If no additional items are allowed
  1038. else if(schema.additionalItems === false) {
  1039. errors.push({
  1040. path: path,
  1041. property: 'additionalItems',
  1042. message: this.translate('error_additionalItems')
  1043. });
  1044. break;
  1045. }
  1046. // Default for `additionalItems` is an empty schema
  1047. else {
  1048. break;
  1049. }
  1050. }
  1051. }
  1052. // `items` is a schema
  1053. else {
  1054. // Each item in the array must validate against the schema
  1055. for(i=0; i<value.length; i++) {
  1056. errors = errors.concat(this._validateSchema(schema.items,value[i],path+'.'+i));
  1057. }
  1058. }
  1059. }
  1060. // `maxItems`
  1061. if(schema.maxItems) {
  1062. if(value.length > schema.maxItems) {
  1063. errors.push({
  1064. path: path,
  1065. property: 'maxItems',
  1066. message: this.translate('error_maxItems', [schema.maxItems])
  1067. });
  1068. }
  1069. }
  1070. // `minItems`
  1071. if(schema.minItems) {
  1072. if(value.length < schema.minItems) {
  1073. errors.push({
  1074. path: path,
  1075. property: 'minItems',
  1076. message: this.translate('error_minItems', [schema.minItems])
  1077. });
  1078. }
  1079. }
  1080. // `uniqueItems`
  1081. if(schema.uniqueItems) {
  1082. var seen = {};
  1083. for(i=0; i<value.length; i++) {
  1084. valid = JSON.stringify(value[i]);
  1085. if(seen[valid]) {
  1086. errors.push({
  1087. path: path,
  1088. property: 'uniqueItems',
  1089. message: this.translate('error_uniqueItems')
  1090. });
  1091. break;
  1092. }
  1093. seen[valid] = true;
  1094. }
  1095. }
  1096. }
  1097. // Object specific validation
  1098. else if(typeof value === "object" && value !== null) {
  1099. // `maxProperties`
  1100. if(schema.maxProperties) {
  1101. valid = 0;
  1102. for(i in value) {
  1103. if(!value.hasOwnProperty(i)) continue;
  1104. valid++;
  1105. }
  1106. if(valid > schema.maxProperties) {
  1107. errors.push({
  1108. path: path,
  1109. property: 'maxProperties',
  1110. message: this.translate('error_maxProperties', [schema.maxProperties])
  1111. });
  1112. }
  1113. }
  1114. // `minProperties`
  1115. if(schema.minProperties) {
  1116. valid = 0;
  1117. for(i in value) {
  1118. if(!value.hasOwnProperty(i)) continue;
  1119. valid++;
  1120. }
  1121. if(valid < schema.minProperties) {
  1122. errors.push({
  1123. path: path,
  1124. property: 'minProperties',
  1125. message: this.translate('error_minProperties', [schema.minProperties])
  1126. });
  1127. }
  1128. }
  1129. // Version 4 `required`
  1130. if(schema.required && Array.isArray(schema.required)) {
  1131. for(i=0; i<schema.required.length; i++) {
  1132. if(typeof value[schema.required[i]] === "undefined") {
  1133. errors.push({
  1134. path: path,
  1135. property: 'required',
  1136. message: this.translate('error_required', [schema.required[i]])
  1137. });
  1138. }
  1139. }
  1140. }
  1141. // `properties`
  1142. var validated_properties = {};
  1143. if(schema.properties) {
  1144. for(i in schema.properties) {
  1145. if(!schema.properties.hasOwnProperty(i)) continue;
  1146. validated_properties[i] = true;
  1147. errors = errors.concat(this._validateSchema(schema.properties[i],value[i],path+'.'+i));
  1148. }
  1149. }
  1150. // `patternProperties`
  1151. if(schema.patternProperties) {
  1152. for(i in schema.patternProperties) {
  1153. if(!schema.patternProperties.hasOwnProperty(i)) continue;
  1154. var regex = new RegExp(i);
  1155. // Check which properties match
  1156. for(j in value) {
  1157. if(!value.hasOwnProperty(j)) continue;
  1158. if(regex.test(j)) {
  1159. validated_properties[j] = true;
  1160. errors = errors.concat(this._validateSchema(schema.patternProperties[i],value[j],path+'.'+j));
  1161. }
  1162. }
  1163. }
  1164. }
  1165. // The no_additional_properties option currently doesn't work with extended schemas that use oneOf or anyOf
  1166. if(typeof schema.additionalProperties === "undefined" && this.jsoneditor.options.no_additional_properties && !schema.oneOf && !schema.anyOf) {
  1167. schema.additionalProperties = false;
  1168. }
  1169. // `additionalProperties`
  1170. if(typeof schema.additionalProperties !== "undefined") {
  1171. for(i in value) {
  1172. if(!value.hasOwnProperty(i)) continue;
  1173. if(!validated_properties[i]) {
  1174. // No extra properties allowed
  1175. if(!schema.additionalProperties) {
  1176. errors.push({
  1177. path: path,
  1178. property: 'additionalProperties',
  1179. message: this.translate('error_additional_properties', [i])
  1180. });
  1181. break;
  1182. }
  1183. // Allowed
  1184. else if(schema.additionalProperties === true) {
  1185. break;
  1186. }
  1187. // Must match schema
  1188. // TODO: incompatibility between version 3 and 4 of the spec
  1189. else {
  1190. errors = errors.concat(this._validateSchema(schema.additionalProperties,value[i],path+'.'+i));
  1191. }
  1192. }
  1193. }
  1194. }
  1195. // `dependencies`
  1196. if(schema.dependencies) {
  1197. for(i in schema.dependencies) {
  1198. if(!schema.dependencies.hasOwnProperty(i)) continue;
  1199. // Doesn't need to meet the dependency
  1200. if(typeof value[i] === "undefined") continue;
  1201. // Property dependency
  1202. if(Array.isArray(schema.dependencies[i])) {
  1203. for(j=0; j<schema.dependencies[i].length; j++) {
  1204. if(typeof value[schema.dependencies[i][j]] === "undefined") {
  1205. errors.push({
  1206. path: path,
  1207. property: 'dependencies',
  1208. message: this.translate('error_dependency', [schema.dependencies[i][j]])
  1209. });
  1210. }
  1211. }
  1212. }
  1213. // Schema dependency
  1214. else {
  1215. errors = errors.concat(this._validateSchema(schema.dependencies[i],value,path));
  1216. }
  1217. }
  1218. }
  1219. }
  1220. // Custom type validation (global)
  1221. $each(JSONEditor.defaults.custom_validators,function(i,validator) {
  1222. errors = errors.concat(validator.call(self,schema,value,path));
  1223. });
  1224. // Custom type validation (instance specific)
  1225. if(this.options.custom_validators) {
  1226. $each(this.options.custom_validators,function(i,validator) {
  1227. errors = errors.concat(validator.call(self,schema,value,path));
  1228. });
  1229. }
  1230. return errors;
  1231. },
  1232. _checkType: function(type, value) {
  1233. // Simple types
  1234. if(typeof type === "string") {
  1235. if(type==="string") return typeof value === "string";
  1236. else if(type==="number") return typeof value === "number";
  1237. else if(type==="integer") return typeof value === "number" && value === Math.floor(value);
  1238. else if(type==="boolean") return typeof value === "boolean";
  1239. else if(type==="array") return Array.isArray(value);
  1240. else if(type === "object") return value !== null && !(Array.isArray(value)) && typeof value === "object";
  1241. else if(type === "null") return value === null;
  1242. else return true;
  1243. }
  1244. // Schema
  1245. else {
  1246. return !this._validateSchema(type,value).length;
  1247. }
  1248. }
  1249. });
  1250. /**
  1251. * All editors should extend from this class
  1252. */
  1253. JSONEditor.AbstractEditor = Class.extend({
  1254. onChildEditorChange: function(editor) {
  1255. this.onChange(true);
  1256. },
  1257. notify: function() {
  1258. this.jsoneditor.notifyWatchers(this.path);
  1259. },
  1260. change: function() {
  1261. if(this.parent) this.parent.onChildEditorChange(this);
  1262. else this.jsoneditor.onChange();
  1263. },
  1264. onChange: function(bubble) {
  1265. this.notify();
  1266. if(this.watch_listener) this.watch_listener();
  1267. if(bubble) this.change();
  1268. },
  1269. register: function() {
  1270. this.jsoneditor.registerEditor(this);
  1271. this.onChange();
  1272. },
  1273. unregister: function() {
  1274. if(!this.jsoneditor) return;
  1275. this.jsoneditor.unregisterEditor(this);
  1276. },
  1277. getNumColumns: function() {
  1278. return 12;
  1279. },
  1280. init: function(options) {
  1281. this.jsoneditor = options.jsoneditor;
  1282. this.theme = this.jsoneditor.theme;
  1283. this.template_engine = this.jsoneditor.template;
  1284. this.iconlib = this.jsoneditor.iconlib;
  1285. this.translate = this.jsoneditor.translate || JSONEditor.defaults.translate;
  1286. this.original_schema = options.schema;
  1287. this.schema = this.jsoneditor.expandSchema(this.original_schema);
  1288. this.options = $extend({}, (this.options || {}), (options.schema.options || {}), options);
  1289. if(!options.path && !this.schema.id) this.schema.id = 'root';
  1290. this.path = options.path || 'root';
  1291. this.formname = options.formname || this.path.replace(/\.([^.]+)/g,'[$1]');
  1292. if(this.jsoneditor.options.form_name_root) this.formname = this.formname.replace(/^root\[/,this.jsoneditor.options.form_name_root+'[');
  1293. this.key = this.path.split('.').pop();
  1294. this.parent = options.parent;
  1295. this.link_watchers = [];
  1296. if(options.container) this.setContainer(options.container);
  1297. },
  1298. setContainer: function(container) {
  1299. this.container = container;
  1300. if(this.schema.id) this.container.setAttribute('data-schemaid',this.schema.id);
  1301. if(this.schema.type && typeof this.schema.type === "string") this.container.setAttribute('data-schematype',this.schema.type);
  1302. this.container.setAttribute('data-schemapath',this.path);
  1303. },
  1304. preBuild: function() {
  1305. },
  1306. build: function() {
  1307. },
  1308. postBuild: function() {
  1309. this.setupWatchListeners();
  1310. this.addLinks();
  1311. this.setValue(this.getDefault(), true);
  1312. this.updateHeaderText();
  1313. this.register();
  1314. this.onWatchedFieldChange();
  1315. },
  1316. setupWatchListeners: function() {
  1317. var self = this;
  1318. // Watched fields
  1319. this.watched = {};
  1320. if(this.schema.vars) this.schema.watch = this.schema.vars;
  1321. this.watched_values = {};
  1322. this.watch_listener = function() {
  1323. if(self.refreshWatchedFieldValues()) {
  1324. self.onWatchedFieldChange();
  1325. }
  1326. };
  1327. this.register();
  1328. if(this.schema.hasOwnProperty('watch')) {
  1329. var path,path_parts,first,root,adjusted_path;
  1330. for(var name in this.schema.watch) {
  1331. if(!this.schema.watch.hasOwnProperty(name)) continue;
  1332. path = this.schema.watch[name];
  1333. if(Array.isArray(path)) {
  1334. if(path.length<2) continue;
  1335. path_parts = [path[0]].concat(path[1].split('.'));
  1336. }
  1337. else {
  1338. path_parts = path.split('.');
  1339. if(!self.theme.closest(self.container,'[data-schemaid="'+path_parts[0]+'"]')) path_parts.unshift('#');
  1340. }
  1341. first = path_parts.shift();
  1342. if(first === '#') first = self.jsoneditor.schema.id || 'root';
  1343. // Find the root node for this template variable
  1344. root = self.theme.closest(self.container,'[data-schemaid="'+first+'"]');
  1345. if(!root) throw "Could not find ancestor node with id "+first;
  1346. // Keep track of the root node and path for use when rendering the template
  1347. adjusted_path = root.getAttribute('data-schemapath') + '.' + path_parts.join('.');
  1348. self.jsoneditor.watch(adjusted_path,self.watch_listener);
  1349. self.watched[name] = adjusted_path;
  1350. }
  1351. }
  1352. // Dynamic header
  1353. if(this.schema.headerTemplate) {
  1354. this.header_template = this.jsoneditor.compileTemplate(this.schema.headerTemplate, this.template_engine);
  1355. }
  1356. },
  1357. addLinks: function() {
  1358. // Add links
  1359. if(!this.no_link_holder) {
  1360. this.link_holder = this.theme.getLinksHolder();
  1361. this.container.appendChild(this.link_holder);
  1362. if(this.schema.links) {
  1363. for(var i=0; i<this.schema.links.length; i++) {
  1364. this.addLink(this.getLink(this.schema.links[i]));
  1365. }
  1366. }
  1367. }
  1368. },
  1369. getButton: function(text, icon, title) {
  1370. var btnClass = 'json-editor-btn-'+icon;
  1371. if(!this.iconlib) icon = null;
  1372. else icon = this.iconlib.getIcon(icon);
  1373. if(!icon && title) {
  1374. text = title;
  1375. title = null;
  1376. }
  1377. var btn = this.theme.getButton(text, icon, title);
  1378. btn.className += ' ' + btnClass + ' ';
  1379. return btn;
  1380. },
  1381. setButtonText: function(button, text, icon, title) {
  1382. if(!this.iconlib) icon = null;
  1383. else icon = this.iconlib.getIcon(icon);
  1384. if(!icon && title) {
  1385. text = title;
  1386. title = null;
  1387. }
  1388. return this.theme.setButtonText(button, text, icon, title);
  1389. },
  1390. addLink: function(link) {
  1391. if(this.link_holder) this.link_holder.appendChild(link);
  1392. },
  1393. getLink: function(data) {
  1394. var holder, link;
  1395. // Get mime type of the link
  1396. var mime = data.mediaType || 'application/javascript';
  1397. var type = mime.split('/')[0];
  1398. // Template to generate the link href
  1399. var href = this.jsoneditor.compileTemplate(data.href,this.template_engine);
  1400. // Template to generate the link's download attribute
  1401. var download = null;
  1402. if(data.download) download = data.download;
  1403. if(download && download !== true) {
  1404. download = this.jsoneditor.compileTemplate(download, this.template_engine);
  1405. }
  1406. // Image links
  1407. if(type === 'image') {
  1408. holder = this.theme.getBlockLinkHolder();
  1409. link = document.createElement('a');
  1410. link.setAttribute('target','_blank');
  1411. var image = document.createElement('img');
  1412. this.theme.createImageLink(holder,link,image);
  1413. // When a watched field changes, update the url
  1414. this.link_watchers.push(function(vars) {
  1415. var url = href(vars);
  1416. link.setAttribute('href',url);
  1417. link.setAttribute('title',data.rel || url);
  1418. image.setAttribute('src',url);
  1419. });
  1420. }
  1421. // Audio/Video links
  1422. else if(['audio','video'].indexOf(type) >=0) {
  1423. holder = this.theme.getBlockLinkHolder();
  1424. link = this.theme.getBlockLink();
  1425. link.setAttribute('target','_blank');
  1426. var media = document.createElement(type);
  1427. media.setAttribute('controls','controls');
  1428. this.theme.createMediaLink(holder,link,media);
  1429. // When a watched field changes, update the url
  1430. this.link_watchers.push(function(vars) {
  1431. var url = href(vars);
  1432. link.setAttribute('href',url);
  1433. link.textContent = data.rel || url;
  1434. media.setAttribute('src',url);
  1435. });
  1436. }
  1437. // Text links
  1438. else {
  1439. link = holder = this.theme.getBlockLink();
  1440. holder.setAttribute('target','_blank');
  1441. holder.textContent = data.rel;
  1442. // When a watched field changes, update the url
  1443. this.link_watchers.push(function(vars) {
  1444. var url = href(vars);
  1445. holder.setAttribute('href',url);
  1446. holder.textContent = data.rel || url;
  1447. });
  1448. }
  1449. if(download && link) {
  1450. if(download === true) {
  1451. link.setAttribute('download','');
  1452. }
  1453. else {
  1454. this.link_watchers.push(function(vars) {
  1455. link.setAttribute('download',download(vars));
  1456. });
  1457. }
  1458. }
  1459. if(data.class) link.className = link.className + ' ' + data.class;
  1460. return holder;
  1461. },
  1462. refreshWatchedFieldValues: function() {
  1463. if(!this.watched_values) return;
  1464. var watched = {};
  1465. var changed = false;
  1466. var self = this;
  1467. if(this.watched) {
  1468. var val,editor;
  1469. for(var name in this.watched) {
  1470. if(!this.watched.hasOwnProperty(name)) continue;
  1471. editor = self.jsoneditor.getEditor(this.watched[name]);
  1472. val = editor? editor.getValue() : null;
  1473. if(self.watched_values[name] !== val) changed = true;
  1474. watched[name] = val;
  1475. }
  1476. }
  1477. watched.self = this.getValue();
  1478. if(this.watched_values.self !== watched.self) changed = true;
  1479. this.watched_values = watched;
  1480. return changed;
  1481. },
  1482. getWatchedFieldValues: function() {
  1483. return this.watched_values;
  1484. },
  1485. updateHeaderText: function() {
  1486. if(this.header) {
  1487. // If the header has children, only update the text node's value
  1488. if(this.header.children.length) {
  1489. for(var i=0; i<this.header.childNodes.length; i++) {
  1490. if(this.header.childNodes[i].nodeType===3) {
  1491. this.header.childNodes[i].nodeValue = this.getHeaderText();
  1492. break;
  1493. }
  1494. }
  1495. }
  1496. // Otherwise, just update the entire node
  1497. else {
  1498. this.header.textContent = this.getHeaderText();
  1499. }
  1500. }
  1501. },
  1502. getHeaderText: function(title_only) {
  1503. if(this.header_text) return this.header_text;
  1504. else if(title_only) return this.schema.title;
  1505. else return this.getTitle();
  1506. },
  1507. onWatchedFieldChange: function() {
  1508. var vars;
  1509. if(this.header_template) {
  1510. vars = $extend(this.getWatchedFieldValues(),{
  1511. key: this.key,
  1512. i: this.key,
  1513. i0: (this.key*1),
  1514. i1: (this.key*1+1),
  1515. title: this.getTitle()
  1516. });
  1517. var header_text = this.header_template(vars);
  1518. if(header_text !== this.header_text) {
  1519. this.header_text = header_text;
  1520. this.updateHeaderText();
  1521. this.notify();
  1522. //this.fireChangeHeaderEvent();
  1523. }
  1524. }
  1525. if(this.link_watchers.length) {
  1526. vars = this.getWatchedFieldValues();
  1527. for(var i=0; i<this.link_watchers.length; i++) {
  1528. this.link_watchers[i](vars);
  1529. }
  1530. }
  1531. },
  1532. setValue: function(value) {
  1533. this.value = value;
  1534. },
  1535. getValue: function() {
  1536. return this.value;
  1537. },
  1538. refreshValue: function() {
  1539. },
  1540. getChildEditors: function() {
  1541. return false;
  1542. },
  1543. destroy: function() {
  1544. var self = this;
  1545. this.unregister(this);
  1546. $each(this.watched,function(name,adjusted_path) {
  1547. self.jsoneditor.unwatch(adjusted_path,self.watch_listener);
  1548. });
  1549. this.watched = null;
  1550. this.watched_values = null;
  1551. this.watch_listener = null;
  1552. this.header_text = null;
  1553. this.header_template = null;
  1554. this.value = null;
  1555. if(this.container && this.container.parentNode) this.container.parentNode.removeChild(this.container);
  1556. this.container = null;
  1557. this.jsoneditor = null;
  1558. this.schema = null;
  1559. this.path = null;
  1560. this.key = null;
  1561. this.parent = null;
  1562. },
  1563. getDefault: function() {
  1564. if(this.schema["default"]) return this.schema["default"];
  1565. if(this.schema["enum"]) return this.schema["enum"][0];
  1566. var type = this.schema.type || this.schema.oneOf;
  1567. if(type && Array.isArray(type)) type = type[0];
  1568. if(type && typeof type === "object") type = type.type;
  1569. if(type && Array.isArray(type)) type = type[0];
  1570. if(typeof type === "string") {
  1571. if(type === "number") return 0.0;
  1572. if(type === "boolean") return false;
  1573. if(type === "integer") return 0;
  1574. if(type === "string") return "";
  1575. if(type === "object") return {};
  1576. if(type === "array") return [];
  1577. }
  1578. return null;
  1579. },
  1580. getTitle: function() {
  1581. return this.schema.title || this.key;
  1582. },
  1583. enable: function() {
  1584. this.disabled = false;
  1585. },
  1586. disable: function() {
  1587. this.disabled = true;
  1588. },
  1589. isEnabled: function() {
  1590. return !this.disabled;
  1591. },
  1592. isRequired: function() {
  1593. if(typeof this.schema.required === "boolean") return this.schema.required;
  1594. else if(this.parent && this.parent.schema && Array.isArray(this.parent.schema.required)) return this.parent.schema.required.indexOf(this.key) > -1;
  1595. else if(this.jsoneditor.options.required_by_default) return true;
  1596. else return false;
  1597. },
  1598. getDisplayText: function(arr) {
  1599. var disp = [];
  1600. var used = {};
  1601. // Determine how many times each attribute name is used.
  1602. // This helps us pick the most distinct display text for the schemas.
  1603. $each(arr,function(i,el) {
  1604. if(el.title) {
  1605. used[el.title] = used[el.title] || 0;
  1606. used[el.title]++;
  1607. }
  1608. if(el.description) {
  1609. used[el.description] = used[el.description] || 0;
  1610. used[el.description]++;
  1611. }
  1612. if(el.format) {
  1613. used[el.format] = used[el.format] || 0;
  1614. used[el.format]++;
  1615. }
  1616. if(el.type) {
  1617. used[el.type] = used[el.type] || 0;
  1618. used[el.type]++;
  1619. }
  1620. });
  1621. // Determine display text for each element of the array
  1622. $each(arr,function(i,el) {
  1623. var name;
  1624. // If it's a simple string
  1625. if(typeof el === "string") name = el;
  1626. // Object
  1627. else if(el.title && used[el.title]<=1) name = el.title;
  1628. else if(el.format && used[el.format]<=1) name = el.format;
  1629. else if(el.type && used[el.type]<=1) name = el.type;
  1630. else if(el.description && used[el.description]<=1) name = el.descripton;
  1631. else if(el.title) name = el.title;
  1632. else if(el.format) name = el.format;
  1633. else if(el.type) name = el.type;
  1634. else if(el.description) name = el.description;
  1635. else if(JSON.stringify(el).length < 50) name = JSON.stringify(el);
  1636. else name = "type";
  1637. disp.push(name);
  1638. });
  1639. // Replace identical display text with "text 1", "text 2", etc.
  1640. var inc = {};
  1641. $each(disp,function(i,name) {
  1642. inc[name] = inc[name] || 0;
  1643. inc[name]++;
  1644. if(used[name] > 1) disp[i] = name + " " + inc[name];
  1645. });
  1646. return disp;
  1647. },
  1648. getOption: function(key) {
  1649. try {
  1650. throw "getOption is deprecated";
  1651. }
  1652. catch(e) {
  1653. window.console.error(e);
  1654. }
  1655. return this.options[key];
  1656. },
  1657. showValidationErrors: function(errors) {
  1658. }
  1659. });
  1660. JSONEditor.defaults.editors["null"] = JSONEditor.AbstractEditor.extend({
  1661. getValue: function() {
  1662. return null;
  1663. },
  1664. setValue: function() {
  1665. this.onChange();
  1666. },
  1667. getNumColumns: function() {
  1668. return 2;
  1669. }
  1670. });
  1671. JSONEditor.defaults.editors.string = JSONEditor.AbstractEditor.extend({
  1672. register: function() {
  1673. this._super();
  1674. if(!this.input) return;
  1675. this.input.setAttribute('name',this.formname);
  1676. },
  1677. unregister: function() {
  1678. this._super();
  1679. if(!this.input) return;
  1680. this.input.removeAttribute('name');
  1681. },
  1682. setValue: function(value,initial,from_template) {
  1683. var self = this;
  1684. if(this.template && !from_template) {
  1685. return;
  1686. }
  1687. if(value === null || typeof value === 'undefined') value = "";
  1688. else if(typeof value === "object") value = JSON.stringify(value);
  1689. else if(typeof value !== "string") value = ""+value;
  1690. if(value === this.serialized) return;
  1691. // Sanitize value before setting it
  1692. var sanitized = this.sanitize(value);
  1693. if(this.input.value === sanitized) {
  1694. return;
  1695. }
  1696. this.input.value = sanitized;
  1697. // If using SCEditor, update the WYSIWYG
  1698. if(this.sceditor_instance) {
  1699. this.sceditor_instance.val(sanitized);
  1700. }
  1701. else if(this.epiceditor) {
  1702. this.epiceditor.importFile(null,sanitized);
  1703. }
  1704. else if(this.ace_editor) {
  1705. this.ace_editor.setValue(sanitized);
  1706. }
  1707. var changed = from_template || this.getValue() !== value;
  1708. this.refreshValue();
  1709. if(initial) this.is_dirty = false;
  1710. else if(this.jsoneditor.options.show_errors === "change") this.is_dirty = true;
  1711. if(this.adjust_height) this.adjust_height(this.input);
  1712. // Bubble this setValue to parents if the value changed
  1713. this.onChange(changed);
  1714. },
  1715. getNumColumns: function() {
  1716. var min = Math.ceil(Math.max(this.getTitle().length,this.schema.maxLength||0,this.schema.minLength||0)/5);
  1717. var num;
  1718. if(this.input_type === 'textarea') num = 6;
  1719. else if(['text','email'].indexOf(this.input_type) >= 0) num = 4;
  1720. else num = 2;
  1721. return Math.min(12,Math.max(min,num));
  1722. },
  1723. build: function() {
  1724. var self = this, i;
  1725. if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
  1726. if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
  1727. this.format = this.schema.format;
  1728. if(!this.format && this.schema.media && this.schema.media.type) {
  1729. this.format = this.schema.media.type.replace(/(^(application|text)\/(x-)?(script\.)?)|(-source$)/g,'');
  1730. }
  1731. if(!this.format && this.options.default_format) {
  1732. this.format = this.options.default_format;
  1733. }
  1734. if(this.options.format) {
  1735. this.format = this.options.format;
  1736. }
  1737. // Specific format
  1738. if(this.format) {
  1739. // Text Area
  1740. if(this.format === 'textarea') {
  1741. this.input_type = 'textarea';
  1742. this.input = this.theme.getTextareaInput();
  1743. }
  1744. // Range Input
  1745. else if(this.format === 'range') {
  1746. this.input_type = 'range';
  1747. var min = this.schema.minimum || 0;
  1748. var max = this.schema.maximum || Math.max(100,min+1);
  1749. var step = 1;
  1750. if(this.schema.multipleOf) {
  1751. if(min%this.schema.multipleOf) min = Math.ceil(min/this.schema.multipleOf)*this.schema.multipleOf;
  1752. if(max%this.schema.multipleOf) max = Math.floor(max/this.schema.multipleOf)*this.schema.multipleOf;
  1753. step = this.schema.multipleOf;
  1754. }
  1755. this.input = this.theme.getRangeInput(min,max,step);
  1756. }
  1757. // Source Code
  1758. else if([
  1759. 'actionscript',
  1760. 'batchfile',
  1761. 'bbcode',
  1762. 'c',
  1763. 'c++',
  1764. 'cpp',
  1765. 'coffee',
  1766. 'csharp',
  1767. 'css',
  1768. 'dart',
  1769. 'django',
  1770. 'ejs',
  1771. 'erlang',
  1772. 'golang',
  1773. 'groovy',
  1774. 'handlebars',
  1775. 'haskell',
  1776. 'haxe',
  1777. 'html',
  1778. 'ini',
  1779. 'jade',
  1780. 'java',
  1781. 'javascript',
  1782. 'json',
  1783. 'less',
  1784. 'lisp',
  1785. 'lua',
  1786. 'makefile',
  1787. 'markdown',
  1788. 'matlab',
  1789. 'mysql',
  1790. 'objectivec',
  1791. 'pascal',
  1792. 'perl',
  1793. 'pgsql',
  1794. 'php',
  1795. 'python',
  1796. 'r',
  1797. 'ruby',
  1798. 'sass',
  1799. 'scala',
  1800. 'scss',
  1801. 'smarty',
  1802. 'sql',
  1803. 'stylus',
  1804. 'svg',
  1805. 'twig',
  1806. 'vbscript',
  1807. 'xml',
  1808. 'yaml'
  1809. ].indexOf(this.format) >= 0
  1810. ) {
  1811. this.input_type = this.format;
  1812. this.source_code = true;
  1813. this.input = this.theme.getTextareaInput();
  1814. }
  1815. // HTML5 Input type
  1816. else {
  1817. this.input_type = this.format;
  1818. this.input = this.theme.getFormInputField(this.input_type);
  1819. }
  1820. }
  1821. // Normal text input
  1822. else {
  1823. this.input_type = 'text';
  1824. this.input = this.theme.getFormInputField(this.input_type);
  1825. }
  1826. // minLength, maxLength, and pattern
  1827. if(typeof this.schema.maxLength !== "undefined") this.input.setAttribute('maxlength',this.schema.maxLength);
  1828. if(typeof this.schema.pattern !== "undefined") this.input.setAttribute('pattern',this.schema.pattern);
  1829. else if(typeof this.schema.minLength !== "undefined") this.input.setAttribute('pattern','.{'+this.schema.minLength+',}');
  1830. if(this.options.compact) {
  1831. this.container.className += ' compact';
  1832. }
  1833. else {
  1834. if(this.options.input_width) this.input.style.width = this.options.input_width;
  1835. }
  1836. if(this.schema.readOnly || this.schema.readonly || this.schema.template) {
  1837. this.always_disabled = true;
  1838. this.input.disabled = true;
  1839. }
  1840. this.input
  1841. .addEventListener('change',function(e) {
  1842. e.preventDefault();
  1843. e.stopPropagation();
  1844. // Don't allow changing if this field is a template
  1845. if(self.schema.template) {
  1846. this.value = self.value;
  1847. return;
  1848. }
  1849. var val = this.value;
  1850. // sanitize value
  1851. var sanitized = self.sanitize(val);
  1852. if(val !== sanitized) {
  1853. this.value = sanitized;
  1854. }
  1855. self.is_dirty = true;
  1856. self.refreshValue();
  1857. self.onChange(true);
  1858. }, {passive: true});
  1859. if(this.options.input_height) this.input.style.height = this.options.input_height;
  1860. if(this.options.expand_height) {
  1861. this.adjust_height = function(el) {
  1862. if(!el) return;
  1863. var i, ch=el.offsetHeight;
  1864. // Input too short
  1865. if(el.offsetHeight < el.scrollHeight) {
  1866. i=0;
  1867. while(el.offsetHeight < el.scrollHeight+3) {
  1868. if(i>100) break;
  1869. i++;
  1870. ch++;
  1871. el.style.height = ch+'px';
  1872. }
  1873. }
  1874. else {
  1875. i=0;
  1876. while(el.offsetHeight >= el.scrollHeight+3) {
  1877. if(i>100) break;
  1878. i++;
  1879. ch--;
  1880. el.style.height = ch+'px';
  1881. }
  1882. el.style.height = (ch+1)+'px';
  1883. }
  1884. };
  1885. this.input.addEventListener('keyup',function(e) {
  1886. self.adjust_height(this);
  1887. }, {passive: true});
  1888. this.input.addEventListener('change',function(e) {
  1889. self.adjust_height(this);
  1890. }, {passive: true});
  1891. this.adjust_height();
  1892. }
  1893. if(this.format) this.input.setAttribute('data-schemaformat',this.format);
  1894. this.control = this.theme.getFormControl(this.label, this.input, this.description);
  1895. this.container.appendChild(this.control);
  1896. // Any special formatting that needs to happen after the input is added to the dom
  1897. window.requestAnimationFrame(function() {
  1898. // Skip in case the input is only a temporary editor,
  1899. // otherwise, in the case of an ace_editor creation,
  1900. // it will generate an error trying to append it to the missing parentNode
  1901. if(self.input.parentNode) self.afterInputReady();
  1902. if(self.adjust_height) self.adjust_height(self.input);
  1903. });
  1904. // Compile and store the template
  1905. if(this.schema.template) {
  1906. this.template = this.jsoneditor.compileTemplate(this.schema.template, this.template_engine);
  1907. this.refreshValue();
  1908. }
  1909. else {
  1910. this.refreshValue();
  1911. }
  1912. },
  1913. enable: function() {
  1914. if(!this.always_disabled) {
  1915. this.input.disabled = false;
  1916. // TODO: WYSIWYG and Markdown editors
  1917. }
  1918. this._super();
  1919. },
  1920. disable: function() {
  1921. this.input.disabled = true;
  1922. // TODO: WYSIWYG and Markdown editors
  1923. this._super();
  1924. },
  1925. afterInputReady: function() {
  1926. var self = this, options;
  1927. // Code editor
  1928. if(this.source_code) {
  1929. // WYSIWYG html and bbcode editor
  1930. if(this.options.wysiwyg &&
  1931. ['html','bbcode'].indexOf(this.input_type) >= 0 &&
  1932. window.jQuery && window.jQuery.fn && window.jQuery.fn.sceditor
  1933. ) {
  1934. options = $extend({},{
  1935. plugins: self.input_type==='html'? 'xhtml' : 'bbcode',
  1936. emoticonsEnabled: false,
  1937. width: '100%',
  1938. height: 300
  1939. },JSONEditor.plugins.sceditor,self.options.sceditor_options||{});
  1940. window.jQuery(self.input).sceditor(options);
  1941. self.sceditor_instance = window.jQuery(self.input).sceditor('instance');
  1942. self.sceditor_instance.blur(function() {
  1943. // Get editor's value
  1944. var val = window.jQuery("<div>"+self.sceditor_instance.val()+"</div>");
  1945. // Remove sceditor spans/divs
  1946. window.jQuery('#sceditor-start-marker,#sceditor-end-marker,.sceditor-nlf',val).remove();
  1947. // Set the value and update
  1948. self.input.value = val.html();
  1949. self.value = self.input.value;
  1950. self.is_dirty = true;
  1951. self.onChange(true);
  1952. });
  1953. }
  1954. // EpicEditor for markdown (if it's loaded)
  1955. else if (this.input_type === 'markdown' && window.EpicEditor) {
  1956. this.epiceditor_container = document.createElement('div');
  1957. this.input.parentNode.insertBefore(this.epiceditor_container,this.input);
  1958. this.input.style.display = 'none';
  1959. options = $extend({},JSONEditor.plugins.epiceditor,{
  1960. container: this.epiceditor_container,
  1961. clientSideStorage: false
  1962. });
  1963. this.epiceditor = new window.EpicEditor(options).load();
  1964. this.epiceditor.importFile(null,this.getValue());
  1965. this.epiceditor.on('update',function() {
  1966. var val = self.epiceditor.exportFile();
  1967. self.input.value = val;
  1968. self.value = val;
  1969. self.is_dirty = true;
  1970. self.onChange(true);
  1971. });
  1972. }
  1973. // ACE editor for everything else
  1974. else if(window.ace) {
  1975. var mode = this.input_type;
  1976. // aliases for c/cpp
  1977. if(mode === 'cpp' || mode === 'c++' || mode === 'c') {
  1978. mode = 'c_cpp';
  1979. }
  1980. this.ace_container = document.createElement('div');
  1981. this.ace_container.style.width = '100%';
  1982. this.ace_container.style.position = 'relative';
  1983. this.ace_container.style.height = '400px';
  1984. this.input.parentNode.insertBefore(this.ace_container,this.input);
  1985. this.input.style.display = 'none';
  1986. this.ace_editor = window.ace.edit(this.ace_container);
  1987. this.ace_editor.setValue(this.getValue());
  1988. // The theme
  1989. if(JSONEditor.plugins.ace.theme) this.ace_editor.setTheme('ace/theme/'+JSONEditor.plugins.ace.theme);
  1990. // The mode
  1991. mode = window.ace.require("ace/mode/"+mode);
  1992. if(mode) this.ace_editor.getSession().setMode(new mode.Mode());
  1993. // Listen for changes
  1994. this.ace_editor.on('change',function() {
  1995. var val = self.ace_editor.getValue();
  1996. self.input.value = val;
  1997. self.refreshValue();
  1998. self.is_dirty = true;
  1999. self.onChange(true);
  2000. });
  2001. }
  2002. }
  2003. self.theme.afterInputReady(self.input);
  2004. },
  2005. refreshValue: function() {
  2006. this.value = this.input.value;
  2007. if(typeof this.value !== "string") this.value = '';
  2008. this.serialized = this.value;
  2009. },
  2010. destroy: function() {
  2011. // If using SCEditor, destroy the editor instance
  2012. if(this.sceditor_instance) {
  2013. this.sceditor_instance.destroy();
  2014. }
  2015. else if(this.epiceditor) {
  2016. this.epiceditor.unload();
  2017. }
  2018. else if(this.ace_editor) {
  2019. this.ace_editor.destroy();
  2020. }
  2021. this.template = null;
  2022. if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
  2023. if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
  2024. if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
  2025. this._super();
  2026. },
  2027. /**
  2028. * This is overridden in derivative editors
  2029. */
  2030. sanitize: function(value) {
  2031. return value;
  2032. },
  2033. /**
  2034. * Re-calculates the value if needed
  2035. */
  2036. onWatchedFieldChange: function() {
  2037. var self = this, vars, j;
  2038. // If this editor needs to be rendered by a macro template
  2039. if(this.template) {
  2040. vars = this.getWatchedFieldValues();
  2041. this.setValue(this.template(vars),false,true);
  2042. }
  2043. this._super();
  2044. },
  2045. showValidationErrors: function(errors) {
  2046. var self = this;
  2047. if(this.jsoneditor.options.show_errors === "always") {}
  2048. else if(!this.is_dirty && this.previous_error_setting===this.jsoneditor.options.show_errors) return;
  2049. this.previous_error_setting = this.jsoneditor.options.show_errors;
  2050. var messages = [];
  2051. $each(errors,function(i,error) {
  2052. if(error.path === self.path) {
  2053. messages.push(error.message);
  2054. }
  2055. });
  2056. if(messages.length) {
  2057. this.theme.addInputError(this.input, messages.join('. ')+'.');
  2058. }
  2059. else {
  2060. this.theme.removeInputError(this.input);
  2061. }
  2062. }
  2063. });
  2064. JSONEditor.defaults.editors.number = JSONEditor.defaults.editors.string.extend({
  2065. sanitize: function(value) {
  2066. return (value+"").replace(/[^0-9\.\-eE]/g,'');
  2067. },
  2068. getNumColumns: function() {
  2069. return 2;
  2070. },
  2071. getValue: function() {
  2072. return this.value*1;
  2073. }
  2074. });
  2075. JSONEditor.defaults.editors.integer = JSONEditor.defaults.editors.number.extend({
  2076. sanitize: function(value) {
  2077. value = value + "";
  2078. return value.replace(/[^0-9\-]/g,'');
  2079. },
  2080. getNumColumns: function() {
  2081. return 2;
  2082. }
  2083. });
  2084. JSONEditor.defaults.editors.object = JSONEditor.AbstractEditor.extend({
  2085. getDefault: function() {
  2086. return $extend({},this.schema["default"] || {});
  2087. },
  2088. getChildEditors: function() {
  2089. return this.editors;
  2090. },
  2091. register: function() {
  2092. this._super();
  2093. if(this.editors) {
  2094. for(var i in this.editors) {
  2095. if(!this.editors.hasOwnProperty(i)) continue;
  2096. this.editors[i].register();
  2097. }
  2098. }
  2099. },
  2100. unregister: function() {
  2101. this._super();
  2102. if(this.editors) {
  2103. for(var i in this.editors) {
  2104. if(!this.editors.hasOwnProperty(i)) continue;
  2105. this.editors[i].unregister();
  2106. }
  2107. }
  2108. },
  2109. getNumColumns: function() {
  2110. return Math.max(Math.min(12,this.maxwidth),3);
  2111. },
  2112. enable: function() {
  2113. if(this.editjson_button) this.editjson_button.disabled = false;
  2114. if(this.addproperty_button) this.addproperty_button.disabled = false;
  2115. this._super();
  2116. if(this.editors) {
  2117. for(var i in this.editors) {
  2118. if(!this.editors.hasOwnProperty(i)) continue;
  2119. this.editors[i].enable();
  2120. }
  2121. }
  2122. },
  2123. disable: function() {
  2124. if(this.editjson_button) this.editjson_button.disabled = true;
  2125. if(this.addproperty_button) this.addproperty_button.disabled = true;
  2126. this.hideEditJSON();
  2127. this._super();
  2128. if(this.editors) {
  2129. for(var i in this.editors) {
  2130. if(!this.editors.hasOwnProperty(i)) continue;
  2131. this.editors[i].disable();
  2132. }
  2133. }
  2134. },
  2135. layoutEditors: function() {
  2136. var self = this, i, j;
  2137. if(!this.row_container) return;
  2138. // Sort editors by propertyOrder
  2139. this.property_order = Object.keys(this.editors);
  2140. this.property_order = this.property_order.sort(function(a,b) {
  2141. var ordera = self.editors[a].schema.propertyOrder;
  2142. var orderb = self.editors[b].schema.propertyOrder;
  2143. if(typeof ordera !== "number") ordera = 1000;
  2144. if(typeof orderb !== "number") orderb = 1000;
  2145. return ordera - orderb;
  2146. });
  2147. var container;
  2148. if(this.format === 'grid') {
  2149. var rows = [];
  2150. $each(this.property_order, function(j,key) {
  2151. var editor = self.editors[key];
  2152. if(editor.property_removed) return;
  2153. var found = false;
  2154. var width = editor.options.hidden? 0 : (editor.options.grid_columns || editor.getNumColumns());
  2155. var height = editor.options.hidden? 0 : editor.container.offsetHeight;
  2156. // See if the editor will fit in any of the existing rows first
  2157. for(var i=0; i<rows.length; i++) {
  2158. // If the editor will fit in the row horizontally
  2159. if(rows[i].width + width <= 12) {
  2160. // If the editor is close to the other elements in height
  2161. // i.e. Don't put a really tall editor in an otherwise short row or vice versa
  2162. if(!height || (rows[i].minh*0.5 < height && rows[i].maxh*2 > height)) {
  2163. found = i;
  2164. }
  2165. }
  2166. }
  2167. // If there isn't a spot in any of the existing rows, start a new row
  2168. if(found === false) {
  2169. rows.push({
  2170. width: 0,
  2171. minh: 999999,
  2172. maxh: 0,
  2173. editors: []
  2174. });
  2175. found = rows.length-1;
  2176. }
  2177. rows[found].editors.push({
  2178. key: key,
  2179. //editor: editor,
  2180. width: width,
  2181. height: height
  2182. });
  2183. rows[found].width += width;
  2184. rows[found].minh = Math.min(rows[found].minh,height);
  2185. rows[found].maxh = Math.max(rows[found].maxh,height);
  2186. });
  2187. // Make almost full rows width 12
  2188. // Do this by increasing all editors' sizes proprotionately
  2189. // Any left over space goes to the biggest editor
  2190. // Don't touch rows with a width of 6 or less
  2191. for(i=0; i<rows.length; i++) {
  2192. if(rows[i].width < 12) {
  2193. var biggest = false;
  2194. var new_width = 0;
  2195. for(j=0; j<rows[i].editors.length; j++) {
  2196. if(biggest === false) biggest = j;
  2197. else if(rows[i].editors[j].width > rows[i].editors[biggest].width) biggest = j;
  2198. rows[i].editors[j].width *= 12/rows[i].width;
  2199. rows[i].editors[j].width = Math.floor(rows[i].editors[j].width);
  2200. new_width += rows[i].editors[j].width;
  2201. }
  2202. if(new_width < 12) rows[i].editors[biggest].width += 12-new_width;
  2203. rows[i].width = 12;
  2204. }
  2205. }
  2206. // layout hasn't changed
  2207. if(this.layout === JSON.stringify(rows)) return false;
  2208. this.layout = JSON.stringify(rows);
  2209. // Layout the form
  2210. container = document.createElement('div');
  2211. for(i=0; i<rows.length; i++) {
  2212. var row = this.theme.getGridRow();
  2213. container.appendChild(row);
  2214. for(j=0; j<rows[i].editors.length; j++) {
  2215. var key = rows[i].editors[j].key;
  2216. var editor = this.editors[key];
  2217. if(editor.options.hidden) editor.container.style.display = 'none';
  2218. else this.theme.setGridColumnSize(editor.container,rows[i].editors[j].width);
  2219. row.appendChild(editor.container);
  2220. }
  2221. }
  2222. }
  2223. // Normal layout
  2224. else {
  2225. container = document.createElement('div');
  2226. $each(this.property_order, function(i,key) {
  2227. var editor = self.editors[key];
  2228. if(editor.property_removed) return;
  2229. var row = self.theme.getGridRow();
  2230. container.appendChild(row);
  2231. if(editor.options.hidden) editor.container.style.display = 'none';
  2232. else self.theme.setGridColumnSize(editor.container,12);
  2233. row.appendChild(editor.container);
  2234. });
  2235. }
  2236. this.row_container.innerHTML = '';
  2237. this.row_container.appendChild(container);
  2238. },
  2239. getPropertySchema: function(key) {
  2240. // Schema declared directly in properties
  2241. var schema = this.schema.properties[key] || {};
  2242. schema = $extend({},schema);
  2243. var matched = this.schema.properties[key]? true : false;
  2244. // Any matching patternProperties should be merged in
  2245. if(this.schema.patternProperties) {
  2246. for(var i in this.schema.patternProperties) {
  2247. if(!this.schema.patternProperties.hasOwnProperty(i)) continue;
  2248. var regex = new RegExp(i);
  2249. if(regex.test(key)) {
  2250. schema.allOf = schema.allOf || [];
  2251. schema.allOf.push(this.schema.patternProperties[i]);
  2252. matched = true;
  2253. }
  2254. }
  2255. }
  2256. // Hasn't matched other rules, use additionalProperties schema
  2257. if(!matched && this.schema.additionalProperties && typeof this.schema.additionalProperties === "object") {
  2258. schema = $extend({},this.schema.additionalProperties);
  2259. }
  2260. return schema;
  2261. },
  2262. preBuild: function() {
  2263. this._super();
  2264. this.editors = {};
  2265. this.cached_editors = {};
  2266. var self = this;
  2267. this.format = this.options.layout || this.options.object_layout || this.schema.format || this.jsoneditor.options.object_layout || 'normal';
  2268. this.schema.properties = this.schema.properties || {};
  2269. this.minwidth = 0;
  2270. this.maxwidth = 0;
  2271. // If the object should be rendered as a table row
  2272. if(this.options.table_row) {
  2273. $each(this.schema.properties, function(key,schema) {
  2274. var editor = self.jsoneditor.getEditorClass(schema);
  2275. self.editors[key] = self.jsoneditor.createEditor(editor,{
  2276. jsoneditor: self.jsoneditor,
  2277. schema: schema,
  2278. path: self.path+'.'+key,
  2279. parent: self,
  2280. compact: true,
  2281. required: true
  2282. });
  2283. self.editors[key].preBuild();
  2284. var width = self.editors[key].options.hidden? 0 : (self.editors[key].options.grid_columns || self.editors[key].getNumColumns());
  2285. self.minwidth += width;
  2286. self.maxwidth += width;
  2287. });
  2288. this.no_link_holder = true;
  2289. }
  2290. // If the object should be rendered as a table
  2291. else if(this.options.table) {
  2292. // TODO: table display format
  2293. throw "Not supported yet";
  2294. }
  2295. // If the object should be rendered as a div
  2296. else {
  2297. if(!this.schema.defaultProperties) {
  2298. if(this.jsoneditor.options.display_required_only || this.options.display_required_only) {
  2299. this.schema.defaultProperties = [];
  2300. $each(this.schema.properties, function(k,s) {
  2301. if(self.isRequired({key: k, schema: s})) {
  2302. self.schema.defaultProperties.push(k);
  2303. }
  2304. });
  2305. }
  2306. else {
  2307. self.schema.defaultProperties = Object.keys(self.schema.properties);
  2308. }
  2309. }
  2310. // Increase the grid width to account for padding
  2311. self.maxwidth += 1;
  2312. $each(this.schema.defaultProperties, function(i,key) {
  2313. self.addObjectProperty(key, true);
  2314. if(self.editors[key]) {
  2315. self.minwidth = Math.max(self.minwidth,(self.editors[key].options.grid_columns || self.editors[key].getNumColumns()));
  2316. self.maxwidth += (self.editors[key].options.grid_columns || self.editors[key].getNumColumns());
  2317. }
  2318. });
  2319. }
  2320. // Sort editors by propertyOrder
  2321. this.property_order = Object.keys(this.editors);
  2322. this.property_order = this.property_order.sort(function(a,b) {
  2323. var ordera = self.editors[a].schema.propertyOrder;
  2324. var orderb = self.editors[b].schema.propertyOrder;
  2325. if(typeof ordera !== "number") ordera = 1000;
  2326. if(typeof orderb !== "number") orderb = 1000;
  2327. return ordera - orderb;
  2328. });
  2329. },
  2330. build: function() {
  2331. var self = this;
  2332. // If the object should be rendered as a table row
  2333. if(this.options.table_row) {
  2334. this.editor_holder = this.container;
  2335. $each(this.editors, function(key,editor) {
  2336. var holder = self.theme.getTableCell();
  2337. self.editor_holder.appendChild(holder);
  2338. editor.setContainer(holder);
  2339. editor.build();
  2340. editor.postBuild();
  2341. if(self.editors[key].options.hidden) {
  2342. holder.style.display = 'none';
  2343. }
  2344. if(self.editors[key].options.input_width) {
  2345. holder.style.width = self.editors[key].options.input_width;
  2346. }
  2347. });
  2348. }
  2349. // If the object should be rendered as a table
  2350. else if(this.options.table) {
  2351. // TODO: table display format
  2352. throw "Not supported yet";
  2353. }
  2354. // If the object should be rendered as a div
  2355. else {
  2356. this.header = document.createElement('span');
  2357. this.header.textContent = this.getTitle();
  2358. this.title = this.theme.getHeader(this.header);
  2359. this.container.appendChild(this.title);
  2360. this.container.style.position = 'relative';
  2361. // Edit JSON modal
  2362. this.editjson_holder = this.theme.getModal();
  2363. this.editjson_textarea = this.theme.getTextareaInput();
  2364. this.editjson_textarea.style.height = '170px';
  2365. this.editjson_textarea.style.width = '300px';
  2366. this.editjson_textarea.style.display = 'block';
  2367. this.editjson_save = this.getButton('Save','save','Save');
  2368. this.editjson_save.addEventListener('click',function(e) {
  2369. e.preventDefault();
  2370. e.stopPropagation();
  2371. self.saveJSON();
  2372. },{passive: true});
  2373. this.editjson_cancel = this.getButton('Cancel','cancel','Cancel');
  2374. this.editjson_cancel.addEventListener('click',function(e) {
  2375. e.preventDefault();
  2376. e.stopPropagation();
  2377. self.hideEditJSON();
  2378. },{ pasive:true });
  2379. this.editjson_holder.appendChild(this.editjson_textarea);
  2380. this.editjson_holder.appendChild(this.editjson_save);
  2381. this.editjson_holder.appendChild(this.editjson_cancel);
  2382. // Manage Properties modal
  2383. this.addproperty_holder = this.theme.getModal();
  2384. this.addproperty_list = document.createElement('div');
  2385. this.addproperty_list.style.width = '295px';
  2386. this.addproperty_list.style.maxHeight = '160px';
  2387. this.addproperty_list.style.padding = '5px 0';
  2388. this.addproperty_list.style.overflowY = 'auto';
  2389. this.addproperty_list.style.overflowX = 'hidden';
  2390. this.addproperty_list.style.paddingLeft = '5px';
  2391. this.addproperty_list.setAttribute('class', 'property-selector');
  2392. this.addproperty_add = this.getButton('add','add','add');
  2393. this.addproperty_input = this.theme.getFormInputField('text');
  2394. this.addproperty_input.setAttribute('placeholder','Property name...');
  2395. this.addproperty_input.style.width = '220px';
  2396. this.addproperty_input.style.marginBottom = '0';
  2397. this.addproperty_input.style.display = 'inline-block';
  2398. this.addproperty_add.addEventListener('click',function(e) {
  2399. e.preventDefault();
  2400. e.stopPropagation();
  2401. if(self.addproperty_input.value) {
  2402. if(self.editors[self.addproperty_input.value]) {
  2403. window.alert('there is already a property with that name');
  2404. return;
  2405. }
  2406. self.addObjectProperty(self.addproperty_input.value);
  2407. if(self.editors[self.addproperty_input.value]) {
  2408. self.editors[self.addproperty_input.value].disable();
  2409. }
  2410. self.onChange(true);
  2411. }
  2412. },{passive: true});
  2413. this.addproperty_holder.appendChild(this.addproperty_list);
  2414. this.addproperty_holder.appendChild(this.addproperty_input);
  2415. this.addproperty_holder.appendChild(this.addproperty_add);
  2416. var spacer = document.createElement('div');
  2417. spacer.style.clear = 'both';
  2418. this.addproperty_holder.appendChild(spacer);
  2419. // Description
  2420. if(this.schema.description) {
  2421. this.description = this.theme.getDescription(this.schema.description);
  2422. this.container.appendChild(this.description);
  2423. }
  2424. // Validation error placeholder area
  2425. this.error_holder = document.createElement('div');
  2426. this.container.appendChild(this.error_holder);
  2427. // Container for child editor area
  2428. this.editor_holder = this.theme.getIndentedPanel();
  2429. this.container.appendChild(this.editor_holder);
  2430. // Container for rows of child editors
  2431. this.row_container = this.theme.getGridContainer();
  2432. this.editor_holder.appendChild(this.row_container);
  2433. $each(this.editors, function(key,editor) {
  2434. var holder = self.theme.getGridColumn();
  2435. self.row_container.appendChild(holder);
  2436. editor.setContainer(holder);
  2437. editor.build();
  2438. editor.postBuild();
  2439. });
  2440. // Control buttons
  2441. this.title_controls = this.theme.getHeaderButtonHolder();
  2442. this.editjson_controls = this.theme.getHeaderButtonHolder();
  2443. this.addproperty_controls = this.theme.getHeaderButtonHolder();
  2444. this.title.appendChild(this.title_controls);
  2445. this.title.appendChild(this.editjson_controls);
  2446. this.title.appendChild(this.addproperty_controls);
  2447. // Show/Hide button
  2448. this.collapsed = false;
  2449. this.toggle_button = this.getButton('','collapse',this.translate('button_collapse'));
  2450. this.title_controls.appendChild(this.toggle_button);
  2451. this.toggle_button.addEventListener('click',function(e) {
  2452. e.preventDefault();
  2453. e.stopPropagation();
  2454. if(self.collapsed) {
  2455. self.editor_holder.style.display = '';
  2456. self.collapsed = false;
  2457. self.setButtonText(self.toggle_button,'','collapse',self.translate('button_collapse'));
  2458. }
  2459. else {
  2460. self.editor_holder.style.display = 'none';
  2461. self.collapsed = true;
  2462. self.setButtonText(self.toggle_button,'','expand',self.translate('button_expand'));
  2463. }
  2464. },);
  2465. // If it should start collapsed
  2466. if(this.options.collapsed) {
  2467. $trigger(this.toggle_button,'click');
  2468. }
  2469. // Collapse button disabled
  2470. if(this.schema.options && typeof this.schema.options.disable_collapse !== "undefined") {
  2471. if(this.schema.options.disable_collapse) this.toggle_button.style.display = 'none';
  2472. }
  2473. else if(this.jsoneditor.options.disable_collapse) {
  2474. this.toggle_button.style.display = 'none';
  2475. }
  2476. // Edit JSON Button
  2477. this.editjson_button = this.getButton('JSON','edit','Edit JSON');
  2478. this.editjson_button.addEventListener('click',function(e) {
  2479. e.preventDefault();
  2480. e.stopPropagation();
  2481. self.toggleEditJSON();
  2482. },{passive: true});
  2483. this.editjson_controls.appendChild(this.editjson_button);
  2484. this.editjson_controls.appendChild(this.editjson_holder);
  2485. // Edit JSON Buttton disabled
  2486. if(this.schema.options && typeof this.schema.options.disable_edit_json !== "undefined") {
  2487. if(this.schema.options.disable_edit_json) this.editjson_button.style.display = 'none';
  2488. }
  2489. else if(this.jsoneditor.options.disable_edit_json) {
  2490. this.editjson_button.style.display = 'none';
  2491. }
  2492. // Object Properties Button
  2493. this.addproperty_button = this.getButton('Properties','edit','Object Properties');
  2494. this.addproperty_button.addEventListener('click',function(e) {
  2495. e.preventDefault();
  2496. e.stopPropagation();
  2497. self.toggleAddProperty();
  2498. },{passive: true});
  2499. this.addproperty_controls.appendChild(this.addproperty_button);
  2500. this.addproperty_controls.appendChild(this.addproperty_holder);
  2501. this.refreshAddProperties();
  2502. }
  2503. // Fix table cell ordering
  2504. if(this.options.table_row) {
  2505. this.editor_holder = this.container;
  2506. $each(this.property_order,function(i,key) {
  2507. self.editor_holder.appendChild(self.editors[key].container);
  2508. });
  2509. }
  2510. // Layout object editors in grid if needed
  2511. else {
  2512. // Initial layout
  2513. this.layoutEditors();
  2514. // Do it again now that we know the approximate heights of elements
  2515. this.layoutEditors();
  2516. }
  2517. },
  2518. showEditJSON: function() {
  2519. if(!this.editjson_holder) return;
  2520. this.hideAddProperty();
  2521. // Position the form directly beneath the button
  2522. // TODO: edge detection
  2523. this.editjson_holder.style.left = this.editjson_button.offsetLeft+"px";
  2524. this.editjson_holder.style.top = this.editjson_button.offsetTop + this.editjson_button.offsetHeight+"px";
  2525. // Start the textarea with the current value
  2526. this.editjson_textarea.value = JSON.stringify(this.getValue(),null,2);
  2527. // Disable the rest of the form while editing JSON
  2528. this.disable();
  2529. this.editjson_holder.style.display = '';
  2530. this.editjson_button.disabled = false;
  2531. this.editing_json = true;
  2532. },
  2533. hideEditJSON: function() {
  2534. if(!this.editjson_holder) return;
  2535. if(!this.editing_json) return;
  2536. this.editjson_holder.style.display = 'none';
  2537. this.enable();
  2538. this.editing_json = false;
  2539. },
  2540. saveJSON: function() {
  2541. if(!this.editjson_holder) return;
  2542. try {
  2543. var json = JSON.parse(this.editjson_textarea.value);
  2544. this.setValue(json);
  2545. this.hideEditJSON();
  2546. }
  2547. catch(e) {
  2548. window.alert('invalid JSON');
  2549. throw e;
  2550. }
  2551. },
  2552. toggleEditJSON: function() {
  2553. if(this.editing_json) this.hideEditJSON();
  2554. else this.showEditJSON();
  2555. },
  2556. insertPropertyControlUsingPropertyOrder: function (property, control, container) {
  2557. var propertyOrder;
  2558. if (this.schema.properties[property])
  2559. propertyOrder = this.schema.properties[property].propertyOrder;
  2560. if (typeof propertyOrder !== "number") propertyOrder = 1000;
  2561. control.propertyOrder = propertyOrder;
  2562. for (var i = 0; i < container.childNodes.length; i++) {
  2563. var child = container.childNodes[i];
  2564. if (control.propertyOrder < child.propertyOrder) {
  2565. this.addproperty_list.insertBefore(control, child);
  2566. control = null;
  2567. break;
  2568. }
  2569. }
  2570. if (control) {
  2571. this.addproperty_list.appendChild(control);
  2572. }
  2573. },
  2574. addPropertyCheckbox: function(key) {
  2575. var self = this;
  2576. var checkbox, label, labelText, control;
  2577. checkbox = self.theme.getCheckbox();
  2578. checkbox.style.width = 'auto';
  2579. if (this.schema.properties[key] && this.schema.properties[key].title)
  2580. labelText = this.schema.properties[key].title;
  2581. else
  2582. labelText = key;
  2583. label = self.theme.getCheckboxLabel(labelText);
  2584. control = self.theme.getFormControl(label,checkbox);
  2585. control.style.paddingBottom = control.style.marginBottom = control.style.paddingTop = control.style.marginTop = 0;
  2586. control.style.height = 'auto';
  2587. //control.style.overflowY = 'hidden';
  2588. this.insertPropertyControlUsingPropertyOrder(key, control, this.addproperty_list);
  2589. checkbox.checked = key in this.editors;
  2590. checkbox.addEventListener('change',function() {
  2591. if(checkbox.checked) {
  2592. self.addObjectProperty(key);
  2593. }
  2594. else {
  2595. self.removeObjectProperty(key);
  2596. }
  2597. self.onChange(true);
  2598. }, {passive: true});
  2599. self.addproperty_checkboxes[key] = checkbox;
  2600. return checkbox;
  2601. },
  2602. showAddProperty: function() {
  2603. if(!this.addproperty_holder) return;
  2604. this.hideEditJSON();
  2605. // Position the form directly beneath the button
  2606. // TODO: edge detection
  2607. this.addproperty_holder.style.left = this.addproperty_button.offsetLeft+"px";
  2608. this.addproperty_holder.style.top = this.addproperty_button.offsetTop + this.addproperty_button.offsetHeight+"px";
  2609. // Disable the rest of the form while editing JSON
  2610. this.disable();
  2611. this.adding_property = true;
  2612. this.addproperty_button.disabled = false;
  2613. this.addproperty_holder.style.display = '';
  2614. this.refreshAddProperties();
  2615. },
  2616. hideAddProperty: function() {
  2617. if(!this.addproperty_holder) return;
  2618. if(!this.adding_property) return;
  2619. this.addproperty_holder.style.display = 'none';
  2620. this.enable();
  2621. this.adding_property = false;
  2622. },
  2623. toggleAddProperty: function() {
  2624. if(this.adding_property) this.hideAddProperty();
  2625. else this.showAddProperty();
  2626. },
  2627. removeObjectProperty: function(property) {
  2628. if(this.editors[property]) {
  2629. this.editors[property].unregister();
  2630. delete this.editors[property];
  2631. this.refreshValue();
  2632. this.layoutEditors();
  2633. }
  2634. },
  2635. addObjectProperty: function(name, prebuild_only) {
  2636. var self = this;
  2637. // Property is already added
  2638. if(this.editors[name]) return;
  2639. // Property was added before and is cached
  2640. if(this.cached_editors[name]) {
  2641. this.editors[name] = this.cached_editors[name];
  2642. if(prebuild_only) return;
  2643. this.editors[name].register();
  2644. }
  2645. // New property
  2646. else {
  2647. if(!this.canHaveAdditionalProperties() && (!this.schema.properties || !this.schema.properties[name])) {
  2648. return;
  2649. }
  2650. var schema = self.getPropertySchema(name);
  2651. // Add the property
  2652. var editor = self.jsoneditor.getEditorClass(schema);
  2653. self.editors[name] = self.jsoneditor.createEditor(editor,{
  2654. jsoneditor: self.jsoneditor,
  2655. schema: schema,
  2656. path: self.path+'.'+name,
  2657. parent: self
  2658. });
  2659. self.editors[name].preBuild();
  2660. if(!prebuild_only) {
  2661. var holder = self.theme.getChildEditorHolder();
  2662. self.editor_holder.appendChild(holder);
  2663. self.editors[name].setContainer(holder);
  2664. self.editors[name].build();
  2665. self.editors[name].postBuild();
  2666. }
  2667. self.cached_editors[name] = self.editors[name];
  2668. }
  2669. // If we're only prebuilding the editors, don't refresh values
  2670. if(!prebuild_only) {
  2671. self.refreshValue();
  2672. self.layoutEditors();
  2673. }
  2674. },
  2675. onChildEditorChange: function(editor) {
  2676. this.refreshValue();
  2677. this._super(editor);
  2678. },
  2679. canHaveAdditionalProperties: function() {
  2680. if (typeof this.schema.additionalProperties === "boolean") {
  2681. return this.schema.additionalProperties;
  2682. }
  2683. return !this.jsoneditor.options.no_additional_properties;
  2684. },
  2685. destroy: function() {
  2686. $each(this.cached_editors, function(i,el) {
  2687. el.destroy();
  2688. });
  2689. if(this.editor_holder) this.editor_holder.innerHTML = '';
  2690. if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
  2691. if(this.error_holder && this.error_holder.parentNode) this.error_holder.parentNode.removeChild(this.error_holder);
  2692. this.editors = null;
  2693. this.cached_editors = null;
  2694. if(this.editor_holder && this.editor_holder.parentNode) this.editor_holder.parentNode.removeChild(this.editor_holder);
  2695. this.editor_holder = null;
  2696. this._super();
  2697. },
  2698. getValue: function() {
  2699. var result = this._super();
  2700. if(this.jsoneditor.options.remove_empty_properties || this.options.remove_empty_properties) {
  2701. for(var i in result) {
  2702. if(result.hasOwnProperty(i)) {
  2703. if(!result[i]) delete result[i];
  2704. }
  2705. }
  2706. }
  2707. return result;
  2708. },
  2709. refreshValue: function() {
  2710. this.value = {};
  2711. var self = this;
  2712. for(var i in this.editors) {
  2713. if(!this.editors.hasOwnProperty(i)) continue;
  2714. this.value[i] = this.editors[i].getValue();
  2715. }
  2716. if(this.adding_property) this.refreshAddProperties();
  2717. },
  2718. refreshAddProperties: function() {
  2719. if(this.options.disable_properties || (this.options.disable_properties !== false && this.jsoneditor.options.disable_properties)) {
  2720. this.addproperty_controls.style.display = 'none';
  2721. return;
  2722. }
  2723. var can_add = false, can_remove = false, num_props = 0, i, show_modal = false;
  2724. // Get number of editors
  2725. for(i in this.editors) {
  2726. if(!this.editors.hasOwnProperty(i)) continue;
  2727. num_props++;
  2728. }
  2729. // Determine if we can add back removed properties
  2730. can_add = this.canHaveAdditionalProperties() && !(typeof this.schema.maxProperties !== "undefined" && num_props >= this.schema.maxProperties);
  2731. if(this.addproperty_checkboxes) {
  2732. this.addproperty_list.innerHTML = '';
  2733. }
  2734. this.addproperty_checkboxes = {};
  2735. // Check for which editors can't be removed or added back
  2736. for(i in this.cached_editors) {
  2737. if(!this.cached_editors.hasOwnProperty(i)) continue;
  2738. this.addPropertyCheckbox(i);
  2739. if(this.isRequired(this.cached_editors[i]) && i in this.editors) {
  2740. this.addproperty_checkboxes[i].disabled = true;
  2741. }
  2742. if(typeof this.schema.minProperties !== "undefined" && num_props <= this.schema.minProperties) {
  2743. this.addproperty_checkboxes[i].disabled = this.addproperty_checkboxes[i].checked;
  2744. if(!this.addproperty_checkboxes[i].checked) show_modal = true;
  2745. }
  2746. else if(!(i in this.editors)) {
  2747. if(!can_add && !this.schema.properties.hasOwnProperty(i)) {
  2748. this.addproperty_checkboxes[i].disabled = true;
  2749. }
  2750. else {
  2751. this.addproperty_checkboxes[i].disabled = false;
  2752. show_modal = true;
  2753. }
  2754. }
  2755. else {
  2756. show_modal = true;
  2757. can_remove = true;
  2758. }
  2759. }
  2760. if(this.canHaveAdditionalProperties()) {
  2761. show_modal = true;
  2762. }
  2763. // Additional addproperty checkboxes not tied to a current editor
  2764. for(i in this.schema.properties) {
  2765. if(!this.schema.properties.hasOwnProperty(i)) continue;
  2766. if(this.cached_editors[i]) continue;
  2767. show_modal = true;
  2768. this.addPropertyCheckbox(i);
  2769. }
  2770. // If no editors can be added or removed, hide the modal button
  2771. if(!show_modal) {
  2772. this.hideAddProperty();
  2773. this.addproperty_controls.style.display = 'none';
  2774. }
  2775. // If additional properties are disabled
  2776. else if(!this.canHaveAdditionalProperties()) {
  2777. this.addproperty_add.style.display = 'none';
  2778. this.addproperty_input.style.display = 'none';
  2779. }
  2780. // If no new properties can be added
  2781. else if(!can_add) {
  2782. this.addproperty_add.disabled = true;
  2783. }
  2784. // If new properties can be added
  2785. else {
  2786. this.addproperty_add.disabled = false;
  2787. }
  2788. },
  2789. isRequired: function(editor) {
  2790. if(typeof editor.schema.required === "boolean") return editor.schema.required;
  2791. else if(Array.isArray(this.schema.required)) return this.schema.required.indexOf(editor.key) > -1;
  2792. else if(this.jsoneditor.options.required_by_default) return true;
  2793. else return false;
  2794. },
  2795. setValue: function(value, initial) {
  2796. var self = this;
  2797. value = value || {};
  2798. if(typeof value !== "object" || Array.isArray(value)) value = {};
  2799. // First, set the values for all of the defined properties
  2800. $each(this.cached_editors, function(i,editor) {
  2801. // Value explicitly set
  2802. if(typeof value[i] !== "undefined") {
  2803. self.addObjectProperty(i);
  2804. editor.setValue(value[i],initial);
  2805. }
  2806. // Otherwise, remove value unless this is the initial set or it's required
  2807. else if(!initial && !self.isRequired(editor)) {
  2808. self.removeObjectProperty(i);
  2809. }
  2810. // Otherwise, set the value to the default
  2811. else {
  2812. editor.setValue(editor.getDefault(),initial);
  2813. }
  2814. });
  2815. $each(value, function(i,val) {
  2816. if(!self.cached_editors[i]) {
  2817. self.addObjectProperty(i);
  2818. if(self.editors[i]) self.editors[i].setValue(val,initial);
  2819. }
  2820. });
  2821. this.refreshValue();
  2822. this.layoutEditors();
  2823. this.onChange();
  2824. },
  2825. showValidationErrors: function(errors) {
  2826. var self = this;
  2827. // Get all the errors that pertain to this editor
  2828. var my_errors = [];
  2829. var other_errors = [];
  2830. $each(errors, function(i,error) {
  2831. if(error.path === self.path) {
  2832. my_errors.push(error);
  2833. }
  2834. else {
  2835. other_errors.push(error);
  2836. }
  2837. });
  2838. // Show errors for this editor
  2839. if(this.error_holder) {
  2840. if(my_errors.length) {
  2841. var message = [];
  2842. this.error_holder.innerHTML = '';
  2843. this.error_holder.style.display = '';
  2844. $each(my_errors, function(i,error) {
  2845. self.error_holder.appendChild(self.theme.getErrorMessage(error.message));
  2846. });
  2847. }
  2848. // Hide error area
  2849. else {
  2850. this.error_holder.style.display = 'none';
  2851. }
  2852. }
  2853. // Show error for the table row if this is inside a table
  2854. if(this.options.table_row) {
  2855. if(my_errors.length) {
  2856. this.theme.addTableRowError(this.container);
  2857. }
  2858. else {
  2859. this.theme.removeTableRowError(this.container);
  2860. }
  2861. }
  2862. // Show errors for child editors
  2863. $each(this.editors, function(i,editor) {
  2864. editor.showValidationErrors(other_errors);
  2865. });
  2866. }
  2867. });
  2868. JSONEditor.defaults.editors.array = JSONEditor.AbstractEditor.extend({
  2869. getDefault: function() {
  2870. return this.schema["default"] || [];
  2871. },
  2872. register: function() {
  2873. this._super();
  2874. if(this.rows) {
  2875. for(var i=0; i<this.rows.length; i++) {
  2876. this.rows[i].register();
  2877. }
  2878. }
  2879. },
  2880. unregister: function() {
  2881. this._super();
  2882. if(this.rows) {
  2883. for(var i=0; i<this.rows.length; i++) {
  2884. this.rows[i].unregister();
  2885. }
  2886. }
  2887. },
  2888. getNumColumns: function() {
  2889. var info = this.getItemInfo(0);
  2890. // Tabs require extra horizontal space
  2891. if(this.tabs_holder) {
  2892. return Math.max(Math.min(12,info.width+2),4);
  2893. }
  2894. else {
  2895. return info.width;
  2896. }
  2897. },
  2898. enable: function() {
  2899. if(this.add_row_button) this.add_row_button.disabled = false;
  2900. if(this.remove_all_rows_button) this.remove_all_rows_button.disabled = false;
  2901. if(this.delete_last_row_button) this.delete_last_row_button.disabled = false;
  2902. if(this.rows) {
  2903. for(var i=0; i<this.rows.length; i++) {
  2904. this.rows[i].enable();
  2905. if(this.rows[i].moveup_button) this.rows[i].moveup_button.disabled = false;
  2906. if(this.rows[i].movedown_button) this.rows[i].movedown_button.disabled = false;
  2907. if(this.rows[i].delete_button) this.rows[i].delete_button.disabled = false;
  2908. }
  2909. }
  2910. this._super();
  2911. },
  2912. disable: function() {
  2913. if(this.add_row_button) this.add_row_button.disabled = true;
  2914. if(this.remove_all_rows_button) this.remove_all_rows_button.disabled = true;
  2915. if(this.delete_last_row_button) this.delete_last_row_button.disabled = true;
  2916. if(this.rows) {
  2917. for(var i=0; i<this.rows.length; i++) {
  2918. this.rows[i].disable();
  2919. if(this.rows[i].moveup_button) this.rows[i].moveup_button.disabled = true;
  2920. if(this.rows[i].movedown_button) this.rows[i].movedown_button.disabled = true;
  2921. if(this.rows[i].delete_button) this.rows[i].delete_button.disabled = true;
  2922. }
  2923. }
  2924. this._super();
  2925. },
  2926. preBuild: function() {
  2927. this._super();
  2928. this.rows = [];
  2929. this.row_cache = [];
  2930. this.hide_delete_buttons = this.options.disable_array_delete || this.jsoneditor.options.disable_array_delete;
  2931. this.hide_delete_all_rows_buttons = this.hide_delete_buttons || this.options.disable_array_delete_all_rows || this.jsoneditor.options.disable_array_delete_all_rows;
  2932. this.hide_delete_last_row_buttons = this.hide_delete_buttons || this.options.disable_array_delete_last_row || this.jsoneditor.options.disable_array_delete_last_row;
  2933. this.hide_move_buttons = this.options.disable_array_reorder || this.jsoneditor.options.disable_array_reorder;
  2934. this.hide_add_button = this.options.disable_array_add || this.jsoneditor.options.disable_array_add;
  2935. },
  2936. build: function() {
  2937. var self = this;
  2938. if(!this.options.compact) {
  2939. this.header = document.createElement('span');
  2940. this.header.textContent = this.getTitle();
  2941. this.title = this.theme.getHeader(this.header);
  2942. this.container.appendChild(this.title);
  2943. this.title_controls = this.theme.getHeaderButtonHolder();
  2944. this.title.appendChild(this.title_controls);
  2945. if(this.schema.description) {
  2946. this.description = this.theme.getDescription(this.schema.description);
  2947. this.container.appendChild(this.description);
  2948. }
  2949. this.error_holder = document.createElement('div');
  2950. this.container.appendChild(this.error_holder);
  2951. if(this.schema.format === 'tabs') {
  2952. this.controls = this.theme.getHeaderButtonHolder();
  2953. this.title.appendChild(this.controls);
  2954. this.tabs_holder = this.theme.getTabHolder();
  2955. this.container.appendChild(this.tabs_holder);
  2956. this.row_holder = this.theme.getTabContentHolder(this.tabs_holder);
  2957. this.active_tab = null;
  2958. }
  2959. else {
  2960. this.panel = this.theme.getIndentedPanel();
  2961. this.container.appendChild(this.panel);
  2962. this.row_holder = document.createElement('div');
  2963. this.panel.appendChild(this.row_holder);
  2964. this.controls = this.theme.getButtonHolder();
  2965. this.panel.appendChild(this.controls);
  2966. }
  2967. }
  2968. else {
  2969. this.panel = this.theme.getIndentedPanel();
  2970. this.container.appendChild(this.panel);
  2971. this.controls = this.theme.getButtonHolder();
  2972. this.panel.appendChild(this.controls);
  2973. this.row_holder = document.createElement('div');
  2974. this.panel.appendChild(this.row_holder);
  2975. }
  2976. // Add controls
  2977. this.addControls();
  2978. },
  2979. onChildEditorChange: function(editor) {
  2980. this.refreshValue();
  2981. this.refreshTabs(true);
  2982. this._super(editor);
  2983. },
  2984. getItemTitle: function() {
  2985. if(!this.item_title) {
  2986. if(this.schema.items && !Array.isArray(this.schema.items)) {
  2987. var tmp = this.jsoneditor.expandRefs(this.schema.items);
  2988. this.item_title = tmp.title || 'item';
  2989. }
  2990. else {
  2991. this.item_title = 'item';
  2992. }
  2993. }
  2994. return this.item_title;
  2995. },
  2996. getItemSchema: function(i) {
  2997. if(Array.isArray(this.schema.items)) {
  2998. if(i >= this.schema.items.length) {
  2999. if(this.schema.additionalItems===true) {
  3000. return {};
  3001. }
  3002. else if(this.schema.additionalItems) {
  3003. return $extend({},this.schema.additionalItems);
  3004. }
  3005. }
  3006. else {
  3007. return $extend({},this.schema.items[i]);
  3008. }
  3009. }
  3010. else if(this.schema.items) {
  3011. return $extend({},this.schema.items);
  3012. }
  3013. else {
  3014. return {};
  3015. }
  3016. },
  3017. getItemInfo: function(i) {
  3018. var schema = this.getItemSchema(i);
  3019. // Check if it's cached
  3020. this.item_info = this.item_info || {};
  3021. var stringified = JSON.stringify(schema);
  3022. if(typeof this.item_info[stringified] !== "undefined") return this.item_info[stringified];
  3023. // Get the schema for this item
  3024. schema = this.jsoneditor.expandRefs(schema);
  3025. this.item_info[stringified] = {
  3026. title: schema.title || "item",
  3027. 'default': schema["default"],
  3028. width: 12,
  3029. child_editors: schema.properties || schema.items
  3030. };
  3031. return this.item_info[stringified];
  3032. },
  3033. getElementEditor: function(i) {
  3034. var item_info = this.getItemInfo(i);
  3035. var schema = this.getItemSchema(i);
  3036. schema = this.jsoneditor.expandRefs(schema);
  3037. schema.title = item_info.title+' '+(i+1);
  3038. var editor = this.jsoneditor.getEditorClass(schema);
  3039. var holder;
  3040. if(this.tabs_holder) {
  3041. holder = this.theme.getTabContent();
  3042. }
  3043. else if(item_info.child_editors) {
  3044. holder = this.theme.getChildEditorHolder();
  3045. }
  3046. else {
  3047. holder = this.theme.getIndentedPanel();
  3048. }
  3049. this.row_holder.appendChild(holder);
  3050. var ret = this.jsoneditor.createEditor(editor,{
  3051. jsoneditor: this.jsoneditor,
  3052. schema: schema,
  3053. container: holder,
  3054. path: this.path+'.'+i,
  3055. parent: this,
  3056. required: true
  3057. });
  3058. ret.preBuild();
  3059. ret.build();
  3060. ret.postBuild();
  3061. if(!ret.title_controls) {
  3062. ret.array_controls = this.theme.getButtonHolder();
  3063. holder.appendChild(ret.array_controls);
  3064. }
  3065. return ret;
  3066. },
  3067. destroy: function() {
  3068. this.empty(true);
  3069. if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
  3070. if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
  3071. if(this.row_holder && this.row_holder.parentNode) this.row_holder.parentNode.removeChild(this.row_holder);
  3072. if(this.controls && this.controls.parentNode) this.controls.parentNode.removeChild(this.controls);
  3073. if(this.panel && this.panel.parentNode) this.panel.parentNode.removeChild(this.panel);
  3074. this.rows = this.row_cache = this.title = this.description = this.row_holder = this.panel = this.controls = null;
  3075. this._super();
  3076. },
  3077. empty: function(hard) {
  3078. if(!this.rows) return;
  3079. var self = this;
  3080. $each(this.rows,function(i,row) {
  3081. if(hard) {
  3082. if(row.tab && row.tab.parentNode) row.tab.parentNode.removeChild(row.tab);
  3083. self.destroyRow(row,true);
  3084. self.row_cache[i] = null;
  3085. }
  3086. self.rows[i] = null;
  3087. });
  3088. self.rows = [];
  3089. if(hard) self.row_cache = [];
  3090. },
  3091. destroyRow: function(row,hard) {
  3092. var holder = row.container;
  3093. if(hard) {
  3094. row.destroy();
  3095. if(holder.parentNode) holder.parentNode.removeChild(holder);
  3096. if(row.tab && row.tab.parentNode) row.tab.parentNode.removeChild(row.tab);
  3097. }
  3098. else {
  3099. if(row.tab) row.tab.style.display = 'none';
  3100. holder.style.display = 'none';
  3101. row.unregister();
  3102. }
  3103. },
  3104. getMax: function() {
  3105. if((Array.isArray(this.schema.items)) && this.schema.additionalItems === false) {
  3106. return Math.min(this.schema.items.length,this.schema.maxItems || Infinity);
  3107. }
  3108. else {
  3109. return this.schema.maxItems || Infinity;
  3110. }
  3111. },
  3112. refreshTabs: function(refresh_headers) {
  3113. var self = this;
  3114. $each(this.rows, function(i,row) {
  3115. if(!row.tab) return;
  3116. if(refresh_headers) {
  3117. row.tab_text.textContent = row.getHeaderText();
  3118. }
  3119. else {
  3120. if(row.tab === self.active_tab) {
  3121. self.theme.markTabActive(row.tab);
  3122. row.container.style.display = '';
  3123. }
  3124. else {
  3125. self.theme.markTabInactive(row.tab);
  3126. row.container.style.display = 'none';
  3127. }
  3128. }
  3129. });
  3130. },
  3131. setValue: function(value, initial) {
  3132. // Update the array's value, adding/removing rows when necessary
  3133. value = value || [];
  3134. if(!(Array.isArray(value))) value = [value];
  3135. var serialized = JSON.stringify(value);
  3136. if(serialized === this.serialized) return;
  3137. // Make sure value has between minItems and maxItems items in it
  3138. if(this.schema.minItems) {
  3139. while(value.length < this.schema.minItems) {
  3140. value.push(this.getItemInfo(value.length)["default"]);
  3141. }
  3142. }
  3143. if(this.getMax() && value.length > this.getMax()) {
  3144. value = value.slice(0,this.getMax());
  3145. }
  3146. var self = this;
  3147. $each(value,function(i,val) {
  3148. if(self.rows[i]) {
  3149. // TODO: don't set the row's value if it hasn't changed
  3150. self.rows[i].setValue(val,initial);
  3151. }
  3152. else if(self.row_cache[i]) {
  3153. self.rows[i] = self.row_cache[i];
  3154. self.rows[i].setValue(val,initial);
  3155. self.rows[i].container.style.display = '';
  3156. if(self.rows[i].tab) self.rows[i].tab.style.display = '';
  3157. self.rows[i].register();
  3158. }
  3159. else {
  3160. self.addRow(val,initial);
  3161. }
  3162. });
  3163. for(var j=value.length; j<self.rows.length; j++) {
  3164. self.destroyRow(self.rows[j]);
  3165. self.rows[j] = null;
  3166. }
  3167. self.rows = self.rows.slice(0,value.length);
  3168. // Set the active tab
  3169. var new_active_tab = null;
  3170. $each(self.rows, function(i,row) {
  3171. if(row.tab === self.active_tab) {
  3172. new_active_tab = row.tab;
  3173. return false;
  3174. }
  3175. });
  3176. if(!new_active_tab && self.rows.length) new_active_tab = self.rows[0].tab;
  3177. self.active_tab = new_active_tab;
  3178. self.refreshValue(initial);
  3179. self.refreshTabs(true);
  3180. self.refreshTabs();
  3181. self.onChange();
  3182. // TODO: sortable
  3183. },
  3184. refreshValue: function(force) {
  3185. var self = this;
  3186. var oldi = this.value? this.value.length : 0;
  3187. this.value = [];
  3188. $each(this.rows,function(i,editor) {
  3189. // Get the value for this editor
  3190. self.value[i] = editor.getValue();
  3191. });
  3192. if(oldi !== this.value.length || force) {
  3193. // If we currently have minItems items in the array
  3194. var minItems = this.schema.minItems && this.schema.minItems >= this.rows.length;
  3195. $each(this.rows,function(i,editor) {
  3196. // Hide the move down button for the last row
  3197. if(editor.movedown_button) {
  3198. if(i === self.rows.length - 1) {
  3199. editor.movedown_button.style.display = 'none';
  3200. }
  3201. else {
  3202. editor.movedown_button.style.display = '';
  3203. }
  3204. }
  3205. // Hide the delete button if we have minItems items
  3206. if(editor.delete_button) {
  3207. if(minItems) {
  3208. editor.delete_button.style.display = 'none';
  3209. }
  3210. else {
  3211. editor.delete_button.style.display = '';
  3212. }
  3213. }
  3214. // Get the value for this editor
  3215. self.value[i] = editor.getValue();
  3216. });
  3217. var controls_needed = false;
  3218. if(!this.value.length) {
  3219. this.delete_last_row_button.style.display = 'none';
  3220. this.remove_all_rows_button.style.display = 'none';
  3221. }
  3222. else if(this.value.length === 1) {
  3223. this.remove_all_rows_button.style.display = 'none';
  3224. // If there are minItems items in the array, or configured to hide the delete_last_row button, hide the delete button beneath the rows
  3225. if(minItems || this.hide_delete_last_row_buttons) {
  3226. this.delete_last_row_button.style.display = 'none';
  3227. }
  3228. else {
  3229. this.delete_last_row_button.style.display = '';
  3230. controls_needed = true;
  3231. }
  3232. }
  3233. else {
  3234. if(minItems || this.hide_delete_last_row_buttons) {
  3235. this.delete_last_row_button.style.display = 'none';
  3236. }
  3237. else {
  3238. this.delete_last_row_button.style.display = '';
  3239. controls_needed = true;
  3240. }
  3241. if(minItems || this.hide_delete_all_rows_buttons) {
  3242. this.remove_all_rows_button.style.display = 'none';
  3243. }
  3244. else {
  3245. this.remove_all_rows_button.style.display = '';
  3246. controls_needed = true;
  3247. }
  3248. }
  3249. // If there are maxItems in the array, hide the add button beneath the rows
  3250. if((this.getMax() && this.getMax() <= this.rows.length) || this.hide_add_button){
  3251. this.add_row_button.style.display = 'none';
  3252. }
  3253. else {
  3254. this.add_row_button.style.display = '';
  3255. controls_needed = true;
  3256. }
  3257. if(!this.collapsed && controls_needed) {
  3258. this.controls.style.display = 'inline-block';
  3259. }
  3260. else {
  3261. this.controls.style.display = 'none';
  3262. }
  3263. }
  3264. },
  3265. addRow: function(value, initial) {
  3266. var self = this;
  3267. var i = this.rows.length;
  3268. self.rows[i] = this.getElementEditor(i);
  3269. self.row_cache[i] = self.rows[i];
  3270. if(self.tabs_holder) {
  3271. self.rows[i].tab_text = document.createElement('span');
  3272. self.rows[i].tab_text.textContent = self.rows[i].getHeaderText();
  3273. self.rows[i].tab = self.theme.getTab(self.rows[i].tab_text);
  3274. self.rows[i].tab.addEventListener('click', function(e) {
  3275. self.active_tab = self.rows[i].tab;
  3276. self.refreshTabs();
  3277. e.preventDefault();
  3278. e.stopPropagation();
  3279. },{passive: true});
  3280. self.theme.addTab(self.tabs_holder, self.rows[i].tab);
  3281. }
  3282. var controls_holder = self.rows[i].title_controls || self.rows[i].array_controls;
  3283. // Buttons to delete row, move row up, and move row down
  3284. if(!self.hide_delete_buttons) {
  3285. self.rows[i].delete_button = this.getButton(self.getItemTitle(),'delete',this.translate('button_delete_row_title',[self.getItemTitle()]));
  3286. self.rows[i].delete_button.className += ' delete';
  3287. self.rows[i].delete_button.setAttribute('data-i',i);
  3288. self.rows[i].delete_button.addEventListener('click',function(e) {
  3289. e.preventDefault();
  3290. e.stopPropagation();
  3291. var i = this.getAttribute('data-i')*1;
  3292. var value = self.getValue();
  3293. var newval = [];
  3294. var new_active_tab = null;
  3295. $each(value,function(j,row) {
  3296. if(j===i) {
  3297. // If the one we're deleting is the active tab
  3298. if(self.rows[j].tab === self.active_tab) {
  3299. // Make the next tab active if there is one
  3300. // Note: the next tab is going to be the current tab after deletion
  3301. if(self.rows[j+1]) new_active_tab = self.rows[j].tab;
  3302. // Otherwise, make the previous tab active if there is one
  3303. else if(j) new_active_tab = self.rows[j-1].tab;
  3304. }
  3305. return; // If this is the one we're deleting
  3306. }
  3307. newval.push(row);
  3308. });
  3309. self.setValue(newval);
  3310. if(new_active_tab) {
  3311. self.active_tab = new_active_tab;
  3312. self.refreshTabs();
  3313. }
  3314. self.onChange(true);
  3315. },{passive: true});
  3316. if(controls_holder) {
  3317. controls_holder.appendChild(self.rows[i].delete_button);
  3318. }
  3319. }
  3320. if(i && !self.hide_move_buttons) {
  3321. self.rows[i].moveup_button = this.getButton('','moveup',this.translate('button_move_up_title'));
  3322. self.rows[i].moveup_button.className += ' moveup';
  3323. self.rows[i].moveup_button.setAttribute('data-i',i);
  3324. self.rows[i].moveup_button.addEventListener('click',function(e) {
  3325. e.preventDefault();
  3326. e.stopPropagation();
  3327. var i = this.getAttribute('data-i')*1;
  3328. if(i<=0) return;
  3329. var rows = self.getValue();
  3330. var tmp = rows[i-1];
  3331. rows[i-1] = rows[i];
  3332. rows[i] = tmp;
  3333. self.setValue(rows);
  3334. self.active_tab = self.rows[i-1].tab;
  3335. self.refreshTabs();
  3336. self.onChange(true);
  3337. },{passive: true});
  3338. if(controls_holder) {
  3339. controls_holder.appendChild(self.rows[i].moveup_button);
  3340. }
  3341. }
  3342. if(!self.hide_move_buttons) {
  3343. self.rows[i].movedown_button = this.getButton('','movedown',this.translate('button_move_down_title'));
  3344. self.rows[i].movedown_button.className += ' movedown';
  3345. self.rows[i].movedown_button.setAttribute('data-i',i);
  3346. self.rows[i].movedown_button.addEventListener('click',function(e) {
  3347. e.preventDefault();
  3348. e.stopPropagation();
  3349. var i = this.getAttribute('data-i')*1;
  3350. var rows = self.getValue();
  3351. if(i>=rows.length-1) return;
  3352. var tmp = rows[i+1];
  3353. rows[i+1] = rows[i];
  3354. rows[i] = tmp;
  3355. self.setValue(rows);
  3356. self.active_tab = self.rows[i+1].tab;
  3357. self.refreshTabs();
  3358. self.onChange(true);
  3359. },{passive: true});
  3360. if(controls_holder) {
  3361. controls_holder.appendChild(self.rows[i].movedown_button);
  3362. }
  3363. }
  3364. if(value) self.rows[i].setValue(value, initial);
  3365. self.refreshTabs();
  3366. },
  3367. addControls: function() {
  3368. var self = this;
  3369. this.collapsed = false;
  3370. this.toggle_button = this.getButton('','collapse',this.translate('button_collapse'));
  3371. this.title_controls.appendChild(this.toggle_button);
  3372. var row_holder_display = self.row_holder.style.display;
  3373. var controls_display = self.controls.style.display;
  3374. this.toggle_button.addEventListener('click',function(e) {
  3375. e.preventDefault();
  3376. e.stopPropagation();
  3377. if(self.collapsed) {
  3378. self.collapsed = false;
  3379. if(self.panel) self.panel.style.display = '';
  3380. self.row_holder.style.display = row_holder_display;
  3381. if(self.tabs_holder) self.tabs_holder.style.display = '';
  3382. self.controls.style.display = controls_display;
  3383. self.setButtonText(this,'','collapse',self.translate('button_collapse'));
  3384. }
  3385. else {
  3386. self.collapsed = true;
  3387. self.row_holder.style.display = 'none';
  3388. if(self.tabs_holder) self.tabs_holder.style.display = 'none';
  3389. self.controls.style.display = 'none';
  3390. if(self.panel) self.panel.style.display = 'none';
  3391. self.setButtonText(this,'','expand',self.translate('button_expand'));
  3392. }
  3393. },{passive: true});
  3394. // If it should start collapsed
  3395. if(this.options.collapsed) {
  3396. $trigger(this.toggle_button,'click');
  3397. }
  3398. // Collapse button disabled
  3399. if(this.schema.options && typeof this.schema.options.disable_collapse !== "undefined") {
  3400. if(this.schema.options.disable_collapse) this.toggle_button.style.display = 'none';
  3401. }
  3402. else if(this.jsoneditor.options.disable_collapse) {
  3403. this.toggle_button.style.display = 'none';
  3404. }
  3405. // Add "new row" and "delete last" buttons below editor
  3406. this.add_row_button = this.getButton(this.getItemTitle(),'add',this.translate('button_add_row_title',[this.getItemTitle()]));
  3407. this.add_row_button.addEventListener('click',function(e) {
  3408. e.preventDefault();
  3409. e.stopPropagation();
  3410. var i = self.rows.length;
  3411. if(self.row_cache[i]) {
  3412. self.rows[i] = self.row_cache[i];
  3413. self.rows[i].setValue(self.rows[i].getDefault(), true);
  3414. self.rows[i].container.style.display = '';
  3415. if(self.rows[i].tab) self.rows[i].tab.style.display = '';
  3416. self.rows[i].register();
  3417. }
  3418. else {
  3419. self.addRow();
  3420. }
  3421. self.active_tab = self.rows[i].tab;
  3422. self.refreshTabs();
  3423. self.refreshValue();
  3424. self.onChange(true);
  3425. },{passive: true});
  3426. self.controls.appendChild(this.add_row_button);
  3427. this.delete_last_row_button = this.getButton(this.translate('button_delete_last',[this.getItemTitle()]),'delete',this.translate('button_delete_last_title',[this.getItemTitle()]));
  3428. this.delete_last_row_button.addEventListener('click',function(e) {
  3429. e.preventDefault();
  3430. e.stopPropagation();
  3431. var rows = self.getValue();
  3432. var new_active_tab = null;
  3433. if(self.rows.length > 1 && self.rows[self.rows.length-1].tab === self.active_tab) new_active_tab = self.rows[self.rows.length-2].tab;
  3434. rows.pop();
  3435. self.setValue(rows);
  3436. if(new_active_tab) {
  3437. self.active_tab = new_active_tab;
  3438. self.refreshTabs();
  3439. }
  3440. self.onChange(true);
  3441. },{passive: true});
  3442. self.controls.appendChild(this.delete_last_row_button);
  3443. this.remove_all_rows_button = this.getButton(this.translate('button_delete_all'),'delete',this.translate('button_delete_all_title'));
  3444. this.remove_all_rows_button.addEventListener('click',function(e) {
  3445. e.preventDefault();
  3446. e.stopPropagation();
  3447. self.setValue([]);
  3448. self.onChange(true);
  3449. },{passive: true});
  3450. self.controls.appendChild(this.remove_all_rows_button);
  3451. if(self.tabs) {
  3452. this.add_row_button.style.width = '100%';
  3453. this.add_row_button.style.textAlign = 'left';
  3454. this.add_row_button.style.marginBottom = '3px';
  3455. this.delete_last_row_button.style.width = '100%';
  3456. this.delete_last_row_button.style.textAlign = 'left';
  3457. this.delete_last_row_button.style.marginBottom = '3px';
  3458. this.remove_all_rows_button.style.width = '100%';
  3459. this.remove_all_rows_button.style.textAlign = 'left';
  3460. this.remove_all_rows_button.style.marginBottom = '3px';
  3461. }
  3462. },
  3463. showValidationErrors: function(errors) {
  3464. var self = this;
  3465. // Get all the errors that pertain to this editor
  3466. var my_errors = [];
  3467. var other_errors = [];
  3468. $each(errors, function(i,error) {
  3469. if(error.path === self.path) {
  3470. my_errors.push(error);
  3471. }
  3472. else {
  3473. other_errors.push(error);
  3474. }
  3475. });
  3476. // Show errors for this editor
  3477. if(this.error_holder) {
  3478. if(my_errors.length) {
  3479. var message = [];
  3480. this.error_holder.innerHTML = '';
  3481. this.error_holder.style.display = '';
  3482. $each(my_errors, function(i,error) {
  3483. self.error_holder.appendChild(self.theme.getErrorMessage(error.message));
  3484. });
  3485. }
  3486. // Hide error area
  3487. else {
  3488. this.error_holder.style.display = 'none';
  3489. }
  3490. }
  3491. // Show errors for child editors
  3492. $each(this.rows, function(i,row) {
  3493. row.showValidationErrors(other_errors);
  3494. });
  3495. }
  3496. });
  3497. JSONEditor.defaults.editors.table = JSONEditor.defaults.editors.array.extend({
  3498. register: function() {
  3499. this._super();
  3500. if(this.rows) {
  3501. for(var i=0; i<this.rows.length; i++) {
  3502. this.rows[i].register();
  3503. }
  3504. }
  3505. },
  3506. unregister: function() {
  3507. this._super();
  3508. if(this.rows) {
  3509. for(var i=0; i<this.rows.length; i++) {
  3510. this.rows[i].unregister();
  3511. }
  3512. }
  3513. },
  3514. getNumColumns: function() {
  3515. return Math.max(Math.min(12,this.width),3);
  3516. },
  3517. preBuild: function() {
  3518. var item_schema = this.jsoneditor.expandRefs(this.schema.items || {});
  3519. this.item_title = item_schema.title || 'row';
  3520. this.item_default = item_schema["default"] || null;
  3521. this.item_has_child_editors = item_schema.properties || item_schema.items;
  3522. this.width = 12;
  3523. this._super();
  3524. },
  3525. build: function() {
  3526. var self = this;
  3527. this.table = this.theme.getTable();
  3528. this.container.appendChild(this.table);
  3529. this.thead = this.theme.getTableHead();
  3530. this.table.appendChild(this.thead);
  3531. this.header_row = this.theme.getTableRow();
  3532. this.thead.appendChild(this.header_row);
  3533. this.row_holder = this.theme.getTableBody();
  3534. this.table.appendChild(this.row_holder);
  3535. // Determine the default value of array element
  3536. var tmp = this.getElementEditor(0,true);
  3537. this.item_default = tmp.getDefault();
  3538. this.width = tmp.getNumColumns() + 2;
  3539. if(!this.options.compact) {
  3540. this.title = this.theme.getHeader(this.getTitle());
  3541. this.container.appendChild(this.title);
  3542. this.title_controls = this.theme.getHeaderButtonHolder();
  3543. this.title.appendChild(this.title_controls);
  3544. if(this.schema.description) {
  3545. this.description = this.theme.getDescription(this.schema.description);
  3546. this.container.appendChild(this.description);
  3547. }
  3548. this.panel = this.theme.getIndentedPanel();
  3549. this.container.appendChild(this.panel);
  3550. this.error_holder = document.createElement('div');
  3551. this.panel.appendChild(this.error_holder);
  3552. }
  3553. else {
  3554. this.panel = document.createElement('div');
  3555. this.container.appendChild(this.panel);
  3556. }
  3557. this.panel.appendChild(this.table);
  3558. this.controls = this.theme.getButtonHolder();
  3559. this.panel.appendChild(this.controls);
  3560. if(this.item_has_child_editors) {
  3561. var ce = tmp.getChildEditors();
  3562. var order = tmp.property_order || Object.keys(ce);
  3563. for(var i=0; i<order.length; i++) {
  3564. var th = self.theme.getTableHeaderCell(ce[order[i]].getTitle());
  3565. if(ce[order[i]].options.hidden) th.style.display = 'none';
  3566. self.header_row.appendChild(th);
  3567. }
  3568. }
  3569. else {
  3570. self.header_row.appendChild(self.theme.getTableHeaderCell(this.item_title));
  3571. }
  3572. tmp.destroy();
  3573. this.row_holder.innerHTML = '';
  3574. // Row Controls column
  3575. this.controls_header_cell = self.theme.getTableHeaderCell(" ");
  3576. self.header_row.appendChild(this.controls_header_cell);
  3577. // Add controls
  3578. this.addControls();
  3579. },
  3580. onChildEditorChange: function(editor) {
  3581. this.refreshValue();
  3582. this._super();
  3583. },
  3584. getItemDefault: function() {
  3585. return $extend({},{"default":this.item_default})["default"];
  3586. },
  3587. getItemTitle: function() {
  3588. return this.item_title;
  3589. },
  3590. getElementEditor: function(i,ignore) {
  3591. var schema_copy = $extend({},this.schema.items);
  3592. var editor = this.jsoneditor.getEditorClass(schema_copy, this.jsoneditor);
  3593. var row = this.row_holder.appendChild(this.theme.getTableRow());
  3594. var holder = row;
  3595. if(!this.item_has_child_editors) {
  3596. holder = this.theme.getTableCell();
  3597. row.appendChild(holder);
  3598. }
  3599. var ret = this.jsoneditor.createEditor(editor,{
  3600. jsoneditor: this.jsoneditor,
  3601. schema: schema_copy,
  3602. container: holder,
  3603. path: this.path+'.'+i,
  3604. parent: this,
  3605. compact: true,
  3606. table_row: true
  3607. });
  3608. ret.preBuild();
  3609. if(!ignore) {
  3610. ret.build();
  3611. ret.postBuild();
  3612. ret.controls_cell = row.appendChild(this.theme.getTableCell());
  3613. ret.row = row;
  3614. ret.table_controls = this.theme.getButtonHolder();
  3615. ret.controls_cell.appendChild(ret.table_controls);
  3616. ret.table_controls.style.margin = 0;
  3617. ret.table_controls.style.padding = 0;
  3618. }
  3619. return ret;
  3620. },
  3621. destroy: function() {
  3622. this.innerHTML = '';
  3623. if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
  3624. if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
  3625. if(this.row_holder && this.row_holder.parentNode) this.row_holder.parentNode.removeChild(this.row_holder);
  3626. if(this.table && this.table.parentNode) this.table.parentNode.removeChild(this.table);
  3627. if(this.panel && this.panel.parentNode) this.panel.parentNode.removeChild(this.panel);
  3628. this.rows = this.title = this.description = this.row_holder = this.table = this.panel = null;
  3629. this._super();
  3630. },
  3631. setValue: function(value, initial) {
  3632. // Update the array's value, adding/removing rows when necessary
  3633. value = value || [];
  3634. // Make sure value has between minItems and maxItems items in it
  3635. if(this.schema.minItems) {
  3636. while(value.length < this.schema.minItems) {
  3637. value.push(this.getItemDefault());
  3638. }
  3639. }
  3640. if(this.schema.maxItems && value.length > this.schema.maxItems) {
  3641. value = value.slice(0,this.schema.maxItems);
  3642. }
  3643. var serialized = JSON.stringify(value);
  3644. if(serialized === this.serialized) return;
  3645. var numrows_changed = false;
  3646. var self = this;
  3647. $each(value,function(i,val) {
  3648. if(self.rows[i]) {
  3649. // TODO: don't set the row's value if it hasn't changed
  3650. self.rows[i].setValue(val);
  3651. }
  3652. else {
  3653. self.addRow(val);
  3654. numrows_changed = true;
  3655. }
  3656. });
  3657. for(var j=value.length; j<self.rows.length; j++) {
  3658. var holder = self.rows[j].container;
  3659. if(!self.item_has_child_editors) {
  3660. self.rows[j].row.parentNode.removeChild(self.rows[j].row);
  3661. }
  3662. self.rows[j].destroy();
  3663. if(holder.parentNode) holder.parentNode.removeChild(holder);
  3664. self.rows[j] = null;
  3665. numrows_changed = true;
  3666. }
  3667. self.rows = self.rows.slice(0,value.length);
  3668. self.refreshValue();
  3669. if(numrows_changed || initial) self.refreshRowButtons();
  3670. self.onChange();
  3671. // TODO: sortable
  3672. },
  3673. refreshRowButtons: function() {
  3674. var self = this;
  3675. // If we currently have minItems items in the array
  3676. var minItems = this.schema.minItems && this.schema.minItems >= this.rows.length;
  3677. var need_row_buttons = false;
  3678. $each(this.rows,function(i,editor) {
  3679. // Hide the move down button for the last row
  3680. if(editor.movedown_button) {
  3681. if(i === self.rows.length - 1) {
  3682. editor.movedown_button.style.display = 'none';
  3683. }
  3684. else {
  3685. need_row_buttons = true;
  3686. editor.movedown_button.style.display = '';
  3687. }
  3688. }
  3689. // Hide the delete button if we have minItems items
  3690. if(editor.delete_button) {
  3691. if(minItems) {
  3692. editor.delete_button.style.display = 'none';
  3693. }
  3694. else {
  3695. need_row_buttons = true;
  3696. editor.delete_button.style.display = '';
  3697. }
  3698. }
  3699. if(editor.moveup_button) {
  3700. need_row_buttons = true;
  3701. }
  3702. });
  3703. // Show/hide controls column in table
  3704. $each(this.rows,function(i,editor) {
  3705. if(need_row_buttons) {
  3706. editor.controls_cell.style.display = '';
  3707. }
  3708. else {
  3709. editor.controls_cell.style.display = 'none';
  3710. }
  3711. });
  3712. if(need_row_buttons) {
  3713. this.controls_header_cell.style.display = '';
  3714. }
  3715. else {
  3716. this.controls_header_cell.style.display = 'none';
  3717. }
  3718. var controls_needed = false;
  3719. if(!this.value.length) {
  3720. this.delete_last_row_button.style.display = 'none';
  3721. this.remove_all_rows_button.style.display = 'none';
  3722. this.table.style.display = 'none';
  3723. }
  3724. else if(this.value.length === 1) {
  3725. this.table.style.display = '';
  3726. this.remove_all_rows_button.style.display = 'none';
  3727. // If there are minItems items in the array, or configured to hide the delete_last_row button, hide the delete button beneath the rows
  3728. if(minItems || this.hide_delete_last_row_buttons) {
  3729. this.delete_last_row_button.style.display = 'none';
  3730. }
  3731. else {
  3732. this.delete_last_row_button.style.display = '';
  3733. controls_needed = true;
  3734. }
  3735. }
  3736. else {
  3737. this.table.style.display = '';
  3738. if(minItems || this.hide_delete_last_row_buttons) {
  3739. this.delete_last_row_button.style.display = 'none';
  3740. }
  3741. else {
  3742. this.delete_last_row_button.style.display = '';
  3743. controls_needed = true;
  3744. }
  3745. if(minItems || this.hide_delete_all_rows_buttons) {
  3746. this.remove_all_rows_button.style.display = 'none';
  3747. }
  3748. else {
  3749. this.remove_all_rows_button.style.display = '';
  3750. controls_needed = true;
  3751. }
  3752. }
  3753. // If there are maxItems in the array, hide the add button beneath the rows
  3754. if((this.schema.maxItems && this.schema.maxItems <= this.rows.length) || this.hide_add_button) {
  3755. this.add_row_button.style.display = 'none';
  3756. }
  3757. else {
  3758. this.add_row_button.style.display = '';
  3759. controls_needed = true;
  3760. }
  3761. if(!controls_needed) {
  3762. this.controls.style.display = 'none';
  3763. }
  3764. else {
  3765. this.controls.style.display = '';
  3766. }
  3767. },
  3768. refreshValue: function() {
  3769. var self = this;
  3770. this.value = [];
  3771. $each(this.rows,function(i,editor) {
  3772. // Get the value for this editor
  3773. self.value[i] = editor.getValue();
  3774. });
  3775. this.serialized = JSON.stringify(this.value);
  3776. },
  3777. addRow: function(value) {
  3778. var self = this;
  3779. var i = this.rows.length;
  3780. self.rows[i] = this.getElementEditor(i);
  3781. var controls_holder = self.rows[i].table_controls;
  3782. // Buttons to delete row, move row up, and move row down
  3783. if(!this.hide_delete_buttons) {
  3784. self.rows[i].delete_button = this.getButton('','delete',this.translate('button_delete_row_title_short'));
  3785. self.rows[i].delete_button.className += ' delete';
  3786. self.rows[i].delete_button.setAttribute('data-i',i);
  3787. self.rows[i].delete_button.addEventListener('click',function(e) {
  3788. e.preventDefault();
  3789. e.stopPropagation();
  3790. var i = this.getAttribute('data-i')*1;
  3791. var value = self.getValue();
  3792. var newval = [];
  3793. $each(value,function(j,row) {
  3794. if(j===i) return; // If this is the one we're deleting
  3795. newval.push(row);
  3796. });
  3797. self.setValue(newval);
  3798. self.onChange(true);
  3799. },{passive: true});
  3800. controls_holder.appendChild(self.rows[i].delete_button);
  3801. }
  3802. if(i && !this.hide_move_buttons) {
  3803. self.rows[i].moveup_button = this.getButton('','moveup',this.translate('button_move_up_title'));
  3804. self.rows[i].moveup_button.className += ' moveup';
  3805. self.rows[i].moveup_button.setAttribute('data-i',i);
  3806. self.rows[i].moveup_button.addEventListener('click',function(e) {
  3807. e.preventDefault();
  3808. e.stopPropagation();
  3809. var i = this.getAttribute('data-i')*1;
  3810. if(i<=0) return;
  3811. var rows = self.getValue();
  3812. var tmp = rows[i-1];
  3813. rows[i-1] = rows[i];
  3814. rows[i] = tmp;
  3815. self.setValue(rows);
  3816. self.onChange(true);
  3817. },{passive: true});
  3818. controls_holder.appendChild(self.rows[i].moveup_button);
  3819. }
  3820. if(!this.hide_move_buttons) {
  3821. self.rows[i].movedown_button = this.getButton('','movedown',this.translate('button_move_down_title'));
  3822. self.rows[i].movedown_button.className += ' movedown';
  3823. self.rows[i].movedown_button.setAttribute('data-i',i);
  3824. self.rows[i].movedown_button.addEventListener('click',function(e) {
  3825. e.preventDefault();
  3826. e.stopPropagation();
  3827. var i = this.getAttribute('data-i')*1;
  3828. var rows = self.getValue();
  3829. if(i>=rows.length-1) return;
  3830. var tmp = rows[i+1];
  3831. rows[i+1] = rows[i];
  3832. rows[i] = tmp;
  3833. self.setValue(rows);
  3834. self.onChange(true);
  3835. },{passive: true});
  3836. controls_holder.appendChild(self.rows[i].movedown_button);
  3837. }
  3838. if(value) self.rows[i].setValue(value);
  3839. },
  3840. addControls: function() {
  3841. var self = this;
  3842. this.collapsed = false;
  3843. this.toggle_button = this.getButton('','collapse',this.translate('button_collapse'));
  3844. if(this.title_controls) {
  3845. this.title_controls.appendChild(this.toggle_button);
  3846. this.toggle_button.addEventListener('click',function(e) {
  3847. e.preventDefault();
  3848. e.stopPropagation();
  3849. if(self.collapsed) {
  3850. self.collapsed = false;
  3851. self.panel.style.display = '';
  3852. self.setButtonText(this,'','collapse',self.translate('button_collapse'));
  3853. }
  3854. else {
  3855. self.collapsed = true;
  3856. self.panel.style.display = 'none';
  3857. self.setButtonText(this,'','expand',self.translate('button_expand'));
  3858. }
  3859. },{passive: true});
  3860. // If it should start collapsed
  3861. if(this.options.collapsed) {
  3862. $trigger(this.toggle_button,'click');
  3863. }
  3864. // Collapse button disabled
  3865. if(this.schema.options && typeof this.schema.options.disable_collapse !== "undefined") {
  3866. if(this.schema.options.disable_collapse) this.toggle_button.style.display = 'none';
  3867. }
  3868. else if(this.jsoneditor.options.disable_collapse) {
  3869. this.toggle_button.style.display = 'none';
  3870. }
  3871. }
  3872. // Add "new row" and "delete last" buttons below editor
  3873. this.add_row_button = this.getButton(this.getItemTitle(),'add',this.translate('button_add_row_title',[this.getItemTitle()]));
  3874. this.add_row_button.addEventListener('click',function(e) {
  3875. e.preventDefault();
  3876. e.stopPropagation();
  3877. self.addRow();
  3878. self.refreshValue();
  3879. self.refreshRowButtons();
  3880. self.onChange(true);
  3881. },{passive: true});
  3882. self.controls.appendChild(this.add_row_button);
  3883. this.delete_last_row_button = this.getButton(this.translate('button_delete_last',[this.getItemTitle()]),'delete',this.translate('button_delete_last_title',[this.getItemTitle()]));
  3884. this.delete_last_row_button.addEventListener('click',function(e) {
  3885. e.preventDefault();
  3886. e.stopPropagation();
  3887. var rows = self.getValue();
  3888. rows.pop();
  3889. self.setValue(rows);
  3890. self.onChange(true);
  3891. },{passive: true});
  3892. self.controls.appendChild(this.delete_last_row_button);
  3893. this.remove_all_rows_button = this.getButton(this.translate('button_delete_all'),'delete',this.translate('button_delete_all_title'));
  3894. this.remove_all_rows_button.addEventListener('click',function(e) {
  3895. e.preventDefault();
  3896. e.stopPropagation();
  3897. self.setValue([]);
  3898. self.onChange(true);
  3899. },{passive: true});
  3900. self.controls.appendChild(this.remove_all_rows_button);
  3901. }
  3902. });
  3903. // Multiple Editor (for when `type` is an array)
  3904. JSONEditor.defaults.editors.multiple = JSONEditor.AbstractEditor.extend({
  3905. register: function() {
  3906. if(this.editors) {
  3907. for(var i=0; i<this.editors.length; i++) {
  3908. if(!this.editors[i]) continue;
  3909. this.editors[i].unregister();
  3910. }
  3911. if(this.editors[this.type]) this.editors[this.type].register();
  3912. }
  3913. this._super();
  3914. },
  3915. unregister: function() {
  3916. this._super();
  3917. if(this.editors) {
  3918. for(var i=0; i<this.editors.length; i++) {
  3919. if(!this.editors[i]) continue;
  3920. this.editors[i].unregister();
  3921. }
  3922. }
  3923. },
  3924. getNumColumns: function() {
  3925. if(!this.editors[this.type]) return 4;
  3926. return Math.max(this.editors[this.type].getNumColumns(),4);
  3927. },
  3928. enable: function() {
  3929. if(this.editors) {
  3930. for(var i=0; i<this.editors.length; i++) {
  3931. if(!this.editors[i]) continue;
  3932. this.editors[i].enable();
  3933. }
  3934. }
  3935. this.switcher.disabled = false;
  3936. this._super();
  3937. },
  3938. disable: function() {
  3939. if(this.editors) {
  3940. for(var i=0; i<this.editors.length; i++) {
  3941. if(!this.editors[i]) continue;
  3942. this.editors[i].disable();
  3943. }
  3944. }
  3945. this.switcher.disabled = true;
  3946. this._super();
  3947. },
  3948. switchEditor: function(i) {
  3949. var self = this;
  3950. if(!this.editors[i]) {
  3951. this.buildChildEditor(i);
  3952. }
  3953. var current_value = self.getValue();
  3954. self.type = i;
  3955. self.register();
  3956. $each(self.editors,function(type,editor) {
  3957. if(!editor) return;
  3958. if(self.type === type) {
  3959. if(self.keep_values) editor.setValue(current_value,true);
  3960. editor.container.style.display = '';
  3961. }
  3962. else editor.container.style.display = 'none';
  3963. });
  3964. self.refreshValue();
  3965. self.refreshHeaderText();
  3966. },
  3967. buildChildEditor: function(i) {
  3968. var self = this;
  3969. var type = this.types[i];
  3970. var holder = self.theme.getChildEditorHolder();
  3971. self.editor_holder.appendChild(holder);
  3972. var schema;
  3973. if(typeof type === "string") {
  3974. schema = $extend({},self.schema);
  3975. schema.type = type;
  3976. }
  3977. else {
  3978. schema = $extend({},self.schema,type);
  3979. schema = self.jsoneditor.expandRefs(schema);
  3980. // If we need to merge `required` arrays
  3981. if(type.required && Array.isArray(type.required) && self.schema.required && Array.isArray(self.schema.required)) {
  3982. schema.required = self.schema.required.concat(type.required);
  3983. }
  3984. }
  3985. var editor = self.jsoneditor.getEditorClass(schema);
  3986. self.editors[i] = self.jsoneditor.createEditor(editor,{
  3987. jsoneditor: self.jsoneditor,
  3988. schema: schema,
  3989. container: holder,
  3990. path: self.path,
  3991. parent: self,
  3992. required: true
  3993. });
  3994. self.editors[i].preBuild();
  3995. self.editors[i].build();
  3996. self.editors[i].postBuild();
  3997. if(self.editors[i].header) self.editors[i].header.style.display = 'none';
  3998. self.editors[i].option = self.switcher_options[i];
  3999. holder.addEventListener('change_header_text',function() {
  4000. self.refreshHeaderText();
  4001. }, {passive: true});
  4002. if(i !== self.type) holder.style.display = 'none';
  4003. },
  4004. preBuild: function() {
  4005. var self = this;
  4006. this.types = [];
  4007. this.type = 0;
  4008. this.editors = [];
  4009. this.validators = [];
  4010. this.keep_values = true;
  4011. if(typeof this.jsoneditor.options.keep_oneof_values !== "undefined") this.keep_values = this.jsoneditor.options.keep_oneof_values;
  4012. if(typeof this.options.keep_oneof_values !== "undefined") this.keep_values = this.options.keep_oneof_values;
  4013. if(this.schema.oneOf) {
  4014. this.oneOf = true;
  4015. this.types = this.schema.oneOf;
  4016. delete this.schema.oneOf;
  4017. }
  4018. else if(this.schema.anyOf) {
  4019. this.anyOf = true;
  4020. this.types = this.schema.anyOf;
  4021. delete this.schema.anyOf;
  4022. }
  4023. else {
  4024. if(!this.schema.type || this.schema.type === "any") {
  4025. this.types = ['string','number','integer','boolean','object','array','null'];
  4026. // If any of these primitive types are disallowed
  4027. if(this.schema.disallow) {
  4028. var disallow = this.schema.disallow;
  4029. if(typeof disallow !== 'object' || !(Array.isArray(disallow))) {
  4030. disallow = [disallow];
  4031. }
  4032. var allowed_types = [];
  4033. $each(this.types,function(i,type) {
  4034. if(disallow.indexOf(type) === -1) allowed_types.push(type);
  4035. });
  4036. this.types = allowed_types;
  4037. }
  4038. }
  4039. else if(Array.isArray(this.schema.type)) {
  4040. this.types = this.schema.type;
  4041. }
  4042. else {
  4043. this.types = [this.schema.type];
  4044. }
  4045. delete this.schema.type;
  4046. }
  4047. this.display_text = this.getDisplayText(this.types);
  4048. },
  4049. build: function() {
  4050. var self = this;
  4051. var container = this.container;
  4052. this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
  4053. this.container.appendChild(this.header);
  4054. this.switcher = this.theme.getSwitcher(this.display_text);
  4055. container.appendChild(this.switcher);
  4056. this.switcher.addEventListener('change',function(e) {
  4057. e.preventDefault();
  4058. e.stopPropagation();
  4059. self.switchEditor(self.display_text.indexOf(this.value));
  4060. self.onChange(true);
  4061. }, {passive: true});
  4062. this.editor_holder = document.createElement('div');
  4063. container.appendChild(this.editor_holder);
  4064. var validator_options = {};
  4065. if(self.jsoneditor.options.custom_validators) {
  4066. validator_options.custom_validators = self.jsoneditor.options.custom_validators;
  4067. }
  4068. this.switcher_options = this.theme.getSwitcherOptions(this.switcher);
  4069. $each(this.types,function(i,type) {
  4070. self.editors[i] = false;
  4071. var schema;
  4072. if(typeof type === "string") {
  4073. schema = $extend({},self.schema);
  4074. schema.type = type;
  4075. }
  4076. else {
  4077. schema = $extend({},self.schema,type);
  4078. // If we need to merge `required` arrays
  4079. if(type.required && Array.isArray(type.required) && self.schema.required && Array.isArray(self.schema.required)) {
  4080. schema.required = self.schema.required.concat(type.required);
  4081. }
  4082. }
  4083. self.validators[i] = new JSONEditor.Validator(self.jsoneditor,schema,validator_options);
  4084. });
  4085. this.switchEditor(0);
  4086. },
  4087. onChildEditorChange: function(editor) {
  4088. if(this.editors[this.type]) {
  4089. this.refreshValue();
  4090. this.refreshHeaderText();
  4091. }
  4092. this._super();
  4093. },
  4094. refreshHeaderText: function() {
  4095. var display_text = this.getDisplayText(this.types);
  4096. $each(this.switcher_options, function(i,option) {
  4097. option.textContent = display_text[i];
  4098. });
  4099. },
  4100. refreshValue: function() {
  4101. this.value = this.editors[this.type].getValue();
  4102. },
  4103. setValue: function(val,initial) {
  4104. // Determine type by getting the first one that validates
  4105. var self = this;
  4106. $each(this.validators, function(i,validator) {
  4107. if(!validator.validate(val).length) {
  4108. self.type = i;
  4109. self.switcher.value = self.display_text[i];
  4110. return false;
  4111. }
  4112. });
  4113. this.switchEditor(this.type);
  4114. this.editors[this.type].setValue(val,initial);
  4115. this.refreshValue();
  4116. self.onChange();
  4117. },
  4118. destroy: function() {
  4119. $each(this.editors, function(type,editor) {
  4120. if(editor) editor.destroy();
  4121. });
  4122. if(this.editor_holder && this.editor_holder.parentNode) this.editor_holder.parentNode.removeChild(this.editor_holder);
  4123. if(this.switcher && this.switcher.parentNode) this.switcher.parentNode.removeChild(this.switcher);
  4124. this._super();
  4125. },
  4126. showValidationErrors: function(errors) {
  4127. var self = this;
  4128. // oneOf and anyOf error paths need to remove the oneOf[i] part before passing to child editors
  4129. if(this.oneOf || this.anyOf) {
  4130. var check_part = this.oneOf? 'oneOf' : 'anyOf';
  4131. $each(this.editors,function(i,editor) {
  4132. if(!editor) return;
  4133. var check = self.path+'.'+check_part+'['+i+']';
  4134. var new_errors = [];
  4135. $each(errors, function(j,error) {
  4136. if(error.path.substr(0,check.length)===check) {
  4137. var new_error = $extend({},error);
  4138. new_error.path = self.path+new_error.path.substr(check.length);
  4139. new_errors.push(new_error);
  4140. }
  4141. });
  4142. editor.showValidationErrors(new_errors);
  4143. });
  4144. }
  4145. else {
  4146. $each(this.editors,function(type,editor) {
  4147. if(!editor) return;
  4148. editor.showValidationErrors(errors);
  4149. });
  4150. }
  4151. }
  4152. });
  4153. // Enum Editor (used for objects and arrays with enumerated values)
  4154. JSONEditor.defaults.editors["enum"] = JSONEditor.AbstractEditor.extend({
  4155. getNumColumns: function() {
  4156. return 4;
  4157. },
  4158. build: function() {
  4159. var container = this.container;
  4160. this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
  4161. this.container.appendChild(this.title);
  4162. this.options.enum_titles = this.options.enum_titles || [];
  4163. this["enum"] = this.schema["enum"];
  4164. this.selected = 0;
  4165. this.select_options = [];
  4166. this.html_values = [];
  4167. var self = this;
  4168. for(var i=0; i<this["enum"].length; i++) {
  4169. this.select_options[i] = this.options.enum_titles[i] || "Value "+(i+1);
  4170. this.html_values[i] = this.getHTML(this["enum"][i]);
  4171. }
  4172. // Switcher
  4173. this.switcher = this.theme.getSwitcher(this.select_options);
  4174. this.container.appendChild(this.switcher);
  4175. // Display area
  4176. this.display_area = this.theme.getIndentedPanel();
  4177. this.container.appendChild(this.display_area);
  4178. if(this.options.hide_display) this.display_area.style.display = "none";
  4179. this.switcher.addEventListener('change',function() {
  4180. self.selected = self.select_options.indexOf(this.value);
  4181. self.value = self["enum"][self.selected];
  4182. self.refreshValue();
  4183. self.onChange(true);
  4184. }, {passive: true});
  4185. this.value = this["enum"][0];
  4186. this.refreshValue();
  4187. if(this["enum"].length === 1) this.switcher.style.display = 'none';
  4188. },
  4189. refreshValue: function() {
  4190. var self = this;
  4191. self.selected = -1;
  4192. var stringified = JSON.stringify(this.value);
  4193. $each(this["enum"], function(i, el) {
  4194. if(stringified === JSON.stringify(el)) {
  4195. self.selected = i;
  4196. return false;
  4197. }
  4198. });
  4199. if(self.selected<0) {
  4200. self.setValue(self["enum"][0]);
  4201. return;
  4202. }
  4203. this.switcher.value = this.select_options[this.selected];
  4204. this.display_area.innerHTML = this.html_values[this.selected];
  4205. },
  4206. enable: function() {
  4207. if(!this.always_disabled) this.switcher.disabled = false;
  4208. this._super();
  4209. },
  4210. disable: function() {
  4211. this.switcher.disabled = true;
  4212. this._super();
  4213. },
  4214. getHTML: function(el) {
  4215. var self = this;
  4216. if(el === null) {
  4217. return '<em>null</em>';
  4218. }
  4219. // Array or Object
  4220. else if(typeof el === "object") {
  4221. // TODO: use theme
  4222. var ret = '';
  4223. $each(el,function(i,child) {
  4224. var html = self.getHTML(child);
  4225. // Add the keys to object children
  4226. if(!(Array.isArray(el))) {
  4227. // TODO: use theme
  4228. html = '<div><em>'+i+'</em>: '+html+'</div>';
  4229. }
  4230. // TODO: use theme
  4231. ret += '<li>'+html+'</li>';
  4232. });
  4233. if(Array.isArray(el)) ret = '<ol>'+ret+'</ol>';
  4234. else ret = "<ul style='margin-top:0;margin-bottom:0;padding-top:0;padding-bottom:0;'>"+ret+'</ul>';
  4235. return ret;
  4236. }
  4237. // Boolean
  4238. else if(typeof el === "boolean") {
  4239. return el? 'true' : 'false';
  4240. }
  4241. // String
  4242. else if(typeof el === "string") {
  4243. return el.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  4244. }
  4245. // Number
  4246. else {
  4247. return el;
  4248. }
  4249. },
  4250. setValue: function(val) {
  4251. if(this.value !== val) {
  4252. this.value = val;
  4253. this.refreshValue();
  4254. this.onChange();
  4255. }
  4256. },
  4257. destroy: function() {
  4258. if(this.display_area && this.display_area.parentNode) this.display_area.parentNode.removeChild(this.display_area);
  4259. if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
  4260. if(this.switcher && this.switcher.parentNode) this.switcher.parentNode.removeChild(this.switcher);
  4261. this._super();
  4262. }
  4263. });
  4264. JSONEditor.defaults.editors.select = JSONEditor.AbstractEditor.extend({
  4265. setValue: function(value,initial) {
  4266. value = this.typecast(value||'');
  4267. // Sanitize value before setting it
  4268. var sanitized = value;
  4269. if(this.enum_values.indexOf(sanitized) < 0) {
  4270. sanitized = this.enum_values[0];
  4271. }
  4272. if(this.value === sanitized) {
  4273. return;
  4274. }
  4275. this.input.value = this.enum_options[this.enum_values.indexOf(sanitized)];
  4276. if(this.select2) this.select2.select2('val',this.input.value);
  4277. this.value = sanitized;
  4278. this.onChange();
  4279. },
  4280. register: function() {
  4281. this._super();
  4282. if(!this.input) return;
  4283. this.input.setAttribute('name',this.formname);
  4284. },
  4285. unregister: function() {
  4286. this._super();
  4287. if(!this.input) return;
  4288. this.input.removeAttribute('name');
  4289. },
  4290. getNumColumns: function() {
  4291. if(!this.enum_options) return 3;
  4292. var longest_text = this.getTitle().length;
  4293. for(var i=0; i<this.enum_options.length; i++) {
  4294. longest_text = Math.max(longest_text,this.enum_options[i].length+4);
  4295. }
  4296. return Math.min(12,Math.max(longest_text/7,2));
  4297. },
  4298. typecast: function(value) {
  4299. if(this.schema.type === "boolean") {
  4300. return !!value;
  4301. }
  4302. else if(this.schema.type === "number") {
  4303. return 1*value;
  4304. }
  4305. else if(this.schema.type === "integer") {
  4306. return Math.floor(value*1);
  4307. }
  4308. else {
  4309. return ""+value;
  4310. }
  4311. },
  4312. getValue: function() {
  4313. return this.value;
  4314. },
  4315. preBuild: function() {
  4316. var self = this;
  4317. this.input_type = 'select';
  4318. this.enum_options = [];
  4319. this.enum_values = [];
  4320. this.enum_display = [];
  4321. var i;
  4322. // Enum options enumerated
  4323. if(this.schema["enum"]) {
  4324. var display = this.schema.options && this.schema.options.enum_titles || [];
  4325. $each(this.schema["enum"],function(i,option) {
  4326. self.enum_options[i] = ""+option;
  4327. self.enum_display[i] = ""+(display[i] || option);
  4328. self.enum_values[i] = self.typecast(option);
  4329. });
  4330. if(!this.isRequired()){
  4331. self.enum_display.unshift(' ');
  4332. self.enum_options.unshift('undefined');
  4333. self.enum_values.unshift(undefined);
  4334. }
  4335. }
  4336. // Boolean
  4337. else if(this.schema.type === "boolean") {
  4338. self.enum_display = this.schema.options && this.schema.options.enum_titles || ['true','false'];
  4339. self.enum_options = ['1',''];
  4340. self.enum_values = [true,false];
  4341. if(!this.isRequired()){
  4342. self.enum_display.unshift(' ');
  4343. self.enum_options.unshift('undefined');
  4344. self.enum_values.unshift(undefined);
  4345. }
  4346. }
  4347. // Dynamic Enum
  4348. else if(this.schema.enumSource) {
  4349. this.enumSource = [];
  4350. this.enum_display = [];
  4351. this.enum_options = [];
  4352. this.enum_values = [];
  4353. // Shortcut declaration for using a single array
  4354. if(!(Array.isArray(this.schema.enumSource))) {
  4355. if(this.schema.enumValue) {
  4356. this.enumSource = [
  4357. {
  4358. source: this.schema.enumSource,
  4359. value: this.schema.enumValue
  4360. }
  4361. ];
  4362. }
  4363. else {
  4364. this.enumSource = [
  4365. {
  4366. source: this.schema.enumSource
  4367. }
  4368. ];
  4369. }
  4370. }
  4371. else {
  4372. for(i=0; i<this.schema.enumSource.length; i++) {
  4373. // Shorthand for watched variable
  4374. if(typeof this.schema.enumSource[i] === "string") {
  4375. this.enumSource[i] = {
  4376. source: this.schema.enumSource[i]
  4377. };
  4378. }
  4379. // Make a copy of the schema
  4380. else if(!(Array.isArray(this.schema.enumSource[i]))) {
  4381. this.enumSource[i] = $extend({},this.schema.enumSource[i]);
  4382. }
  4383. else {
  4384. this.enumSource[i] = this.schema.enumSource[i];
  4385. }
  4386. }
  4387. }
  4388. // Now, enumSource is an array of sources
  4389. // Walk through this array and fix up the values
  4390. for(i=0; i<this.enumSource.length; i++) {
  4391. if(this.enumSource[i].value) {
  4392. this.enumSource[i].value = this.jsoneditor.compileTemplate(this.enumSource[i].value, this.template_engine);
  4393. }
  4394. if(this.enumSource[i].title) {
  4395. this.enumSource[i].title = this.jsoneditor.compileTemplate(this.enumSource[i].title, this.template_engine);
  4396. }
  4397. if(this.enumSource[i].filter) {
  4398. this.enumSource[i].filter = this.jsoneditor.compileTemplate(this.enumSource[i].filter, this.template_engine);
  4399. }
  4400. }
  4401. }
  4402. // Other, not supported
  4403. else {
  4404. throw "'select' editor requires the enum property to be set.";
  4405. }
  4406. },
  4407. build: function() {
  4408. var self = this;
  4409. if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
  4410. if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
  4411. if(this.options.compact) this.container.className += ' compact';
  4412. this.input = this.theme.getSelectInput(this.enum_options);
  4413. this.theme.setSelectOptions(this.input,this.enum_options,this.enum_display);
  4414. if(this.schema.readOnly || this.schema.readonly) {
  4415. this.always_disabled = true;
  4416. this.input.disabled = true;
  4417. }
  4418. this.input.addEventListener('change',function(e) {
  4419. e.preventDefault();
  4420. e.stopPropagation();
  4421. self.onInputChange();
  4422. }, {passive: true});
  4423. this.control = this.theme.getFormControl(this.label, this.input, this.description);
  4424. this.container.appendChild(this.control);
  4425. this.value = this.enum_values[0];
  4426. },
  4427. onInputChange: function() {
  4428. var val = this.input.value;
  4429. var new_val;
  4430. // Invalid option, use first option instead
  4431. if(this.enum_options.indexOf(val) === -1) {
  4432. new_val = this.enum_values[0];
  4433. }
  4434. else {
  4435. new_val = this.enum_values[this.enum_options.indexOf(val)];
  4436. }
  4437. // If valid hasn't changed
  4438. if(new_val === this.value) return;
  4439. // Store new value and propogate change event
  4440. this.value = new_val;
  4441. this.onChange(true);
  4442. },
  4443. setupSelect2: function() {
  4444. // If the Select2 library is loaded use it when we have lots of items
  4445. if( window.jQuery && window.jQuery.fn && window.jQuery.fn.select2 && (this.enum_options.length > 2 || (this.enum_options.length && this.enumSource))) {
  4446. var options = $extend({},JSONEditor.plugins.select2);
  4447. if(this.schema.options && this.schema.options.select2_options) options = $extend(options,this.schema.options.select2_options);
  4448. this.select2 = window.jQuery(this.input).select2(options);
  4449. var self = this;
  4450. this.select2.on('select2-blur',function() {
  4451. self.input.value = self.select2.select2('val');
  4452. self.onInputChange();
  4453. });
  4454. this.select2.on('change',function() {
  4455. self.input.value = self.select2.select2('val');
  4456. self.onInputChange();
  4457. });
  4458. }
  4459. else {
  4460. this.select2 = null;
  4461. }
  4462. },
  4463. postBuild: function() {
  4464. this._super();
  4465. this.theme.afterInputReady(this.input);
  4466. this.setupSelect2();
  4467. },
  4468. onWatchedFieldChange: function() {
  4469. var self = this, vars, j;
  4470. // If this editor uses a dynamic select box
  4471. if(this.enumSource) {
  4472. vars = this.getWatchedFieldValues();
  4473. var select_options = [];
  4474. var select_titles = [];
  4475. for(var i=0; i<this.enumSource.length; i++) {
  4476. // Constant values
  4477. if(Array.isArray(this.enumSource[i])) {
  4478. select_options = select_options.concat(this.enumSource[i]);
  4479. select_titles = select_titles.concat(this.enumSource[i]);
  4480. }
  4481. else {
  4482. var items = [];
  4483. // Static list of items
  4484. if(Array.isArray(this.enumSource[i].source)) {
  4485. items = this.enumSource[i].source;
  4486. // A watched field
  4487. } else {
  4488. items = vars[this.enumSource[i].source];
  4489. }
  4490. if(items) {
  4491. // Only use a predefined part of the array
  4492. if(this.enumSource[i].slice) {
  4493. items = Array.prototype.slice.apply(items,this.enumSource[i].slice);
  4494. }
  4495. // Filter the items
  4496. if(this.enumSource[i].filter) {
  4497. var new_items = [];
  4498. for(j=0; j<items.length; j++) {
  4499. if(this.enumSource[i].filter({i:j,item:items[j],watched:vars})) new_items.push(items[j]);
  4500. }
  4501. items = new_items;
  4502. }
  4503. var item_titles = [];
  4504. var item_values = [];
  4505. for(j=0; j<items.length; j++) {
  4506. var item = items[j];
  4507. // Rendered value
  4508. if(this.enumSource[i].value) {
  4509. item_values[j] = this.enumSource[i].value({
  4510. i: j,
  4511. item: item
  4512. });
  4513. }
  4514. // Use value directly
  4515. else {
  4516. item_values[j] = items[j];
  4517. }
  4518. // Rendered title
  4519. if(this.enumSource[i].title) {
  4520. item_titles[j] = this.enumSource[i].title({
  4521. i: j,
  4522. item: item
  4523. });
  4524. }
  4525. // Use value as the title also
  4526. else {
  4527. item_titles[j] = item_values[j];
  4528. }
  4529. }
  4530. // TODO: sort
  4531. select_options = select_options.concat(item_values);
  4532. select_titles = select_titles.concat(item_titles);
  4533. }
  4534. }
  4535. }
  4536. var prev_value = this.value;
  4537. this.theme.setSelectOptions(this.input, select_options, select_titles);
  4538. this.enum_options = select_options;
  4539. this.enum_display = select_titles;
  4540. this.enum_values = select_options;
  4541. if(this.select2) {
  4542. this.select2.select2('destroy');
  4543. }
  4544. // If the previous value is still in the new select options, stick with it
  4545. if(select_options.indexOf(prev_value) !== -1) {
  4546. this.input.value = prev_value;
  4547. this.value = prev_value;
  4548. }
  4549. // Otherwise, set the value to the first select option
  4550. else {
  4551. this.input.value = select_options[0];
  4552. this.value = select_options[0] || "";
  4553. if(this.parent) this.parent.onChildEditorChange(this);
  4554. else this.jsoneditor.onChange();
  4555. this.jsoneditor.notifyWatchers(this.path);
  4556. }
  4557. this.setupSelect2();
  4558. }
  4559. this._super();
  4560. },
  4561. enable: function() {
  4562. if(!this.always_disabled) {
  4563. this.input.disabled = false;
  4564. if(this.select2) this.select2.select2("enable",true);
  4565. }
  4566. this._super();
  4567. },
  4568. disable: function() {
  4569. this.input.disabled = true;
  4570. if(this.select2) this.select2.select2("enable",false);
  4571. this._super();
  4572. },
  4573. destroy: function() {
  4574. if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
  4575. if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
  4576. if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
  4577. if(this.select2) {
  4578. this.select2.select2('destroy');
  4579. this.select2 = null;
  4580. }
  4581. this._super();
  4582. }
  4583. });
  4584. JSONEditor.defaults.editors.selectize = JSONEditor.AbstractEditor.extend({
  4585. setValue: function(value,initial) {
  4586. value = this.typecast(value||'');
  4587. // Sanitize value before setting it
  4588. var sanitized = value;
  4589. if(this.enum_values.indexOf(sanitized) < 0) {
  4590. sanitized = this.enum_values[0];
  4591. }
  4592. if(this.value === sanitized) {
  4593. return;
  4594. }
  4595. this.input.value = this.enum_options[this.enum_values.indexOf(sanitized)];
  4596. if(this.selectize) {
  4597. this.selectize[0].selectize.addItem(sanitized);
  4598. }
  4599. this.value = sanitized;
  4600. this.onChange();
  4601. },
  4602. register: function() {
  4603. this._super();
  4604. if(!this.input) return;
  4605. this.input.setAttribute('name',this.formname);
  4606. },
  4607. unregister: function() {
  4608. this._super();
  4609. if(!this.input) return;
  4610. this.input.removeAttribute('name');
  4611. },
  4612. getNumColumns: function() {
  4613. if(!this.enum_options) return 3;
  4614. var longest_text = this.getTitle().length;
  4615. for(var i=0; i<this.enum_options.length; i++) {
  4616. longest_text = Math.max(longest_text,this.enum_options[i].length+4);
  4617. }
  4618. return Math.min(12,Math.max(longest_text/7,2));
  4619. },
  4620. typecast: function(value) {
  4621. if(this.schema.type === "boolean") {
  4622. return !!value;
  4623. }
  4624. else if(this.schema.type === "number") {
  4625. return 1*value;
  4626. }
  4627. else if(this.schema.type === "integer") {
  4628. return Math.floor(value*1);
  4629. }
  4630. else {
  4631. return ""+value;
  4632. }
  4633. },
  4634. getValue: function() {
  4635. return this.value;
  4636. },
  4637. preBuild: function() {
  4638. var self = this;
  4639. this.input_type = 'select';
  4640. this.enum_options = [];
  4641. this.enum_values = [];
  4642. this.enum_display = [];
  4643. var i;
  4644. // Enum options enumerated
  4645. if(this.schema.enum) {
  4646. var display = this.schema.options && this.schema.options.enum_titles || [];
  4647. $each(this.schema.enum,function(i,option) {
  4648. self.enum_options[i] = ""+option;
  4649. self.enum_display[i] = ""+(display[i] || option);
  4650. self.enum_values[i] = self.typecast(option);
  4651. });
  4652. }
  4653. // Boolean
  4654. else if(this.schema.type === "boolean") {
  4655. self.enum_display = this.schema.options && this.schema.options.enum_titles || ['true','false'];
  4656. self.enum_options = ['1','0'];
  4657. self.enum_values = [true,false];
  4658. }
  4659. // Dynamic Enum
  4660. else if(this.schema.enumSource) {
  4661. this.enumSource = [];
  4662. this.enum_display = [];
  4663. this.enum_options = [];
  4664. this.enum_values = [];
  4665. // Shortcut declaration for using a single array
  4666. if(!(Array.isArray(this.schema.enumSource))) {
  4667. if(this.schema.enumValue) {
  4668. this.enumSource = [
  4669. {
  4670. source: this.schema.enumSource,
  4671. value: this.schema.enumValue
  4672. }
  4673. ];
  4674. }
  4675. else {
  4676. this.enumSource = [
  4677. {
  4678. source: this.schema.enumSource
  4679. }
  4680. ];
  4681. }
  4682. }
  4683. else {
  4684. for(i=0; i<this.schema.enumSource.length; i++) {
  4685. // Shorthand for watched variable
  4686. if(typeof this.schema.enumSource[i] === "string") {
  4687. this.enumSource[i] = {
  4688. source: this.schema.enumSource[i]
  4689. };
  4690. }
  4691. // Make a copy of the schema
  4692. else if(!(Array.isArray(this.schema.enumSource[i]))) {
  4693. this.enumSource[i] = $extend({},this.schema.enumSource[i]);
  4694. }
  4695. else {
  4696. this.enumSource[i] = this.schema.enumSource[i];
  4697. }
  4698. }
  4699. }
  4700. // Now, enumSource is an array of sources
  4701. // Walk through this array and fix up the values
  4702. for(i=0; i<this.enumSource.length; i++) {
  4703. if(this.enumSource[i].value) {
  4704. this.enumSource[i].value = this.jsoneditor.compileTemplate(this.enumSource[i].value, this.template_engine);
  4705. }
  4706. if(this.enumSource[i].title) {
  4707. this.enumSource[i].title = this.jsoneditor.compileTemplate(this.enumSource[i].title, this.template_engine);
  4708. }
  4709. if(this.enumSource[i].filter) {
  4710. this.enumSource[i].filter = this.jsoneditor.compileTemplate(this.enumSource[i].filter, this.template_engine);
  4711. }
  4712. }
  4713. }
  4714. // Other, not supported
  4715. else {
  4716. throw "'select' editor requires the enum property to be set.";
  4717. }
  4718. },
  4719. build: function() {
  4720. var self = this;
  4721. if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
  4722. if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
  4723. if(this.options.compact) this.container.className += ' compact';
  4724. this.input = this.theme.getSelectInput(this.enum_options);
  4725. this.theme.setSelectOptions(this.input,this.enum_options,this.enum_display);
  4726. if(this.schema.readOnly || this.schema.readonly) {
  4727. this.always_disabled = true;
  4728. this.input.disabled = true;
  4729. }
  4730. this.input.addEventListener('change',function(e) {
  4731. e.preventDefault();
  4732. e.stopPropagation();
  4733. self.onInputChange();
  4734. });
  4735. this.control = this.theme.getFormControl(this.label, this.input, this.description);
  4736. this.container.appendChild(this.control);
  4737. this.value = this.enum_values[0];
  4738. },
  4739. onInputChange: function() {
  4740. var val = this.input.value;
  4741. var sanitized = val;
  4742. if(this.enum_options.indexOf(val) === -1) {
  4743. sanitized = this.enum_options[0];
  4744. }
  4745. this.value = this.enum_values[this.enum_options.indexOf(val)];
  4746. this.onChange(true);
  4747. },
  4748. setupSelectize: function() {
  4749. // If the Selectize library is loaded use it when we have lots of items
  4750. var self = this;
  4751. if(window.jQuery && window.jQuery.fn && window.jQuery.fn.selectize && (this.enum_options.length >= 2 || (this.enum_options.length && this.enumSource))) {
  4752. var options = $extend({},JSONEditor.plugins.selectize);
  4753. if(this.schema.options && this.schema.options.selectize_options) options = $extend(options,this.schema.options.selectize_options);
  4754. this.selectize = window.jQuery(this.input).selectize($extend(options,
  4755. {
  4756. create: true,
  4757. onChange : function() {
  4758. self.onInputChange();
  4759. }
  4760. }));
  4761. }
  4762. else {
  4763. this.selectize = null;
  4764. }
  4765. },
  4766. postBuild: function() {
  4767. this._super();
  4768. this.theme.afterInputReady(this.input);
  4769. this.setupSelectize();
  4770. },
  4771. onWatchedFieldChange: function() {
  4772. var self = this, vars, j;
  4773. // If this editor uses a dynamic select box
  4774. if(this.enumSource) {
  4775. vars = this.getWatchedFieldValues();
  4776. var select_options = [];
  4777. var select_titles = [];
  4778. for(var i=0; i<this.enumSource.length; i++) {
  4779. // Constant values
  4780. if(Array.isArray(this.enumSource[i])) {
  4781. select_options = select_options.concat(this.enumSource[i]);
  4782. select_titles = select_titles.concat(this.enumSource[i]);
  4783. }
  4784. // A watched field
  4785. else if(vars[this.enumSource[i].source]) {
  4786. var items = vars[this.enumSource[i].source];
  4787. // Only use a predefined part of the array
  4788. if(this.enumSource[i].slice) {
  4789. items = Array.prototype.slice.apply(items,this.enumSource[i].slice);
  4790. }
  4791. // Filter the items
  4792. if(this.enumSource[i].filter) {
  4793. var new_items = [];
  4794. for(j=0; j<items.length; j++) {
  4795. if(this.enumSource[i].filter({i:j,item:items[j]})) new_items.push(items[j]);
  4796. }
  4797. items = new_items;
  4798. }
  4799. var item_titles = [];
  4800. var item_values = [];
  4801. for(j=0; j<items.length; j++) {
  4802. var item = items[j];
  4803. // Rendered value
  4804. if(this.enumSource[i].value) {
  4805. item_values[j] = this.enumSource[i].value({
  4806. i: j,
  4807. item: item
  4808. });
  4809. }
  4810. // Use value directly
  4811. else {
  4812. item_values[j] = items[j];
  4813. }
  4814. // Rendered title
  4815. if(this.enumSource[i].title) {
  4816. item_titles[j] = this.enumSource[i].title({
  4817. i: j,
  4818. item: item
  4819. });
  4820. }
  4821. // Use value as the title also
  4822. else {
  4823. item_titles[j] = item_values[j];
  4824. }
  4825. }
  4826. // TODO: sort
  4827. select_options = select_options.concat(item_values);
  4828. select_titles = select_titles.concat(item_titles);
  4829. }
  4830. }
  4831. var prev_value = this.value;
  4832. this.theme.setSelectOptions(this.input, select_options, select_titles);
  4833. this.enum_options = select_options;
  4834. this.enum_display = select_titles;
  4835. this.enum_values = select_options;
  4836. // If the previous value is still in the new select options, stick with it
  4837. if(select_options.indexOf(prev_value) !== -1) {
  4838. this.input.value = prev_value;
  4839. this.value = prev_value;
  4840. }
  4841. // Otherwise, set the value to the first select option
  4842. else {
  4843. this.input.value = select_options[0];
  4844. this.value = select_options[0] || "";
  4845. if(this.parent) this.parent.onChildEditorChange(this);
  4846. else this.jsoneditor.onChange();
  4847. this.jsoneditor.notifyWatchers(this.path);
  4848. }
  4849. if(this.selectize) {
  4850. // Update the Selectize options
  4851. this.updateSelectizeOptions(select_options);
  4852. }
  4853. else {
  4854. this.setupSelectize();
  4855. }
  4856. this._super();
  4857. }
  4858. },
  4859. updateSelectizeOptions: function(select_options) {
  4860. var selectized = this.selectize[0].selectize,
  4861. self = this;
  4862. selectized.off();
  4863. selectized.clearOptions();
  4864. for(var n in select_options) {
  4865. selectized.addOption({value:select_options[n],text:select_options[n]});
  4866. }
  4867. selectized.addItem(this.value);
  4868. selectized.on('change',function() {
  4869. self.onInputChange();
  4870. });
  4871. },
  4872. enable: function() {
  4873. if(!this.always_disabled) {
  4874. this.input.disabled = false;
  4875. if(this.selectize) {
  4876. this.selectize[0].selectize.unlock();
  4877. }
  4878. }
  4879. this._super();
  4880. },
  4881. disable: function() {
  4882. this.input.disabled = true;
  4883. if(this.selectize) {
  4884. this.selectize[0].selectize.lock();
  4885. }
  4886. this._super();
  4887. },
  4888. destroy: function() {
  4889. if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
  4890. if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
  4891. if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
  4892. if(this.selectize) {
  4893. this.selectize[0].selectize.destroy();
  4894. this.selectize = null;
  4895. }
  4896. this._super();
  4897. }
  4898. });
  4899. JSONEditor.defaults.editors.multiselect = JSONEditor.AbstractEditor.extend({
  4900. preBuild: function() {
  4901. this._super();
  4902. var i;
  4903. this.select_options = {};
  4904. this.select_values = {};
  4905. var items_schema = this.jsoneditor.expandRefs(this.schema.items || {});
  4906. var e = items_schema["enum"] || [];
  4907. var t = items_schema.options? items_schema.options.enum_titles || [] : [];
  4908. this.option_keys = [];
  4909. this.option_titles = [];
  4910. for(i=0; i<e.length; i++) {
  4911. // If the sanitized value is different from the enum value, don't include it
  4912. if(this.sanitize(e[i]) !== e[i]) continue;
  4913. this.option_keys.push(e[i]+"");
  4914. this.option_titles.push((t[i]||e[i])+"");
  4915. this.select_values[e[i]+""] = e[i];
  4916. }
  4917. },
  4918. build: function() {
  4919. var self = this, i;
  4920. if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
  4921. if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
  4922. if((!this.schema.format && this.option_keys.length < 8) || this.schema.format === "checkbox") {
  4923. this.input_type = 'checkboxes';
  4924. this.inputs = {};
  4925. this.controls = {};
  4926. for(i=0; i<this.option_keys.length; i++) {
  4927. this.inputs[this.option_keys[i]] = this.theme.getCheckbox();
  4928. this.select_options[this.option_keys[i]] = this.inputs[this.option_keys[i]];
  4929. var label = this.theme.getCheckboxLabel(this.option_titles[i]);
  4930. this.controls[this.option_keys[i]] = this.theme.getFormControl(label, this.inputs[this.option_keys[i]]);
  4931. }
  4932. this.control = this.theme.getMultiCheckboxHolder(this.controls,this.label,this.description);
  4933. }
  4934. else {
  4935. this.input_type = 'select';
  4936. this.input = this.theme.getSelectInput(this.option_keys);
  4937. this.theme.setSelectOptions(this.input,this.option_keys,this.option_titles);
  4938. this.input.multiple = true;
  4939. this.input.size = Math.min(10,this.option_keys.length);
  4940. for(i=0; i<this.option_keys.length; i++) {
  4941. this.select_options[this.option_keys[i]] = this.input.children[i];
  4942. }
  4943. if(this.schema.readOnly || this.schema.readonly) {
  4944. this.always_disabled = true;
  4945. this.input.disabled = true;
  4946. }
  4947. this.control = this.theme.getFormControl(this.label, this.input, this.description);
  4948. }
  4949. this.container.appendChild(this.control);
  4950. this.control.addEventListener('change',function(e) {
  4951. e.preventDefault();
  4952. e.stopPropagation();
  4953. var new_value = [];
  4954. for(i = 0; i<self.option_keys.length; i++) {
  4955. if(self.select_options[self.option_keys[i]].selected || self.select_options[self.option_keys[i]].checked) new_value.push(self.select_values[self.option_keys[i]]);
  4956. }
  4957. self.updateValue(new_value);
  4958. self.onChange(true);
  4959. }, {passive: true});
  4960. },
  4961. setValue: function(value, initial) {
  4962. var i;
  4963. value = value || [];
  4964. if(typeof value !== "object") value = [value];
  4965. else if(!(Array.isArray(value))) value = [];
  4966. // Make sure we are dealing with an array of strings so we can check for strict equality
  4967. for(i=0; i<value.length; i++) {
  4968. if(typeof value[i] !== "string") value[i] += "";
  4969. }
  4970. // Update selected status of options
  4971. for(i in this.select_options) {
  4972. if(!this.select_options.hasOwnProperty(i)) continue;
  4973. this.select_options[i][this.input_type === "select"? "selected" : "checked"] = (value.indexOf(i) !== -1);
  4974. }
  4975. this.updateValue(value);
  4976. this.onChange();
  4977. },
  4978. setupSelect2: function() {
  4979. if(window.jQuery && window.jQuery.fn && window.jQuery.fn.select2) {
  4980. var options = window.jQuery.extend({},JSONEditor.plugins.select2);
  4981. if(this.schema.options && this.schema.options.select2_options) options = $extend(options,this.schema.options.select2_options);
  4982. this.select2 = window.jQuery(this.input).select2(options);
  4983. var self = this;
  4984. this.select2.on('select2-blur',function() {
  4985. var val =self.select2.select2('val');
  4986. self.value = val;
  4987. self.onChange(true);
  4988. });
  4989. }
  4990. else {
  4991. this.select2 = null;
  4992. }
  4993. },
  4994. onInputChange: function() {
  4995. this.value = this.input.value;
  4996. this.onChange(true);
  4997. },
  4998. postBuild: function() {
  4999. this._super();
  5000. this.setupSelect2();
  5001. },
  5002. register: function() {
  5003. this._super();
  5004. if(!this.input) return;
  5005. this.input.setAttribute('name',this.formname);
  5006. },
  5007. unregister: function() {
  5008. this._super();
  5009. if(!this.input) return;
  5010. this.input.removeAttribute('name');
  5011. },
  5012. getNumColumns: function() {
  5013. var longest_text = this.getTitle().length;
  5014. for(var i in this.select_values) {
  5015. if(!this.select_values.hasOwnProperty(i)) continue;
  5016. longest_text = Math.max(longest_text,(this.select_values[i]+"").length+4);
  5017. }
  5018. return Math.min(12,Math.max(longest_text/7,2));
  5019. },
  5020. updateValue: function(value) {
  5021. var changed = false;
  5022. var new_value = [];
  5023. for(var i=0; i<value.length; i++) {
  5024. if(!this.select_options[value[i]+""]) {
  5025. changed = true;
  5026. continue;
  5027. }
  5028. var sanitized = this.sanitize(this.select_values[value[i]]);
  5029. new_value.push(sanitized);
  5030. if(sanitized !== value[i]) changed = true;
  5031. }
  5032. this.value = new_value;
  5033. if(this.select2) this.select2.select2('val',this.value);
  5034. return changed;
  5035. },
  5036. sanitize: function(value) {
  5037. if(this.schema.items.type === "number") {
  5038. return 1*value;
  5039. }
  5040. else if(this.schema.items.type === "integer") {
  5041. return Math.floor(value*1);
  5042. }
  5043. else {
  5044. return ""+value;
  5045. }
  5046. },
  5047. enable: function() {
  5048. if(!this.always_disabled) {
  5049. if(this.input) {
  5050. this.input.disabled = false;
  5051. }
  5052. else if(this.inputs) {
  5053. for(var i in this.inputs) {
  5054. if(!this.inputs.hasOwnProperty(i)) continue;
  5055. this.inputs[i].disabled = false;
  5056. }
  5057. }
  5058. if(this.select2) this.select2.select2("enable",true);
  5059. }
  5060. this._super();
  5061. },
  5062. disable: function() {
  5063. if(this.input) {
  5064. this.input.disabled = true;
  5065. }
  5066. else if(this.inputs) {
  5067. for(var i in this.inputs) {
  5068. if(!this.inputs.hasOwnProperty(i)) continue;
  5069. this.inputs[i].disabled = true;
  5070. }
  5071. }
  5072. if(this.select2) this.select2.select2("enable",false);
  5073. this._super();
  5074. },
  5075. destroy: function() {
  5076. if(this.select2) {
  5077. this.select2.select2('destroy');
  5078. this.select2 = null;
  5079. }
  5080. this._super();
  5081. }
  5082. });
  5083. JSONEditor.defaults.editors.base64 = JSONEditor.AbstractEditor.extend({
  5084. getNumColumns: function() {
  5085. return 4;
  5086. },
  5087. build: function() {
  5088. var self = this;
  5089. this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
  5090. // Input that holds the base64 string
  5091. this.input = this.theme.getFormInputField('hidden');
  5092. this.container.appendChild(this.input);
  5093. // Don't show uploader if this is readonly
  5094. if(!this.schema.readOnly && !this.schema.readonly) {
  5095. if(!window.FileReader) throw "FileReader required for base64 editor";
  5096. // File uploader
  5097. this.uploader = this.theme.getFormInputField('file');
  5098. this.uploader.addEventListener('change',function(e) {
  5099. e.preventDefault();
  5100. e.stopPropagation();
  5101. if(this.files && this.files.length) {
  5102. var fr = new FileReader();
  5103. fr.onload = function(evt) {
  5104. self.value = evt.target.result;
  5105. self.refreshPreview();
  5106. self.onChange(true);
  5107. fr = null;
  5108. };
  5109. fr.readAsDataURL(this.files[0]);
  5110. }
  5111. }, {passive: true});
  5112. }
  5113. this.preview = this.theme.getFormInputDescription(this.schema.description);
  5114. this.container.appendChild(this.preview);
  5115. this.control = this.theme.getFormControl(this.label, this.uploader||this.input, this.preview);
  5116. this.container.appendChild(this.control);
  5117. },
  5118. refreshPreview: function() {
  5119. if(this.last_preview === this.value) return;
  5120. this.last_preview = this.value;
  5121. this.preview.innerHTML = '';
  5122. if(!this.value) return;
  5123. var mime = this.value.match(/^data:([^;,]+)[;,]/);
  5124. if(mime) mime = mime[1];
  5125. if(!mime) {
  5126. this.preview.innerHTML = '<em>Invalid data URI</em>';
  5127. }
  5128. else {
  5129. this.preview.innerHTML = '<strong>Type:</strong> '+mime+', <strong>Size:</strong> '+Math.floor((this.value.length-this.value.split(',')[0].length-1)/1.33333)+' bytes';
  5130. if(mime.substr(0,5)==="image") {
  5131. this.preview.innerHTML += '<br>';
  5132. var img = document.createElement('img');
  5133. img.style.maxWidth = '100%';
  5134. img.style.maxHeight = '100px';
  5135. img.src = this.value;
  5136. this.preview.appendChild(img);
  5137. }
  5138. }
  5139. },
  5140. enable: function() {
  5141. if(this.uploader) this.uploader.disabled = false;
  5142. this._super();
  5143. },
  5144. disable: function() {
  5145. if(this.uploader) this.uploader.disabled = true;
  5146. this._super();
  5147. },
  5148. setValue: function(val) {
  5149. if(this.value !== val) {
  5150. this.value = val;
  5151. this.input.value = this.value;
  5152. this.refreshPreview();
  5153. this.onChange();
  5154. }
  5155. },
  5156. destroy: function() {
  5157. if(this.preview && this.preview.parentNode) this.preview.parentNode.removeChild(this.preview);
  5158. if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
  5159. if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
  5160. if(this.uploader && this.uploader.parentNode) this.uploader.parentNode.removeChild(this.uploader);
  5161. this._super();
  5162. }
  5163. });
  5164. JSONEditor.defaults.editors.upload = JSONEditor.AbstractEditor.extend({
  5165. getNumColumns: function() {
  5166. return 4;
  5167. },
  5168. build: function() {
  5169. var self = this;
  5170. this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
  5171. // Input that holds the base64 string
  5172. this.input = this.theme.getFormInputField('hidden');
  5173. this.container.appendChild(this.input);
  5174. // Don't show uploader if this is readonly
  5175. if(!this.schema.readOnly && !this.schema.readonly) {
  5176. if(!this.jsoneditor.options.upload) throw "Upload handler required for upload editor";
  5177. // File uploader
  5178. this.uploader = this.theme.getFormInputField('file');
  5179. this.uploader.addEventListener('change',function(e) {
  5180. e.preventDefault();
  5181. e.stopPropagation();
  5182. if(this.files && this.files.length) {
  5183. var fr = new FileReader();
  5184. fr.onload = function(evt) {
  5185. self.preview_value = evt.target.result;
  5186. self.refreshPreview();
  5187. self.onChange(true);
  5188. fr = null;
  5189. };
  5190. fr.readAsDataURL(this.files[0]);
  5191. }
  5192. }, {passive: true});
  5193. }
  5194. var description = this.schema.description;
  5195. if (!description) description = '';
  5196. this.preview = this.theme.getFormInputDescription(description);
  5197. this.container.appendChild(this.preview);
  5198. this.control = this.theme.getFormControl(this.label, this.uploader||this.input, this.preview);
  5199. this.container.appendChild(this.control);
  5200. },
  5201. refreshPreview: function() {
  5202. if(this.last_preview === this.preview_value) return;
  5203. this.last_preview = this.preview_value;
  5204. this.preview.innerHTML = '';
  5205. if(!this.preview_value) return;
  5206. var self = this;
  5207. var mime = this.preview_value.match(/^data:([^;,]+)[;,]/);
  5208. if(mime) mime = mime[1];
  5209. if(!mime) mime = 'unknown';
  5210. var file = this.uploader.files[0];
  5211. this.preview.innerHTML = '<strong>Type:</strong> '+mime+', <strong>Size:</strong> '+file.size+' bytes';
  5212. if(mime.substr(0,5)==="image") {
  5213. this.preview.innerHTML += '<br>';
  5214. var img = document.createElement('img');
  5215. img.style.maxWidth = '100%';
  5216. img.style.maxHeight = '100px';
  5217. img.src = this.preview_value;
  5218. this.preview.appendChild(img);
  5219. }
  5220. this.preview.innerHTML += '<br>';
  5221. var uploadButton = this.getButton('Upload', 'upload', 'Upload');
  5222. this.preview.appendChild(uploadButton);
  5223. uploadButton.addEventListener('click',function(event) {
  5224. event.preventDefault();
  5225. uploadButton.setAttribute("disabled", "disabled");
  5226. self.theme.removeInputError(self.uploader);
  5227. if (self.theme.getProgressBar) {
  5228. self.progressBar = self.theme.getProgressBar();
  5229. self.preview.appendChild(self.progressBar);
  5230. }
  5231. self.jsoneditor.options.upload(self.path, file, {
  5232. success: function(url) {
  5233. self.setValue(url);
  5234. if(self.parent) self.parent.onChildEditorChange(self);
  5235. else self.jsoneditor.onChange();
  5236. if (self.progressBar) self.preview.removeChild(self.progressBar);
  5237. uploadButton.removeAttribute("disabled");
  5238. },
  5239. failure: function(error) {
  5240. self.theme.addInputError(self.uploader, error);
  5241. if (self.progressBar) self.preview.removeChild(self.progressBar);
  5242. uploadButton.removeAttribute("disabled");
  5243. },
  5244. updateProgress: function(progress) {
  5245. if (self.progressBar) {
  5246. if (progress) self.theme.updateProgressBar(self.progressBar, progress);
  5247. else self.theme.updateProgressBarUnknown(self.progressBar);
  5248. }
  5249. }
  5250. });
  5251. });
  5252. },
  5253. enable: function() {
  5254. if(this.uploader) this.uploader.disabled = false;
  5255. this._super();
  5256. },
  5257. disable: function() {
  5258. if(this.uploader) this.uploader.disabled = true;
  5259. this._super();
  5260. },
  5261. setValue: function(val) {
  5262. if(this.value !== val) {
  5263. this.value = val;
  5264. this.input.value = this.value;
  5265. this.onChange();
  5266. }
  5267. },
  5268. destroy: function() {
  5269. if(this.preview && this.preview.parentNode) this.preview.parentNode.removeChild(this.preview);
  5270. if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
  5271. if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
  5272. if(this.uploader && this.uploader.parentNode) this.uploader.parentNode.removeChild(this.uploader);
  5273. this._super();
  5274. }
  5275. });
  5276. JSONEditor.defaults.editors.checkbox = JSONEditor.AbstractEditor.extend({
  5277. setValue: function(value,initial) {
  5278. this.value = !!value;
  5279. this.input.checked = this.value;
  5280. this.onChange();
  5281. },
  5282. register: function() {
  5283. this._super();
  5284. if(!this.input) return;
  5285. this.input.setAttribute('name',this.formname);
  5286. },
  5287. unregister: function() {
  5288. this._super();
  5289. if(!this.input) return;
  5290. this.input.removeAttribute('name');
  5291. },
  5292. getNumColumns: function() {
  5293. return Math.min(12,Math.max(this.getTitle().length/7,2));
  5294. },
  5295. build: function() {
  5296. var self = this;
  5297. if(!this.options.compact) {
  5298. this.label = this.header = this.theme.getCheckboxLabel(this.getTitle());
  5299. }
  5300. if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
  5301. if(this.options.compact) this.container.className += ' compact';
  5302. this.input = this.theme.getCheckbox();
  5303. this.control = this.theme.getFormControl(this.label, this.input, this.description);
  5304. if(this.schema.readOnly || this.schema.readonly) {
  5305. this.always_disabled = true;
  5306. this.input.disabled = true;
  5307. }
  5308. this.input.addEventListener('change',function(e) {
  5309. e.preventDefault();
  5310. e.stopPropagation();
  5311. self.value = this.checked;
  5312. self.onChange(true);
  5313. }, {passive: true});
  5314. this.container.appendChild(this.control);
  5315. },
  5316. enable: function() {
  5317. if(!this.always_disabled) {
  5318. this.input.disabled = false;
  5319. }
  5320. this._super();
  5321. },
  5322. disable: function() {
  5323. this.input.disabled = true;
  5324. this._super();
  5325. },
  5326. destroy: function() {
  5327. if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label);
  5328. if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
  5329. if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
  5330. this._super();
  5331. }
  5332. });
  5333. JSONEditor.defaults.editors.arraySelectize = JSONEditor.AbstractEditor.extend({
  5334. build: function() {
  5335. this.title = this.theme.getFormInputLabel(this.getTitle());
  5336. this.title_controls = this.theme.getHeaderButtonHolder();
  5337. this.title.appendChild(this.title_controls);
  5338. this.error_holder = document.createElement('div');
  5339. if(this.schema.description) {
  5340. this.description = this.theme.getDescription(this.schema.description);
  5341. }
  5342. this.input = document.createElement('select');
  5343. this.input.setAttribute('multiple', 'multiple');
  5344. var group = this.theme.getFormControl(this.title, this.input, this.description);
  5345. this.container.appendChild(group);
  5346. this.container.appendChild(this.error_holder);
  5347. window.jQuery(this.input).selectize({
  5348. delimiter: false,
  5349. createOnBlur: true,
  5350. create: true
  5351. });
  5352. },
  5353. postBuild: function() {
  5354. var self = this;
  5355. this.input.selectize.on('change', function(event) {
  5356. self.refreshValue();
  5357. self.onChange(true);
  5358. });
  5359. },
  5360. destroy: function() {
  5361. this.empty(true);
  5362. if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title);
  5363. if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description);
  5364. if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input);
  5365. this._super();
  5366. },
  5367. empty: function(hard) {},
  5368. setValue: function(value, initial) {
  5369. var self = this;
  5370. // Update the array's value, adding/removing rows when necessary
  5371. value = value || [];
  5372. if(!(Array.isArray(value))) value = [value];
  5373. this.input.selectize.clearOptions();
  5374. this.input.selectize.clear(true);
  5375. value.forEach(function(item) {
  5376. self.input.selectize.addOption({text: item, value: item});
  5377. });
  5378. this.input.selectize.setValue(value);
  5379. this.refreshValue(initial);
  5380. },
  5381. refreshValue: function(force) {
  5382. this.value = this.input.selectize.getValue();
  5383. },
  5384. showValidationErrors: function(errors) {
  5385. var self = this;
  5386. // Get all the errors that pertain to this editor
  5387. var my_errors = [];
  5388. var other_errors = [];
  5389. $each(errors, function(i,error) {
  5390. if(error.path === self.path) {
  5391. my_errors.push(error);
  5392. }
  5393. else {
  5394. other_errors.push(error);
  5395. }
  5396. });
  5397. // Show errors for this editor
  5398. if(this.error_holder) {
  5399. if(my_errors.length) {
  5400. var message = [];
  5401. this.error_holder.innerHTML = '';
  5402. this.error_holder.style.display = '';
  5403. $each(my_errors, function(i,error) {
  5404. self.error_holder.appendChild(self.theme.getErrorMessage(error.message));
  5405. });
  5406. }
  5407. // Hide error area
  5408. else {
  5409. this.error_holder.style.display = 'none';
  5410. }
  5411. }
  5412. }
  5413. });
  5414. var matchKey = (function () {
  5415. var elem = document.documentElement;
  5416. if (elem.matches) return 'matches';
  5417. else if (elem.webkitMatchesSelector) return 'webkitMatchesSelector';
  5418. else if (elem.mozMatchesSelector) return 'mozMatchesSelector';
  5419. else if (elem.msMatchesSelector) return 'msMatchesSelector';
  5420. else if (elem.oMatchesSelector) return 'oMatchesSelector';
  5421. })();
  5422. JSONEditor.AbstractTheme = Class.extend({
  5423. getContainer: function() {
  5424. return document.createElement('div');
  5425. },
  5426. getFloatRightLinkHolder: function() {
  5427. var el = document.createElement('div');
  5428. el.style = el.style || {};
  5429. el.style.cssFloat = 'right';
  5430. el.style.marginLeft = '10px';
  5431. return el;
  5432. },
  5433. getModal: function() {
  5434. var el = document.createElement('div');
  5435. el.style.backgroundColor = 'white';
  5436. el.style.border = '1px solid black';
  5437. el.style.boxShadow = '3px 3px black';
  5438. el.style.position = 'absolute';
  5439. el.style.zIndex = '10';
  5440. el.style.display = 'none';
  5441. return el;
  5442. },
  5443. getGridContainer: function() {
  5444. var el = document.createElement('div');
  5445. return el;
  5446. },
  5447. getGridRow: function() {
  5448. var el = document.createElement('div');
  5449. el.className = 'row';
  5450. return el;
  5451. },
  5452. getGridColumn: function() {
  5453. var el = document.createElement('div');
  5454. return el;
  5455. },
  5456. setGridColumnSize: function(el,size) {
  5457. },
  5458. getLink: function(text) {
  5459. var el = document.createElement('a');
  5460. el.setAttribute('href','#');
  5461. el.appendChild(document.createTextNode(text));
  5462. return el;
  5463. },
  5464. disableHeader: function(header) {
  5465. header.style.color = '#ccc';
  5466. },
  5467. disableLabel: function(label) {
  5468. label.style.color = '#ccc';
  5469. },
  5470. enableHeader: function(header) {
  5471. header.style.color = '';
  5472. },
  5473. enableLabel: function(label) {
  5474. label.style.color = '';
  5475. },
  5476. getFormInputLabel: function(text) {
  5477. var el = document.createElement('label');
  5478. el.appendChild(document.createTextNode(text));
  5479. return el;
  5480. },
  5481. getCheckboxLabel: function(text) {
  5482. var el = this.getFormInputLabel(text);
  5483. el.style.fontWeight = 'normal';
  5484. return el;
  5485. },
  5486. getHeader: function(text) {
  5487. var el = document.createElement('h3');
  5488. if(typeof text === "string") {
  5489. el.textContent = text;
  5490. }
  5491. else {
  5492. el.appendChild(text);
  5493. }
  5494. return el;
  5495. },
  5496. getCheckbox: function() {
  5497. var el = this.getFormInputField('checkbox');
  5498. el.style.display = 'inline-block';
  5499. el.style.width = 'auto';
  5500. return el;
  5501. },
  5502. getMultiCheckboxHolder: function(controls,label,description) {
  5503. var el = document.createElement('div');
  5504. if(label) {
  5505. label.style.display = 'block';
  5506. el.appendChild(label);
  5507. }
  5508. for(var i in controls) {
  5509. if(!controls.hasOwnProperty(i)) continue;
  5510. controls[i].style.display = 'inline-block';
  5511. controls[i].style.marginRight = '20px';
  5512. el.appendChild(controls[i]);
  5513. }
  5514. if(description) el.appendChild(description);
  5515. return el;
  5516. },
  5517. getSelectInput: function(options) {
  5518. var select = document.createElement('select');
  5519. if(options) this.setSelectOptions(select, options);
  5520. return select;
  5521. },
  5522. getSwitcher: function(options) {
  5523. var switcher = this.getSelectInput(options);
  5524. switcher.style.backgroundColor = 'transparent';
  5525. switcher.style.display = 'inline-block';
  5526. switcher.style.fontStyle = 'italic';
  5527. switcher.style.fontWeight = 'normal';
  5528. switcher.style.height = 'auto';
  5529. switcher.style.marginBottom = 0;
  5530. switcher.style.marginLeft = '5px';
  5531. switcher.style.padding = '0 0 0 3px';
  5532. switcher.style.width = 'auto';
  5533. return switcher;
  5534. },
  5535. getSwitcherOptions: function(switcher) {
  5536. return switcher.getElementsByTagName('option');
  5537. },
  5538. setSwitcherOptions: function(switcher, options, titles) {
  5539. this.setSelectOptions(switcher, options, titles);
  5540. },
  5541. setSelectOptions: function(select, options, titles) {
  5542. titles = titles || [];
  5543. select.innerHTML = '';
  5544. for(var i=0; i<options.length; i++) {
  5545. var option = document.createElement('option');
  5546. option.setAttribute('value',options[i]);
  5547. option.textContent = titles[i] || options[i];
  5548. select.appendChild(option);
  5549. }
  5550. },
  5551. getTextareaInput: function() {
  5552. var el = document.createElement('textarea');
  5553. el.style = el.style || {};
  5554. el.style.width = '100%';
  5555. el.style.height = '300px';
  5556. el.style.boxSizing = 'border-box';
  5557. return el;
  5558. },
  5559. getRangeInput: function(min,max,step) {
  5560. var el = this.getFormInputField('range');
  5561. el.setAttribute('min',min);
  5562. el.setAttribute('max',max);
  5563. el.setAttribute('step',step);
  5564. return el;
  5565. },
  5566. getFormInputField: function(type) {
  5567. var el = document.createElement('input');
  5568. el.setAttribute('type',type);
  5569. return el;
  5570. },
  5571. afterInputReady: function(input) {
  5572. },
  5573. getFormControl: function(label, input, description) {
  5574. var el = document.createElement('div');
  5575. el.className = 'form-control';
  5576. if(label) el.appendChild(label);
  5577. if(input.type === 'checkbox') {
  5578. label.insertBefore(input,label.firstChild);
  5579. }
  5580. else {
  5581. el.appendChild(input);
  5582. }
  5583. if(description) el.appendChild(description);
  5584. return el;
  5585. },
  5586. getIndentedPanel: function() {
  5587. var el = document.createElement('div');
  5588. el.style = el.style || {};
  5589. el.style.paddingLeft = '10px';
  5590. el.style.marginLeft = '10px';
  5591. el.style.borderLeft = '1px solid #ccc';
  5592. return el;
  5593. },
  5594. getChildEditorHolder: function() {
  5595. return document.createElement('div');
  5596. },
  5597. getDescription: function(text) {
  5598. var el = document.createElement('p');
  5599. el.innerHTML = text;
  5600. return el;
  5601. },
  5602. getCheckboxDescription: function(text) {
  5603. return this.getDescription(text);
  5604. },
  5605. getFormInputDescription: function(text) {
  5606. return this.getDescription(text);
  5607. },
  5608. getHeaderButtonHolder: function() {
  5609. return this.getButtonHolder();
  5610. },
  5611. getButtonHolder: function() {
  5612. return document.createElement('div');
  5613. },
  5614. getButton: function(text, icon, title) {
  5615. var el = document.createElement('button');
  5616. el.type = 'button';
  5617. this.setButtonText(el,text,icon,title);
  5618. return el;
  5619. },
  5620. setButtonText: function(button, text, icon, title) {
  5621. button.innerHTML = '';
  5622. if(icon) {
  5623. button.appendChild(icon);
  5624. button.innerHTML += ' ';
  5625. }
  5626. button.appendChild(document.createTextNode(text));
  5627. if(title) button.setAttribute('title',title);
  5628. },
  5629. getTable: function() {
  5630. return document.createElement('table');
  5631. },
  5632. getTableRow: function() {
  5633. return document.createElement('tr');
  5634. },
  5635. getTableHead: function() {
  5636. return document.createElement('thead');
  5637. },
  5638. getTableBody: function() {
  5639. return document.createElement('tbody');
  5640. },
  5641. getTableHeaderCell: function(text) {
  5642. var el = document.createElement('th');
  5643. el.textContent = text;
  5644. return el;
  5645. },
  5646. getTableCell: function() {
  5647. var el = document.createElement('td');
  5648. return el;
  5649. },
  5650. getErrorMessage: function(text) {
  5651. var el = document.createElement('p');
  5652. el.style = el.style || {};
  5653. el.style.color = 'red';
  5654. el.appendChild(document.createTextNode(text));
  5655. return el;
  5656. },
  5657. addInputError: function(input, text) {
  5658. },
  5659. removeInputError: function(input) {
  5660. },
  5661. addTableRowError: function(row) {
  5662. },
  5663. removeTableRowError: function(row) {
  5664. },
  5665. getTabHolder: function() {
  5666. var el = document.createElement('div');
  5667. el.innerHTML = "<div style='float: left; width: 130px;' class='tabs'></div><div class='content' style='margin-left: 130px;'></div><div style='clear:both;'></div>";
  5668. return el;
  5669. },
  5670. applyStyles: function(el,styles) {
  5671. el.style = el.style || {};
  5672. for(var i in styles) {
  5673. if(!styles.hasOwnProperty(i)) continue;
  5674. el.style[i] = styles[i];
  5675. }
  5676. },
  5677. closest: function(elem, selector) {
  5678. while (elem && elem !== document) {
  5679. if (elem[matchKey]) {
  5680. if (elem[matchKey](selector)) {
  5681. return elem;
  5682. } else {
  5683. elem = elem.parentNode;
  5684. }
  5685. }
  5686. else {
  5687. return false;
  5688. }
  5689. }
  5690. return false;
  5691. },
  5692. getTab: function(span) {
  5693. var el = document.createElement('div');
  5694. el.appendChild(span);
  5695. el.style = el.style || {};
  5696. this.applyStyles(el,{
  5697. border: '1px solid #ccc',
  5698. borderWidth: '1px 0 1px 1px',
  5699. textAlign: 'center',
  5700. lineHeight: '30px',
  5701. borderRadius: '5px',
  5702. borderBottomRightRadius: 0,
  5703. borderTopRightRadius: 0,
  5704. fontWeight: 'bold',
  5705. cursor: 'pointer'
  5706. });
  5707. return el;
  5708. },
  5709. getTabContentHolder: function(tab_holder) {
  5710. return tab_holder.children[1];
  5711. },
  5712. getTabContent: function() {
  5713. return this.getIndentedPanel();
  5714. },
  5715. markTabActive: function(tab) {
  5716. this.applyStyles(tab,{
  5717. opacity: 1,
  5718. background: 'white'
  5719. });
  5720. },
  5721. markTabInactive: function(tab) {
  5722. this.applyStyles(tab,{
  5723. opacity:0.5,
  5724. background: ''
  5725. });
  5726. },
  5727. addTab: function(holder, tab) {
  5728. holder.children[0].appendChild(tab);
  5729. },
  5730. getBlockLink: function() {
  5731. var link = document.createElement('a');
  5732. link.style.display = 'block';
  5733. return link;
  5734. },
  5735. getBlockLinkHolder: function() {
  5736. var el = document.createElement('div');
  5737. return el;
  5738. },
  5739. getLinksHolder: function() {
  5740. var el = document.createElement('div');
  5741. return el;
  5742. },
  5743. createMediaLink: function(holder,link,media) {
  5744. holder.appendChild(link);
  5745. media.style.width='100%';
  5746. holder.appendChild(media);
  5747. },
  5748. createImageLink: function(holder,link,image) {
  5749. holder.appendChild(link);
  5750. link.appendChild(image);
  5751. }
  5752. });
  5753. JSONEditor.defaults.themes.bootstrap2 = JSONEditor.AbstractTheme.extend({
  5754. getRangeInput: function(min, max, step) {
  5755. // TODO: use bootstrap slider
  5756. return this._super(min, max, step);
  5757. },
  5758. getGridContainer: function() {
  5759. var el = document.createElement('div');
  5760. el.className = 'container-fluid';
  5761. return el;
  5762. },
  5763. getGridRow: function() {
  5764. var el = document.createElement('div');
  5765. el.className = 'row-fluid';
  5766. return el;
  5767. },
  5768. getFormInputLabel: function(text) {
  5769. var el = this._super(text);
  5770. el.style.display = 'inline-block';
  5771. el.style.fontWeight = 'bold';
  5772. return el;
  5773. },
  5774. setGridColumnSize: function(el,size) {
  5775. el.className = 'span'+size;
  5776. },
  5777. getSelectInput: function(options) {
  5778. var input = this._super(options);
  5779. input.style.width = 'auto';
  5780. input.style.maxWidth = '98%';
  5781. return input;
  5782. },
  5783. getFormInputField: function(type) {
  5784. var el = this._super(type);
  5785. el.style.width = '98%';
  5786. return el;
  5787. },
  5788. afterInputReady: function(input) {
  5789. if(input.controlgroup) return;
  5790. input.controlgroup = this.closest(input,'.control-group');
  5791. input.controls = this.closest(input,'.controls');
  5792. if(this.closest(input,'.compact')) {
  5793. input.controlgroup.className = input.controlgroup.className.replace(/control-group/g,'').replace(/[ ]{2,}/g,' ');
  5794. input.controls.className = input.controlgroup.className.replace(/controls/g,'').replace(/[ ]{2,}/g,' ');
  5795. input.style.marginBottom = 0;
  5796. }
  5797. // TODO: use bootstrap slider
  5798. },
  5799. getIndentedPanel: function() {
  5800. var el = document.createElement('div');
  5801. el.className = 'well well-small';
  5802. el.style.paddingBottom = 0;
  5803. return el;
  5804. },
  5805. getFormInputDescription: function(text) {
  5806. var el = document.createElement('p');
  5807. el.className = 'help-inline';
  5808. el.textContent = text;
  5809. return el;
  5810. },
  5811. getFormControl: function(label, input, description) {
  5812. var ret = document.createElement('div');
  5813. ret.className = 'control-group';
  5814. var controls = document.createElement('div');
  5815. controls.className = 'controls';
  5816. if(label && input.getAttribute('type') === 'checkbox') {
  5817. ret.appendChild(controls);
  5818. label.className += ' checkbox';
  5819. label.appendChild(input);
  5820. controls.appendChild(label);
  5821. controls.style.height = '30px';
  5822. }
  5823. else {
  5824. if(label) {
  5825. label.className += ' control-label';
  5826. ret.appendChild(label);
  5827. }
  5828. controls.appendChild(input);
  5829. ret.appendChild(controls);
  5830. }
  5831. if(description) controls.appendChild(description);
  5832. return ret;
  5833. },
  5834. getHeaderButtonHolder: function() {
  5835. var el = this.getButtonHolder();
  5836. el.style.marginLeft = '10px';
  5837. return el;
  5838. },
  5839. getButtonHolder: function() {
  5840. var el = document.createElement('div');
  5841. el.className = 'btn-group';
  5842. return el;
  5843. },
  5844. getButton: function(text, icon, title) {
  5845. var el = this._super(text, icon, title);
  5846. el.className += ' btn btn-default';
  5847. return el;
  5848. },
  5849. getTable: function() {
  5850. var el = document.createElement('table');
  5851. el.className = 'table table-bordered';
  5852. el.style.width = 'auto';
  5853. el.style.maxWidth = 'none';
  5854. return el;
  5855. },
  5856. addInputError: function(input,text) {
  5857. if(!input.controlgroup || !input.controls) return;
  5858. input.controlgroup.className += ' error';
  5859. if(!input.errmsg) {
  5860. input.errmsg = document.createElement('p');
  5861. input.errmsg.className = 'help-block errormsg';
  5862. input.controls.appendChild(input.errmsg);
  5863. }
  5864. else {
  5865. input.errmsg.style.display = '';
  5866. }
  5867. input.errmsg.textContent = text;
  5868. },
  5869. removeInputError: function(input) {
  5870. if(!input.errmsg) return;
  5871. input.errmsg.style.display = 'none';
  5872. input.controlgroup.className = input.controlgroup.className.replace(/\s?error/g,'');
  5873. },
  5874. getTabHolder: function() {
  5875. var el = document.createElement('div');
  5876. el.className = 'tabbable tabs-left';
  5877. el.innerHTML = "<ul class='nav nav-tabs span2' style='margin-right: 0;'></ul><div class='tab-content span10' style='overflow:visible;'></div>";
  5878. return el;
  5879. },
  5880. getTab: function(text) {
  5881. var el = document.createElement('li');
  5882. var a = document.createElement('a');
  5883. a.setAttribute('href','#');
  5884. a.appendChild(text);
  5885. el.appendChild(a);
  5886. return el;
  5887. },
  5888. getTabContentHolder: function(tab_holder) {
  5889. return tab_holder.children[1];
  5890. },
  5891. getTabContent: function() {
  5892. var el = document.createElement('div');
  5893. el.className = 'tab-pane active';
  5894. return el;
  5895. },
  5896. markTabActive: function(tab) {
  5897. tab.className += ' active';
  5898. },
  5899. markTabInactive: function(tab) {
  5900. tab.className = tab.className.replace(/\s?active/g,'');
  5901. },
  5902. addTab: function(holder, tab) {
  5903. holder.children[0].appendChild(tab);
  5904. },
  5905. getProgressBar: function() {
  5906. var container = document.createElement('div');
  5907. container.className = 'progress';
  5908. var bar = document.createElement('div');
  5909. bar.className = 'bar';
  5910. bar.style.width = '0%';
  5911. container.appendChild(bar);
  5912. return container;
  5913. },
  5914. updateProgressBar: function(progressBar, progress) {
  5915. if (!progressBar) return;
  5916. progressBar.firstChild.style.width = progress + "%";
  5917. },
  5918. updateProgressBarUnknown: function(progressBar) {
  5919. if (!progressBar) return;
  5920. progressBar.className = 'progress progress-striped active';
  5921. progressBar.firstChild.style.width = '100%';
  5922. }
  5923. });
  5924. JSONEditor.defaults.themes.bootstrap3 = JSONEditor.AbstractTheme.extend({
  5925. getSelectInput: function(options) {
  5926. var el = this._super(options);
  5927. el.className += 'form-control';
  5928. //el.style.width = 'auto';
  5929. return el;
  5930. },
  5931. setGridColumnSize: function(el,size) {
  5932. el.className = 'col-md-'+size;
  5933. },
  5934. afterInputReady: function(input) {
  5935. if(input.controlgroup) return;
  5936. input.controlgroup = this.closest(input,'.form-group');
  5937. if(this.closest(input,'.compact')) {
  5938. input.controlgroup.style.marginBottom = 0;
  5939. }
  5940. // TODO: use bootstrap slider
  5941. },
  5942. getTextareaInput: function() {
  5943. var el = document.createElement('textarea');
  5944. el.className = 'form-control';
  5945. return el;
  5946. },
  5947. getRangeInput: function(min, max, step) {
  5948. // TODO: use better slider
  5949. return this._super(min, max, step);
  5950. },
  5951. getFormInputField: function(type) {
  5952. var el = this._super(type);
  5953. if(type !== 'checkbox') {
  5954. el.className += 'form-control';
  5955. }
  5956. return el;
  5957. },
  5958. getFormControl: function(label, input, description) {
  5959. var group = document.createElement('div');
  5960. if(label && input.type === 'checkbox') {
  5961. group.className += ' checkbox';
  5962. label.appendChild(input);
  5963. label.style.fontSize = '14px';
  5964. group.style.marginTop = '0';
  5965. group.appendChild(label);
  5966. input.style.position = 'relative';
  5967. input.style.cssFloat = 'left';
  5968. }
  5969. else {
  5970. group.className += ' form-group';
  5971. if(label) {
  5972. label.className += ' control-label';
  5973. group.appendChild(label);
  5974. }
  5975. group.appendChild(input);
  5976. }
  5977. if(description) group.appendChild(description);
  5978. return group;
  5979. },
  5980. getIndentedPanel: function() {
  5981. var el = document.createElement('div');
  5982. el.className = 'well well-sm';
  5983. el.style.paddingBottom = 0;
  5984. return el;
  5985. },
  5986. getFormInputDescription: function(text) {
  5987. var el = document.createElement('p');
  5988. el.className = 'help-block';
  5989. el.innerHTML = text;
  5990. return el;
  5991. },
  5992. getHeaderButtonHolder: function() {
  5993. var el = this.getButtonHolder();
  5994. el.style.marginLeft = '10px';
  5995. return el;
  5996. },
  5997. getButtonHolder: function() {
  5998. var el = document.createElement('div');
  5999. el.className = 'btn-group';
  6000. return el;
  6001. },
  6002. getButton: function(text, icon, title) {
  6003. var el = this._super(text, icon, title);
  6004. el.className += 'btn btn-default';
  6005. return el;
  6006. },
  6007. getTable: function() {
  6008. var el = document.createElement('table');
  6009. el.className = 'table table-bordered';
  6010. el.style.width = 'auto';
  6011. el.style.maxWidth = 'none';
  6012. return el;
  6013. },
  6014. addInputError: function(input,text) {
  6015. if(!input.controlgroup) return;
  6016. input.controlgroup.className += ' has-error';
  6017. if(!input.errmsg) {
  6018. input.errmsg = document.createElement('p');
  6019. input.errmsg.className = 'help-block errormsg';
  6020. input.controlgroup.appendChild(input.errmsg);
  6021. }
  6022. else {
  6023. input.errmsg.style.display = '';
  6024. }
  6025. input.errmsg.textContent = text;
  6026. },
  6027. removeInputError: function(input) {
  6028. if(!input.errmsg) return;
  6029. input.errmsg.style.display = 'none';
  6030. input.controlgroup.className = input.controlgroup.className.replace(/\s?has-error/g,'');
  6031. },
  6032. getTabHolder: function() {
  6033. var el = document.createElement('div');
  6034. el.innerHTML = "<div class='tabs list-group list-group-horizontal col-md-12'></div><div class='col-md-12'></div>";
  6035. el.className = 'rows';
  6036. return el;
  6037. },
  6038. getTab: function(text) {
  6039. var el = document.createElement('a');
  6040. el.className = 'list-group-item col-md-2';
  6041. el.setAttribute('href','#');
  6042. el.appendChild(text);
  6043. return el;
  6044. },
  6045. markTabActive: function(tab) {
  6046. tab.className += ' active';
  6047. },
  6048. markTabInactive: function(tab) {
  6049. tab.className = tab.className.replace(/\s?active/g,'');
  6050. },
  6051. getProgressBar: function() {
  6052. var min = 0, max = 100, start = 0;
  6053. var container = document.createElement('div');
  6054. container.className = 'progress';
  6055. var bar = document.createElement('div');
  6056. bar.className = 'progress-bar';
  6057. bar.setAttribute('role', 'progressbar');
  6058. bar.setAttribute('aria-valuenow', start);
  6059. bar.setAttribute('aria-valuemin', min);
  6060. bar.setAttribute('aria-valuenax', max);
  6061. bar.innerHTML = start + "%";
  6062. container.appendChild(bar);
  6063. return container;
  6064. },
  6065. updateProgressBar: function(progressBar, progress) {
  6066. if (!progressBar) return;
  6067. var bar = progressBar.firstChild;
  6068. var percentage = progress + "%";
  6069. bar.setAttribute('aria-valuenow', progress);
  6070. bar.style.width = percentage;
  6071. bar.innerHTML = percentage;
  6072. },
  6073. updateProgressBarUnknown: function(progressBar) {
  6074. if (!progressBar) return;
  6075. var bar = progressBar.firstChild;
  6076. progressBar.className = 'progress progress-striped active';
  6077. bar.removeAttribute('aria-valuenow');
  6078. bar.style.width = '100%';
  6079. bar.innerHTML = '';
  6080. }
  6081. });
  6082. // Base Foundation theme
  6083. JSONEditor.defaults.themes.foundation = JSONEditor.AbstractTheme.extend({
  6084. getChildEditorHolder: function() {
  6085. var el = document.createElement('div');
  6086. el.style.marginBottom = '15px';
  6087. return el;
  6088. },
  6089. getSelectInput: function(options) {
  6090. var el = this._super(options);
  6091. el.style.minWidth = 'none';
  6092. el.style.padding = '5px';
  6093. el.style.marginTop = '3px';
  6094. return el;
  6095. },
  6096. getSwitcher: function(options) {
  6097. var el = this._super(options);
  6098. el.style.paddingRight = '8px';
  6099. return el;
  6100. },
  6101. afterInputReady: function(input) {
  6102. if(this.closest(input,'.compact')) {
  6103. input.style.marginBottom = 0;
  6104. }
  6105. input.group = this.closest(input,'.form-control');
  6106. },
  6107. getFormInputLabel: function(text) {
  6108. var el = this._super(text);
  6109. el.style.display = 'inline-block';
  6110. return el;
  6111. },
  6112. getFormInputField: function(type) {
  6113. var el = this._super(type);
  6114. el.style.width = '100%';
  6115. el.style.marginBottom = type==='checkbox'? '0' : '12px';
  6116. return el;
  6117. },
  6118. getFormInputDescription: function(text) {
  6119. var el = document.createElement('p');
  6120. el.textContent = text;
  6121. el.style.marginTop = '-10px';
  6122. el.style.fontStyle = 'italic';
  6123. return el;
  6124. },
  6125. getIndentedPanel: function() {
  6126. var el = document.createElement('div');
  6127. el.className = 'panel';
  6128. el.style.paddingBottom = 0;
  6129. return el;
  6130. },
  6131. getHeaderButtonHolder: function() {
  6132. var el = this.getButtonHolder();
  6133. el.style.display = 'inline-block';
  6134. el.style.marginLeft = '10px';
  6135. el.style.verticalAlign = 'middle';
  6136. return el;
  6137. },
  6138. getButtonHolder: function() {
  6139. var el = document.createElement('div');
  6140. el.className = 'button-group';
  6141. return el;
  6142. },
  6143. getButton: function(text, icon, title) {
  6144. var el = this._super(text, icon, title);
  6145. el.className += ' small button';
  6146. return el;
  6147. },
  6148. addInputError: function(input,text) {
  6149. if(!input.group) return;
  6150. input.group.className += ' error';
  6151. if(!input.errmsg) {
  6152. input.insertAdjacentHTML('afterend','<small class="error"></small>');
  6153. input.errmsg = input.parentNode.getElementsByClassName('error')[0];
  6154. }
  6155. else {
  6156. input.errmsg.style.display = '';
  6157. }
  6158. input.errmsg.textContent = text;
  6159. },
  6160. removeInputError: function(input) {
  6161. if(!input.errmsg) return;
  6162. input.group.className = input.group.className.replace(/ error/g,'');
  6163. input.errmsg.style.display = 'none';
  6164. },
  6165. getProgressBar: function() {
  6166. var progressBar = document.createElement('div');
  6167. progressBar.className = 'progress';
  6168. var meter = document.createElement('span');
  6169. meter.className = 'meter';
  6170. meter.style.width = '0%';
  6171. progressBar.appendChild(meter);
  6172. return progressBar;
  6173. },
  6174. updateProgressBar: function(progressBar, progress) {
  6175. if (!progressBar) return;
  6176. progressBar.firstChild.style.width = progress + '%';
  6177. },
  6178. updateProgressBarUnknown: function(progressBar) {
  6179. if (!progressBar) return;
  6180. progressBar.firstChild.style.width = '100%';
  6181. }
  6182. });
  6183. // Foundation 3 Specific Theme
  6184. JSONEditor.defaults.themes.foundation3 = JSONEditor.defaults.themes.foundation.extend({
  6185. getHeaderButtonHolder: function() {
  6186. var el = this._super();
  6187. el.style.fontSize = '.6em';
  6188. return el;
  6189. },
  6190. getFormInputLabel: function(text) {
  6191. var el = this._super(text);
  6192. el.style.fontWeight = 'bold';
  6193. return el;
  6194. },
  6195. getTabHolder: function() {
  6196. var el = document.createElement('div');
  6197. el.className = 'row';
  6198. el.innerHTML = "<dl class='tabs vertical two columns'></dl><div class='tabs-content ten columns'></div>";
  6199. return el;
  6200. },
  6201. setGridColumnSize: function(el,size) {
  6202. var sizes = ['zero','one','two','three','four','five','six','seven','eight','nine','ten','eleven','twelve'];
  6203. el.className = 'columns '+sizes[size];
  6204. },
  6205. getTab: function(text) {
  6206. var el = document.createElement('dd');
  6207. var a = document.createElement('a');
  6208. a.setAttribute('href','#');
  6209. a.appendChild(text);
  6210. el.appendChild(a);
  6211. return el;
  6212. },
  6213. getTabContentHolder: function(tab_holder) {
  6214. return tab_holder.children[1];
  6215. },
  6216. getTabContent: function() {
  6217. var el = document.createElement('div');
  6218. el.className = 'content active';
  6219. el.style.paddingLeft = '5px';
  6220. return el;
  6221. },
  6222. markTabActive: function(tab) {
  6223. tab.className += ' active';
  6224. },
  6225. markTabInactive: function(tab) {
  6226. tab.className = tab.className.replace(/\s*active/g,'');
  6227. },
  6228. addTab: function(holder, tab) {
  6229. holder.children[0].appendChild(tab);
  6230. }
  6231. });
  6232. // Foundation 4 Specific Theme
  6233. JSONEditor.defaults.themes.foundation4 = JSONEditor.defaults.themes.foundation.extend({
  6234. getHeaderButtonHolder: function() {
  6235. var el = this._super();
  6236. el.style.fontSize = '.6em';
  6237. return el;
  6238. },
  6239. setGridColumnSize: function(el,size) {
  6240. el.className = 'columns large-'+size;
  6241. },
  6242. getFormInputDescription: function(text) {
  6243. var el = this._super(text);
  6244. el.style.fontSize = '.8rem';
  6245. return el;
  6246. },
  6247. getFormInputLabel: function(text) {
  6248. var el = this._super(text);
  6249. el.style.fontWeight = 'bold';
  6250. return el;
  6251. }
  6252. });
  6253. // Foundation 5 Specific Theme
  6254. JSONEditor.defaults.themes.foundation5 = JSONEditor.defaults.themes.foundation.extend({
  6255. getFormInputDescription: function(text) {
  6256. var el = this._super(text);
  6257. el.style.fontSize = '.8rem';
  6258. return el;
  6259. },
  6260. setGridColumnSize: function(el,size) {
  6261. el.className = 'columns medium-'+size;
  6262. },
  6263. getButton: function(text, icon, title) {
  6264. var el = this._super(text,icon,title);
  6265. el.className = el.className.replace(/\s*small/g,'') + ' tiny';
  6266. return el;
  6267. },
  6268. getTabHolder: function() {
  6269. var el = document.createElement('div');
  6270. el.innerHTML = "<dl class='tabs vertical'></dl><div class='tabs-content vertical'></div>";
  6271. return el;
  6272. },
  6273. getTab: function(text) {
  6274. var el = document.createElement('dd');
  6275. var a = document.createElement('a');
  6276. a.setAttribute('href','#');
  6277. a.appendChild(text);
  6278. el.appendChild(a);
  6279. return el;
  6280. },
  6281. getTabContentHolder: function(tab_holder) {
  6282. return tab_holder.children[1];
  6283. },
  6284. getTabContent: function() {
  6285. var el = document.createElement('div');
  6286. el.className = 'content active';
  6287. el.style.paddingLeft = '5px';
  6288. return el;
  6289. },
  6290. markTabActive: function(tab) {
  6291. tab.className += ' active';
  6292. },
  6293. markTabInactive: function(tab) {
  6294. tab.className = tab.className.replace(/\s*active/g,'');
  6295. },
  6296. addTab: function(holder, tab) {
  6297. holder.children[0].appendChild(tab);
  6298. }
  6299. });
  6300. JSONEditor.defaults.themes.foundation6 = JSONEditor.defaults.themes.foundation5.extend({
  6301. getIndentedPanel: function() {
  6302. var el = document.createElement('div');
  6303. el.className = 'callout secondary';
  6304. return el;
  6305. },
  6306. getButtonHolder: function() {
  6307. var el = document.createElement('div');
  6308. el.className = 'button-group tiny';
  6309. el.style.marginBottom = 0;
  6310. return el;
  6311. },
  6312. getFormInputLabel: function(text) {
  6313. var el = this._super(text);
  6314. el.style.display = 'block';
  6315. return el;
  6316. },
  6317. getFormControl: function(label, input, description) {
  6318. var el = document.createElement('div');
  6319. el.className = 'form-control';
  6320. if(label) el.appendChild(label);
  6321. if(input.type === 'checkbox') {
  6322. label.insertBefore(input,label.firstChild);
  6323. }
  6324. else if (label) {
  6325. label.appendChild(input);
  6326. } else {
  6327. el.appendChild(input);
  6328. }
  6329. if(description) label.appendChild(description);
  6330. return el;
  6331. },
  6332. addInputError: function(input,text) {
  6333. if(!input.group) return;
  6334. input.group.className += ' error';
  6335. if(!input.errmsg) {
  6336. var errorEl = document.createElement('span');
  6337. errorEl.className = 'form-error is-visible';
  6338. input.group.getElementsByTagName('label')[0].appendChild(errorEl);
  6339. input.className = input.className + ' is-invalid-input';
  6340. input.errmsg = errorEl;
  6341. }
  6342. else {
  6343. input.errmsg.style.display = '';
  6344. input.className = '';
  6345. }
  6346. input.errmsg.textContent = text;
  6347. },
  6348. removeInputError: function(input) {
  6349. if(!input.errmsg) return;
  6350. input.className = input.className.replace(/ is-invalid-input/g,'');
  6351. if(input.errmsg.parentNode) {
  6352. input.errmsg.parentNode.removeChild(input.errmsg);
  6353. }
  6354. },
  6355. });
  6356. JSONEditor.defaults.themes.html = JSONEditor.AbstractTheme.extend({
  6357. getFormInputLabel: function(text) {
  6358. var el = this._super(text);
  6359. el.style.display = 'block';
  6360. el.style.marginBottom = '3px';
  6361. el.style.fontWeight = 'bold';
  6362. return el;
  6363. },
  6364. getFormInputDescription: function(text) {
  6365. var el = this._super(text);
  6366. el.style.fontSize = '.8em';
  6367. el.style.margin = 0;
  6368. el.style.display = 'inline-block';
  6369. el.style.fontStyle = 'italic';
  6370. return el;
  6371. },
  6372. getIndentedPanel: function() {
  6373. var el = this._super();
  6374. el.style.border = '1px solid #ddd';
  6375. el.style.padding = '5px';
  6376. el.style.margin = '5px';
  6377. el.style.borderRadius = '3px';
  6378. return el;
  6379. },
  6380. getChildEditorHolder: function() {
  6381. var el = this._super();
  6382. el.style.marginBottom = '8px';
  6383. return el;
  6384. },
  6385. getHeaderButtonHolder: function() {
  6386. var el = this.getButtonHolder();
  6387. el.style.display = 'inline-block';
  6388. el.style.marginLeft = '10px';
  6389. el.style.fontSize = '.8em';
  6390. el.style.verticalAlign = 'middle';
  6391. return el;
  6392. },
  6393. getTable: function() {
  6394. var el = this._super();
  6395. el.style.borderBottom = '1px solid #ccc';
  6396. el.style.marginBottom = '5px';
  6397. return el;
  6398. },
  6399. addInputError: function(input, text) {
  6400. input.style.borderColor = 'red';
  6401. if(!input.errmsg) {
  6402. var group = this.closest(input,'.form-control');
  6403. input.errmsg = document.createElement('div');
  6404. input.errmsg.setAttribute('class','errmsg');
  6405. input.errmsg.style = input.errmsg.style || {};
  6406. input.errmsg.style.color = 'red';
  6407. group.appendChild(input.errmsg);
  6408. }
  6409. else {
  6410. input.errmsg.style.display = 'block';
  6411. }
  6412. input.errmsg.innerHTML = '';
  6413. input.errmsg.appendChild(document.createTextNode(text));
  6414. },
  6415. removeInputError: function(input) {
  6416. input.style.borderColor = '';
  6417. if(input.errmsg) input.errmsg.style.display = 'none';
  6418. },
  6419. getProgressBar: function() {
  6420. var max = 100, start = 0;
  6421. var progressBar = document.createElement('progress');
  6422. progressBar.setAttribute('max', max);
  6423. progressBar.setAttribute('value', start);
  6424. return progressBar;
  6425. },
  6426. updateProgressBar: function(progressBar, progress) {
  6427. if (!progressBar) return;
  6428. progressBar.setAttribute('value', progress);
  6429. },
  6430. updateProgressBarUnknown: function(progressBar) {
  6431. if (!progressBar) return;
  6432. progressBar.removeAttribute('value');
  6433. }
  6434. });
  6435. JSONEditor.defaults.themes.jqueryui = JSONEditor.AbstractTheme.extend({
  6436. getTable: function() {
  6437. var el = this._super();
  6438. el.setAttribute('cellpadding',5);
  6439. el.setAttribute('cellspacing',0);
  6440. return el;
  6441. },
  6442. getTableHeaderCell: function(text) {
  6443. var el = this._super(text);
  6444. el.className = 'ui-state-active';
  6445. el.style.fontWeight = 'bold';
  6446. return el;
  6447. },
  6448. getTableCell: function() {
  6449. var el = this._super();
  6450. el.className = 'ui-widget-content';
  6451. return el;
  6452. },
  6453. getHeaderButtonHolder: function() {
  6454. var el = this.getButtonHolder();
  6455. el.style.marginLeft = '10px';
  6456. el.style.fontSize = '.6em';
  6457. el.style.display = 'inline-block';
  6458. return el;
  6459. },
  6460. getFormInputDescription: function(text) {
  6461. var el = this.getDescription(text);
  6462. el.style.marginLeft = '10px';
  6463. el.style.display = 'inline-block';
  6464. return el;
  6465. },
  6466. getFormControl: function(label, input, description) {
  6467. var el = this._super(label,input,description);
  6468. if(input.type === 'checkbox') {
  6469. el.style.lineHeight = '25px';
  6470. el.style.padding = '3px 0';
  6471. }
  6472. else {
  6473. el.style.padding = '4px 0 8px 0';
  6474. }
  6475. return el;
  6476. },
  6477. getDescription: function(text) {
  6478. var el = document.createElement('span');
  6479. el.style.fontSize = '.8em';
  6480. el.style.fontStyle = 'italic';
  6481. el.textContent = text;
  6482. return el;
  6483. },
  6484. getButtonHolder: function() {
  6485. var el = document.createElement('div');
  6486. el.className = 'ui-buttonset';
  6487. el.style.fontSize = '.7em';
  6488. return el;
  6489. },
  6490. getFormInputLabel: function(text) {
  6491. var el = document.createElement('label');
  6492. el.style.fontWeight = 'bold';
  6493. el.style.display = 'block';
  6494. el.textContent = text;
  6495. return el;
  6496. },
  6497. getButton: function(text, icon, title) {
  6498. var button = document.createElement("button");
  6499. button.className = 'ui-button ui-widget ui-state-default ui-corner-all';
  6500. // Icon only
  6501. if(icon && !text) {
  6502. button.className += ' ui-button-icon-only';
  6503. icon.className += ' ui-button-icon-primary ui-icon-primary';
  6504. button.appendChild(icon);
  6505. }
  6506. // Icon and Text
  6507. else if(icon) {
  6508. button.className += ' ui-button-text-icon-primary';
  6509. icon.className += ' ui-button-icon-primary ui-icon-primary';
  6510. button.appendChild(icon);
  6511. }
  6512. // Text only
  6513. else {
  6514. button.className += ' ui-button-text-only';
  6515. }
  6516. var el = document.createElement('span');
  6517. el.className = 'ui-button-text';
  6518. el.textContent = text||title||".";
  6519. button.appendChild(el);
  6520. button.setAttribute('title',title);
  6521. return button;
  6522. },
  6523. setButtonText: function(button,text, icon, title) {
  6524. button.innerHTML = '';
  6525. button.className = 'ui-button ui-widget ui-state-default ui-corner-all';
  6526. // Icon only
  6527. if(icon && !text) {
  6528. button.className += ' ui-button-icon-only';
  6529. icon.className += ' ui-button-icon-primary ui-icon-primary';
  6530. button.appendChild(icon);
  6531. }
  6532. // Icon and Text
  6533. else if(icon) {
  6534. button.className += ' ui-button-text-icon-primary';
  6535. icon.className += ' ui-button-icon-primary ui-icon-primary';
  6536. button.appendChild(icon);
  6537. }
  6538. // Text only
  6539. else {
  6540. button.className += ' ui-button-text-only';
  6541. }
  6542. var el = document.createElement('span');
  6543. el.className = 'ui-button-text';
  6544. el.textContent = text||title||".";
  6545. button.appendChild(el);
  6546. button.setAttribute('title',title);
  6547. },
  6548. getIndentedPanel: function() {
  6549. var el = document.createElement('div');
  6550. el.className = 'ui-widget-content ui-corner-all';
  6551. el.style.padding = '1em 1.4em';
  6552. el.style.marginBottom = '20px';
  6553. return el;
  6554. },
  6555. afterInputReady: function(input) {
  6556. if(input.controls) return;
  6557. input.controls = this.closest(input,'.form-control');
  6558. },
  6559. addInputError: function(input,text) {
  6560. if(!input.controls) return;
  6561. if(!input.errmsg) {
  6562. input.errmsg = document.createElement('div');
  6563. input.errmsg.className = 'ui-state-error';
  6564. input.controls.appendChild(input.errmsg);
  6565. }
  6566. else {
  6567. input.errmsg.style.display = '';
  6568. }
  6569. input.errmsg.textContent = text;
  6570. },
  6571. removeInputError: function(input) {
  6572. if(!input.errmsg) return;
  6573. input.errmsg.style.display = 'none';
  6574. },
  6575. markTabActive: function(tab) {
  6576. tab.className = tab.className.replace(/\s*ui-widget-header/g,'')+' ui-state-active';
  6577. },
  6578. markTabInactive: function(tab) {
  6579. tab.className = tab.className.replace(/\s*ui-state-active/g,'')+' ui-widget-header';
  6580. }
  6581. });
  6582. JSONEditor.defaults.themes.barebones = JSONEditor.AbstractTheme.extend({
  6583. getFormInputLabel: function (text) {
  6584. var el = this._super(text);
  6585. return el;
  6586. },
  6587. getFormInputDescription: function (text) {
  6588. var el = this._super(text);
  6589. return el;
  6590. },
  6591. getIndentedPanel: function () {
  6592. var el = this._super();
  6593. return el;
  6594. },
  6595. getChildEditorHolder: function () {
  6596. var el = this._super();
  6597. return el;
  6598. },
  6599. getHeaderButtonHolder: function () {
  6600. var el = this.getButtonHolder();
  6601. return el;
  6602. },
  6603. getTable: function () {
  6604. var el = this._super();
  6605. return el;
  6606. },
  6607. addInputError: function (input, text) {
  6608. if (!input.errmsg) {
  6609. var group = this.closest(input, '.form-control');
  6610. input.errmsg = document.createElement('div');
  6611. input.errmsg.setAttribute('class', 'errmsg');
  6612. group.appendChild(input.errmsg);
  6613. }
  6614. else {
  6615. input.errmsg.style.display = 'block';
  6616. }
  6617. input.errmsg.innerHTML = '';
  6618. input.errmsg.appendChild(document.createTextNode(text));
  6619. },
  6620. removeInputError: function (input) {
  6621. input.style.borderColor = '';
  6622. if (input.errmsg) input.errmsg.style.display = 'none';
  6623. },
  6624. getProgressBar: function () {
  6625. var max = 100, start = 0;
  6626. var progressBar = document.createElement('progress');
  6627. progressBar.setAttribute('max', max);
  6628. progressBar.setAttribute('value', start);
  6629. return progressBar;
  6630. },
  6631. updateProgressBar: function (progressBar, progress) {
  6632. if (!progressBar) return;
  6633. progressBar.setAttribute('value', progress);
  6634. },
  6635. updateProgressBarUnknown: function (progressBar) {
  6636. if (!progressBar) return;
  6637. progressBar.removeAttribute('value');
  6638. }
  6639. });
  6640. JSONEditor.AbstractIconLib = Class.extend({
  6641. mapping: {
  6642. collapse: '',
  6643. expand: '',
  6644. "delete": '',
  6645. edit: '',
  6646. add: '',
  6647. cancel: '',
  6648. save: '',
  6649. moveup: '',
  6650. movedown: ''
  6651. },
  6652. icon_prefix: '',
  6653. getIconClass: function(key) {
  6654. if(this.mapping[key]) return this.icon_prefix+this.mapping[key];
  6655. else return null;
  6656. },
  6657. getIcon: function(key) {
  6658. var iconclass = this.getIconClass(key);
  6659. if(!iconclass) return null;
  6660. var i = document.createElement('i');
  6661. i.className = iconclass;
  6662. return i;
  6663. }
  6664. });
  6665. JSONEditor.defaults.iconlibs.bootstrap2 = JSONEditor.AbstractIconLib.extend({
  6666. mapping: {
  6667. collapse: 'chevron-down',
  6668. expand: 'chevron-up',
  6669. "delete": 'trash',
  6670. edit: 'pencil',
  6671. add: 'plus',
  6672. cancel: 'ban-circle',
  6673. save: 'ok',
  6674. moveup: 'arrow-up',
  6675. movedown: 'arrow-down'
  6676. },
  6677. icon_prefix: 'icon-'
  6678. });
  6679. JSONEditor.defaults.iconlibs.bootstrap3 = JSONEditor.AbstractIconLib.extend({
  6680. mapping: {
  6681. collapse: 'chevron-down',
  6682. expand: 'chevron-right',
  6683. "delete": 'remove',
  6684. edit: 'pencil',
  6685. add: 'plus',
  6686. cancel: 'floppy-remove',
  6687. save: 'floppy-saved',
  6688. moveup: 'arrow-up',
  6689. movedown: 'arrow-down'
  6690. },
  6691. icon_prefix: 'glyphicon glyphicon-'
  6692. });
  6693. JSONEditor.defaults.iconlibs.fontawesome3 = JSONEditor.AbstractIconLib.extend({
  6694. mapping: {
  6695. collapse: 'chevron-down',
  6696. expand: 'chevron-right',
  6697. "delete": 'remove',
  6698. edit: 'pencil',
  6699. add: 'plus',
  6700. cancel: 'ban-circle',
  6701. save: 'save',
  6702. moveup: 'arrow-up',
  6703. movedown: 'arrow-down'
  6704. },
  6705. icon_prefix: 'icon-'
  6706. });
  6707. JSONEditor.defaults.iconlibs.fontawesome4 = JSONEditor.AbstractIconLib.extend({
  6708. mapping: {
  6709. collapse: 'caret-square-o-down',
  6710. expand: 'caret-square-o-right',
  6711. "delete": 'times',
  6712. edit: 'pencil',
  6713. add: 'plus',
  6714. cancel: 'ban',
  6715. save: 'save',
  6716. moveup: 'arrow-up',
  6717. movedown: 'arrow-down'
  6718. },
  6719. icon_prefix: 'fa fa-'
  6720. });
  6721. JSONEditor.defaults.iconlibs.foundation2 = JSONEditor.AbstractIconLib.extend({
  6722. mapping: {
  6723. collapse: 'minus',
  6724. expand: 'plus',
  6725. "delete": 'remove',
  6726. edit: 'edit',
  6727. add: 'add-doc',
  6728. cancel: 'error',
  6729. save: 'checkmark',
  6730. moveup: 'up-arrow',
  6731. movedown: 'down-arrow'
  6732. },
  6733. icon_prefix: 'foundicon-'
  6734. });
  6735. JSONEditor.defaults.iconlibs.foundation3 = JSONEditor.AbstractIconLib.extend({
  6736. mapping: {
  6737. collapse: 'minus',
  6738. expand: 'plus',
  6739. "delete": 'x',
  6740. edit: 'pencil',
  6741. add: 'page-add',
  6742. cancel: 'x-circle',
  6743. save: 'save',
  6744. moveup: 'arrow-up',
  6745. movedown: 'arrow-down'
  6746. },
  6747. icon_prefix: 'fi-'
  6748. });
  6749. JSONEditor.defaults.iconlibs.jqueryui = JSONEditor.AbstractIconLib.extend({
  6750. mapping: {
  6751. collapse: 'triangle-1-s',
  6752. expand: 'triangle-1-e',
  6753. "delete": 'trash',
  6754. edit: 'pencil',
  6755. add: 'plusthick',
  6756. cancel: 'closethick',
  6757. save: 'disk',
  6758. moveup: 'arrowthick-1-n',
  6759. movedown: 'arrowthick-1-s'
  6760. },
  6761. icon_prefix: 'ui-icon ui-icon-'
  6762. });
  6763. JSONEditor.defaults.templates["default"] = function() {
  6764. return {
  6765. compile: function(template) {
  6766. var matches = template.match(/{{\s*([a-zA-Z0-9\-_ \.]+)\s*}}/g);
  6767. var l = matches && matches.length;
  6768. // Shortcut if the template contains no variables
  6769. if(!l) return function() { return template; };
  6770. // Pre-compute the search/replace functions
  6771. // This drastically speeds up template execution
  6772. var replacements = [];
  6773. var get_replacement = function(i) {
  6774. var p = matches[i].replace(/[{}]+/g,'').trim().split('.');
  6775. var n = p.length;
  6776. var func;
  6777. if(n > 1) {
  6778. var cur;
  6779. func = function(vars) {
  6780. cur = vars;
  6781. for(i=0; i<n; i++) {
  6782. cur = cur[p[i]];
  6783. if(!cur) break;
  6784. }
  6785. return cur;
  6786. };
  6787. }
  6788. else {
  6789. p = p[0];
  6790. func = function(vars) {
  6791. return vars[p];
  6792. };
  6793. }
  6794. replacements.push({
  6795. s: matches[i],
  6796. r: func
  6797. });
  6798. };
  6799. for(var i=0; i<l; i++) {
  6800. get_replacement(i);
  6801. }
  6802. // The compiled function
  6803. return function(vars) {
  6804. var ret = template+"";
  6805. var r;
  6806. for(i=0; i<l; i++) {
  6807. r = replacements[i];
  6808. ret = ret.replace(r.s, r.r(vars));
  6809. }
  6810. return ret;
  6811. };
  6812. }
  6813. };
  6814. };
  6815. JSONEditor.defaults.templates.ejs = function() {
  6816. if(!window.EJS) return false;
  6817. return {
  6818. compile: function(template) {
  6819. var compiled = new window.EJS({
  6820. text: template
  6821. });
  6822. return function(context) {
  6823. return compiled.render(context);
  6824. };
  6825. }
  6826. };
  6827. };
  6828. JSONEditor.defaults.templates.handlebars = function() {
  6829. return window.Handlebars;
  6830. };
  6831. JSONEditor.defaults.templates.hogan = function() {
  6832. if(!window.Hogan) return false;
  6833. return {
  6834. compile: function(template) {
  6835. var compiled = window.Hogan.compile(template);
  6836. return function(context) {
  6837. return compiled.render(context);
  6838. };
  6839. }
  6840. };
  6841. };
  6842. JSONEditor.defaults.templates.markup = function() {
  6843. if(!window.Mark || !window.Mark.up) return false;
  6844. return {
  6845. compile: function(template) {
  6846. return function(context) {
  6847. return window.Mark.up(template,context);
  6848. };
  6849. }
  6850. };
  6851. };
  6852. JSONEditor.defaults.templates.mustache = function() {
  6853. if(!window.Mustache) return false;
  6854. return {
  6855. compile: function(template) {
  6856. return function(view) {
  6857. return window.Mustache.render(template, view);
  6858. };
  6859. }
  6860. };
  6861. };
  6862. JSONEditor.defaults.templates.swig = function() {
  6863. return window.swig;
  6864. };
  6865. JSONEditor.defaults.templates.underscore = function() {
  6866. if(!window._) return false;
  6867. return {
  6868. compile: function(template) {
  6869. return function(context) {
  6870. return window._.template(template, context);
  6871. };
  6872. }
  6873. };
  6874. };
  6875. // Set the default theme
  6876. JSONEditor.defaults.theme = 'html';
  6877. // Set the default template engine
  6878. JSONEditor.defaults.template = 'default';
  6879. // Default options when initializing JSON Editor
  6880. JSONEditor.defaults.options = {};
  6881. // String translate function
  6882. JSONEditor.defaults.translate = function(key, variables) {
  6883. var lang = JSONEditor.defaults.languages[JSONEditor.defaults.language];
  6884. if(!lang) throw "Unknown language "+JSONEditor.defaults.language;
  6885. var string = lang[key] || JSONEditor.defaults.languages[JSONEditor.defaults.default_language][key];
  6886. if(typeof string === "undefined") throw "Unknown translate string "+key;
  6887. if(variables) {
  6888. for(var i=0; i<variables.length; i++) {
  6889. string = string.replace(new RegExp('\\{\\{'+i+'}}','g'),variables[i]);
  6890. }
  6891. }
  6892. return string;
  6893. };
  6894. // Translation strings and default languages
  6895. JSONEditor.defaults.default_language = 'en';
  6896. JSONEditor.defaults.language = JSONEditor.defaults.default_language;
  6897. JSONEditor.defaults.languages.en = {
  6898. /**
  6899. * When a property is not set
  6900. */
  6901. error_notset: "Property must be set",
  6902. /**
  6903. * When a string must not be empty
  6904. */
  6905. error_notempty: "Value required",
  6906. /**
  6907. * When a value is not one of the enumerated values
  6908. */
  6909. error_enum: "Value must be one of the enumerated values",
  6910. /**
  6911. * When a value doesn't validate any schema of a 'anyOf' combination
  6912. */
  6913. error_anyOf: "Value must validate against at least one of the provided schemas",
  6914. /**
  6915. * When a value doesn't validate
  6916. * @variables This key takes one variable: The number of schemas the value does not validate
  6917. */
  6918. error_oneOf: 'Value must validate against exactly one of the provided schemas. It currently validates against {{0}} of the schemas.',
  6919. /**
  6920. * When a value does not validate a 'not' schema
  6921. */
  6922. error_not: "Value must not validate against the provided schema",
  6923. /**
  6924. * When a value does not match any of the provided types
  6925. */
  6926. error_type_union: "Value must be one of the provided types",
  6927. /**
  6928. * When a value does not match the given type
  6929. * @variables This key takes one variable: The type the value should be of
  6930. */
  6931. error_type: "Value must be of type {{0}}",
  6932. /**
  6933. * When the value validates one of the disallowed types
  6934. */
  6935. error_disallow_union: "Value must not be one of the provided disallowed types",
  6936. /**
  6937. * When the value validates a disallowed type
  6938. * @variables This key takes one variable: The type the value should not be of
  6939. */
  6940. error_disallow: "Value must not be of type {{0}}",
  6941. /**
  6942. * When a value is not a multiple of or divisible by a given number
  6943. * @variables This key takes one variable: The number mentioned above
  6944. */
  6945. error_multipleOf: "Value must be a multiple of {{0}}",
  6946. /**
  6947. * When a value is greater than it's supposed to be (exclusive)
  6948. * @variables This key takes one variable: The maximum
  6949. */
  6950. error_maximum_excl: "Value must be less than {{0}}",
  6951. /**
  6952. * When a value is greater than it's supposed to be (inclusive
  6953. * @variables This key takes one variable: The maximum
  6954. */
  6955. error_maximum_incl: "Value must be at most {{0}}",
  6956. /**
  6957. * When a value is lesser than it's supposed to be (exclusive)
  6958. * @variables This key takes one variable: The minimum
  6959. */
  6960. error_minimum_excl: "Value must be greater than {{0}}",
  6961. /**
  6962. * When a value is lesser than it's supposed to be (inclusive)
  6963. * @variables This key takes one variable: The minimum
  6964. */
  6965. error_minimum_incl: "Value must be at least {{0}}",
  6966. /**
  6967. * When a value have too many characters
  6968. * @variables This key takes one variable: The maximum character count
  6969. */
  6970. error_maxLength: "Value must be at most {{0}} characters long",
  6971. /**
  6972. * When a value does not have enough characters
  6973. * @variables This key takes one variable: The minimum character count
  6974. */
  6975. error_minLength: "Value must be at least {{0}} characters long",
  6976. /**
  6977. * When a value does not match a given pattern
  6978. */
  6979. error_pattern: "Value must match the pattern {{0}}",
  6980. /**
  6981. * When an array has additional items whereas it is not supposed to
  6982. */
  6983. error_additionalItems: "No additional items allowed in this array",
  6984. /**
  6985. * When there are to many items in an array
  6986. * @variables This key takes one variable: The maximum item count
  6987. */
  6988. error_maxItems: "Value must have at most {{0}} items",
  6989. /**
  6990. * When there are not enough items in an array
  6991. * @variables This key takes one variable: The minimum item count
  6992. */
  6993. error_minItems: "Value must have at least {{0}} items",
  6994. /**
  6995. * When an array is supposed to have unique items but has duplicates
  6996. */
  6997. error_uniqueItems: "Array must have unique items",
  6998. /**
  6999. * When there are too many properties in an object
  7000. * @variables This key takes one variable: The maximum property count
  7001. */
  7002. error_maxProperties: "Object must have at most {{0}} properties",
  7003. /**
  7004. * When there are not enough properties in an object
  7005. * @variables This key takes one variable: The minimum property count
  7006. */
  7007. error_minProperties: "Object must have at least {{0}} properties",
  7008. /**
  7009. * When a required property is not defined
  7010. * @variables This key takes one variable: The name of the missing property
  7011. */
  7012. error_required: "Object is missing the required property '{{0}}'",
  7013. /**
  7014. * When there is an additional property is set whereas there should be none
  7015. * @variables This key takes one variable: The name of the additional property
  7016. */
  7017. error_additional_properties: "No additional properties allowed, but property {{0}} is set",
  7018. /**
  7019. * When a dependency is not resolved
  7020. * @variables This key takes one variable: The name of the missing property for the dependency
  7021. */
  7022. error_dependency: "Must have property {{0}}",
  7023. /**
  7024. * Text on Delete All buttons
  7025. */
  7026. button_delete_all: "All",
  7027. /**
  7028. * Title on Delete All buttons
  7029. */
  7030. button_delete_all_title: "Delete All",
  7031. /**
  7032. * Text on Delete Last buttons
  7033. * @variable This key takes one variable: The title of object to delete
  7034. */
  7035. button_delete_last: "Last {{0}}",
  7036. /**
  7037. * Title on Delete Last buttons
  7038. * @variable This key takes one variable: The title of object to delete
  7039. */
  7040. button_delete_last_title: "Delete Last {{0}}",
  7041. /**
  7042. * Title on Add Row buttons
  7043. * @variable This key takes one variable: The title of object to add
  7044. */
  7045. button_add_row_title: "Add {{0}}",
  7046. /**
  7047. * Title on Move Down buttons
  7048. */
  7049. button_move_down_title: "Move down",
  7050. /**
  7051. * Title on Move Up buttons
  7052. */
  7053. button_move_up_title: "Move up",
  7054. /**
  7055. * Title on Delete Row buttons
  7056. * @variable This key takes one variable: The title of object to delete
  7057. */
  7058. button_delete_row_title: "Delete {{0}}",
  7059. /**
  7060. * Title on Delete Row buttons, short version (no parameter with the object title)
  7061. */
  7062. button_delete_row_title_short: "Delete",
  7063. /**
  7064. * Title on Collapse buttons
  7065. */
  7066. button_collapse: "Collapse",
  7067. /**
  7068. * Title on Expand buttons
  7069. */
  7070. button_expand: "Expand"
  7071. };
  7072. JSONEditor.defaults.languages.es = {
  7073. /**
  7074. * When a property is not set
  7075. */
  7076. error_notset: "La propiedad debe estar asignada",
  7077. /**
  7078. * When a string must not be empty
  7079. */
  7080. error_notempty: "Valor requerido",
  7081. /**
  7082. * When a value is not one of the enumerated values
  7083. */
  7084. error_enum: "El valor debe se uno de los valores enumerados",
  7085. /**
  7086. * When a value doesn't validate any schema of a 'anyOf' combination
  7087. */
  7088. error_anyOf: "El valor debe validarse contra al menos uno de los esquemás definidos",
  7089. /**
  7090. * When a value doesn't validate
  7091. * @variables This key takes one variable: The number of schemas the value does not validate
  7092. */
  7093. error_oneOf: 'El valor debe validarse contra exactamente uno de los esquemas definidos. Actualmente se valida contra {{0}} de los esquemas.',
  7094. /**
  7095. * When a value does not validate a 'not' schema
  7096. */
  7097. error_not: "El valor no debe validarse contra el esquema definido",
  7098. /**
  7099. * When a value does not match any of the provided types
  7100. */
  7101. error_type_union: "El valor debe ser de uno de los tipos definidos",
  7102. /**
  7103. * When a value does not match the given type
  7104. * @variables This key takes one variable: The type the value should be of
  7105. */
  7106. error_type: "El valor debe ser de tipo {{0}}",
  7107. /**
  7108. * When the value validates one of the disallowed types
  7109. */
  7110. error_disallow_union: "El valor no debe ser uno de los tipos deshabilitados",
  7111. /**
  7112. * When the value validates a disallowed type
  7113. * @variables This key takes one variable: The type the value should not be of
  7114. */
  7115. error_disallow: "El valor no debe ser de tipo {{0}}",
  7116. /**
  7117. * When a value is not a multiple of or divisible by a given number
  7118. * @variables This key takes one variable: The number mentioned above
  7119. */
  7120. error_multipleOf: "El valor debe ser un multiplo de {{0}}",
  7121. /**
  7122. * When a value is greater than it's supposed to be (exclusive)
  7123. * @variables This key takes one variable: The maximum
  7124. */
  7125. error_maximum_excl: "El valor debe ser menor que {{0}}",
  7126. /**
  7127. * When a value is greater than it's supposed to be (inclusive
  7128. * @variables This key takes one variable: The maximum
  7129. */
  7130. error_maximum_incl: "El valor debe ser como máximo {{0}}",
  7131. /**
  7132. * When a value is lesser than it's supposed to be (exclusive)
  7133. * @variables This key takes one variable: The minimum
  7134. */
  7135. error_minimum_excl: "El valor debe ser mayor que {{0}}",
  7136. /**
  7137. * When a value is lesser than it's supposed to be (inclusive)
  7138. * @variables This key takes one variable: The minimum
  7139. */
  7140. error_minimum_incl: "El valor debe ser al menos {{0}}",
  7141. /**
  7142. * When a value have too many characters
  7143. * @variables This key takes one variable: The maximum character count
  7144. */
  7145. error_maxLength: "El valor debe tener como máximo {{0}} caracteres",
  7146. /**
  7147. * When a value does not have enough characters
  7148. * @variables This key takes one variable: The minimum character count
  7149. */
  7150. error_minLength: "El valor debe tener al menos {{0}} caracteres",
  7151. /**
  7152. * When a value does not match a given pattern
  7153. */
  7154. error_pattern: "El valor debe tener el patrón {{0}}",
  7155. /**
  7156. * When an array has additional items whereas it is not supposed to
  7157. */
  7158. error_additionalItems: "No se permiten elementos adicionales en este array",
  7159. /**
  7160. * When there are to many items in an array
  7161. * @variables This key takes one variable: The maximum item count
  7162. */
  7163. error_maxItems: "El valor debe tener como máximo {{0}} elementos",
  7164. /**
  7165. * When there are not enough items in an array
  7166. * @variables This key takes one variable: The minimum item count
  7167. */
  7168. error_minItems: "El valor debe tener al menos {{0}} elementos",
  7169. /**
  7170. * When an array is supposed to have unique items but has duplicates
  7171. */
  7172. error_uniqueItems: "El Array debe tener elementos únicos",
  7173. /**
  7174. * When there are too many properties in an object
  7175. * @variables This key takes one variable: The maximum property count
  7176. */
  7177. error_maxProperties: "El objeto debe tener como máximo {{0}} propiedades",
  7178. /**
  7179. * When there are not enough properties in an object
  7180. * @variables This key takes one variable: The minimum property count
  7181. */
  7182. error_minProperties: "El objeto debe tener al menos {{0}} propiedades",
  7183. /**
  7184. * When a required property is not defined
  7185. * @variables This key takes one variable: The name of the missing property
  7186. */
  7187. error_required: "El objeto perdió la propiedad requerida '{{0}}'",
  7188. /**
  7189. * When there is an additional property is set whereas there should be none
  7190. * @variables This key takes one variable: The name of the additional property
  7191. */
  7192. error_additional_properties: "No se permiten propiedades adicionales, pero la propiedad {{0}} esta asignada",
  7193. /**
  7194. * When a dependency is not resolved
  7195. * @variables This key takes one variable: The name of the missing property for the dependency
  7196. */
  7197. error_dependency: "Debe tener la propiedad {{0}}",
  7198. /**
  7199. * Text on Delete All buttons
  7200. */
  7201. button_delete_all: "Todos",
  7202. /**
  7203. * Title on Delete All buttons
  7204. */
  7205. button_delete_all_title: "Eliminar Todos",
  7206. /**
  7207. * Text on Delete Last buttons
  7208. * @variable This key takes one variable: The title of object to delete
  7209. */
  7210. button_delete_last: "Último {{0}}",
  7211. /**
  7212. * Title on Delete Last buttons
  7213. * @variable This key takes one variable: The title of object to delete
  7214. */
  7215. button_delete_last_title: "Eliminar Último {{0}}",
  7216. /**
  7217. * Title on Add Row buttons
  7218. * @variable This key takes one variable: The title of object to add
  7219. */
  7220. button_add_row_title: "Agregar {{0}}",
  7221. /**
  7222. * Title on Move Down buttons
  7223. */
  7224. button_move_down_title: "Bajar",
  7225. /**
  7226. * Title on Move Up buttons
  7227. */
  7228. button_move_up_title: "Subir",
  7229. /**
  7230. * Title on Delete Row buttons
  7231. * @variable This key takes one variable: The title of object to delete
  7232. */
  7233. button_delete_row_title: "Eliminar {{0}}",
  7234. /**
  7235. * Title on Delete Row buttons, short version (no parameter with the object title)
  7236. */
  7237. button_delete_row_title_short: "Eliminar",
  7238. /**
  7239. * Title on Collapse buttons
  7240. */
  7241. button_collapse: "Contraer",
  7242. /**
  7243. * Title on Expand buttons
  7244. */
  7245. button_expand: "Expandir"
  7246. };
  7247. // Miscellaneous Plugin Settings
  7248. JSONEditor.plugins = {
  7249. ace: {
  7250. theme: ''
  7251. },
  7252. epiceditor: {
  7253. },
  7254. sceditor: {
  7255. },
  7256. select2: {
  7257. },
  7258. selectize: {
  7259. }
  7260. };
  7261. // Default per-editor options
  7262. $each(JSONEditor.defaults.editors, function(i,editor) {
  7263. JSONEditor.defaults.editors[i].options = editor.options || {};
  7264. });
  7265. // Set the default resolvers
  7266. // Use "multiple" as a fall back for everything
  7267. JSONEditor.defaults.resolvers.unshift(function(schema) {
  7268. if(typeof schema.type !== "string") return "multiple";
  7269. });
  7270. // If the type is not set but properties are defined, we can infer the type is actually object
  7271. JSONEditor.defaults.resolvers.unshift(function(schema) {
  7272. // If the schema is a simple type
  7273. if(!schema.type && schema.properties ) return "object";
  7274. });
  7275. // If the type is set and it's a basic type, use the primitive editor
  7276. JSONEditor.defaults.resolvers.unshift(function(schema) {
  7277. // If the schema is a simple type
  7278. if(typeof schema.type === "string") return schema.type;
  7279. });
  7280. // Boolean editors
  7281. JSONEditor.defaults.resolvers.unshift(function(schema) {
  7282. if(schema.type === 'boolean') {
  7283. // If explicitly set to 'checkbox', use that
  7284. if(schema.format === "checkbox" || (schema.options && schema.options.checkbox)) {
  7285. return "checkbox";
  7286. }
  7287. // Otherwise, default to select menu
  7288. return (JSONEditor.plugins.selectize.enable) ? 'selectize' : 'select';
  7289. }
  7290. });
  7291. // Use the multiple editor for schemas where the `type` is set to "any"
  7292. JSONEditor.defaults.resolvers.unshift(function(schema) {
  7293. // If the schema can be of any type
  7294. if(schema.type === "any") return "multiple";
  7295. });
  7296. // Editor for base64 encoded files
  7297. JSONEditor.defaults.resolvers.unshift(function(schema) {
  7298. // If the schema can be of any type
  7299. if(schema.type === "string" && schema.media && schema.media.binaryEncoding==="base64") {
  7300. return "base64";
  7301. }
  7302. });
  7303. // Editor for uploading files
  7304. JSONEditor.defaults.resolvers.unshift(function(schema) {
  7305. if(schema.type === "string" && schema.format === "url" && schema.options && schema.options.upload === true) {
  7306. if(window.FileReader) return "upload";
  7307. }
  7308. });
  7309. // Use the table editor for arrays with the format set to `table`
  7310. JSONEditor.defaults.resolvers.unshift(function(schema) {
  7311. // Type `array` with format set to `table`
  7312. if(schema.type == "array" && schema.format == "table") {
  7313. return "table";
  7314. }
  7315. });
  7316. // Use the `select` editor for dynamic enumSource enums
  7317. JSONEditor.defaults.resolvers.unshift(function(schema) {
  7318. if(schema.enumSource) return (JSONEditor.plugins.selectize.enable) ? 'selectize' : 'select';
  7319. });
  7320. // Use the `enum` or `select` editors for schemas with enumerated properties
  7321. JSONEditor.defaults.resolvers.unshift(function(schema) {
  7322. if(schema["enum"]) {
  7323. if(schema.type === "array" || schema.type === "object") {
  7324. return "enum";
  7325. }
  7326. else if(schema.type === "number" || schema.type === "integer" || schema.type === "string") {
  7327. return (JSONEditor.plugins.selectize.enable) ? 'selectize' : 'select';
  7328. }
  7329. }
  7330. });
  7331. // Specialized editors for arrays of strings
  7332. JSONEditor.defaults.resolvers.unshift(function(schema) {
  7333. if(schema.type === "array" && schema.items && !(Array.isArray(schema.items)) && schema.uniqueItems && ['string','number','integer'].indexOf(schema.items.type) >= 0) {
  7334. // For enumerated strings, number, or integers
  7335. if(schema.items.enum) {
  7336. return 'multiselect';
  7337. }
  7338. // For non-enumerated strings (tag editor)
  7339. else if(JSONEditor.plugins.selectize.enable && schema.items.type === "string") {
  7340. return 'arraySelectize';
  7341. }
  7342. }
  7343. });
  7344. // Use the multiple editor for schemas with `oneOf` set
  7345. JSONEditor.defaults.resolvers.unshift(function(schema) {
  7346. // If this schema uses `oneOf` or `anyOf`
  7347. if(schema.oneOf || schema.anyOf) return "multiple";
  7348. });
  7349. /**
  7350. * This is a small wrapper for using JSON Editor like a typical jQuery plugin.
  7351. */
  7352. (function() {
  7353. if(window.jQuery || window.Zepto) {
  7354. var $ = window.jQuery || window.Zepto;
  7355. $.jsoneditor = JSONEditor.defaults;
  7356. $.fn.jsoneditor = function(options) {
  7357. var self = this;
  7358. var editor = this.data('jsoneditor');
  7359. if(options === 'value') {
  7360. if(!editor) throw "Must initialize jsoneditor before getting/setting the value";
  7361. // Set value
  7362. if(arguments.length > 1) {
  7363. editor.setValue(arguments[1]);
  7364. }
  7365. // Get value
  7366. else {
  7367. return editor.getValue();
  7368. }
  7369. }
  7370. else if(options === 'validate') {
  7371. if(!editor) throw "Must initialize jsoneditor before validating";
  7372. // Validate a specific value
  7373. if(arguments.length > 1) {
  7374. return editor.validate(arguments[1]);
  7375. }
  7376. // Validate current value
  7377. else {
  7378. return editor.validate();
  7379. }
  7380. }
  7381. else if(options === 'destroy') {
  7382. if(editor) {
  7383. editor.destroy();
  7384. this.data('jsoneditor',null);
  7385. }
  7386. }
  7387. else {
  7388. // Destroy first
  7389. if(editor) {
  7390. editor.destroy();
  7391. }
  7392. // Create editor
  7393. editor = new JSONEditor(this.get(0),options);
  7394. this.data('jsoneditor',editor);
  7395. // Setup event listeners
  7396. editor.on('change',function() {
  7397. self.trigger('change');
  7398. });
  7399. editor.on('ready',function() {
  7400. self.trigger('ready');
  7401. });
  7402. }
  7403. return this;
  7404. };
  7405. }
  7406. })();
  7407. window.JSONEditor = JSONEditor;
  7408. })();
  7409. //# sourceMappingURL=jsoneditor.js.map