-
-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
input-duplicator: migrate to TypeScript
- Loading branch information
Showing
3 changed files
with
167 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { inputDuplicatorCreator } from '../input-duplicator'; | ||
import { assertNotNull } from '../utils/assert'; | ||
import { $, $$, removeEl } from '../utils/dom'; | ||
|
||
describe('Input duplicator functionality', () => { | ||
beforeEach(() => { | ||
document.documentElement.insertAdjacentHTML('beforeend', `<form action="/"> | ||
<div class="js-max-input-count">3</div> | ||
<div class="js-input-source"> | ||
<input id="0" name="0" class="js-input" type="text"/> | ||
<label> | ||
<a href="#" class="js-remove-input">Delete</a> | ||
</label> | ||
</div> | ||
<div class="js-button-container"> | ||
<button type="button" class="js-add-input">Add input</button> | ||
</div> | ||
</form>`); | ||
}); | ||
|
||
afterEach(() => { | ||
removeEl($$<HTMLFormElement>('form')); | ||
}); | ||
|
||
function runCreator() { | ||
inputDuplicatorCreator({ | ||
addButtonSelector: '.js-add-input', | ||
fieldSelector: '.js-input-source', | ||
maxInputCountSelector: '.js-max-input-count', | ||
removeButtonSelector: '.js-remove-input', | ||
}); | ||
} | ||
|
||
it('should ignore forms without a duplicator button', () => { | ||
removeEl($$<HTMLButtonElement>('button')); | ||
expect(runCreator()).toBeUndefined(); | ||
}); | ||
|
||
it('should duplicate the input elements', () => { | ||
runCreator(); | ||
|
||
expect($$('input')).toHaveLength(1); | ||
|
||
assertNotNull($<HTMLButtonElement>('.js-add-input')).click(); | ||
|
||
expect($$('input')).toHaveLength(2); | ||
}); | ||
|
||
it('should duplicate the input elements when the button is before the inputs', () => { | ||
const form = assertNotNull($<HTMLFormElement>('form')); | ||
const buttonDiv = assertNotNull($<HTMLDivElement>('.js-button-container')); | ||
removeEl(buttonDiv); | ||
form.insertAdjacentElement('afterbegin', buttonDiv); | ||
runCreator(); | ||
|
||
assertNotNull($<HTMLButtonElement>('.js-add-input')).click(); | ||
|
||
expect($$('input')).toHaveLength(2); | ||
}); | ||
|
||
it('should not create more input elements than the limit', () => { | ||
runCreator(); | ||
|
||
for (let i = 0; i < 5; i += 1) { | ||
assertNotNull($<HTMLButtonElement>('.js-add-input')).click(); | ||
} | ||
|
||
expect($$('input')).toHaveLength(3); | ||
}); | ||
|
||
it('should remove duplicated input elements', () => { | ||
runCreator(); | ||
|
||
assertNotNull($<HTMLButtonElement>('.js-add-input')).click(); | ||
assertNotNull($<HTMLAnchorElement>('.js-remove-input')).click(); | ||
|
||
expect($$('input')).toHaveLength(1); | ||
}); | ||
|
||
it('should not remove the last input element', () => { | ||
runCreator(); | ||
|
||
assertNotNull($<HTMLAnchorElement>('.js-remove-input')).click(); | ||
assertNotNull($<HTMLAnchorElement>('.js-remove-input')).click(); | ||
for (let i = 0; i < 5; i += 1) { | ||
assertNotNull($<HTMLAnchorElement>('.js-remove-input')).click(); | ||
} | ||
|
||
expect($$('input')).toHaveLength(1); | ||
}); | ||
}); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { assertNotNull } from './utils/assert'; | ||
import { $, $$, disableEl, enableEl, removeEl } from './utils/dom'; | ||
import { delegate, leftClick } from './utils/events'; | ||
|
||
export interface InputDuplicatorOptions { | ||
addButtonSelector: string; | ||
fieldSelector: string; | ||
maxInputCountSelector: string; | ||
removeButtonSelector: string; | ||
} | ||
|
||
export function inputDuplicatorCreator({ | ||
addButtonSelector, | ||
fieldSelector, | ||
maxInputCountSelector, | ||
removeButtonSelector | ||
}: InputDuplicatorOptions) { | ||
const addButton = $<HTMLButtonElement>(addButtonSelector); | ||
if (!addButton) { | ||
return; | ||
} | ||
|
||
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 | ||
const existingFields = $$(fieldSelector, form); | ||
if (existingFields.length <= 1) { | ||
return; | ||
} | ||
|
||
removeEl(assertNotNull(target.closest<HTMLElement>(fieldSelector))); | ||
enableEl(addButton); | ||
}; | ||
|
||
delegate(form, 'click', { | ||
[removeButtonSelector]: leftClick(fieldRemover) | ||
}); | ||
|
||
|
||
const maxOptionCountElement = assertNotNull($(maxInputCountSelector, form)); | ||
const maxOptionCount = parseInt(maxOptionCountElement.innerHTML, 10); | ||
|
||
addButton.addEventListener('click', e => { | ||
e.preventDefault(); | ||
|
||
const existingFields = $$<HTMLElement>(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) as HTMLElement; | ||
|
||
$$<HTMLInputElement>('input', prevFieldCopy).forEach(prevFieldCopyInput => { | ||
// Reset new input's value | ||
prevFieldCopyInput.value = ''; | ||
prevFieldCopyInput.removeAttribute('value'); | ||
|
||
// Increment sequential attributes of the input | ||
prevFieldCopyInput.setAttribute('name', prevFieldCopyInput.name.replace(/\d+/g, `${existingFieldsLength}`)); | ||
prevFieldCopyInput.setAttribute('id', prevFieldCopyInput.id.replace(/\d+/g, `${existingFieldsLength}`)); | ||
}); | ||
|
||
prevField.insertAdjacentElement('afterend', prevFieldCopy); | ||
|
||
existingFieldsLength++; | ||
} | ||
|
||
// Remove the button if we reached the max number of options | ||
if (existingFieldsLength >= maxOptionCount) { | ||
disableEl(addButton); | ||
} | ||
}); | ||
} |