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;
};