Live search
x-model.debounce feeds a fetch of the same page with padFormat=json; the PHP that rendered the first results answers the next ones as JSON - $padExpose names what may leave.
$padExposepadFormat=json
x-model.debouncex-forfetch
Live
24 found - the first eight shown
Nothing in the catalogue matches.
Requests to PAD
Nothing yet - use the example above, and every request a component makes to PAD shows here.
How it is built
PAD, on the server Alpine, in the browser
<div x-data="search({^result})">
<div class="toolbar">
<div class="search">
<input class="input" type="search" x-model.debounce.250ms="q" @input.debounce.250ms="ask()" placeholder="Search - try lamp, audio or usb" aria-label="Search the products">
</div>
<span class="spinner" x-show="busy" x-cloak aria-hidden="true"></span>
</div>
<p class="muted small"><span x-text="count">{$result.count}</span> found - the first eight shown</p>
<div class="grid results">
<template x-for="item in items" :key="item.id">
<div class="card result">
<span class="result-emoji" x-text="item.emoji"></span>
<div>
<strong x-text="item.name"></strong>
<div class="muted small" x-text="item.category + ' ยท โฌ ' + item.price.toFixed(2)"></div>
</div>
</div>
</template>
</div>
<p class="empty-state" x-show="count === 0" x-cloak>Nothing in the catalogue matches.</p>
</div>
<script src="{$padRoot}alpine/examples/search.js"></script><?php
// The page renders the first results into the component's argument; every later query
// asks the same page with padFormat=json, and the same PHP answers - the templates never
// run for that request, and only $result leaves the server ($padExpose).
$q = trim ( alpineText ( 'q' ) );
$found = array_values ( array_filter ( alpineData ( 'products' ), fn ( $product ) =>
$q === '' or stripos ( $product ['name'] . ' ' . $product ['category'] . ' ' . $product ['blurb'], $q ) !== FALSE ) );
$result = [ 'q' => $q, 'count' => count ( $found ), 'items' => array_slice ( $found, 0, 8 ) ];
$padExpose = [ 'result' ];
?>// The search: its first result is the argument PAD wrote - search({^result}) - and every
// later query asks the same page for JSON. pad.get is www/alpine/alpine-app.js: it adds
// padFormat=json and logs the request under the example.
document.addEventListener('alpine:init', () => {
Alpine.data('search', (first) => ({
q: first.q,
count: first.count,
items: first.items,
busy: false,
asked: 0,
async ask() {
const mine = ++this.asked;
this.busy = true;
try {
const answer = await pad.get('examples/search', { q: this.q });
if (mine !== this.asked) return; // an older answer that came in late
this.count = answer.result.count;
this.items = answer.result.items;
} finally {
if (mine === this.asked) this.busy = false;
}
}
}));
});