htmx/table
< >

return function () {
    $products = _class('sample_products', YF_PATH . '.dev/samples/classes/');
    $all = $products->load();
    $p = $products->params($_GET, $all);
    $rows = $products->apply($all, $p);
    $self = url('/@object/@action/' . urlencode('htmx/table'));

    $fragment = '<div id="hx-products" hx-boost="true" hx-target="#hx-products" hx-swap="outerHTML" hx-push-url="true">'
        . '<p class="text-muted">' . count($rows) . ' of ' . count($all) . ' products, rendered at ' . date('H:i:s') . '</p>'
        . table($rows, [
            'pager_records_on_page' => 5,
            // the pager of array data takes its base url from extra.url_path; the current GET values are appended to it
            'extra' => ['url_path' => $self . '?'],
            'no_records_html' => 'Nothing matches this filter.',
            'condensed' => 1,
        ])
            // the tooltip works again on every swapped-in table (bs5: asset yf_bs5_tooltips)
            ->text('name', ['header_tip' => 'Product name'])
            ->text('colour')
            ->text('price')
            ->text('sold')
        . '</div>';

    // htmx asks for the part of the page it swaps (header HX-Request): answer with that fragment only, no layout.
    // One URL, two answers: Vary keeps a browser cache from showing the fragment as a page.
    header('Vary: HX-Request, HX-History-Restore-Request');
    $htmx = _class('htmx');
    if ($htmx->_wants_fragment()) {
        return $htmx->_result($fragment);
    }

    asset('htmx');
    $intro = '<p class="docs-demo-intro">Partial page updates with htmx 2: typing in the search box or changing a select sends
        <code>hx-get</code> with the form values, and the pager links are boosted (<code>hx-boost</code>), so only the table below is
        replaced. The module answers an htmx request (<code>_class(\'htmx\')->_wants_fragment()</code>) with the fragment through
        <code>_result()</code> and any other request with the whole page; the address bar follows (<code>hx-push-url</code>), so every
        state can be reloaded or bookmarked. Classes: <code>yf_htmx</code>, <code>yf_table2</code>, asset <code>htmx</code>.</p>';
    $colours = '';
    foreach (['' => 'Any colour'] + $products->colours($all) as $value => $label) {
        $colours .= '<option value="' . _prepare_html($value, 0) . '"' . ($value === $p['colour'] ? ' selected' : '') . '>' . _prepare_html($label, 0) . '</option>';
    }
    $orders = '';
    foreach (array_keys($products->ORDER_FIELDS) as $value) {
        $orders .= '<option value="' . $value . '"' . ($value === $p['order'] ? ' selected' : '') . '>Sort: ' . str_replace('_', ' ', $value) . '</option>';
    }
    $form = '<form class="form-inline docs-demo-htmx-filter" action="' . $self . '" method="get"
            hx-get="' . $self . '" hx-target="#hx-products" hx-swap="outerHTML" hx-push-url="true" hx-trigger="input delay:300ms, change, submit">
        <input type="search" name="q" class="form-control" placeholder="Name contains" value="' . _prepare_html($p['q'], 0) . '">
        <select name="colour" class="form-control form-select">' . $colours . '</select>
        <select name="order" class="form-control form-select">' . $orders . '</select>
        <select name="dir" class="form-control form-select"><option value="asc">Ascending</option><option value="desc"' . ($p['dir'] === 'desc' ? ' selected' : '') . '>Descending</option></select>
        <noscript><button type="submit" class="btn btn-primary">Filter</button></noscript>
    </form>';
    css('.docs-demo-htmx-filter { display:flex; flex-wrap:wrap; gap:6px; margin-bottom:10px; }
        .docs-demo-htmx-filter .form-control { width:auto; display:inline-block; }
        #hx-products.htmx-request { opacity:.5; transition:opacity .2s; }');
    return $intro . $form . $fragment;
};

Partial page updates with htmx 2: typing in the search box or changing a select sends hx-get with the form values, and the pager links are boosted (hx-boost), so only the table below is replaced. The module answers an htmx request (_class('htmx')->_wants_fragment()) with the fragment through _result() and any other request with the whole page; the address bar follows (hx-push-url), so every state can be reloaded or bookmarked. Classes: yf_htmx, yf_table2, asset htmx.

20 of 20 products, rendered at 15:16:49

Name   Colour Price Sold
Archives T-shirt, black black 37.00 270
Archives T-shirt, navy navy 37.00 719
Archives T-shirt, purple purple 37.00 993
Attacker T-shirt, white white 37.00 412
Blamo T-shirt, black black 37.00 208