1

I have a relatively simple search of a section called articles. Each entry within articles has a category assigned. I've named the category animal.

Layout is as follows:

  1. Header
  2. Search/filtering section
  3. Articles container
  4. Pagination container

The problem I'm having, is that the search field gets replaced whenever the sprigboard updates. The only way to stop this, is to wrap the #articleContainer and #articlePagination into a div (#articles-and-pagination) and use s-replace="#articles-and-pagination". However, this doesn't work in this case because I need to also replace the contents of the Header, which is above the search field.

So the next solution was to use s-preserve="true" on the search field itself, but that seems to have absolutely no effect because it still gets replaced on each ajax load.

Below is the full code I've written thus far. I realise that parts of it is quite messy. I haven't gotten near to finalizing this :).

{% set section  = 'articles' %}
{% set search   = search ?? '' %}
{% set animal   = animal ?? '' %}
{% set page     = page ?? 1 %}

{% set filters = {
    search: search,
    relatedTo: animal,
} %}

{% set entryQuery = craft.entries(filters|merge({
    section: section,
    limit: 21,
})) %}

{% set animals = craft.categories.group('animal').all() %}
{% set pageInfo = sprig.paginate(entryQuery, page) %}
{% set entries = pageInfo.pageResults %}
{% do sprig.pushUrl('?' ~ filters|merge({
    page: page
})|url_encode) %}

{% set pageTitle = '<h1>' ~ 'Articles and Guides'|t ~ '</h1>' %}
{% if animal is defined and animal != '' %}
    {% set animalObject = craft.categories.id(animal).one() %}
    {% set pageTitle = '<h1>' ~ animalObject.title ~ '</h1>' %}
    {% if animalObject.textArea|length %}
        {% set supportingText = '<p class="text-lg m-auto max-w-xl">' ~ animalObject.textArea ~ '</p>' %}
    {% endif %}
{% else %}
    {% if entry is defined and entry.redactorSimple|length %}
        {% set pageTitle = entry.redactorSimple %}
    {% endif %}
{% endif %}

<div>
    <section class="pt-5 pb-5 bg-fv-vlight-beige relative">
        <header class="container text-center relative z-10">
            <div id="articleTitleContainer" class="max-w-xl m-auto c-redactor">
                {{ pageTitle|raw }}
                {{ supportingText is defined ? supportingText|raw : '' }}
            </div>
        </header>
    </section>

    {# ARTICLES SEARCH BAR #}
    <section class="bg-fv-white py-4">
        <div class="max-w-2xl mx-auto px-3">
            <form class="flex justify-center flex-wrap">
                <div class="article-search-category-wrapper" id="animal" sprig >
                    <div class="item category-all">
                        <label>
                            <input type="radio" id="c_all" name="animal" value="" {% if animal == '' %} checked {% endif %} >
                            <div class="icon category-all"></div>
                            <span>{{ 'All'|t }}</span>
                        </label>
                    </div>
                    
                    {% for categoryOption in animals %}
                        {% if categoryOption.id is not null %}
                            {% set categoryUrl = siteUrl ~ categoryOption.uri %}
                            {% set catID = categoryOption.id %}
                            <div class="item category-{{ catID }}">
                                <label>
                                    {% set entryCount = craft.entries.relatedTo(categoryOption).count() %}
                                    {% if entryCount > 0 %}
                                        <input
                                            class="filter-button" 
                                            type="radio" 
                                            id="c_{{ catID }}" 
                                            name="animal" 
                                            value="{{ catID }}"
                                        >
                                        <div class="icon category-{{ categoryOption.id }}"></div>
                                        <span>{{ categoryOption.title }}</span>
                                    {% endif %}
                                </label>
                            </div>
                        {% endif %}
                    {% endfor %}

                </div>

                <div class="article-search-input" id="article-search-input-wrapper">
                    {% set placeholder = 'Search for'|t ~ '...' %}
                    {% set searchPrefix = '' %}

                    <input
                        sprig
                        s-trigger="keyup changed delay:300ms"
                        s-preserve="true"
                        name="search"
                        id="search"

                        class="p-3 text-xl border-2 rounded w-full md:h-full mb-2 md:mb-0 outline-none focus:border-fv-pink"
                        placeholder="{{ placeholder }}"
                        value="{{ search ?? '' }}"
                        aria-label="{{ 'Search articles'|t }}"
                    >
                </div>

                <div class="article-search-button">
                    <button type="submit" class="bg-fv-pink hover:bg-fv-more-pink transition-all w-full text-white md:h-full p-3 rounded text-xl ">
                        <span class="font-bold">{{ 'Search'|t }}</span>
                    </button>
                </div>
            </form>
        </div>
    </section>

    {# LISTING SECTION #}
    <div id="articles-and-pagination">
        <section class="pb-4 md:pb-5 pt-4 relative">
            <div class="container">
                <div class="articles articlesContainer max-w-2xl mx-auto" id="articleContainer">
                    {% for article in entries %}
                        <a href="{{ article.url }}" class="article-card">

                            <div class="article-card__image" style="position: relative; overflow:hidden;">IMAGE HERE</div>

                            <div class="article-card__inner">
                                <div>
                                    <h2 class="article-card__title text-lg">{{ article.title }}</h2>

                                    {% if article.articleIntro|length >= 200 %}
                                        <p class="article-card__intro">{{ article.articleIntro|slice(0, 200) ~ '...' }}</p>
                                    {% else %}
                                        <p class="article-card__intro">{{ article.articleIntro }}</p>
                                    {% endif %}
                                </div>

                                <span class="article-card__link">{{ 'Read article'|t }}</span>
                            </div>
                        </a>
                    {% endfor %}
                </div>
                
            </div>
        </section>

        <section class="mb-4 mt-4">
            <div class="max-w-2xl mx-auto px-3" id="articlePagination">

                <div class="text-center text-sm ">
                    {{ 'Showing page'|t }}
                    {{ pageInfo.currentPage }}
                    {{ 'of'|t }}
                    {{ pageInfo.totalPages }}
                </div>
                
                <ul class="flex flex-wrap justify-center w-auto font-sans">
                    {% if page > 1 %}
                        <li>
                            <a sprig s-val:page="{{ page - 1 }}" href="#" 
                                class="block m-1 border rounded px-0 py-2 border-transparent">
                                PREVIOUS
                            </a>
                        </li>
                    {% endif %}
                    {% for pageNumber in pageInfo.dynamicRange(5) %}
                        <li>
                            <a sprig s-val:page="{{ pageNumber }}" href="#" 
                                class="block m-1 border rounded px-3 py-2 {{ pageNumber == page ? 'border-fv-blue text-white font-bold bg-fv-blue' : 'border-fv-grey' }} ">
                                {{ pageNumber }}
                            </a>
                        </li>
                    {% endfor %}
                    {% if page < pageInfo.totalPages %}
                        <li>
                            <a sprig s-val:page="{{ page + 1 }}" href="#" 
                                class="block m-1 border rounded px-0 py-2 border-transparent">
                                NEXT
                            </a>
                        </li>
                    {% endif %}
                </ul>
            </div>
        </section>
    </div>
</div>

Edit: To clarify the question; Why doesn't s-preserve work? Where have I gone wrong? I realize it's difficult to test of course, but I'm hoping someone with more experience with sprig can see some glaring mistake :)

2
  • The s-preserve attribute should do the trick. You haven't asked a question though, so it's impossible to give you an answer...
    – Ben Croker
    Apr 11 at 11:22
  • @BenCroker Sorry, I thought the question was implied. I've clarified it Apr 11 at 13:51

1 Answer 1

2

Why doesn't s-preserve work? Where have I gone wrong?

Your question is very vague, but I'll try to answer as best I can. The s-preserve attribute (see https://htmx.org/attributes/hx-preserve/) will preserve the element, provided it has a unique ID. So looking at your code, the search input field element will be preserved, as expected, when the component is re-rendered.

Now to decode your question, it could be that the element is not being preserved in the way you expect – perhaps it is losing focus? This would likely be due to how hx-preserve works, but that would be a htmx-specific question. Restructuring your code so that you can use s-replace="#articles-and-pagination" would be optimal, resorting to JavaScript to replace other elements on the page if necessary, see https://putyourlightson.com/plugins/sprig#javascript

1
  • Thanks for your answer. I have considered changing the code to fit sprig better and then just re-positioning it afterwards using flex order, but since I asked this question, the clients priorities have shifted so I haven't had time to try it out. Definitely will eventually though. All in all, sprig is fantastic and a fantastic time-saver. Apr 26 at 11:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.