text: 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.SimpleMDE) {
        this.SimpleMDE.value(sanitized);
      }
      else if(this.ace_editor) {
        this.ace_editor.setValue(sanitized);
      }
      
      var changed = from_template || this.getValue() !== value;
      
      this.refreshValue();
      
      if(initial) this.is_dirty = false;
      else if(this.jsoneditor.options.show_errors === "change") this.is_dirty = true;
      
      if(this.adjust_height) this.adjust_height(this.input);
  
      // Bubble this setValue to parents if the value changed
      this.onChange(changed);
    },
    getNumColumns: function() {
      var min = Math.ceil(Math.max(this.getTitle().length,this.schema.maxLength||0,this.schema.minLength||0)/5);
      var num;
      
      if(this.input_type === 'textarea') num = 6;
      else if(['text','email'].indexOf(this.input_type) >= 0) num = 4;
      else num = 2;
      
      return Math.min(12,Math.max(min,num));
    },
    build: function() {
      var self = this, i;
      if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle());
      if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description);
  
      this.format = this.schema.format;
      if(!this.format && this.schema.media && this.schema.media.type) {
        this.format = this.schema.media.type.replace(/(^(application|text)\/(x-)?(script\.)?)|(-source$)/g,'');
      }
      if(!this.format && this.options.default_format) {
        this.format = this.options.default_format;
      }
      if(this.options.format) {
        this.format = this.options.format;
      }
  
      // Specific format
      if(this.format) {
        // Text Area
        if(this.format === 'textarea') {
          this.input_type = 'textarea';
          this.input = this.theme.getTextareaInput();
        }
        // Range Input
        else if(this.format === 'range') {
          this.input_type = 'range';
          var min = this.schema.minimum || 0;
          var max = this.schema.maximum || Math.max(100,min+1);
          var step = 1;
          if(this.schema.multipleOf) {
            if(min%this.schema.multipleOf) min = Math.ceil(min/this.schema.multipleOf)*this.schema.multipleOf;
            if(max%this.schema.multipleOf) max = Math.floor(max/this.schema.multipleOf)*this.schema.multipleOf;
            step = this.schema.multipleOf;
          }
  
          this.input = this.theme.getRangeInput(min,max,step);
        }
        // Source Code
        else if([
            'actionscript',
            'batchfile',
            'bbcode',
            'c',
            'c++',
            'cpp',
            'coffee',
            'csharp',
            'css',
            'dart',
            'django',
            'ejs',
            'erlang',
            'golang',
            'groovy',
            'handlebars',
            'haskell',
            'haxe',
            'html',
            'ini',
            'jade',
            'java',
            'javascript',
            'json',
            'less',
            'lisp',
            'lua',
            'makefile',
            'markdown',
            'matlab',
            'mysql',
            'objectivec',
            'pascal',
            'perl',
            'pgsql',
            'php',
            'python',
            'r',
            'ruby',
            'sass',
            'scala',
            'scss',
            'smarty',
            'sql',
            'stylus',
            'svg',
            'twig',
            'vbscript',
            'xml',
            'yaml'
          ].indexOf(this.format) >= 0
        ) {
          this.input_type = this.format;
          this.source_code = true;
          
          this.input = this.theme.getTextareaInput();
        }
        // HTML5 Input type
        else {
          this.input_type = this.format;
          this.input = this.theme.getFormInputField(this.input_type);
        }
      }
      // Normal text input
      else {
        this.input_type = 'text';
        this.input = this.theme.getFormInputField(this.input_type);
      }
      
      // minLength, maxLength, and pattern
      if(typeof this.schema.maxLength !== "undefined") this.input.setAttribute('maxlength',this.schema.maxLength);
      if(typeof this.schema.pattern !== "undefined") this.input.setAttribute('pattern',this.schema.pattern);
      else if(typeof this.schema.minLength !== "undefined") this.input.setAttribute('pattern','.{'+this.schema.minLength+',}');
  
      if(this.options.compact) {
        this.container.className += ' compact';
      }
      else {
        if(this.options.input_width) this.input.style.width = this.options.input_width;
      }
  
      if(this.schema.readOnly || this.schema.readonly || this.schema.template) {
        this.always_disabled = true;
        this.input.disabled = true;
      }
  
      this.input
        .addEventListener('change',function(e) {        
          e.preventDefault();
          e.stopPropagation();
          
          // Don't allow changing if this field is a template
          if(self.schema.template) {
            this.value = self.value;
            return;
          }
  
          var val = this.value;
          
          // sanitize value
          var sanitized = self.sanitize(val);
          if(val !== sanitized) {
            this.value = sanitized;
          }
          
          self.is_dirty = true;
  
          self.refreshValue();
          self.onChange(true);
        });
        
      if(this.options.input_height) this.input.style.height = this.options.input_height;
      if(this.options.expand_height) {
        this.adjust_height = function(el) {
          if(!el) return;
          var i, ch=el.offsetHeight;
          // Input too short
          if(el.offsetHeight < el.scrollHeight) {
            i=0;
            while(el.offsetHeight < el.scrollHeight+3) {
              if(i>100) break;
              i++;
              ch++;
              el.style.height = ch+'px';
            }
          }
          else {
            i=0;
            while(el.offsetHeight >= el.scrollHeight+3) {
              if(i>100) break;
              i++;
              ch--;
              el.style.height = ch+'px';
            }
            el.style.height = (ch+1)+'px';
          }
        };
        
        this.input.addEventListener('keyup',function(e) {
          self.adjust_height(this);
        });
        this.input.addEventListener('change',function(e) {
          self.adjust_height(this);
        });
        this.adjust_height();
      }
  
      if(this.format) this.input.setAttribute('data-schemaformat',this.format);
  
      this.control = this.theme.getFormControl(this.label, this.input, this.description);
      this.container.appendChild(this.control);
  
      // Any special formatting that needs to happen after the input is added to the dom
      window.requestAnimationFrame(function() {
        // Skip in case the input is only a temporary editor,
        // otherwise, in the case of an ace_editor creation,
        // it will generate an error trying to append it to the missing parentNode
        if(self.input.parentNode) self.afterInputReady();
        if(self.adjust_height) self.adjust_height(self.input);
      });
  
      // Compile and store the template
      if(this.schema.template) {
        this.template = this.jsoneditor.compileTemplate(this.schema.template, this.template_engine);
        this.refreshValue();
      }
      else {
        this.refreshValue();
      }
    },
    enable: function() {
      if(!this.always_disabled) {
        this.input.disabled = false;
        // TODO: WYSIWYG and Markdown editors
      }
      this._super();
    },
    disable: function() {
      this.input.disabled = true;
      // TODO: WYSIWYG and Markdown editors
      this._super();
    },
    afterInputReady: function() {
      var self = this, options;
      
      // Code editor
      if(this.source_code) {      
        // WYSIWYG html and bbcode editor
        if(this.options.wysiwyg && 
          ['html','bbcode'].indexOf(this.input_type) >= 0 && 
          window.jQuery && window.jQuery.fn && window.jQuery.fn.sceditor
        ) {
          options = $extend({},{
            plugins: self.input_type==='html'? 'xhtml' : 'bbcode',
            emoticonsEnabled: false,
            width: '100%',
            height: 300
          },JSONEditor.plugins.sceditor,self.options.sceditor_options||{});
          
          window.jQuery(self.input).sceditor(options);
          
          self.sceditor_instance = window.jQuery(self.input).sceditor('instance');
          
          self.sceditor_instance.blur(function() {
            // Get editor's value
            var val = window.jQuery("<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);
          });
        }
        // SimpleMDE for markdown (if it's loaded)
        else if (this.input_type === 'markdown' && window.SimpleMDE) {
          options = $extend({},JSONEditor.plugins.SimpleMDE,{
            element: this.input
          });
  
          this.SimpleMDE = new window.SimpleMDE((options));
  
          this.SimpleMDE.codemirror.on("change",function() {
            self.value = self.SimpleMDE.value();
            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.SimpleMDE) {
        this.SimpleMDE.toTextArea();
        this.SimpleMDE = null;
      }
      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);
      }
    }
  });
  