diff --git a/assets/js/input-duplicator.js b/assets/js/input-duplicator.ts similarity index 52% rename from assets/js/input-duplicator.js rename to assets/js/input-duplicator.ts index 2ffa89bc1..9fa05e7d0 100644 --- a/assets/js/input-duplicator.js +++ b/assets/js/input-duplicator.ts @@ -1,30 +1,27 @@ +import { assertNotNull } from './utils/assert'; import { $, $$, disableEl, enableEl, removeEl } from './utils/dom'; import { delegate, leftClick } from './utils/events'; -/** - * @typedef InputDuplicatorOptions - * @property {string} addButtonSelector - * @property {string} fieldSelector - * @property {string} maxInputCountSelector - * @property {string} removeButtonSelector - */ - -/** - * @param {InputDuplicatorOptions} options - */ -function inputDuplicatorCreator({ +export interface InputDuplicatorOptions { + addButtonSelector: string; + fieldSelector: string; + maxInputCountSelector: string; + removeButtonSelector: string; +} + +export function inputDuplicatorCreator({ addButtonSelector, fieldSelector, maxInputCountSelector, removeButtonSelector -}) { - const addButton = $(addButtonSelector); +}: InputDuplicatorOptions) { + const addButton = $(addButtonSelector); if (!addButton) { return; } - const form = addButton.closest('form'); - const fieldRemover = (event, target) => { + const form = assertNotNull(addButton.closest('form')); + const fieldRemover = (event: MouseEvent, target: HTMLElement) => { event.preventDefault(); // Prevent removing the final field element to not "brick" the form @@ -33,7 +30,7 @@ function inputDuplicatorCreator({ return; } - removeEl(target.closest(fieldSelector)); + removeEl(assertNotNull(target.closest(fieldSelector))); enableEl(addButton); }; @@ -42,34 +39,40 @@ function inputDuplicatorCreator({ }); - const maxOptionCount = parseInt($(maxInputCountSelector, form).innerHTML, 10); + const maxOptionCountElement = assertNotNull($(maxInputCountSelector, form)); + const maxOptionCount = parseInt(maxOptionCountElement.innerHTML, 10); + addButton.addEventListener('click', e => { e.preventDefault(); - const existingFields = $$(fieldSelector, form); + const existingFields = $$(fieldSelector, form); let existingFieldsLength = existingFields.length; + if (existingFieldsLength < maxOptionCount) { // The last element matched by the `fieldSelector` will be the last field, make a copy const prevField = existingFields[existingFieldsLength - 1]; - const prevFieldCopy = prevField.cloneNode(true); - const prevFieldCopyInputs = $$('input', prevFieldCopy); - prevFieldCopyInputs.forEach(prevFieldCopyInput => { + const prevFieldCopy = prevField.cloneNode(true) as HTMLElement; + + $$('input', prevFieldCopy).forEach(prevFieldCopyInput => { // Reset new input's value prevFieldCopyInput.value = ''; prevFieldCopyInput.removeAttribute('value'); + // Increment sequential attributes of the input - ['name', 'id'].forEach(attr => { - prevFieldCopyInput.setAttribute(attr, prevFieldCopyInput[attr].replace(/\d+/g, `${existingFieldsLength}`)); - }); + prevFieldCopyInput.setAttribute('name', prevFieldCopyInput.name.replace(/\d+/g, `${existingFieldsLength}`)); + prevFieldCopyInput.setAttribute('id', prevFieldCopyInput.id.replace(/\d+/g, `${existingFieldsLength}`)); }); + const parentNode = assertNotNull(prevField.parentNode); + // Insert copy before the last field's next sibling, or if none, at the end of its parent if (prevField.nextElementSibling) { - prevField.parentNode.insertBefore(prevFieldCopy, prevField.nextElementSibling); + parentNode.insertBefore(prevFieldCopy, prevField.nextElementSibling); } else { - prevField.parentNode.appendChild(prevFieldCopy); + parentNode.appendChild(prevFieldCopy); } + existingFieldsLength++; } @@ -79,5 +82,3 @@ function inputDuplicatorCreator({ } }); } - -export { inputDuplicatorCreator };