putyourlightson / craft-sprig

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

Accounting for successful / unsuccessful form submissions in an Alpine JS slideout #309

Closed shifuma closed 1 year ago

shifuma commented 1 year ago

I'm building an address management system for Commerce and would like it to work similarly to how it does in the control panel, meaning Slideouts.

I've got adding, editing and deleting addresses working well but adding server-side validation complicates things. Basically, the slideout is closed when the form is submitted, meaning the errors don't show.

If I add s-replace="#address-fields" to <form id="address-form"> the slideout stays open and the validation errors show. However, the slideout stays open after a successful submission and the notification doesn't show until the page is refreshed.

I would like to combine these two scenarios. I thought adding {% do sprig.retarget('#manage-addresses') %} to {% if success ... %} would do it, but that has some strange effect. I've played around with a few other options like this, but not found a solution yet. My code is below and I know it's a lot, but I'd appreciate any pointers!

{% set addresses = currentUser ? currentUser.getAddresses() : [] %}
{% set addressId = addressId ?? '' %}

<div id="manage-addresses" x-data="{ slideout: false }" x-cloak>
  {% if addresses | length %}
    <div id="saved-addresses">
      {% for address in addresses %}
        <div>
          {{ address|address }}
          <button x-on:click="slideout = true" sprig s-val:addressId="{{ address.id }}" s-replace="#address-form" s-indicator="#spinner">Edit</button>
          <form id="delete-address-{{ address.id }}" sprig s-method="post" s-action="users/delete-address" s-val:addressId="{{ address.id }}" s-val:successMessage="{{ 'Address deleted.'|hash }}" s-include="delete-address-{{ address.id }} *" s-confirm="Are you sure you wish to delete this address?">
            <button type="submit">Delete</button>
          </form>
        {% endfor %}
      </div>
    {% endif %}
    <button @click="slideout = true" sprig s-val:addressId="" s-replace="#address-form" s-indicator="#spinner">Add an Address</button>

    <div id="slideout" x-show="slideout">
      <form id="address-form" sprig s-method="post" s-action="users/save-address" s-indicator="#spinner" s-val:successMessage="{{ 'Address saved.'|hash }}">
        {% set addressToEdit = craft.addresses().id(addressId).one() %}
        {% if addressToEdit %}
          {{ hiddenInput('addressId', addressToEdit.id) }}
        {% endif %}
        <div id="address-fields">
          {% include '_components/addressFields' %}
        </div>
        {% if success is defined and success %}
          {% do craft.app.session.setNotice(successMessage) %}
          <script>
            htmx.trigger('#notification', 'refresh');
          </script>
        {% endif %}
      </form>
    {% include '_components/spinner' %}
  </div>
</div>
bencroker commented 1 year ago

I‘d start by setting the value of slideout in the x-data definition to true when there are errors and seeing how that goes.

shifuma commented 1 year ago

Thanks for the reply. I tried this:

{% if address is defined and address.hasErrors() %}
  {% do craft.app.session.setError('Error.') %}
  <script>
    htmx.trigger('#notification', 'refresh');
    document.addEventListener('alpine:init', () => {
    Alpine.data('slideout', () => ({
      slideout: true,
    }))
  })
  </script>
{% endif %}

But unfortunately no luck; it still closes the slideout. I note that you also say not to use Alpine.data here: https://putyourlightson.com/plugins/sprig#alpine-js

I'm really poor with JS so appreciate if you can help. Otherwise I'll try over on the Alpine discord. Thanks!

bencroker commented 1 year ago

I actually meant inlining the condition as follows.

x-data="{ slideout: {{ address is defined and address.hasErrors() ? 'true' : 'false' }} }"
shifuma commented 1 year ago

Ah that did it! I'd actually tried it before, but I must have still had s-replace="#address-fields" on the form inside the slideout.

Thanks Ben!

bencroker commented 1 year ago

Great!