putyourlightson / craft-sprig

A reactive Twig component framework for Craft CMS.
https://putyourlightson.com/plugins/sprig
MIT License
129 stars 9 forks source link

Using HTMX to update a component based on the clicked trigger #355

Closed lennonpuype closed 8 months ago

lennonpuype commented 8 months ago

Hi,

I am kinda stuck on this thing using HTMX since I think this is the only way to go to get what I want, using Sprig. I will explain with a small example on what I want and what I might miss

There are 2 buttons on the screen. Both buttons are outside of a sprig component, since the sprig component that should update is something globally and the buttons are randomly on the webpage, I mean they can be inside text or whatever, so not inside the sprig component.

When clicking button 1, the sprig component should load FORM 1. When clicking button 2, it should obviously load FORM 2. Just tried something basic to see if the Trigger works, and after clicking, the sprig component actually updates. So that gives me the idea that refreshing the component using HTMX is the way to go, just as I read. I tried this by dumping the current time, and after clicking one of the buttons it making the sprig component refresh that time using the following codes:

The component is available in the DOM at: body > sprigcomponent

{{ sprig('_sprig/_popup/_popup.twig', {}, {
       'id': 'sprig-popup',
       's-trigger': 'load, refresh',
}) }}

Following triggers are available in the DOM at: body > main > .text > .button1 or body > main > nav > .button2

The JS:

// This code gets called when the trigger has been clicked
const $sprigPopup = document.querySelector('#sprig-popup');

if($sprigPopup) {
        $sprigPopup.dispatchEvent(new Event('refresh'));
        htmx.trigger('#sprig-popup', 'refresh');
}

My question: As you see I am just missing how I can update a form (or whatever content) based on the clicked trigger. fyi, I call a form on the website (not using sprig) as following: {{ craft.formie.renderForm(formHandle) }}

Could you assist me in the right direction to be able to update that formhandle based on what I clicked?

bencroker commented 8 months ago

If you use the htmx API then this might be easier to write. See https://putyourlightson.com/articles/htmx-has-a-javascript-api-btw

<button class="button1" hx-on:click="htmx.trigger('#sprig-popup', 'refresh')">

The above will cause the Sprig component to re-render. If you want a specific form to submit, then you should be able to do something like this.

<button class="button1" hx-on:click="htmx.find('#form1').submit()">

If that form has been Sprigified, then it will do its thing (otherwise it will submit as normal).

<form id="form1" sprig s-method="post" s-action="some/controller/action">
lennonpuype commented 8 months ago

Hi Ben,

Thanks for the fast reply.

I tried some things with these inline hx-on attributes, but I don't really get what I want.

I kinda found a solution for what I want to do, but I don't really think this is THE way to go.

In Javascript when triggering the component like above in this issue, I append some info to the parameters of the URL.

const $sprigPopup = document.querySelector('#sprig-popup');

if($sprigPopup) {
    $sprigPopup.dispatchEvent(new Event('refresh'));

    const urlParams = new URLSearchParams(window.location.search);
    urlParams.set('hx-trigger-id', popupType);
    const newUrl = `${window.location.pathname}?${urlParams.toString()}`;

    // Push the new URL to the browser's history
    window.history.pushState({ path: newUrl }, '', newUrl);
    htmx.trigger('#sprig-popup', 'refresh');        
}

After doing that when the component has been refreshed, I do following to get the parameter like this:

{% set popupReferer = craft.app.request.getReferrer() ?? null %}
{% set parts = popupReferer|split('?') %}
{% set hxTriggerId = '' %}

{# SETS TRIGGER ID #}
{% if parts|length > 1 %}
    {% set params = parts[1]|split('&') %}

    {% if params %}
            {% for param in params %}
                {% set keyValue = param|split('=') %}
                {% if keyValue[0] == 'hx-trigger-id' %}
                    {% set hxTriggerId = keyValue[1] %}
                {% endif %}
            {% endfor %}
    {% endif %}        
{% endif %}   

As you see this is a tricky way and probably not the way to go. Do you have any suggestions so I am able to get the clicked trigger ID inside the component in a better way then I do now?

bencroker commented 8 months ago

Can you explain to me in clear terms, with a simple example, what it is you’re trying to do?

lennonpuype commented 8 months ago

Sure, so we have a site that is using barba.js for smooth page transitions. All pages can have a popup that is activated on button click. This popup can be a slideshow or a form, depending on what button you click. There are also a lot of possible forms. Because of barba js, we are running into some limitations with the current setup of predefining every possible slideshow or form we update and populate with the needed fields after changing the page. It just should get optimised.

That is where Sprig comes in handy to load server side data as if it is a page refresh and everything would work more convenient than it is now.

There are happening some frontend things with gsap to animate the opening of that popup as you see in the screenrecording below. I want the content of that popup to update after the popup has been activated when a button with certain id has been clicked. This ID can be slideshow, form-1, form-2...

https://github.com/putyourlightson/craft-sprig/assets/30866096/cd0563c9-4922-4eda-9118-c338f5694b95

As you see in the screenrecording, when clicking on the "Sprig trigger" button, inside that element that is opening. The sprig component should update to what button the user clicked on. What you see here is with the code I added above in my last message. You see it works, but it might just not be the best way of doing so.

So maybe you see a more convenient way of updating the ID, rather then how I now worked using the params in the URL. I tried to push a header with the sprig request, but didn't succeed in that.

I dont know if I am looking to far away from an easy solution? I looked into the htmx docs, but cant really find what I am searching for. I'm also pretty new to htmx since I only used sprig for simple filters until now

bencroker commented 8 months ago

Thanks for the explanation. Could sprig.trigger be what are looking for?

Returns the ID of the element that triggered the request.

https://putyourlightson.com/plugins/sprig#sprig-trigger

lennonpuype commented 8 months ago

I fixed it in another way. The way you said by using sprig.trigger didn't really work because the trigger was the sprig-component since the component got refreshed using the hx:on=... there was like no way to get the ID of the clicked button as the trigger inside the sprig component that got refreshed.

That being said, how I did it now was the same way as above

...

const $sprigPopup = document.querySelector('#sprig-popup');

if($sprigPopup) {
    $sprigPopup.dispatchEvent(new Event('refresh'));

    const urlParams = new URLSearchParams(window.location.search);
    urlParams.set('hx-trigger-id', popupType);
    const newUrl = `${window.location.pathname}?${urlParams.toString()}`;

    // Push the new URL to the browser's history
    window.history.pushState({ path: newUrl }, '', newUrl);
    htmx.trigger('#sprig-popup', 'refresh');        
}

...

I changed the working of things up by using a cookie since this is more user friendly, then inside the sprig component I get the cookie that was set in javascript. This feels more convenient than getting a url parameter.

bencroker commented 8 months ago

Glad to here you found a solution you’re happy with! FYI you can also use a hidden input to store and change the state within a component.

In the Sprig component:

<input type="hidden" id="popupType" name="popupType" value="{{ popupType ?? '' }}>

Changing the value before refreshing:

<button class="button1" hx-on:click="htmx.find('#popupType').value = 'button1'; htmx.trigger('#sprig-popup', 'refresh')">

Then popupType will be available in the Sprig component as a variable:

{{ popupType ?? '' }}

See https://putyourlightson.com/plugins/sprig#component-state

lennonpuype commented 8 months ago

Oh thanks, this is exactly what I needed. It is a little difficult to find this when being new to htmx.

Now I understand how to do these kind of things, so thanks for the help!

bencroker commented 8 months ago

Yeah I get that. Check out the Sprig trainings at https://putyourlightson.com/sprig/training