mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-23 12:08:00 +01:00
Convert search help box to TypeScript
This commit is contained in:
parent
50ceeab6f8
commit
aed938bc17
3 changed files with 184 additions and 45 deletions
99
assets/js/__tests__/search.spec.ts
Normal file
99
assets/js/__tests__/search.spec.ts
Normal file
|
@ -0,0 +1,99 @@
|
|||
import { $ } from '../utils/dom';
|
||||
import { assertNotNull } from '../utils/assert';
|
||||
import { setupSearch } from '../search';
|
||||
import { setupTagListener } from '../tagsinput';
|
||||
|
||||
const formData = `<form class="js-search-form">
|
||||
<input type="text" class="js-search-field">
|
||||
<a data-search-prepend="-">NOT</a>
|
||||
<a data-search-add="id.lte:10" data-search-select-last="2" data-search-show-help="numeric">Numeric ID</a>
|
||||
<a data-search-add="my:faves" data-search-show-help=" ">My favorites</a>
|
||||
<div class="hidden" data-search-help="boolean">
|
||||
<span class="js-search-help-subject"></span> is a Boolean value field
|
||||
</div>
|
||||
<div class="hidden" data-search-help="numeric">
|
||||
<span class="js-search-help-subject"></span> is a numerical range field
|
||||
</div>
|
||||
</form>`;
|
||||
|
||||
describe('Search form help', () => {
|
||||
beforeAll(() => {
|
||||
setupSearch();
|
||||
setupTagListener();
|
||||
});
|
||||
|
||||
let input: HTMLInputElement;
|
||||
let prependAnchor: HTMLAnchorElement;
|
||||
let idAnchor: HTMLAnchorElement;
|
||||
let favesAnchor: HTMLAnchorElement;
|
||||
let helpNumeric: HTMLDivElement;
|
||||
let subjectSpan: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = formData;
|
||||
|
||||
input = assertNotNull($<HTMLInputElement>('input'));
|
||||
prependAnchor = assertNotNull($<HTMLAnchorElement>('a[data-search-prepend]'));
|
||||
idAnchor = assertNotNull($<HTMLAnchorElement>('a[data-search-add="id.lte:10"]'));
|
||||
favesAnchor = assertNotNull($<HTMLAnchorElement>('a[data-search-add="my:faves"]'));
|
||||
helpNumeric = assertNotNull($<HTMLDivElement>('[data-search-help="numeric"]'));
|
||||
subjectSpan = assertNotNull($<HTMLSpanElement>('span', helpNumeric));
|
||||
});
|
||||
|
||||
it('should add text to input field', () => {
|
||||
idAnchor.click();
|
||||
expect(input.value).toBe('id.lte:10');
|
||||
|
||||
favesAnchor.click();
|
||||
expect(input.value).toBe('id.lte:10, my:faves');
|
||||
});
|
||||
|
||||
it('should focus and select text in input field when requested', () => {
|
||||
idAnchor.click();
|
||||
expect(input).toHaveFocus();
|
||||
expect(input.selectionStart).toBe(7);
|
||||
expect(input.selectionEnd).toBe(9);
|
||||
});
|
||||
|
||||
it('should highlight subject name when requested', () => {
|
||||
expect(helpNumeric).toHaveClass('hidden');
|
||||
idAnchor.click();
|
||||
expect(helpNumeric).not.toHaveClass('hidden');
|
||||
expect(subjectSpan).toHaveTextContent('Numeric ID');
|
||||
});
|
||||
|
||||
it('should not focus and select text in input field when unavailable', () => {
|
||||
favesAnchor.click();
|
||||
expect(input).not.toHaveFocus();
|
||||
expect(input.selectionStart).toBe(8);
|
||||
expect(input.selectionEnd).toBe(8);
|
||||
});
|
||||
|
||||
it('should not highlight subject name when unavailable', () => {
|
||||
favesAnchor.click();
|
||||
expect(helpNumeric).toHaveClass('hidden');
|
||||
});
|
||||
|
||||
it('should prepend to empty input', () => {
|
||||
prependAnchor.click();
|
||||
expect(input.value).toBe('-');
|
||||
});
|
||||
|
||||
it('should prepend to single input', () => {
|
||||
input.value = 'a';
|
||||
prependAnchor.click();
|
||||
expect(input.value).toBe('-a');
|
||||
});
|
||||
|
||||
it('should prepend to comma-separated input', () => {
|
||||
input.value = 'a,b';
|
||||
prependAnchor.click();
|
||||
expect(input.value).toBe('a,-b');
|
||||
});
|
||||
|
||||
it('should prepend to comma and space-separated input', () => {
|
||||
input.value = 'a, b';
|
||||
prependAnchor.click();
|
||||
expect(input.value).toBe('a, -b');
|
||||
});
|
||||
});
|
|
@ -1,45 +0,0 @@
|
|||
import { $, $$ } from './utils/dom';
|
||||
import { addTag } from './tagsinput';
|
||||
|
||||
function showHelp(subject, type) {
|
||||
$$('[data-search-help]').forEach(helpBox => {
|
||||
if (helpBox.getAttribute('data-search-help') === type) {
|
||||
$('.js-search-help-subject', helpBox).textContent = subject;
|
||||
helpBox.classList.remove('hidden');
|
||||
} else {
|
||||
helpBox.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function prependToLast(field, value) {
|
||||
const separatorIndex = field.value.lastIndexOf(',');
|
||||
const advanceBy = field.value[separatorIndex + 1] === ' ' ? 2 : 1;
|
||||
field.value =
|
||||
field.value.slice(0, separatorIndex + advanceBy) + value + field.value.slice(separatorIndex + advanceBy);
|
||||
}
|
||||
|
||||
function selectLast(field, characterCount) {
|
||||
field.focus();
|
||||
|
||||
field.selectionStart = field.value.length - characterCount;
|
||||
field.selectionEnd = field.value.length;
|
||||
}
|
||||
|
||||
function executeFormHelper(e) {
|
||||
const searchField = $('.js-search-field');
|
||||
const attr = name => e.target.getAttribute(name);
|
||||
|
||||
attr('data-search-add') && addTag(searchField, attr('data-search-add'));
|
||||
attr('data-search-show-help') && showHelp(e.target.textContent, attr('data-search-show-help'));
|
||||
attr('data-search-select-last') && selectLast(searchField, parseInt(attr('data-search-select-last'), 10));
|
||||
attr('data-search-prepend') && prependToLast(searchField, attr('data-search-prepend'));
|
||||
}
|
||||
|
||||
function setupSearch() {
|
||||
const form = $('.js-search-form');
|
||||
|
||||
form && form.addEventListener('click', executeFormHelper);
|
||||
}
|
||||
|
||||
export { setupSearch };
|
85
assets/js/search.ts
Normal file
85
assets/js/search.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import { assertNotNull, assertNotUndefined } from './utils/assert';
|
||||
import { $, $$, showEl, hideEl } from './utils/dom';
|
||||
import { delegate, leftClick } from './utils/events';
|
||||
import { addTag } from './tagsinput';
|
||||
|
||||
function focusAndSelectLast(field: HTMLInputElement, characterCount: number) {
|
||||
field.focus();
|
||||
field.selectionStart = field.value.length - characterCount;
|
||||
field.selectionEnd = field.value.length;
|
||||
}
|
||||
|
||||
function prependToLast(field: HTMLInputElement, value: string) {
|
||||
// Find the last comma in the input and advance past it
|
||||
const separatorIndex = field.value.lastIndexOf(',');
|
||||
const advanceBy = field.value[separatorIndex + 1] === ' ' ? 2 : 1;
|
||||
|
||||
// Insert the value string at the new location
|
||||
field.value = [
|
||||
field.value.slice(0, separatorIndex + advanceBy),
|
||||
value,
|
||||
field.value.slice(separatorIndex + advanceBy),
|
||||
].join('');
|
||||
}
|
||||
|
||||
function getAssociatedData(target: HTMLElement) {
|
||||
const form = assertNotNull(target.closest('form'));
|
||||
const input = assertNotNull($<HTMLInputElement>('.js-search-field', form));
|
||||
const helpBoxes = $$<HTMLDivElement>('[data-search-help]', form);
|
||||
|
||||
return { input, helpBoxes };
|
||||
}
|
||||
|
||||
function showHelp(helpBoxes: HTMLDivElement[], typeName: string, subject: string) {
|
||||
for (const helpBox of helpBoxes) {
|
||||
// Get the subject name span
|
||||
const subjectName = assertNotNull($<HTMLElement>('.js-search-help-subject', helpBox));
|
||||
|
||||
// Take the appropriate action for this help box
|
||||
if (helpBox.dataset.searchHelp === typeName) {
|
||||
subjectName.textContent = subject;
|
||||
showEl(helpBox);
|
||||
} else {
|
||||
hideEl(helpBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onSearchAdd(_event: Event, target: HTMLAnchorElement) {
|
||||
// Load form
|
||||
const { input, helpBoxes } = getAssociatedData(target);
|
||||
|
||||
// Get data for this link
|
||||
const addValue = assertNotUndefined(target.dataset.searchAdd);
|
||||
const showHelpValue = assertNotUndefined(target.dataset.searchShowHelp);
|
||||
const selectLastValue = target.dataset.searchSelectLast;
|
||||
|
||||
// Add the tag
|
||||
addTag(input, addValue);
|
||||
|
||||
// Show associated help, if available
|
||||
showHelp(helpBoxes, showHelpValue, assertNotNull(target.textContent));
|
||||
|
||||
// Select last characters, if requested
|
||||
if (selectLastValue) {
|
||||
focusAndSelectLast(input, Number(selectLastValue));
|
||||
}
|
||||
}
|
||||
|
||||
function onSearchPrepend(_event: Event, target: HTMLAnchorElement) {
|
||||
// Load form
|
||||
const { input } = getAssociatedData(target);
|
||||
|
||||
// Get data for this link
|
||||
const prependValue = assertNotUndefined(target.dataset.searchPrepend);
|
||||
|
||||
// Prepend
|
||||
prependToLast(input, prependValue);
|
||||
}
|
||||
|
||||
export function setupSearch() {
|
||||
delegate(document, 'click', {
|
||||
'form.js-search-form a[data-search-add][data-search-show-help]': leftClick(onSearchAdd),
|
||||
'form.js-search-form a[data-search-prepend]': leftClick(onSearchPrepend),
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue