6

I'm using HTMX and would like to have a <select> form field that automatically loads a specific URL into a <div>.

The "value select" example is similar to this idea, but it includes the value as a query parameter such as /models?make=audi. I would like to use a specific URL for each <option> instead.

This is an approximation of what I would think should work.

<select name="make" hx-target="#models">
  <option hx-get="/models/audi">Audi</option>
  <option hx-get="/models/toyota">Toyota</option>
  <option hx-get="/models/bmw">BMW</option>
</select>

<div id="models">
  <!-- hx-get results would go here -->
</div>

However, this does not work and I'm unable to find a way to implement what I describe. How can I achieve the desired behavior?

Edit: Related Twitter thread

Benjamin Oakes
  • 12,262
  • 12
  • 65
  • 83

4 Answers4

12

I had a conversation on Twitter and came to the conclusion that this does not yet exist.

I ended up playing with htmx:configRequest, resulting in this reusable JavaScript, which may make sense to contribute back to the HTMX library.

    document.body.addEventListener("htmx:configRequest", function (event) {
      let pathWithParameters = event.detail.path.replace(/:([A-Za-z0-9_]+)/g, function (_match, parameterName) {
        let parameterValue = event.detail.parameters[parameterName]
        delete event.detail.parameters[parameterName]

        return parameterValue
      })

      event.detail.path = pathWithParameters
    })

This is an example of how it is used:

    <select name="make" hx-get="/models/:make" hx-target="#models">
      <option name="audi">Audi</option>
      <option name="toyota">Toyota</option>
      <option name="bmw">BMW</option>
    </select>
Benjamin Oakes
  • 12,262
  • 12
  • 65
  • 83
  • Thank you for sharing your solution! – guettli Jun 30 '21 at 13:33
  • 2
    No problem! I hope to make a PR for HTMX including it sometime soon. – Benjamin Oakes Jul 01 '21 at 14:08
  • I'm looking for a way to use parameters in my URL for an HTMX request and this seems like the current way to do it. My regex skills are awful so I'll need a moment for a suggested fix, but if the request URL contains a port value - ie `127.0.0.1:5000` - this tries to parse the port as well and it breaks. – pspahn Mar 01 '22 at 18:45
  • @BenjaminOakes I might suggest enclosing the param like `` instead of a colon. This follows Flask style params. – pspahn Mar 01 '22 at 18:53
1

I got it to work with some extra help from AlpineJS.

The complexity of this solution is because Chrome-based browsers don't trigger HTMX when the <select>'s value changes.

This solution uses Alpine to:

  • react to the event (with the @change attribute)
  • find the option element corresponding to the value (using document.evaluate and an xPath selector to look for children of the select element, $el.)
  • then trigger HTMX on that element.
<select 
  name="make"
  hx-target="#models"
  x-data="{
    renderXPathSelector(value) {return `.//option[contains(., '${value}')]`},
    getChosenOption(value) {return document.evaluate(this.renderXPathSelector(value), $el).iterateNext()},
    tellHTMXOptionChanged(event) { htmx.trigger(this.getChosenOption(event.target.value), 'click')}
  }"
  @change="tellHTMXOptionChanged($event)"
>
  <option hx-get="/models/audi">Audi</option>
  <option hx-get="/models/toyota">Toyota</option>
  <option hx-get="/models/bmw">BMW</option>
</select>

If you have selects whose values are different than the text, you can use select the option in a more simple way:

el.querySelector(`[value="${el.value}"]`)

You can support both cases by setting the chosenOption function to:

const chosenOption = el.querySelector(`[value="${el.value}"]`) ||
                      getChosenOption(event.target.value)

If you want to use this pattern several times across your app, you can create an "Alpine magic" to simplify the HTML:

<script>
  document.addEventListener('alpine:init', () => {
    Alpine.magic('tellHTMXOptionChanged', (el) => {
      {# This is needed for cross-browser compatability for when a <select> changes #}
      return (event) => {
        function renderXPathSelector(value) {return `.//option[contains(., '${value}')]`}
        function getChosenOption(value) {return document.evaluate(renderXPathSelector(value), el).iterateNext()}
        const chosenOption = el.querySelector(`[value="${el.value}"]`) || getChosenOption(event.target.value)
        htmx.trigger(chosenOption, 'click')
      }
    })
  })
</script>

...

<select 
  name="make"
  hx-target="#models"
  x-data
  @change="$tellHTMXOptionChanged($event)"
>
  <option hx-get="/models/audi">Audi</option>
  <option hx-get="/models/toyota">Toyota</option>
  <option hx-get="/models/bmw">BMW</option>
</select>

Chris May
  • 607
  • 7
  • 12
0

I know it's an old question. And the author probably helped in the development of the feature.

But you can find on htmx documentation a way to do Cascading select

Copy/Paste from the doc link :

<div>
    <label>Make</label>
    <select name="make" hx-get="/models" hx-target="#models" hx-indicator=".htmx-indicator">
      <option value="audi">Audi</option>
      <option value="toyota">Toyota</option>
      <option value="bmw">BMW</option>
    </select>
  </div>
  <div>
    <label>Model</label>
    <select id="models" name="model">
      <option value="a1">A1</option>
      ...
    </select>
</div>

Has I understand, htmx will take name attribute from <select> and use it in GET method to /models url and finaly send a request to /models?make=value_selected

I hope it will help future people who didn't find the answer elswhere.

Florian
  • 61
  • 1
  • 11
-2

You load the external page into iframe using javascript

Or use Ajax to append the loaded content to the <div id="models"></div> based on selected value.

unigg
  • 466
  • 3
  • 8