CloudCannon / pagefind

Static low-bandwidth search at scale
https://pagefind.app
MIT License
3.42k stars 109 forks source link

Feature request: Allow PageFind UI for results only #242

Open Kerrick opened 1 year ago

Kerrick commented 1 year ago

As a site developer, I'd like to use my own search form (input, button, submit event listener, etc.) but I'd like to skip the work of developing a fully custom SERP. I'd love to be able to create a new PagefindResultsUI({ results: results, element: '#my-dialog' }) with the object that is returned from a call to pagefind.search, and have it render the usual Pagefind Results UI into that element. Currently, I don't see how that's possible without also opting into PagefindUI's search box UI, which I don't want to do.

Example use case:

<!-- site-search.webc -->
<form action="https://google.com/search" method="GET">
    <input type="search" id="search" name="q" placeholder="Search our site&hellip;" aria-labeledby="search-label" />
    <label class="invisible" for="search" id="search-label">Search Query</label>
    <input type="hidden" name="as_sitesearch" value="example.com" />
    <button type="submit">Search</button>
    <dialog id="serp"></dialog>
</form>
<script>
    class SiteSearch extends HTMLElement {
        async connectedCallback() {
            this.pagefind = await import("/_pagefind/pagefind.js");
            this.querySelector('form').addEventListener('submit', this.formSubmitted);
        }
        formSubmitted = async (event) => {
            event.preventDefault();
            try {
                await this.search(event.currentTarget.querySelector('input[type="search"]').value);
            } catch(err) {
                console.error(err);
                this.querySelector('form').submit();
            }
        }
        async search(term) {
            const results = await this.pagefind.search(term);
            this.querySelector('#serp').showModal();
            new PagefindResultsUI({ element: '#serp', results });
        }
    }
    window.customElements.define('site-search', SiteSearch);
</script>
bglw commented 1 year ago

What excellent timing!

Something close to what you're after is currently in development as the Pagefind Modular UI — you can see the current (prerelease) version here: https://www.npmjs.com/package/@pagefind/modular-ui (NB: The documentation is not yet complete (or finalized))

To achieve what you're after, it would look something like:

const instance = new PagefindModularUI.Instance({ bundlePath: "/_pagefind/" });
instance.add(new PagefindModularUI.ResultList({ containerElement: "#serp" }));

//later
instance.triggerSearch(term);

There are some slight differences — mainly that the Instance you create handles loading Pagefind and all of the search interactions, rather than you using the JS API directly.

If you want the Modular UI to handle the input interactions, the Input module can also connect to an existing element without touching the DOM or styles:

instance.add(new PagefindModularUI.Input({ inputElement: "input[type="search"]" }));

But you're also free to use the triggerSearch function on the instance itself to bypass that.


I will note that the interface of the Modular UI might change, which is why it is not yet referenced from the documentation or considered as stable. If you do want to get started using it, make sure to pin all Pagefind versions to exactly 0.12.0. Do let me know if you take this for a spin, and how it works out for you! Eager to collect any feedback before putting out a stable Modular UI release 🙂

Kerrick commented 1 year ago

Well heck, that’s awesome! If this thread was a TV show people would think I’m an audience plant.

I’ll give it a try—I’m not afraid of using & pinning pre-release versions. :)

bglw commented 1 year ago

👋 @Kerrick just checking in — did you get a chance to dabble? Any qualms?

bglw commented 1 year ago

Removing this from the 1.0 milestone as the Modular UI is now targeting a later release (see #382)