En smartare sök-dropdown med HTMX (Svenska (Swedish))

En smartare sök-dropdown med HTMX

Monday, 16 September 2024

//

5 minute read

Inleiding

I en tidigare artikel visade jag er hur man skapar sök nedåt med Alpine.js och HTMX så visade jag hur vi kunde aktivera Cross-Site Request förfalskningsskydd genom att använda AntiforgeryRequestToken i ASP.NET Core med JavaScript med HTMX för att implementera en Donut Hole cache. Ett olöst problem var hur den laddade sidorna

Problemet

Problemet var att jag använde HTMX AJAX för att ladda upp pagen efter att du hade valt resultatet från den nedgående sidan.

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

Problemet var att medan det skulle ladda upp den rätta sidan och uppdatera den visat URL med den nya, störde det baken knappen.

Som med min den sista artikeln om bak-knappen shennanigans det var något jag ville fixa

Lösningen

Som förut så var lösningen att låta HTMX hantera detta direkt. För att göra det uppdaterade jag min template som jag använder för sökresultaten

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

Du ' ser att jag nu genererar rätt HTMX länkar i detta kodblock. Att låta oss använda det korrekta HT MX-beteendet.

typeahead.js

För att möjliggöra detta i min backend JavaScript-kod så lade jag in följande till min sökmetod ( som visas härunder this.$nextTick är en Alpine.js konstruktion som skjuter upp det här tills Alpine har avslutat att bearbeta mallen jag visade förut

Jag använder sedan htmx.process() på sökelementet som säkerställer att HTMX-Attributen fungerar som väntat.


.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");
                });
        }
Senare, när en sida är väljad, så tar jag hand om koden för att välja sidan.
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
            });
        }

Detta är valt genom att klicka på länken i sökresultatet.

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

Vilket sedan laddar sidan och uppdaterar URL: s korrekt.

Jag har också kod i moderboksen som låter dig använda pilspetsar och svara:.

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

Du kommer att se att det här har all koden som behövs för att kunna klicka och navigera till den markerade sidan.

I slutsats

Bara en snabb uppdaterade artikel till den existerande sökdownen för att förbättra användarupplevelsen när man använder sökningen. Återigen är detta en MINIMAL användar som står inför förändring men som bara förbättrar användar upplevelsen ; som som webutvecklare är ditt huvudsakliga intresse M SK2 förutom att få betalt :)).

Finding related posts...
logo

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