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