# Vol tekst zoeken (Pt 1.1)

<!--category-- Postgres, Alpine -->
<datetime class="hidden">2024-08-21T20:30</datetime>

## Inleiding

In de [laatste artikel](/blog/textsearchingpt1) Ik liet jullie zien hoe je een volledige tekst zoekopstelling opstelt met de ingebouwde volledige tekst-zoekcapaciteiten van Postgres. Terwijl ik een search api blootlegde, had ik geen manier om het te gebruiken.

Vorige onderdelen in deze reeks:

- [Vol tekst zoeken met Postgres](/blog/textsearchingpt1)

Volgende onderdelen in deze reeks:

- [Inleiding in OpenSearch](/blog/textsearchingpt2)
- [Opensearch met C#](/blog/textsearchingpt3)

Dit voegt een kleine zoekbox toe aan de opskrif van de site waardoor gebruikers kunnen zoeken naar tekst in de blogposts.

![Opzoeken](searchbox.png?format=webp&quality=25)

**Let op: De olifant in de zaal is dat ik het beste manier niet beschouw om dit te doen . Multilinguïsme te ondersteunen is supercomplicerend (IM SK4 heb je per taal een andere kolom nodigMska5 en IMske6 heb ik 'stemming' en andere taalspecifieke dingen moeten aanpakkenM Ska7 IkMskka8 ga dit nu negeren en focussen alleen op Engels Mska9 Alsjeblieft laten we zien hoe we dit in OpenSearch kunnen aanpakken.**

[TOC]

## Op zoek naar tekst

Om een zoekfunctie toe te voegen moest ik wat veranderingen aan de search api. doen. `EF.Functions.WebSearchToTsQuery("english", processedQuery)`

```csharp
    private async Task<List<(string Title, string Slug)>> GetSearchResultForQuery(string query)
    {
        var processedQuery = query;
        var posts = await context.BlogPosts
            .Include(x => x.Categories)
            .Include(x => x.LanguageEntity)
            .Where(x =>
                // Search using the precomputed SearchVector
                (x.SearchVector.Matches(EF.Functions.WebSearchToTsQuery("english", processedQuery)) // Use precomputed SearchVector for title and content
                || x.Categories.Any(c =>
                    EF.Functions.ToTsVector("english", c.Name)
                        .Matches(EF.Functions.WebSearchToTsQuery("english", processedQuery)))) // Search in categories
                && x.LanguageEntity.Name == "en")// Filter by language
            
            .OrderByDescending(x =>
                // Rank based on the precomputed SearchVector
                x.SearchVector.Rank(EF.Functions.WebSearchToTsQuery("english", processedQuery))) // Use precomputed SearchVector for ranking
            .Select(x => new { x.Title, x.Slug,  })
            .Take(5)
            .ToListAsync();
        return posts.Select(x=> (x.Title, x.Slug)).ToList();
    }
```

Dit wordt opsionele gebruikt als er een ruimte is in de zoekopdracht.

```csharp
    if (!query.Contains(" "))
        {
            posts = await GetSearchResultForComplete(query);
        }
        else
        {
            posts = await GetSearchResultForQuery(query);
        }
```

Anders gebruik ik de bestaande zoekmethode die de voorvoegsel-gedrag toevoegt.

```csharp
EF.Functions.ToTsQuery("english", query + ":*")

```

## Soektog-controle

Met [Alpine.js](https://alpinejs.dev/) Ik maakte een simpele gedeeltelijke controle die een super eenvoudige zoekbox biedt.

```razor
<div x-data="window.mostlylucid.typeahead()" class="relative"    x-on:click.outside="results = []">

    <label class="input input-sm dark:bg-custom-dark-bg bg-white input-bordered flex items-center gap-2">
       
        
        <input
            type="text"
            x-model="query"

            x-on:input.debounce.300ms="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"
        class="absolute z-10 my-2 w-full bg-white dark:bg-custom-dark-bg border border-1 text-black dark:text-white border-b-neutral-600 dark:border-gray-300   rounded-lg shadow-lg">
        <template x-for="(result, index) in results" :key="result.slug">
            <li
                x-on:click="selectResult(result)"
                :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"
                x-text="result.title"
            ></li>
        </template>
    </ul>
</div>
```

Dit heeft een heleboel CSS-klassen om goed te weergeven voor donker of licht.. De Alpine code is vrij simpel.
We hebben ook een kleine code om niet te focussen om de zoekresultaten af te sluiten.

```html
   x-on:click.outside="results = []"
```

Let op dat we hier een debounce hebben om de server niet te hameren met verzoeken.

## De Typeahead JS

Dit roept in onze JS-functie (, gedefinieerd in `src/js/main.js`)

```javascript
window.mostlylucid = window.mostlylucid || {};

window.mostlylucid.typeahead = function () {
    return {
        query: '',
        results: [],
        highlightedIndex: -1, // Tracks the currently highlighted index

        search() {
            if (this.query.length < 2) {
                this.results = [];
                this.highlightedIndex = -1;
                return;
            }

            fetch(`/api/search/${encodeURIComponent(this.query)}`)
                .then(response => response.json())
                .then(data => {
                    this.results = data;
                    this.highlightedIndex = -1; // Reset index on new search
                });
        },

        moveDown() {
            if (this.highlightedIndex < this.results.length - 1) {
                this.highlightedIndex++;
            }
        },

        moveUp() {
            if (this.highlightedIndex > 0) {
                this.highlightedIndex--;
            }
        },

        selectHighlighted() {
            if (this.highlightedIndex >= 0 && this.highlightedIndex < this.results.length) {
                this.selectResult(this.results[this.highlightedIndex]);
            }
        },

        selectResult(result) {
           window.location.href = result.url;
            this.results = []; // Clear the results
            this.highlightedIndex = -1; // Reset the highlighted index
        }
    }
}
```

Zoals je kunt zien is dit vrij simpel. ( veel van de complexiteit gaat over de up en down-toets om resultaten te selecteren.
Dit post naar ons. `SearchApi`
Als een resultaat gekies is navigeren we naar de url van het resultaat.

```javascript
     search() {
            if (this.query.length < 2) {
                this.results = [];
                this.highlightedIndex = -1;
                return;
            }

            fetch(`/api/search/${encodeURIComponent(this.query)}`)
                .then(response => response.json())
                .then(data => {
                    this.results = data;
                    this.highlightedIndex = -1; // Reset index on new search
                });
        },
```

### HTMX

Ik veranderde ook het afhalen om te werken met HTMX, dit verandert simpelweg de `search` methode om een HTMX refresh te gebruiken:

```javascript
    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); // Push the new url to the history
    });

    this.results = []; // Clear the results
    this.highlightedIndex = -1; // Reset the highlighted index
    this.query = ''; // Clear the query
}
```

Let op dat we de innerHTML van de `contentcontainer` Met het resultaat van de zoekopdracht. Dit is een eenvoudige manier om de inhoud van de pagina met het resultaat te updaten zonder pagina-refrisering.
We veranderen ook de url in de geschiedenis naar de nieuwe url.

## In slotting:

Dit voegt een krachtige maar eenvoudige zoekcapaciteit toe aan de site.
Het geeft deze site een professioneler gevoel en maakt het makkelijker om te navigeren.