WildflowerJS Demos
Searchable List: data-action, data-bind, data-show, computed

HTTP Status Codes

Live filter over an array in state. No data-list.

of shown filtering ""

Built with wildflower.nano.min.js

HTML

<div data-component="searchable-list">
    <input type="search" data-action="input:filter" placeholder="Search...">

    <span data-bind="count"></span> of <span data-bind="total"></span> shown
    <span data-show="hasQuery">filtering "<span data-bind="query"></span>"</span>

    <div id="results"></div>
</div>

<!-- One row, written once. Same shape a data-list template would hold. -->
<template id="row-template">
    <div class="row">
        <span class="code"></span>
        <span class="text"></span>
        <span class="badge"></span>
    </div>
</template>

JavaScript

// Plain data. It never changes, so it does not need to be reactive state.
const CODES = [
    [200,'OK'], [404,'Not Found'], [500,'Internal Server Error'] /* ...etc */
].map(([code, text]) => ({ code, text, group: Math.floor(code / 100) }));

const tpl = document.getElementById('row-template');

wildflower.component('searchable-list', {
    state: { query: '' },
    computed: {
        // A computed derived from the query. Cheap and declarative — keep it reactive.
        results() {
            const q = this.query.trim().toLowerCase();
            if (!q) return CODES;
            return CODES.filter(c => String(c.code).includes(q) || c.text.toLowerCase().includes(q));
        },
        count() { return this.results.length; },
        total() { return CODES.length; },
        hasQuery() { return this.query.trim().length > 0; }
    },
    init() { this.renderRows(); },
    // The action both updates state (so the reactive count/label repaint)
    // and re-renders the list body.
    filter(event) {
        this.query = event.target.value;
        this.renderRows();
    },
    // The one imperative bit: replace the visible set. For a filtered view you
    // are re-rendering all of it anyway, so keyed diffing buys nothing.
    renderRows() {
        const list = this.element.querySelector('#results');
        list.textContent = '';
        for (const c of this.results) {
            const el = tpl.content.firstElementChild.cloneNode(true);
            el.querySelector('.code').textContent = c.code;
            el.querySelector('.text').textContent = c.text;
            const badge = el.querySelector('.badge');
            badge.textContent = c.group + 'xx';
            badge.classList.add('g' + c.group);
            list.appendChild(el);
        }
    }
});