Manual Reference Source Test

src/elements/formelement.js

/**
*  @file FormElement definition class
*  @author  Liqueur de Toile <contact@liqueurdetoile.com>
*  @license Apache-2.0 {@link https://www.apache.org/licenses/LICENSE-2.0}
*/

import ObjectArray from 'dot-object-array';
import HtmlElement from 'elements/htmlelement';
import Q from 'query';

/**
*  A FormElement instance node may contains only `<form>` Element.
*
*  Its main purpose is to provide an easy way to validate a whole form and prepare
*  data for submission
*
*  @version 1.0.0
*  @since 1.0.0
*  @author Liqueur de Toile <contact@liqueurdetoile.com>
*  @todo
*
*  - Add support for binary data
*/

export default class FormElement extends HtmlElement {
  /**
  *  The form constructor
  *
  *  @version 1.0.0
  *  @since 1.0.0
  *  @author Liqueur de Toile <contact@liqueurdetoile.com>
  *
  *  @private
  *  @param {Element} node Form Element
  *  @param {KeyValueObject} [options = {}]
  *  @returns {FormElement}
  *  @see https://developer.mozilla.org/fr/docs/Web/HTML/Element/Form
  */
  constructor(node, options = {}) {
    super(node, options);

    /**
    *  Errors after validation
    *
    *  @type {ObjectArray}
    *  @since 1.0.0
    */
    this._errors = new ObjectArray();
  }

  /**
  *  Returns a field by its name
  *
  *  @version 1.0.0
  *  @since 1.0.0
  *  @author Liqueur de Toile <contact@liqueurdetoile.com>
  *
  *  @param {string} f  Field name
  *  @returns {InputElement}
  */
  field(f) {
    return Q('@' + f, this.node);
  }

  /**
  *  Returns a Collection of all fields. The collection is
  *  dynamic and regenerated by querying.
  *  To ignore an input Element, just add class 'form-ignore' to it.
  *
  *  @type {Collection}
  *  @version 1.0.0
  *  @since 1.0.0
  *  @author Liqueur de Toile <contact@liqueurdetoile.com>
  */
  get fields() {
    return Q('input:not(.form-ignore), select:not(.form-ignore), ' +
      'textarea:not(.form-ignore), button:not(.form-ignore)', this.element);
  }

  /**
  *  Dirty state of the form. Set by fetching all dirty
  *  properties of the form's fields.
  *
  *  @version 1.0.0
  *  @since 1.0.0
  *  @author Liqueur de Toile <contact@liqueurdetoile.com>
  *
  *  @type {boolean}
  */
  get dirty() {
    var dirty = false;

    this.fields.forEach(function (field) { if (field.dirty) dirty = true; });
    return dirty;
  }

  /**
  *  Validate the form by validating each field rule.
  *  The class `field-validate` is added to validated fields Element
  *  otherwise class `field-not-validate` is added to the Element
  *
  *  @version 1.0.0
  *  @since 1.0.0
  *  @author Liqueur de Toile <contact@liqueurdetoile.com>
  *
  *  @see {@link InputElement}
  *  @returns {Boolean} `true`if validation is OK, `false` otherwise
  */
  validate() {
    this._errors.empty();

    this.fields.forEach(function (field) {
      if (!field.validate()) {
        field.addClass('field-not-validate').removeClass('field-validate');
        this._errors.push(field.name, field.errors);
      } else field.removeClass('field-not-validate').addClass('field-validate');
    }.bind(this));

    return this._errors.length() === 0;
  }

  /**
  *  Fetch validation errors
  *
  *  @type {Object}
  *  @version 1.0.0
  *  @since 1.0.0
  *  @author Liqueur de Toile <contact@liqueurdetoile.com>
  */
  get errors() {
    return this._errors.data;
  }

  /**
  *  Form data as an {@link ObjectArray} object
  *
  *  @type {ObjectArray}
  *  @since 1.0.0
  *  @version 1.0.0
  *  @author Liqueur de Toile <contact@liqueurdetoile.com>
  */
  get datas() {
    let data = new ObjectArray();

    this.fields.forEach(function (field) {
      if (typeof field.value !== undefined) data.push(field.name, field.value);
    });
    return data;
  }

  /**
  *  Import data into form fields
  *  from a {@link KeyValueObject} or an {@link ObjectArray} object
  *
  *  @type {keyValueObject|ObjectArray}
  *  @since 1.0.0
  *  @version 1.0.0
  *  @author Liqueur de Toile <contact@liqueurdetoile.com>
  */
  set datas(o) {
    let data = new ObjectArray(o).flatten(true);

    data.forEach(function (v, f) {
      this.field(f).value = v;
    }.bind(this));
  }

  /**
  *  Data of the form serialized in urlEncode standard. This
  *  string is suitable for a query part or an URI.
  *
  *  @type {string}
  *  @since 1.0.0
  *  @version 1.0.0
  *  @author Liqueur de Toile <contact@liqueurdetoile.com>
  */
  get urlEncode() {
    return this.datas.urlEncode();
  }

  /**
  *  Data of the form serialized in urlEncode standard. This
  *  string is suitable for a form-url-encoded data submission
  *
  *  @type {string}
  *  @since 1.0.0
  *  @version 1.0.0
  *  @author Liqueur de Toile <contact@liqueurdetoile.com>
  */
  get formUrlEncode() {
    return this.datas.formUrlEncode();
  }

  /**
  *  Form data as a {@link FormData} object
  *
  *  @type {FormData}
  *  @since 1.0.0
  *  @version 1.0.0
  *  @author Liqueur de Toile <contact@liqueurdetoile.com>
  */
  get formData() {
    var fd = new FormData();

    this.datas.forEach(function (v, k) { fd.append(k, v); });
    return fd;
  }

  /**
  *  Data of the form returned as a JSON string
  *
  *  @type {string}
  *  @since 1.0.0
  *  @version 1.0.0
  *  @author Liqueur de Toile <contact@liqueurdetoile.com>
  */
  get json() {
    return JSON.stringify(this.datas.data);
  }

  /**
  *  Import data into form fields from JSON
  *
  *  @type {string}
  *  @since 1.0.0
  *  @version 1.0.0
  *  @author Liqueur de Toile <contact@liqueurdetoile.com>
  *  @throws {TypeError}
  */
  set json(j) {
    try {
      this.datas = JSON.parse(j);
    } catch (e) {
      throw new TypeError('Argument is not a valid JSON string');
    }
  }
}