Small utility library for working with browser web forms.

/**
 * Various form utils from Google Closure Library.
 * https://github.com/google/closure-library/
 */

var forms = forms || {};

/**
 * Enum of all input types (for INPUT, BUTTON, SELECT and TEXTAREA elements)
 * specified by the W3C HTML4.01 and HTML5 specifications.
 *
 * @enum {string}
 */
forms.InputType = {
  BUTTON: 'button',
  CHECKBOX: 'checkbox',
  COLOR: 'color',
  DATE: 'date',
  DATETIME: 'datetime',
  DATETIME_LOCAL: 'datetime-local',
  EMAIL: 'email',
  FILE: 'file',
  HIDDEN: 'hidden',
  IMAGE: 'image',
  MENU: 'menu',
  MONTH: 'month',
  NUMBER: 'number',
  PASSWORD: 'password',
  RADIO: 'radio',
  RANGE: 'range',
  RESET: 'reset',
  SEARCH: 'search',
  SELECT_MULTIPLE: 'select-multiple',
  SELECT_ONE: 'select-one',
  SUBMIT: 'submit',
  TEL: 'tel',
  TEXT: 'text',
  TEXTAREA: 'textarea',
  TIME: 'time',
  URL: 'url',
  WEEK: 'week'
};

/**
 * Whether the form has a file input.
 *
 * @param {HTMLFormElement} form The form.
 * @return {boolean} Whether the form has a file input.
 */
forms.hasFileInput = function(form) {
  var els = form.elements;
  for (var el, i = 0; el = els[i]; i++) {
    if (!el.disabled && el.type &&
        el.type.toLowerCase() == forms.InputType.FILE) {
      return true;
    }
  }
  return false;
};

/**
 * Enables or disables either all elements in a form or a single form element.
 *
 * @param {Element} el The element, either a form or an element within a form.
 * @param {boolean} disabled Whether the element should be disabled.
 */
forms.setDisabled = function(el, disabled) {
  // disable all elements in a form
  if (el.tagName == "FORM") {
    var els = /** @type {!HTMLFormElement} */ (el).elements;
    for (var i = 0; (el = els.item(i)); i++) {
      forms.setDisabled(el, disabled);
    }
  } else {
    // makes sure to blur buttons, multi-selects, and any elements which
    // maintain keyboard/accessibility focus when disabled
    if (disabled == true) {
      el.blur();
    }
    el.disabled = disabled;
  }
};

/**
 * Focuses, and optionally selects the content of, a form element.
 *
 * @param {Element} el The form element.
 */
forms.focusAndSelect = function(el) {
  el.focus();
  if (el.select) {
    el.select();
  }
};

/**
 * Whether a form element has a value.
 *
 * @param {Element} el The element.
 * @return {boolean} Whether the form has a value.
 */
forms.hasValue = function(el) {
  var value = forms.getValue(el);
  return !!value;
};

/**
 * Whether a named form field has a value.
 *
 * @param {HTMLFormElement} form The form element.
 * @param {string} name Name of an input to the form.
 * @return {boolean} Whether the form has a value.
 */
forms.hasValueByName = function(form, name) {
  var value = forms.getValueByName(form, name);
  return !!value;
};

/**
 * Gets the current value of any element with a type.
 *
 * @param {null|!Element|!RadioNodeList<?>} input The element.
 * @return {string|Array<string>|null} The current value of the element (or null).
 */
forms.getValue = function(input) {
  // Elements with a type may need more specialized logic.
  var type = /** {{type: (string|undefined)}} */ input.type;

  if (typeof type === "string") {
    var el = /** @type {!Element} */ (input);

    switch (type.toLowerCase()) {
      case forms.InputType.CHECKBOX:
      case forms.InputType.RADIO:
        return forms.getInputChecked_(el);
      case forms.InputType.SELECT_ONE:
        return forms.getSelectSingle_(el);
      case forms.InputType.SELECT_MULTIPLE:
        return forms.getSelectMultiple_(el);
      default:
      // Not every element with a value has a type (e.g. meter and progress).
    }
  }

  // Coerce `undefined` to `null`.
  return input.value != null ? input.value : null;
};

/**
 * Returns the value of the named form field. In the case of radio buttons,
 * returns the value of the checked button with the given name.
 *
 * @param {HTMLFormElement} form The form element.
 * @param {string} name Name of an input to the form.
 * @return {Array<string>|string|null} The value of the form element, or null if the form element does not exist or has no value.
 */
forms.getValueByName = function(form, name) {
  var els = form.elements[name];

  if (!els) {
    return null;
  } else if (els.type) {
    return forms.getValue(/** @type {!Element} */ (els));
  } else {
    for (var i = 0; i < els.length; i++) {
      var val = forms.getValue(els[i]);
      if (val) {
        return val;
      }
    }
    return null;
  }
};

/**
 * Gets the current value of a checkable input element.
 *
 * @param {Element} el The element.
 * @return {?string} The value of the form element (or null).
 * @private
 */
forms.getInputChecked_ = function(el) {
  return el.checked ? /** @type {?} */ (el).value : null;
};

/**
 * Gets the current value of a select-one element.
 *
 * @param {Element} el The element.
 * @return {?string} The value of the form element (or null).
 * @private
 */
forms.getSelectSingle_ = function(el) {
  var selectedIndex = /** @type {!HTMLSelectElement} */ (el).selectedIndex;
  return selectedIndex >= 0
    ? /** @type {!HTMLSelectElement} */ (el).options[selectedIndex].value
    : null;
};

/**
 * Gets the current value of a select-multiple element.
 *
 * @param {Element} el The element.
 * @return {Array<string>?} The value of the form element (or null).
 * @private
 */
forms.getSelectMultiple_ = function(el) {
  var values = [];
  for (var option, i = 0;
       option = /** @type {!HTMLSelectElement} */ (el).options[i]; i++) {
    if (option.selected) {
      values.push(option.value);
    }
  }
  return values.length ? values : null;
};

/**
 * Sets a checkable input element's checked property.
 *
 * #TODO(user): This seems potentially unintuitive since it doesn't set
 * the value property but my hunch is that the primary use case is to check a
 * checkbox, not to reset its value property.
 *
 * @param {Element} el The element.
 * @param {string|boolean=} opt_value The value, sets the element checked if val is set.
 * @private
 */
forms.setInputChecked_ = function(el, opt_value) {
  el.checked = opt_value;
};


/**
 * Sets the value of a select-one element.
 *
 * @param {Element} el The element.
 * @param {string=} opt_value The value of the selected option element.
 * @private
 */
forms.setSelectSingle_ = function(el, opt_value) {
  // unset any prior selections
  el.selectedIndex = -1;
  if (typeof opt_value === "string") {
    for (var option, i = 0;
         option = /** @type {!HTMLSelectElement} */ (el).options[i]; i++) {
      if (option.value == opt_value) {
        option.selected = true;
        break;
      }
    }
  }
};

/**
 * Sets the value of a select-multiple element.
 *
 * @param {Element} el The element.
 * @param {Array<string>|string=} opt_value The value of the selected option element(s).
 * @private
 */
forms.setSelectMultiple_ = function(el, opt_value) {
  // reset string opt_values as an array
  if (typeof opt_value === "string") {
    opt_value = [opt_value];
  }
  for (var option, i = 0;
       option = /** @type {!HTMLSelectElement} */ (el).options[i]; i++) {
    // we have to reset the other options to false for select-multiple
    option.selected = false;
    if (opt_value) {
      for (var value, j = 0; value = opt_value[j]; j++) {
        if (option.value == value) {
          option.selected = true;
        }
      }
    }
  }
};