/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shakaDemo.BoolInput'); goog.provide('shakaDemo.DatalistInput'); goog.provide('shakaDemo.Input'); goog.provide('shakaDemo.NumberInput'); goog.provide('shakaDemo.SelectInput'); goog.provide('shakaDemo.TextInput'); goog.requireType('shakaDemo.InputContainer'); /** * Creates and contains the MDL elements of a type of input. */ shakaDemo.Input = class { /** * @param {!shakaDemo.InputContainer} parentContainer * @param {string} inputType The element type for the input object. * @param {string} containerType The element type for the container containing * the input object. * @param {string} extraType The element type for the "sibling element" to the * input object. If null, it adds no such element. * @param {function(!HTMLInputElement, !shakaDemo.Input)} onChange */ constructor(parentContainer, inputType, containerType, extraType, onChange) { /** @private {!Element} */ this.container_ = document.createElement(containerType); parentContainer.latestElementContainer.appendChild(this.container_); /** @private {!HTMLInputElement} */ this.input_ = /** @type {!HTMLInputElement} */(document.createElement(inputType)); this.input_.onchange = () => { onChange(this.input_, this); }; // <textarea> elements need to also react to 'input' events. if (inputType == 'textarea') { this.input_.oninput = () => { onChange(this.input_, this); }; } this.input_.id = shakaDemo.Input.generateNewId_('input'); this.container_.appendChild(this.input_); if (parentContainer.latestTooltip) { // Since the row isn't focusable, add the tooltip information into the // accessibility data of the input, so it can be accessed by users using // screen readers. const extraInfo = document.createElement('span'); extraInfo.textContent = parentContainer.latestTooltip; extraInfo.classList.add('hidden'); extraInfo.id = shakaDemo.Input.generateNewId_('extra-info'); this.container_.appendChild(extraInfo); this.input_.setAttribute('aria-describedby', extraInfo.id); } /** * Most MDL inputs require some sort of "sibling element" that exists at * the same level as the input itself. These other elements are used to * create various visual effects, such as the ripple effect. * @private {?Element} */ this.extra_ = null; if (extraType) { this.extra_ = document.createElement(extraType); this.container_.appendChild(this.extra_); } } /** @return {!HTMLInputElement} */ input() { return this.input_; } /** @return {!Element} */ container() { return this.container_; } /** @return {?Element} */ extra() { return this.extra_; } /** @param {boolean} valid */ setValid(valid) { if (valid) { this.input_.setCustomValidity(''); // valid this.container_.classList.remove('is-invalid'); } else { this.input_.setCustomValidity('invalid'); // any message will do this.container_.parentElement.classList.add('is-invalid'); } } /** * @param {string} prefix * @return {string} * @private */ static generateNewId_(prefix) { const idNumber = shakaDemo.Input.lastId_; shakaDemo.Input.lastId_ += 1; return prefix + '-labeled-' + idNumber; } }; /** @private {number} */ shakaDemo.Input.lastId_ = 0; /** * Creates and contains the MDL elements of a select input. */ shakaDemo.SelectInput = class extends shakaDemo.Input { /** * @param {!shakaDemo.InputContainer} parentContainer * @param {?string} name * @param {function(!HTMLInputElement, !shakaDemo.Input)} onChange * @param {!Object.<string, string>} values */ constructor(parentContainer, name, onChange, values) { super(parentContainer, 'select', 'div', 'label', onChange); this.container_.classList.add('mdl-textfield'); this.container_.classList.add('mdl-js-textfield'); this.container_.classList.add('mdl-textfield--floating-label'); this.input_.classList.add('mdl-textfield__input'); this.extra_.classList.add('mdl-textfield__label'); this.extra_.setAttribute('for', this.input_.id); if (name) { this.extra_.textContent = name; } for (const value of Object.keys(values)) { const option = /** @type {!HTMLOptionElement} */(document.createElement('option')); option.textContent = values[value]; option.value = value; this.input_.appendChild(option); } } }; /** * Creates and contains the MDL elements of a bool input. */ shakaDemo.BoolInput = class extends shakaDemo.Input { /** * @param {!shakaDemo.InputContainer} parentContainer * @param {string} name * @param {function(!HTMLInputElement, !shakaDemo.Input)} onChange */ constructor(parentContainer, name, onChange) { super(parentContainer, 'input', 'label', 'span', onChange); this.input_.type = 'checkbox'; this.container_.classList.add('mdl-switch'); this.container_.classList.add('mdl-js-switch'); this.container_.classList.add('mdl-js-ripple-effect'); this.container_.setAttribute('for', this.input_.id); this.input_.classList.add('mdl-switch__input'); this.extra_.classList.add('mdl-switch__label'); } }; /** * Creates and contains the MDL elements of a text input. */ shakaDemo.TextInput = class extends shakaDemo.Input { /** * @param {!shakaDemo.InputContainer} parentContainer * @param {string} name * @param {function(!HTMLInputElement, !shakaDemo.Input)} onChange * @param {boolean=} isTextArea */ constructor(parentContainer, name, onChange, isTextArea) { super(parentContainer, isTextArea ? 'textarea' : 'input', 'div', 'label', onChange); this.container_.classList.add('mdl-textfield'); this.container_.classList.add('mdl-js-textfield'); this.container_.classList.add('mdl-textfield--floating-label'); this.input_.classList.add('mdl-textfield__input'); this.extra_.classList.add('mdl-textfield__label'); this.extra_.setAttribute('for', this.input_.id); } }; /** * Creates and contains the MDL elements of a datalist input. */ shakaDemo.DatalistInput = class extends shakaDemo.TextInput { /** * @param {!shakaDemo.InputContainer} parentContainer * @param {string} name * @param {function(!HTMLInputElement, !shakaDemo.Input)} onChange * @param {!Array.<string>} values */ constructor(parentContainer, name, onChange, values) { super(parentContainer, name, onChange, /* isTextArea= */ false); // This element is not literally a datalist, as those are not supported on // all platforms (and they also have no MDL style support). // Instead, this is using the third-party "awesomplete" module, which acts // as a text field with autocomplete selection. const awesomplete = new Awesomplete(this.input_); awesomplete.list = values.slice(); // Make a local copy of the values list. awesomplete.minChars = 0; this.input_.addEventListener('focus', () => { // By default, awesomplete does not show suggestions on focusing on the // input, only on typing something. // This manually updates the suggestions, so that they will show up. awesomplete.evaluate(); }); this.input_.addEventListener('awesomplete-selectcomplete', () => { onChange(this.input_, this); }); } }; /** * Creates and contains the MDL elements of a number input. */ shakaDemo.NumberInput = class extends shakaDemo.TextInput { /** * @param {!shakaDemo.InputContainer} parentContainer * @param {string} name * @param {function(!HTMLInputElement, !shakaDemo.Input)} onChange * @param {boolean} canBeDecimal * @param {boolean} canBeZero * @param {boolean} canBeUnset */ constructor( parentContainer, name, onChange, canBeDecimal, canBeZero, canBeUnset) { super(parentContainer, name, onChange, /* isTextArea= */ false); const error = document.createElement('span'); error.classList.add('mdl-textfield__error'); this.container_.appendChild(error); if (canBeZero && canBeDecimal) { error.textContent = 'Must be a positive number.'; } else if (canBeZero) { error.textContent = 'Must be a positive integer.'; } else if (canBeDecimal) { error.textContent = 'Must be a positive, nonzero number.'; } else { error.textContent = 'Must be a positive, nonzero integer.'; } this.input_.pattern = '(Infinity|'; if (canBeZero) { this.input_.pattern += '0+|'; } this.input_.pattern += '([0-9]*[1-9][0-9]*)'; if (canBeDecimal) { // strictly allow for 0.xxxx decimals this.input_.pattern += '?(0(?=.))?'; // TODO: Handle commas as decimal delimeters, for appropriate regions? this.input_.pattern += '(.[0-9]+)?'; } this.input_.pattern += ')'; if (canBeUnset) { this.input_.pattern += '?'; } } };