Back to "Une recherche plus intelligente avec 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

Une recherche plus intelligente avec HTMX

Monday, 16 September 2024

Présentation

Dans un post précédent, je vous ai montré comment créer un recherche déroulante en utilisant Alpine.js et HTMX puis j'ai montré comment nous pourrions activer la protection Cross-Site Request Forgery en utilisant le AntiforgeryRequestToken dans ASP.NET Core avec JavaScript utilisant HTMX pour implémenter un cache Donut HoleC'est ce que j'ai dit. L'une des questions en suspens était de savoir comment il a chargé des pages.

Le problème

Le problème était que j'utilisais HTMX AJAX pour effectuer le chargement de la page demandée une fois que vous aviez sélectionné le résultat de la page déroulante. Ce seul KINDA a fonctionné.

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

Le problème était que même si cela allait charger la page droite et mettre à jour l'URL affichée avec la nouvelle, il a gâché le bouton arrière. Comme la page n'était pas vraiment chargée dans l'histoire correctement.

Comme avec mon dernier article sur le bouton arrière shennanigans C'était quelque chose que je voulais réparer.

La solution

Comme précédemment, la solution consistait à laisser HTMX s'en occuper directement. Pour ce faire, j'ai mis à jour mon modèle que j'utilise pour les résultats de recherche.

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

Vous verrez que je génère maintenant approprié Les liens HTMX dans ce bloc de code. Laissez-nous utiliser le comportement HTMX correct.

typeahead.js

Pour l'activer dans mon code JavaScript, j'ai ajouté ce qui suit à ma méthode de recherche (voir ci-dessous). Les this.$nextTick est une construction Alpine.js qui retarde cela jusqu'à ce qu'Alpine ait terminé le traitement du modèle que j'ai montré ci-dessus.

J'utilise alors htmx.process() sur l'élément de recherche qui assurera le fonctionnement des attributs HTMX comme prévu.


.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");
                });
        }
Plus tard, une fois qu'une page est sélectionnée, je gère le code pour sélectionner la page, cliquez sur le lien pour effacer les résultats (pour fermer la zone de recherche).
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
            });
        }

Ceci est sélectionné par le biais du clic sur le lien dans les résultats de recherche.

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

Ce qui va ensuite charger la page et mettre à jour l'URL correctement.

J'ai aussi du code dans la boîte parent whick vous permet d'utiliser les touches fléchées et entrer.

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

Vous verrez que cela a tout le code nécessaire pour vous permettre de simplement cliquer sur Entrée et naviguer sur la page sélectionnée.

En conclusion

Juste un article de mise à jour rapide à la page déroulante de recherche existante pour améliorer l'expérience utilisateur lors de l'utilisation de la recherche. Encore une fois, il s'agit d'un utilisateur MINIMAL face au changement, mais il améliore simplement l'expérience utilisateur ; qui en tant que développeur web est votre principale préoccupation (au-delà d'être payé :)).

logo

©2024 Scott Galloway