JavaScript/js/ui-utils/form.js

/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this sample’s licensing information
 
Abstract:
A helper class for building forms.
 */
 
/**
 * The Form class which contains the properties 'el' (HTML element) and 'fields' (a map of name -> HTML input field).
 * @param id The id of the form element.
 * @constructor
 */
CKCatalog.Form = function Form(id) {
  this.el = document.createElement('form');
  this.el.setAttribute('action','#');
  this.el.setAttribute('method','post');
  this.el.id = id;
  this.table = this.el.appendChild(document.createElement('table'));
 
  var submitButton = document.createElement('input');
  submitButton.setAttribute('type','submit');
  submitButton.setAttribute('name','submit');
  submitButton.style.display = 'none';
 
  this.el.appendChild(submitButton);
  this.fields = [];
  this._multipleFields = 0;
  this._multipleFieldsContainer = null;
};
 
/**
 * Sets the fields to follow to be wrapped inside a single row
 * @param {object} opts An object containing keys 'number' and 'hidden' (optional).
 */
CKCatalog.Form.prototype.addMultipleFields = function(opts) {
  this._multipleFieldsContainer = this._createFieldContainer(opts);
  this._multipleFields = opts.number;
  return this;
};
 
CKCatalog.Form.prototype._createRelativeId = function(name) {
  return this.el.id + '-' + name;
};
 
CKCatalog.Form.prototype._createFieldContainer = function(opts) {
  if(this._multipleFields) {
    this._multipleFields--;
    return this._multipleFieldsContainer;
  }
 
  var tr = document.createElement('tr');
  tr.className = 'field';
  if(opts.hidden) {
    tr.classList.add('hide');
  }
  if(opts.number) {
    tr.classList.add('multiple');
    tr.classList.add('has-' + opts.number + '-fields');
  }
 
  var labelContainer = document.createElement('th');
 
  if(opts.label) {
    var label = document.createElement('label');
    label.textContent = opts.label;
    if (opts.name) {
      label.setAttribute('for', this._createRelativeId(opts.name));
    }
    labelContainer.appendChild(label);
  }
 
  tr.appendChild(labelContainer);
  var td = document.createElement('td');
  return tr.appendChild(td);
};
 
/**
 * Adds an email input field to the form object.
 * @param {object} opts An object containing keys 'name','placeholder','value','label','type','hidden' (optional).
 * @returns {CKCatalog.Form}
 */
CKCatalog.Form.prototype.addInputField = function(opts) {
  var fieldContainer = this._createFieldContainer(opts);
  var inputContainer = document.createElement('div');
  inputContainer.className = 'border';
 
  var input = document.createElement('input');
  input.setAttribute('type',opts.type);
  input.setAttribute('name',opts.name);
  input.setAttribute('placeholder',opts.placeholder);
  input.id = this._createRelativeId(opts.name);
  input.value = opts.value;
 
  this.fields[opts.name] = input;
  inputContainer.appendChild(input);
  fieldContainer.appendChild(inputContainer);
  this.table.appendChild(fieldContainer.parentNode);
 
  return this;
};
 
/**
 * Adds a file input field that is stylable.
 * @param {object} opts An object with keys 'name', 'label', 'hidden' (optional).
 * @returns {CKCatalog.Form}
 */
CKCatalog.Form.prototype.addFileInputField = function(opts) {
  var fieldContainer = this._createFieldContainer(opts);
  var inputContainer = document.createElement('div');
  inputContainer.className = 'border';
 
  var input = document.createElement('input');
  input.setAttribute('type','file');
  input.setAttribute('name',opts.name);
  input.id = this._createRelativeId(opts.name);
 
  var fakeInput = document.createElement('div');
  fakeInput.className = 'fake-file-input';
 
  var selectFileButton = document.createElement('button');
  selectFileButton.className = 'link';
  selectFileButton.textContent = 'Choose File…';
 
  var span = document.createElement('span');
  span.className = 'file-name';
 
  input.addEventListener('change', function() {
    var file = input.files[0];
    span.textContent = file ? file.name : '';
  });
 
  fakeInput.appendChild(selectFileButton);
  fakeInput.appendChild(span);
  inputContainer.appendChild(fakeInput);
  inputContainer.appendChild(input);
  fieldContainer.appendChild(inputContainer);
 
  this.fields[opts.name] = input;
  this.table.appendChild(fieldContainer.parentNode);
 
  return this;
};
 
/**
 * Adds a select dropdown field to the form object.
 * @param {object} opts An object containing keys 'name','options','label','hidden' (optional)
 *                      where options is an array of objects containing keys
 *                      'title', 'value', and 'selected' (optional).
 * @returns {CKCatalog.Form}
 */
CKCatalog.Form.prototype.addSelectField = function(opts) {
  var fieldContainer = this._createFieldContainer(opts);
  var selectContainer = document.createElement('div');
  selectContainer.className = 'border select';
 
  var select = document.createElement('select');
  select.setAttribute('name',opts.name);
  select.id = this._createRelativeId(opts.name);
  opts.options.forEach(function(opt) {
    var option = document.createElement('option');
    option.textContent = opt.title || opt.value;
    option.setAttribute('value',opt.value);
    if(opt.selected) {
      option.setAttribute('selected','');
    }
    select.appendChild(option);
  });
  this.fields[opts.name] = select;
 
  selectContainer.appendChild(select);
  fieldContainer.appendChild(selectContainer);
  this.table.appendChild(fieldContainer.parentNode);
 
  return this;
};
 
/**
 * Adds a row of checkboxes.
 * @param {object} opts An object containing keys 'checkboxes', 'hidden' (optional)
 *                      where checkboxes is an array of objects containing keys
 *                      'label','name','value','checked' (optional).
 * @returns {CKCatalog.Form}
 */
CKCatalog.Form.prototype.addCheckboxes = function(opts) {
  var fieldContainer = this._createFieldContainer(opts);
  var checkboxesContainer = document.createElement('div');
  checkboxesContainer.className = 'checkboxes';
  var fields = this.fields;
  var that = this;
 
  opts.checkboxes.forEach(function(checkbox) {
    var checkboxContainer = document.createElement('div');
    checkboxContainer.className = 'checkbox';
 
    var label = document.createElement('label');
    label.setAttribute('for',that._createRelativeId(checkbox.name));
    label.textContent = checkbox.label;
 
    var input = document.createElement('input');
    input.setAttribute('type','checkbox');
    input.setAttribute('name',checkbox.name);
    input.setAttribute('value',checkbox.value);
    input.id = that._createRelativeId(checkbox.name);
 
    if(checkbox.checked) {
      input.setAttribute('checked','');
    }
    fields[checkbox.name] = input;
 
    checkboxContainer.appendChild(label);
    checkboxContainer.appendChild(input);
    checkboxesContainer.appendChild(checkboxContainer);
  });
 
  fieldContainer.appendChild(checkboxesContainer);
  this.table.appendChild(fieldContainer.parentNode);
 
  return this;
};
 
/**
 * Attaches a submit handler to the form.
 * @param {function} handler
 */
CKCatalog.Form.prototype.onSubmit = function(handler) {
  this.el.onsubmit = function(e) {
    e.preventDefault();
    handler();
  };
};
 
 
/**
 * Gets the row container element for an input field by name.
 * @param {string} fieldName
 * @returns {Node | null}
 */
CKCatalog.Form.prototype.getFieldRowForFieldName = function(fieldName) {
  var field = this.fields[fieldName];
  if(field) {
    var el = field;
    while(el.parentNode) {
      el = el.parentNode;
      if(el.classList.contains('field')) {
        return el;
      }
    }
  }
  return null;
};