UNPKG

samagra-x-enketo-core

Version:

Extensible Enketo form engine

195 lines (175 loc) 5.38 kB
import input from './input'; import event from './event'; const range = document.createRange(); /** * A Widget class that can be extended to provide some of the basic widget functionality out of the box. */ class Widget { /** * @class * @param {Element} element - The DOM element the widget is applied on * @param {(boolean|{touch: boolean})} [options] - Options passed to the widget during instantiation */ constructor( element, options ) { this.element = element; this.options = options || {}; this.question = element.closest( '.question' ); this._props = this._getProps(); // Some widgets (e.g. ImageMap) initialize asynchronously and init returns a promise. return this._init() || this; } /** * Meant to be overridden, but automatically called. * */ _init() { // load default value into the widget this.value = this.originalInputValue; // if widget initializes asynchronously return a promise here. Otherwise, return nothing/undefined/null. } /** * Not meant to be overridden, but could be. Recommend to extend `get props()` instead. * * @return {object} props object */ _getProps() { const that = this; return { get readonly() { return that.element.nodeName.toLowerCase() === 'select' ? that.element.hasAttribute( 'readonly' ) : !!that.element.readOnly; }, appearances: [ ...this.element.closest( '.question, form.or' ).classList ] .filter( cls => cls.indexOf( 'or-appearance-' ) === 0 ) .map( cls => cls.substring( 14 ) ), multiple: !!this.element.multiple, disabled: !!this.element.disabled, type: this.element.getAttribute( 'data-type-xml' ), }; } /** * Disallow user input into widget by making it readonly. */ disable() { // leave empty in Widget.js } /** * Performs opposite action of disable() function. */ enable() { // leave empty in Widget.js } /** * Updates form-defined language strings, <option>s (cascading selects, and (calculated) values. * Most of the times, this function needs to be overridden in the widget. */ update() {} /** * Returns widget properties. May need to be extended. * * @readonly * @type {object} */ get props() { return this._props; } /** * Returns a HTML document fragment for a reset button. * * @readonly * @type {Element} */ get resetButtonHtml() { return range.createContextualFragment( `<button type="button" class="btn-icon-only btn-reset" aria-label="reset"> <i class="icon icon-refresh"> </i> </button>` ); } /** * Returns a HTML document fragment for a download button. * * @readonly * @type {Element} */ get downloadButtonHtml() { return range.createContextualFragment( `<a class="btn-icon-only btn-download" aria-label="download" download href=""><i class="icon icon-download"> </i></a>` ); } /** * Obtains the value from the current widget state. Should be overridden. * * @readonly * @type {*} */ get value() { return undefined; } /** * Sets a value in the widget. Should be overridden. * * @param {*} value - value to set * @type {*} */ set value( value ) {} /** * Obtains the value from the original form control the widget is instantiated on. * This form control is often hidden by the widget. * * @readonly * @type {*} */ get originalInputValue() { return input.getVal( this.element ); } /** * Updates the value in the original form control the widget is instantiated on. * This form control is often hidden by the widget. * * @param {*} value - value to set * @type {*} */ set originalInputValue( value ) { // Avoid unnecessary change events as they could have significant negative consequences! // However, to add a check for this.originalInputValue !== value here would affect performance too much, // so we rely on widget code to only use this setter when the value changes. input.setVal( this.element, value, null ); this.element.dispatchEvent( event.Change() ); } /** * Returns its own name. * * @static * @readonly * @type {string} */ static get name() { return this.constructor.name; } /** * Returns true if the widget is using a list of options. * * @readonly * @static * @type {boolean} */ static get list() { return false; } /** * Tests whether widget needs to be instantiated (e.g. if not to be used for touchscreens). * Note that the Element (used in the constructor) will be provided as parameter. * * @static * @return {boolean} to instantiate or not to instantiate, that is the question */ static condition() { return true; } } export default Widget;