Source: TextInput.js

///////////////////////// Text /////////////////////////

/**
 * A text field
 * <pre><strong>options:</strong> {
 *  element : «Element | ID»,
 *  name : «String»,
 *  key : «String»,
 *  maxHeight : «<strong>100</strong> | integer»,
 *  animateUserChange : «<strong>true</strong> | false»
 * }
 *
 * <strong>Events:</strong>
 * $valueChanged(value) - When the value of the field is changed by the user
 * @constructor
 */
hui.ui.TextInput = function(options) {
  this.options = hui.override({key:null,lines:1,maxHeight:100,animateUserChange:true,submitOnEnter:null},options);
  this.element = hui.get(options.element);
  this.name = options.name;
  hui.ui.extend(this);
  this.input = this.element;
  this.multiline = this.input.tagName.toLowerCase() == 'textarea';
  this.placeholder = hui.get.firstByClass(this.element,'hui_field_placeholder');
  this.value = this.input.value;
  this.modified = false;
  this._attach();
};

/**
 * Creates a new text field
 * <pre><strong>options:</strong> {
 *  value : «String»,
 *  multiline : «true | <strong>false</strong>»,
 *  lines : «<strong>1</strong> | integer»,
 *
 *  name : «String»,
 *  key : «String»,
 *  maxHeight : «<strong>100</strong> | integer»,
 *  animateUserChange : «<strong>true</strong> | false»
 * }
 * </pre>
 */
hui.ui.TextInput.create = function(options) {
  options = hui.override({lines:1},options);
  var node,input;
  if (options.lines>1 || options.multiline) {
    input = hui.build('textarea',
      {'class':'hui_textinput','rows':options.lines}
    );
  } else {
    input = hui.build('input',{'class':'hui_textinput'});
    if (options.secret) {
      input.setAttribute('type','password');
    }
  }
  if (options.testName) {
    input.setAttribute('data-test', options.testName);
  }
  if (options.value!==undefined) {
    input.value=options.value;
  }
  options.element = input;
  return new hui.ui.TextInput(options);
};

hui.ui.TextInput.prototype = {
  _attach : function() {
    if (this.placeholder || this.input.type=='password') {
      var self = this;
      hui.ui.onReady(function() {
        window.setTimeout(function() {
          self.value = self.input.value;
          self._updateClass();
        }, 500);
      });
    }
    hui.ui.addFocusClass({
      element: this.input,
      classElement: this.element,
      'class': 'hui_field_focused',
      widget: this
    });
    hui.listen(this.input, 'keyup', this._onKeyUp.bind(this));
    hui.listen(this.input, 'keydown', this._onKeyDown.bind(this));
    hui.listen(this.input, 'input', this._onKeyUp.bind(this));
    hui.listen(this.input, 'change', this._onChange.bind(this));
    var p = this.element.getElementsByTagName('em')[0];
    if (p) {
      this._updateClass();
      hui.listen(p, 'mousedown', function() {
        window.setTimeout(function() {
          this.input.focus();
          this.input.select();
        }.bind(this));
      }.bind(this));
      hui.listen(p, 'mouseup', function() {
        this.input.focus();
        this.input.select();
      }.bind(this));
    }
  },
  _updateClass : function() {
    hui.cls.set(this.element,'hui_field_dirty',this.value.length>0);
  },
  _onKeyDown : function(e) {
    if (e.keyCode === 13) {
      if (this.multiline && !(e.ctrlKey || e.metaKey)) {
        return;
      }
      hui.stop(e);
      this.fire('submit');
      var form = hui.ui.getAncestor(this,'hui_form');
      if (form) {form.submit();}
    }
  },
  _onChange : function(e) {
    this._onKeyUp(e);
  },
  _onKeyUp : function(e) {
    if (this.input.value==this.value) {return;}
    this.value=this.input.value;
    this._updateClass();
    this._expand(this.options.animateUserChange);
    hui.ui.callAncestors(this,'childValueChanged',this.input.value);
    this.fire('valueChanged',this.input.value);
    this.modified = true;
  },
  /** Focus the text field */
  focus : function() {
    try {
      this.input.focus();
    } catch (e) {}
  },
  /** Select the text in the text field */
  select : function() {
    try {
      this.input.focus();
      this.input.select();
    } catch (e) {}
  },
  /** Clear the value of the text field */
  reset : function() {
    this.setValue('');
  },
  isModified : function() {
      return this.modified;
  },
  /** Draw attention to the field */
  stress : function() {
    hui.ui.stress(this);
  },
  /** Set the value of the field
   * @value {String} The value
   */
  setValue : function(value) {
    if (value===undefined || value===null) {
      value='';
    }
    this.value = value;
    this.input.value = value;
    this._expand(this.options.animateValueChange);
    this.modified = false;
  },
  /** Get the value
   * @returns {String} The value
   */
  getValue : function() {
    return this.input.value;
  },
  /** Check if the value is empty ('' / the empty string)
   * @returns {Boolean} True if the value the empty string
   */
  isEmpty : function() {
    return this.input.value === '';
  },
  /** Check if the value has any non-white-space characters
   * @returns {Boolean} True if the value is blank
   */
  isBlank : function() {
    return hui.isBlank(this.input.value);
  },
  /** Mark the field with error
   * @value {String | Boolean} The error message or true to mark the field
   */
  setError : function(error) {
    var isError = error ? true : false;
    hui.cls.set(this.element,'hui_field_error',isError);
    if (typeof(error) == 'string') {
      hui.ui.showToolTip({text:error,element:this.element,key:this.name});
    }
    if (!isError) {
      hui.ui.hideToolTip({key:this.name});
    }
  },


  // Expanding...

  /** @private */
  $visibilityChanged : function() {
    if (hui.dom.isVisible(this.element)) {
      window.setTimeout(this._expand.bind(this));
    }
  },
  _expand : function(animate) {
    if (!this.multiline || !hui.dom.isVisible(this.element)) {
      return;
    }
    var textHeight = this._getTextAreaHeight(this.input);
    textHeight = Math.min(textHeight,this.options.maxHeight);
    if (animate) {
      hui.animate({
        node : this.input,
        duration : 300,
        css : {height:textHeight+'px'},
        ease : hui.ease.slowFastSlow
      });
    } else {
      this.input.style.height = textHeight+'px';
    }
  },
  _getTextAreaHeight : function(input) {
    var t = this.textAreaDummy;
    if (!t) {
      t = this.textAreaDummy = hui.build('div.hui_textinput_dummy', {parent: document.body});
    }
    var html = input.value;
    if (html[html.length-1]==='\n') {
      html+='x';
    }
    html = hui.string.escape(html).replace(/\n/g,'<br/>');
    t.innerHTML = html;
    t.style.width = input.clientWidth + 'px';
    return t.clientHeight + 2;
  }
};