Una ricerca più intelligente Dropdown con HTMX (Italiano (Italian))

Una ricerca più intelligente Dropdown con HTMX

Comments

NOTE: Apart from English (and even then it's questionable, I'm Scottish). These are machine translated in languages I don't read. If they're terrible please contact me.
You can see how this translation was done in this article.

Monday, 16 September 2024

//

5 minute read

Introduzione

In un post precedente ti ho mostrato come creare un ricerca a discesa utilizzando Alpine.js e HTMX poi ho mostrato come abbiamo potuto abilitare Cross-Site Richiesta di protezione falso utilizzando il AntiforgeryRequestToken in ASP.NET Core con JavaScript utilizzando HTMX per implementare una cache Donut Hole. Un problema in sospeso era come ha caricato le pagine.

Il problema

Il problema era che stavo usando HTMX AJAX per effettuare il caricamento della pagina richiesta una volta selezionato il risultato dalla pagina a discesa. Questo solo KINDA ha funzionato.

  selectResult(result) {
            htmx.ajax('get', result.url, {
                target: '#contentcontainer',  // The container to update
                swap: 'innerHTML',            // Replace the content inside the target
            }).then(function() {
                history.pushState(null, '', result.url);
                window.scrollTo({
                    top: 0,
                    behavior: 'smooth'
                });
            });

Il problema era che mentre questo avrebbe caricato la pagina giusta e aggiornare l'URL visualizzato con il nuovo, ha incasinato il pulsante posteriore. Dato che la pagina non e' stata davvero caricata correttamente nella cronologia.

Come con la mia ultimo articolo sul pulsante posteriore shennanigans Era una cosa che volevo sistemare.

La soluzione

Come in precedenza la soluzione era quella di lasciare che HDMX gestisse direttamente questo. Per fare questo ho aggiornato il mio modello che uso per i risultati di ricerca.

_typeahead.cshtml

<div x-data="window.mostlylucid.typeahead()" class="relative" id="searchelement" x-on:click.outside="results = []">
    @Html.AntiForgeryToken()
    <label class="input input-sm bg-neutral-500 bg-opacity-10 input-bordered flex items-center gap-2">
        <input
            type="text"
            x-model="query"
            x-on:input.debounce.200ms="search"
            x-on:keydown.down.prevent="moveDown"
            x-on:keydown.up.prevent="moveUp"
            x-on:keydown.enter.prevent="selectHighlighted"
            placeholder="Search..."
            class="border-0 grow input-sm text-black dark:text-white bg-transparent w-full"/>
        <i class="bx bx-search"></i>
    </label>
    <!-- Dropdown -->
    <ul x-show="results.length > 0"
        id="searchresults"
        class="absolute z-100 my-2 w-full bg-white dark:bg-custom-dark-bg border border-1 text-black dark:text-white border-neutral-600 rounded-lg shadow-lg">
        <template x-for="(result, index) in results" :key="result.slug">
            <li :class="{
                'dark:bg-blue-dark bg-blue-light': index === highlightedIndex,
                'dark:hover:bg-blue-dark hover:bg-blue-light': true
            }"
                class="cursor-pointer text-sm p-2 m-2">
                <!-- These are the key changes.-->
                <a
                    x-on:click="selectResult(index)"
                    @* :href="result.url" *@
                    :hx-get="result.url"
                    hx-target="#contentcontainer"
                    hx-swap="innerHTML"
                    hx-push-url="true"
                    x-text="result.title"
                   >
                </a>
                <-- End of changes -->
            </li>
        </template>
    </ul>

</div>

Vedrai che ora sono io a generare corretta Collegamenti HTMX in questo blocco di codice. Permettendoci di utilizzare il corretto comportamento HTMX.

typeahead.js

Per abilitare questo nel mio codice JavaScript di backend ho aggiunto il seguente al mio metodo di ricerca (mostrato qui sotto). La this.$nextTick è una costruzione Alpine.js che ritarda questo fino a quando Alpine ha finito di elaborare il modello che ho mostrato sopra.

Poi uso htmx.process() sull'elemento di ricerca che garantirà che gli attributi HTMX funzionino come previsto.


.then(data => {
 this.results = data;
this.highlightedIndex = -1; // Reset index on new search
 this.$nextTick(() => {
    htmx.process(document.getElementById('searchresults'));
 });
})
typeahead.js search
   search() {
            if (this.query.length < 2) {
                this.results = [];
                this.highlightedIndex = -1;
                return;
            }
            let token = document.querySelector('#searchelement input[name="__RequestVerificationToken"]').value;
            console.log(token);
            fetch(`/api/search/${encodeURIComponent(this.query)}`, { // Fixed the backtick and closing bracket
                method: 'GET', // or 'POST' depending on your needs
                headers: {
                    'Content-Type': 'application/json',
                    'X-CSRF-TOKEN': token // Attach the AntiForgery token in the headers
                }
            })
                .then(response => {
                    if(response.ok){
                        return  response.json();
                    }
                    return Promise.reject(response);
                })
                .then(data => {
                    this.results = data;
                    this.highlightedIndex = -1; // Reset index on new search
                    this.$nextTick(() => {
                        htmx.process(document.getElementById('searchresults'));
                    });
                })
                .catch((response) => {
                    console.log(response.status, response.statusText);
                    if(response.status === 400)
                    {
                        console.log('Bad request, reloading page to try to fix it.');
                        window.location.reload();
                    }
                    response.json().then((json) => {
                        console.log(json);
                    })
                    console.log("Error fetching search results");
                });
        }
Più tardi, una volta selezionata una pagina, gestisco il codice per selezionare la pagina, clicchi sul link e chiarisca i risultati (per chiudere la casella di ricerca).
selectHighlighted() {
            if (this.highlightedIndex >= 0 && this.highlightedIndex < this.results.length) {
                this.selectResult(this.highlightedIndex);
                
            }
        },

        selectResult(selectedIndex) {
       let links = document.querySelectorAll('#searchresults a');
       links[selectedIndex].click();
            this.$nextTick(() => {
                this.results = []; // Clear the results
                this.highlightedIndex = -1; // Reset the highlighted index
                this.query = ''; // Clear the query
            });
        }

Questo viene selezionato tramite il clic del link nei risultati della ricerca.

 <a
  x-on:click="selectResult(index)"
  :hx-get="result.url"
  hx-target="#contentcontainer"
  hx-swap="innerHTML"
  hx-push-url="true"
  x-text="result.title"
  >
  </a>

Che poi caricherà la pagina e aggiornerà correttamente l'URL.

Ho anche il codice nella casella genitore whick permette di utilizzare i tasti freccia e entrare.

    <label class="input input-sm bg-neutral-500 bg-opacity-10 input-bordered flex items-center gap-2">
        <input
            type="text"
            x-model="query"
            x-on:input.debounce.200ms="search"
            x-on:keydown.down.prevent="moveDown"
            x-on:keydown.up.prevent="moveUp"
            x-on:keydown.enter.prevent="selectHighlighted"
            placeholder="Search..."
            class="border-0 grow input-sm text-black dark:text-white bg-transparent w-full"/>
        <i class="bx bx-search"></i>
    </label>

Vedrete che questo ha tutto il codice necessario per permettervi di premere Invio e navigare nella pagina selezionata.

In conclusione

Solo un articolo di aggiornamento rapido per la ricerca esistente a discesa per migliorare l'esperienza dell'utente quando si utilizza la ricerca. Ancora una volta questo è un utente MINIMAL che affronta il cambiamento, ma solo migliora l'esperienza dell'utente; che come sviluppatore web sono la vostra preoccupazione principale (oltre a essere pagato:)).

logo

©2024 Scott Galloway