mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-24 04:27:59 +01:00
Merge pull request #366 from philomena-dev/search-ts
Convert search help box to TypeScript
This commit is contained in:
commit
d749907d0d
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