benopotamus/htmx-ext-shoelace

sl-input validation not respected in htmx form submission!

Closed this issue · 2 comments

Hello! I just started using this extension, which is great, and was hoping that it could be expanded to help with this case:

<form hx-post="/s/htmxservicestarter/todos" hx-target="#todos" hx-swap="afterbegin swap:1s" data-form-type="other" class="">
   <div class="hflex">
      <sl-input name="description" type="text" required="" placeholder="Enter a new todo" size="medium" form="" data-required=""  data-invalid="" data-user-invalid=""></sl-input>
      <sl-button type="submit" variant="default" size="medium" data-optional="" data-valid="" data-user-valid="">New</sl-button>
   </div>
</form>

The input is required, but htmx still fires off the post when clicked:

Screen.Recording.2024-05-05.at.11.53.00.AM.mov

Is this something that could be easily added here? (I'm happy to do it but don't know enough about the htmx implementation yet to get started). Or is this possibly just user error?

Either way, thanks for the help and thanks for this extension!

Ok immediately after posting this I found something that worked for me (though I'm not sure it is the best solution)

I just added a check on elt.checkValidity and preventDefault if false. It appears to work in the above case.

  const slTypes = 'sl-checkbox, sl-color-picker, sl-input, sl-radio-group, sl-range, sl-rating, sl-select, sl-switch, sl-textarea'

  /* Lightly modified version of the same function in htmx.js */
  function shouldInclude(elt) {

      // sl-rating doesn't have a name attribute exposed through the Shoelace API (for elt.name) so this check needs to come before the name==="" check
      if (elt.tagName === 'SL-RATING' && elt.getAttribute('name')) {
          return true
      }

      if (elt.name === "" || elt.name == null || elt.disabled || elt.closest("fieldset[disabled]")) {
          return false
      }

      if (elt.tagName === 'SL-CHECKBOX' || elt.tagName === 'SL-SWITCH') {
          return elt.checked
      }

      if (elt.tagName === "SL-RADIO-GROUP") {
          return elt.value.length > 0
      }

      return true;
  }

  htmx.defineExtension('shoelace', {
      onEvent: function(name, evt) {
          if ((name === "htmx:configRequest") && (evt.detail.elt.tagName === 'FORM')) {
              evt.detail.elt.querySelectorAll(slTypes).forEach((elt) => {
                  if (shouldInclude(elt)) {
                      if (!elt.checkValidity()) {
                          evt.preventDefault();
                          return;
                      }
                      if (elt.tagName === 'SL-CHECKBOX' || elt.tagName === 'SL-SWITCH') {
                          // Shoelace normally does this bit internally when the formdata event fires, but htmx doesn't fire the formdata event, so we do it here instead. See https://github.com/shoelace-style/shoelace/issues/1891
                          evt.detail.parameters[elt.name] = elt.value || 'on'

                      } else if (elt.tagName == 'SL-RATING') {
                          evt.detail.parameters[elt.getAttribute('name')] = elt.value

                      } else {
                          evt.detail.parameters[elt.name] = elt.value
                      }
                  }
              })
          }
      }
  })

Fixed in latest version