Back to "Een slimmere zoekopspring met HTMX"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Alpine.js HTMX

Een slimmere zoekopspring met HTMX

Monday, 16 September 2024

Inleiding

In een vorige post toonde ik jullie hoe je een Search dropdown met Alpine.js en HTMX Toen toonde ik hoe we Cross-Site Request Vervalsbescherming konden toepassen met behulp van de AntiforgeryRequestToken in ASP.NET Core met JavaScript Met HTMX implementeren we een Donut Hole Cache.. Een probleem was hoe het pagina's laadde.

Het probleem

Het probleem was dat ik HTMX AJAX gebruikte om de aangevraagde pagina te laden nadat je het resultaat uit de afvalpagina had gekozen.. Dit werkte alleen bij KINDA.

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

Het probleem was dat terwijl dit de juiste pagina laadde en de vertoonde URL met een nieuwe aktualiseerde, het de achterkant van de knop verstoordeM SK1 Omdat de pagina WASN' Wél correct in de geschiedenis geladen was .

Zoals bij mij. laatste artikel over 'back button shennanigans' Dit was iets dat ik wilde repareren.

De oplossing

Zoals vroeger was de oplossing om HTMX dit direct te laten beheren. Om dit te doen heb ik mijn werkvoorbeeld opgedateerd dat ik gebruik voor zoekresultaten.

_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>

Je zal zien dat ik nu genereer. Gepaste HTMX-verbindingen in dit codeblok. Laat ons het juiste HT MX-gedrag gebruiken.

typeahead.js

Om dit in mijn backend JavaScript-code te doen, voegde ik de volgende toe aan mijn zoekmethode: (vertoond onder this.$nextTick is een Alpine.js constructie die dit vertraagt totdat Alpine het werkvoorbeeld dat ik liet zien, klaar heeft.

Ik gebruik dan htmx.process() op het zoekelement dat ervoor zorgt dat de HTMX-attributen werken zoals verwacht.


.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");
                });
        }
Later, zodra een pagina gekies is, verwerk ik de code om de pagina te selecteren, kliek op de link en maak de resultaten duidelijk ( om het zoekhokje te sluiten
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
            });
        }

Dit wordt gekies door het opklikken van de link in de zoekresultaten.

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

Wat dan de pagina laadt en de URL correct aktualiseert.

Ik heb ook code in de oudere doos waarmee je de pijlsleutels kunt gebruiken en invoert.

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

Je zal zien dat dit alle nodige code heeft om je te laten invoeren en navigeren naar de gekose pagina.

In slotting:

Gewoon een snelle update-artikel naar de bestaande zoekoplaai om het gebruikerservaring te verbeteren bij het gebruik van zoeken. Nogmaals, dit is een MINIMAL gebruiker met verandering te maken maar alleen het gebruikersleven verbetert.

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.