Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Un campo di ricerca con htmx

Un campo di ricerca con htmx: creare un endpoint che intercetti la chiave di ricerca e restituisca i post corrispondenti
Un campo di ricerca con htmx: creare un endpoint che intercetti la chiave di ricerca e restituisca i post corrispondenti
Link copiato negli appunti

Con la pubblicazione di un numero sempre maggiore di contenuti, anche con htmx può essere utile aggiungere una funzione di ricerca per permettere ai lettori di un blog di filtrare gli articoli in base alle proprie query.

Per aggiungere tale funzione al sito, in questa lezione creeremo un nuovo endpoint che intercetti la chiave di ricerca e restituisca i post corrispondenti. Modificheremo quindi il layout generale del sito aggiungendo un campo di ricerca nel menu di navigazione e, infine, creeremo una pagina specifica per visualizzare i risultati. Utilizzeremo htmx per rendere il processo dinamico ed evitare di dover caricare la pagina ad ogni richiesta dell'utente.

Creiamo una nuova cartella /src/pages/search e, al suo interno, aggiungiamo un file index.astro, la cui funzione sarà mostrare i risultati della ricerca.

Una pagina per i risultati della ricerca con htmx

Apriamo il file /src/pages/search/index.astro e inseriamo il seguente codice JavaScript:

---
export const prerender = false;
import { getCollection } from 'astro:content';
import Layout from '../../layouts/Layout.astro';
import ArticleCard from '../../components/ArticleCard.astro';
const query = Astro.url.searchParams.get('q') || '';
const posts = await getCollection('blog');
const filteredPosts = query
	? posts.filter(
			post =>
				post.data.title.toLowerCase().includes(query.toLowerCase()) ||
				post.data.description.toLowerCase().includes(query.toLowerCase()) ||
				(post.data.tags || []).some(tag => tag.toLowerCase().includes(query.toLowerCase()))
		)
	: posts;
const sortedPosts = filteredPosts.sort(
	(a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime()
);
// Rileva se è una richiesta htmx (parziale)
const isHtmxRequest = Astro.request.headers.get('HX-Request') === 'true';
---

Analizziamo il codice

  • export const prerender = false; abilita il rendering dinamico (SSR), in modo che i parametri della query string vengano letti al momento della richiesta.
  • Le clausole import importano il componente ArticleCard, il layout generale e la funzione getCollection (vedi la lezione precedente).
  • L'istruzione Astro.url.searchParams.get('q') || ''; estrae il parametro di ricerca q dall'URL e lo memorizza nella costante query. Se non è presente, viene impostata una stringa vuota('').
  • posts.filter filtra i post cercando corrispondenze del valore di q in title, description e tags. toLowerCase converte le stringhe in caratteri minuscoli, in modo da rendere la ricerca case insensitive.
  • Il metodo some() verifica che almeno uno dei tag del post contenga la query di ricerca. In caso negativo, restituisce false.
  • sortedPosts ordina i risultati contenuti in filteredPosts per data di pubblicazione decrescente.
  • Astro.request.headers.get('HX-Request') === 'true'; verifica se la richiesta proviene da htmx (markup parziale) o da una navigazione diretta (pagina HTML completa).

Passiamo ora al markup

{isHtmxRequest ? (
	<div id="posts-container" class="grid gap-10">
		{sortedPosts.length > 0 ? (
			sortedPosts.map(post => <ArticleCard post={post} />)
		) : (
			<p class="text-gray-500 italic text-sm">Nessun articolo trovato per "{query}".</p>
		)}
	</div>
) : (
	<Layout
		title={`Risultati per "${query || 'tutti gli articoli'}"`}
		description={`Risultati della ricerca per "${query || 'tutti gli articoli'}" sul blog Astro.`}
	>
		<h1 class="text-4xl font-extrabold text-gray-900 mb-8">
			Risultati per "{query || 'tutti gli articoli'}"
		</h1>
		<div id="posts-container" class="grid gap-10">
			{sortedPosts.length > 0 ? (
				sortedPosts.map(post => <ArticleCard post={post} />)
			) : (
				<p class="text-gray-500 italic text-sm">Nessun articolo trovato per "{query}".</p>
			)}
		</div>
	</Layout>
)}

Ecco cosa fa questo codice:

  • Questo blocco di codice inizia con la verifica della condizione isHtmxRequest: se la richiesta proviene da htmx (markup parziale), allora genera solo il codice racchiuso tra parentesi (<div id="posts-container">), altrimenti genera l'intera pagina comprensiva di layout;
  • Se sortedPosts contiene almeno un elemento, allora il metodo map() itera tra gli elementi dell'array per generare una ArticleCard per ogni elemento. Se non vengono trovati risultati utili, viene mandato a video il messaggio "Nessun articolo trovato...".

Manca ancora il campo di ricerca. Avendo deciso di inserirlo nel menu di navigazione, dovremo modificare il layout generale del sito. Prima di questo, però, abbiamo bisogno di inserire un id al container dei contenuti delle pagine blog e archivio.

Modifiche alla pagina del blog e alla pagina di archivio

Prima di creare il modulo, dobbiamo definire l'elemento target dove inserire i risultati della ricerca. È necessario, quindi, assicurarci che la div che contiene i post abbia id="posts-container" in tutte le pagine del sito che usano il Layout. Nel nostro progetto, si tratta della pagina del blog e della pagina di archivio.

Apriamo, quindi, il file /src/pages/blog/index.astro e aggiungiamo id="posts-container" alla div esterna:

<div
	x-data={`{ visibleItems: ${POSTS_PER_PAGE} }`}
	x-cloak
	id="posts-container"
	class="grid gap-10"
>

È il momento di creare il modulo di ricerca.

Un pulsante di ricerca nel menu di navigazione del sito

Apriamo ora il file /src/layouts/Layout.astro e modifichiamo il codice JavaScript del frontmatter come segue:

---
import '../styles/global.css'
interface Props {
	title: string;
	description?: string;
	showSearch?: boolean;
}
const { title, description = "Il mio fantastico blog creato con Astro", showSearch = true } = Astro.props;
---

Ecco cosa è cambiato rispetto alla versione precedente:

  • Abbiamo aggiunto la proprietà showSearch che ci permetterà di visualizzare o nascondere condizionalmente il campo di ricerca. In questo modo potremo decidere in modo preciso in quali pagine visualizzarlo.
  • Di default, showSearch = true.

Ora modifichiamo il menu di navigazione come segue:

<nav class="max-w-4xl mx-auto px-6 py-4 flex justify-between items-center">
	<a href="/" class="text-xl font-bold text-purple-700">{title}</a>
	<div class="flex items-center gap-x-6">
		<a href="/" class="text-gray-600 hover:text-purple-700 mr-4">Home</a>
		<a href="/blog" class="text-gray-600 hover:text-purple-700 mr-4">Blog</a>
		<a href="/archive" class="text-gray-600 hover:text-purple-700 mr-4">Archive</a>
		{showSearch && (
			<form action="/search" class="relative">
				<input
					type="text"
					name="q"
					placeholder="Cerca..."
					hx-get="/search"
					hx-trigger="input changed delay:500ms"
					hx-target="#posts-container"
					hx-swap="innerHTML"
					hx-push-url="/search"
					class="p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 w-40 md:w-64"
				/>
				<div class="htmx-indicator absolute top-0 right-0 mt-2 mr-2 text-gray-500">🔍</div>
			</form>
		)}
	</div>
</nav>

  • Il <form action="/search"> gestisce l'invio del form con la query inserita dall'utente nel campo di ricerca /search?q=query;
  • hx-get="/search" invia una richiesta GET all'endpoint specificato;
  • hx-trigger="input changed delay:500ms" attiva la richiesta con un intervallo di 500ms dopo che l'utente ha smesso di digitare;
  • hx-target="#posts-container" stabilisce il target della risposta;
  • hx-swap="innerHTML" sostituisce il contenuto del target (innerHTML) con i risultati della ricerca;
  • hx-push-url="true" aggiorna l'URL del browser in base alla richiesta dell'utente, mantenendo lo "stato" della pagina coerente con le azioni dell'utente e migliorando l'esperienza di navigazione.
  • Se showSearch è true, allora genera il modulo di ricerca.

Risoluzione dei problemi con htmx

Sembra funzionare tutto correttamente ma c'è un problema. Quando digitiamo la query, la ricerca viene eseguita correttamente ogni volta che l'utente smette di digitare per almeno mezzo secondo. Tuttavia, se proviamo a dare invio, la pagina sarà caricata in modo non corretto. Questo avviene perché il browser riceve i risultati di una richiesta htmx e carica il markup parziale quando dovrebbe caricare l'intera pagina.

Per risolvere il problema, dobbiamo fare in modo che htmx gestisca la richiesta anche quando l'utente dà invio dopo aver inserito la query ma prima dei 500ms di ritardo.

Aggiorniamo il layout generale del blog (/src/layouts/Layout.astro) modificando l'elemento form come segue:

<nav class="max-w-4xl mx-auto px-6 py-4 flex justify-between items-center">
	<a href="/" class="text-xl font-bold text-purple-700">{title}</a>
	<div class="flex items-center gap-x-6">
		<a href="/" class="text-gray-600 hover:text-purple-700 mr-4">Home</a>
		<a href="/blog" class="text-gray-600 hover:text-purple-700">Blog</a>
		<a href="/archive" class="text-gray-600 hover:text-purple-700">Archive</a>
		{showSearch && (
			<form
				action="/search"
				hx-get="/search"
				hx-trigger="submit"
				hx-target="#posts-container"
				hx-swap="innerHTML"
				hx-push-url="true"
				class="relative"
			>
				<input
					type="text"
					name="q"
					placeholder="Cerca..."
					hx-get="/search"
					hx-trigger="input changed delay:500ms"
					hx-target="#posts-container"
					hx-swap="innerHTML"
					hx-push-url="true"
					class="p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 w-40 md:w-64"
				/>
				<div class="htmx-indicator absolute top-0 right-0 mt-2 mr-2 text-gray-500">🔍</div>
			</form>
		)}
	</div>
</nav>

La pagina di ricerca ora è completa. Per nascondere il campo di ricerca nelle pagine in cui non desideriamo visualizzarlo, basterà impostare la proprietà showSerach a false, come ad esempio nel componente PostLayout.astro che genera le pagine dei singoli post:

<Layout title={frontmatter.title} description={frontmatter.description} showSearch={false}>

La pagina che mostra i risultati della ricerca in Astro con htmx

La pagina che mostra i risultati della ricerca in Astro con htmx

Se vuoi aggiornamenti su Un campo di ricerca con htmx inserisci la tua email nel box qui sotto:

Compilando il presente form acconsento a ricevere le informazioni relative ai servizi di cui alla presente pagina ai sensi dell'informativa sulla privacy.

Ti consigliamo anche