ndf-elements
Version:
My collection of useful custom elements.
138 lines (110 loc) • 3.33 kB
JavaScript
/**
* Filter a collection of elements, based on the value of a search input field.
*
* @copyright © Nick Freear, 09-Mar-2025.
*
* @see https://codepen.io/nfreear/pen/LEYjYEK
* @see https://github.com/nfreear/elements
* @status beta
* @since 1.7.0
*/
import MyMinElement from '../MyMinElement.js';
const MIN_SIZE = 1;
const TEMPLATE = `
<template>
<span part="row">
<label part="label" for="search"></label>
<input part="input" id="search" type="search">
<output part="output"></output>
</span>
<x-data part="slot">
<slot></slot>
</x-data>
</template>
`;
export class MyElementFilterElement extends MyMinElement {
/*
Public API.
*/
static getTag () {
return 'my-element-filter';
}
get selector () {
const selector = this.getAttribute('selector');
if (!selector) {
throw new Error('"selector" is a required attribute (CSS selector syntax).');
}
return selector;
}
get label () {
return this.getAttribute('label') || 'Filter';
}
get autocomplete () {
return this.getAttribute('autocomplete');
}
get minlength () {
return this.getAttribute('minlength') || MIN_SIZE;
}
get outputTemplate () {
return this.getAttribute('output-template') || '%d results';
}
get value () {
return this._searchField.value;
}
set value (data) {
this._inputEventHandler(this._mockEvent(data));
this._searchField.value = data;
}
/*
Life cycle callbacks.
*/
connectedCallback () {
this._attachLocalTemplate(TEMPLATE);
const labelElement = this.shadowRoot.querySelector('label');
labelElement.textContent = this.label;
this._searchField.setAttribute('autocomplete', this.autocomplete);
this._searchField.addEventListener('input', (ev) => this._inputEventHandler(ev));
console.debug('my-element-filter:', [this]);
}
/*
Private helpers.
*/
get _searchField () { return this.shadowRoot.querySelector('#search'); }
get elements () { return this._privElements || []; }
_loadElements () {
if (!this._privElements) {
this._privElements = this.querySelectorAll(this.selector);
}
if (!this._privElements) {
throw new Error(`No elements found with selector: ${this.selector}`);
}
}
_inputEventHandler (ev) {
// Late initialization - allow other (custom element) JS to run first!
this._loadElements();
const QUERY = ev.target.value.trim().toLowerCase();
let count = 0;
if (QUERY.length >= this.minlength) {
this.elements.forEach((el) => {
const TEXT = el.textContent.toLowerCase();
const FOUND = TEXT.includes(QUERY);
el.dataset.myElementFilter = FOUND ? 'in' : 'out';
count += FOUND ? 1 : 0;
});
} else {
this.elements.forEach((el) => {
el.removeAttribute('data-my-element-filter');
});
}
this.setAttribute('value', QUERY);
this.setAttribute('count', count);
this.setAttribute('total', this.elements.length);
this._setOutput(count);
console.debug('input:', count, QUERY, ev);
}
_mockEvent (value) { return { mockEvent: true, target: { value } }; }
_setOutput (count) {
const outputElement = this.shadowRoot.querySelector('output');
outputElement.value = this.outputTemplate.replace('%d', count);
}
}