UNPKG

form-js

Version:

Create better ui form elements. Supports IE9+, all modern browsers, and mobile.

369 lines (331 loc) 12.4 kB
'use strict'; import _ from 'underscore'; import FormElement from './form-element'; /** * A callback function that fires when one of the form elements are selected * @callback FormElementGroup~onChange * @param {string} value - The value of the input element that was changed * @param {HTMLInputElement} input - The input element that was changed * @param {HTMLElement} UIElement - The container of the input element that was changed */ /** * Base class to handle grouped form elements. * @class FormElementGroup * @extends FormElement */ class FormElementGroup extends FormElement { /** * Initialization. * @param {object} options - Options passed into instance * @param {Array|HTMLInputElement} options.inputs - The collection of input elements to be made into form elements * @param {FormElementGroup~onChange} [options.onChange] - A callback function that fires when one of the form elements are selected * @param {string} [options.containerClass] - The css class that will be applied to each form element's container * @param {string} [options.inputClass] - The css class that will be applied to each form element item (input element) * @param {string} [options.selectedClass] - The css class that will be applied to a form element item (UI-version) when it is selected * @param {string} [options.disabledClass] - The css class that will be applied to a form element item (UI-version) when it is disabled * @param {string|Array} [options.value] - The string matching the name attribute of the form element button to have selected initially (or an array of such strings) */ constructor (options) { options = _.extend({ inputs: [], onChange: null, containerClass: 'ui-form-element', inputClass: 'ui-form-element-input', selectedClass: 'ui-form-element-selected', disabledClass: 'ui-form-element-disabled', value: null }, options); super(options); this._container = this.options.container; if (!this.options.inputs.length && this._container) { this.options.inputs = this._container.querySelectorAll('input'); } if (!this.options.inputs.length) { console.error('could not build ' + this.getElementKey() + ': no form element input elements were passed'); } else { this.setup(); } } /** * Sets up the form elements. */ setup () { this._formElements = this._setupFormElements(this.options.inputs); this._UIElements = this._buildUIElements(this._formElements); this._setupEvents(); } /** * Sets up form elements. * @param {HTMLCollection|Array} elements - The array of form elements * @returns {Array} Returns the form elements after they've been setup */ _setupFormElements (elements) { var value = this.options.value, values = []; // convert to real array if HTMLCollection elements = Array.prototype.slice.call(elements); if (typeof value === 'string') { values.push(value); } else if (value && value.length) { // assume its an array values = Array.prototype.slice.call(value); //ensure array } // perform work on all form element elements, checking them if necessary elements.forEach(function (formElement) { if (values.indexOf(formElement.value) !== -1) { // value exists formElement.checked = true; } // add initial class formElement.classList.add(this.options.inputClass); }.bind(this)); return elements; } /** * Sets up events. * @private */ _setupEvents () { this.triggerAll(function (formElement, UIElement) { this.addEventListener(formElement, 'click', '_onFormElementClickEventListener', this); this.addEventListener(UIElement, 'click', '_onUIElementClickEventListener', this, true); }.bind(this)); } /** * Gets all the current input form elements. * @returns {Array|*} */ getFormElementGroup () { return this._formElements || []; } /** * Gets all current ui-versions of input form elements. * @returns {Array|*} */ getUIElements () { return this._UIElements || []; } /** * Delegator that triggers a callback on each of the current form element elements. * Useful for performing an operation across all elements * @param {Function} callback - The function that should be executed for each input item */ triggerAll (callback) { var i, FormElementGroup = this.getFormElementGroup(), UIElements = this.getUIElements(); for (i = 0; i < FormElementGroup.length; i++) { callback(FormElementGroup[i], UIElements[i], i); } } /** * When one of the UI elements (or its parent <label>) is clicked. * @param {Event} e * @private */ _onFormElementClickEventListener (e) { let formElement = e.target; let UIElement = formElement.parentElement; if (e.target === e.currentTarget) { this._onFormElementClick(formElement, UIElement); } } /** * An abstract method to handle clicks to any given form element * @param {HTMLInputElement} formElement - The form element that was clicked * @param {HTMLElement} UIElement - The UI version of the form element that was clicked * @private * @abstract */ _onFormElementClick (formElement, UIElement) {} /** * When one of the UI elements (or its parent <label>) is clicked. * @param {Event} e * @private */ _onUIElementClickEventListener (e) { var formElement; var UIElement; // respond to clicks made to the UI element ONLY if (e.target === e.currentTarget && e.target.classList.contains(this.options.containerClass)) { // we are preventing default here to ensure default // checkbox is not going to be checked since // we're updating the checked boolean manually later e.preventDefault(); UIElement = e.target; formElement = e.target.getElementsByClassName(this.options.inputClass)[0]; this._onUIElementClick(formElement, UIElement); } } /** * An abstract method to handle clicks to any given UI element * @param {HTMLInputElement} formElement - The form element nested under the UI element that was clicked * @param {HTMLElement} UIElement - The UI version of the form element that was clicked * @private * @abstract */ _onUIElementClick (formElement, UIElement) {} /** * Builds the UI-friendly version of the form elements and wraps them in their appropriate containers. * @param {Array} elements - The input elements * @returns {Array} Returns an array of the ui-versions of the elements * @private */ _buildUIElements (elements) { var count = elements.length, arr = [], i, formElement, UIElement; for (i = 0; i < count; i++) { formElement = elements[i]; UIElement = this._buildContainerEl(formElement); // add selected class if selected initially if (formElement.checked) { UIElement.classList.add(this.options.selectedClass); } if (formElement.disabled) { UIElement.classList.add(this.options.disabledClass); } arr.push(UIElement); } return arr; } /** * Wraps the passed element inside of a custom container element. * @param {HTMLElement} el - The element to be wrapped inside of the container * @returns {Element} Returns the container element that contains the passed el * @private */ _buildContainerEl (el) { let parent = el.parentNode; var outerEl = document.createElement('div'); outerEl.classList.add(this.options.containerClass); parent.replaceChild(outerEl, el); outerEl.appendChild(el); return outerEl; } /** * Triggers a change on the form element. * @param {HTMLInputElement} formElement - The input element * @param {HTMLElement} UIElement - The ui element */ triggerChange (formElement, UIElement) { if (this.options.onChange) { this.options.onChange(formElement.value, formElement, UIElement); } } /** * Selects the form element item. * @param {Number} index - The index of the form element item */ select (index) { var input = this.getFormElement(index), uiEl = this.getUIElement(index); if (!input.checked) { input.checked = true; uiEl.classList.add(this.options.selectedClass); this.triggerChange(input, uiEl); } } /** * De-selects the form element item. * @param {Number} index - The index of the form element item */ deselect (index) { var input = this.getFormElement(index), uiEl = this.getUIElement(index); uiEl.classList.remove(this.options.selectedClass); if (input.checked) { input.checked = false; this.triggerChange(input, uiEl); } } /** * Gets the selected value of the form element. * @returns {Array} Returns the value of the currently selected form elements */ getValue () { var values = []; this.getFormElementGroup().forEach(function (el) { if (el.checked) { values.push(el.value); } }, this); return values; } /** * Selects the form element that matches the supplied value. * @param {string|Array} value - The value of the form element that should be selected */ setValue (value) { this.getFormElementGroup().forEach(function (el, idx) { if (el.value === value || value.indexOf(el.value) !== -1) { this.select(idx); } else { this.deselect(idx); } }, this); } /** * Gets the form element input element by an index. * @param {Number} [index] - The index of the form element input element * @returns {HTMLInputElement} Returns the checkbox input element */ getFormElement (index) { return this.getFormElementGroup()[(index || 0)]; } /** * Gets the ui-version of the form element element. * @param {Number} [index] - The index of the form element element * @returns {HTMLElement} Returns the checkbox div element. */ getUIElement (index) { return this.getUIElements()[(index || 0)]; } /** * Deselects all form elements. */ clear () { this.triggerAll(function (formElement, UIElement, idx) { this.deselect(idx); }.bind(this)); } /** * Enables the form element. */ enable () { this.triggerAll(function (formElement, UIElement) { formElement.disabled = false; UIElement.classList.remove(this.options.disabledClass); }.bind(this)); } /** * Disables the form element. */ disable () { this.triggerAll(function (formElement, UIElement) { formElement.disabled = true; UIElement.classList.add(this.options.disabledClass); }.bind(this)); } /** * Gets the unique identifier for form elements. * @returns {string} */ getElementKey () { return 'FormElementGroup'; } /** * Destruction of this class. */ destroy () { this.triggerAll(function (formElement, UIElement) { UIElement.parentNode.replaceChild(formElement, UIElement); this.removeEventListener(formElement, 'click', '_onFormElementClickEventListener', this); this.removeEventListener(UIElement, 'click', '_onUIElementClickEventListener', this, true); }.bind(this)); super.destroy(); } } module.exports = FormElementGroup;