Live Filter
This example filters an existing list as the user types, which is a good fit for HTML-first interfaces where the list already exists in the markup.
Live Demo
Section titled “Live Demo”<section data-filter> <label> Search <input type="search" data-filter-input /> </label>
<ul data-filter-list> <li data-filter-item>Alpha</li> <li data-filter-item>Beta</li> <li data-filter-item>Gamma</li> </ul></section>Component
Section titled “Component”import { defineComponent } from 'ornata';
export const LiveFilter = defineComponent({ name: 'LiveFilter', state: { query: { default: '' }, }, elements: { input: { resolve(root) { return root.querySelector('[data-filter-input]'); }, }, items: { queryAll: '[data-filter-item]' }, }, render: { input() { return { events: { input: (event) => { const target = event.currentTarget;
if (!(target instanceof HTMLInputElement)) return;
this.state.query = target.value; }, }, }; }, items({ index }) { const item = this.elements.items[index ?? 0]; const text = item?.textContent?.toLowerCase() || ''; const matches = text.includes(this.state.query.toLowerCase());
return { attributes: { hidden: !matches, }, }; }, },});import { defineComponent } from 'ornata';
export const LiveFilter = defineComponent<{ state: { query: string; }; elements: { input: HTMLInputElement | null; items: Element[]; };}>({ name: 'LiveFilter', state: { query: { default: '' }, }, elements: { input: { resolve(root) { return root.querySelector('[data-filter-input]'); }, }, items: { queryAll: '[data-filter-item]' }, }, render: { input() { return { events: { input: (event) => { const target = event.currentTarget as HTMLInputElement; this.state.query = target.value; }, }, }; }, items({ index }) { const item = this.elements.items[index ?? 0]; const text = item?.textContent?.toLowerCase() || ''; const matches = text.includes(this.state.query.toLowerCase());
return { attributes: { hidden: !matches, }, }; }, },});Why this example matters
Section titled “Why this example matters”This example highlights a nice Ornata pattern:
resolve()gives the component one explicit place to describe its DOM contractqueryAllhandles the list collection cleanly- the DOM lookup contract is defined once, then reused by render code
LiveFilter.mount('[data-filter]');LiveFilter.mount('[data-filter]');