You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by ad...@apache.org on 2017/05/23 10:34:03 UTC

[05/49] ambari git commit: AMBARI-20436. Create a prototype of ambari-server swagger integration. (jaimin)

http://git-wip-us.apache.org/repos/asf/ambari/blob/fb86fb3b/ambari-web/api-docs/lib/jsoneditor.js
----------------------------------------------------------------------
diff --git a/ambari-web/api-docs/lib/jsoneditor.js b/ambari-web/api-docs/lib/jsoneditor.js
new file mode 100644
index 0000000..ba48b06
--- /dev/null
+++ b/ambari-web/api-docs/lib/jsoneditor.js
@@ -0,0 +1,7287 @@
+/*! 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.addproper

<TRUNCATED>