Integrate Fuse.js with Statamic

Arlind Musliu Portrait
Arlind Musliu

March 27, 2024 · 4 min read

Statamic and Fuse.js logos

Fuzzy Search with Fuse.js and Alpine.js in Statamic

In this blog post, we'll explore how to enhance the search functionality of your Statamic site by integrating Fuse.js with Alpine.js. Fuse.js is a powerful lightweight JavaScript library for fuzzy searching, making it an excellent choice for implementing advanced search features on your website. When combined with Alpine.js and Statamic, you can create a seamless and responsive search experience for your users. This is useful if you have an SSG site.

Setting up the Environment

Before we begin, ensure that Alpine.js is installed in your project. You can add it along with Fuse.js and Axios, which we'll use for fetching data, by running the following commands:

npm install alpinejs fuse.js axios

Implementing Fuse.js with Alpine.js in Statamic

We'll be working within the Site.js file, where we construct the logic to display search results. Alpine.js will be our tool for managing the reactive components and user input.

To utilize Fuse.js for searching, we need a dataset to search against. This dataset should be in a JSON format, accessible to our JavaScript code. In our case, we'll be using a JSON file that contains all the searchable content of our Statamic site.

Creating the Searchable Data File

Before we can perform any search operations, we need to generate the /search/data.json file. This file will hold the data that Fuse.js will search through, such as titles, categories, and tags of all posts. To automate the creation and updating of this JSON file, we can utilize Statamic's event system, specifically the EntrySaved event.

For detailed instructions on how to set up an event listener to generate and refresh the /search/data.json file, refer to our blog post Event Listeners for Statamic. There, we explain how to create a custom event listener that triggers whenever a new post is added or an existing one is updated, ensuring that your search data is always current.

Setup the search

Here's how to set up the search functionality:


php artisan make:listener SavePostsToJson

Inside the SavePostsToJson.php it should look like this

<?php

namespace App\Listeners;

use Statamic\Facades\Entry;
use Statamic\Events\EntrySaved;

class SavePostsToJson
{
    public function handle(EntrySaved $event)
    {
        $data = Entry::query()
            ->where('collection', 'news')
            ->get()
            ->transform(function ($post) {
                return [
                    'title' => $post->title,
                    'url' => $post->url,
                    // Searches for relevant information based on the given query string.
                ];
            })
            ->toArray();

        file_put_contents(public_path() . '/search/data.json', json_encode($data));
    }
}

Modify the EventServiceProvider.php file to include the created event listener:

<?php

namespace App\Providers;

use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use App\Listeners\SavePostsToJson;
use Statamic\Events\EntrySaved;


class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
        EntrySaved::class => [
            SavePostsToJson::class,
        ]
    ];

    /**
     * Register any events for your application.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}
  1. Navigate to the "public" folder in your directory.

  2. Create a new folder within "public" and name it "search".

  3. Inside the "search" folder, create a file named "data.json".

import Alpine from "alpinejs";
import axios from "axios";
import Fuse from "fuse.js";

window.Alpine = Alpine;

Alpine.data("search", () => ({
    fuse: null,
    search: null,
    results: [],
    init() {
        axios(`/search/data.json`).then((response) => {
            this.fuse = new Fuse(response.data, {
                includeScore: true,
                threshold: 0.4,
                keys: ["title"],
            });
        });

        this.$watch("search", () => {
            let data = this.fuse.search(this.search);
            data = data
                .sort((resultA, resultB) => resultA.score - resultB.score)
                .map(({ item }) => {
                    return {
                        title: item.title,
                        url: item.url,
                    };
                });

            this.results = data;
        });
    },
}));

Alpine.start();

In the above script, we've initialized Alpine.js with a search component, which fetches our search data and sets up Fuse.js. It also watches the searchQuery property for changes and updates the results array with the sorted search results accordingly.

Define the keys for the properties you wish to search through. In this example, we're searching the 'title' property only, but you can add more properties to the keys array as needed.

The search interface is defined in the markup as follows:

<div x-data="search">
    <input type="text" name="search" x-model="search" placeholder="Search">
    <div>
        <template x-for="item in results">
            <a x-bind:href="item.url">
                <p x-text="item.title"></p>
            </a>
        </template>
        <template x-if="search && results.length === 0">
            <p>No Results For <span x-text="search"></span>.</p>
        </template>
    </div>
</div>

In this snippet, we've created an input field bound to the searchQuery model. We've also set up conditional rendering for when there are search results or when no results are found. The x-for directive loops through the results array, displaying each result as a clickable link.

Conclusion

By following these steps, you've now equipped your Statamic site with an enhanced search feature that provides instant feedback and a better user experience. Make sure to check how you can use more advanced searching integrations such as Algolia, Meilisearch, and Elasticsearch.


Bring Your Ideas to Life 🚀

Kickstart Your Statamic Project with the #1 Statamic Agency

Are you planning a new Statamic project or thinking about migrating your WordPress site to Statamic? Learn more about our expertise as a renowned Statamic development agency.

Arlind Musliu Portrait
Arlind Musliu

Cofounder and CFO of Lucky Media

Technologies:

Statamic
Heading Pattern

Related Posts

Stay up to date

Be updated with all news, products and tips we share!