htmx: e.querySelectorAll is not a function

Viewed 321

If I use hx-swap-oob, then I get an error:

<script src="https://unpkg.com/htmx.org@1.3.3/dist/htmx.js"></script>
<div id="sum">?</div>
<table>
 <tr>
  <td><button 
   hx-get="https://run.mocky.io/v3/b5a6902e-fc46-479b-92e4-9b4befc7a920"
   hx-target="closest tr">1</button>
  </td>
  <td>one</td>
 </tr>
</table>

After pressing "Run Code Snippet" and then press 1:

htmx:swapError
{
  "message": "e.querySelectorAll is not a function",
  "filename": undefined,
  "lineno": undefined,
  "colno": undefined
}

The mocky http endpoint returns this:


<tr>
  <td>2</td><td>two</td>
 </tr>

<div id="sum" hx-swap-oob="true">MAGIC</div>

In above example I use the non minified version, so the error message is: eltOrSelector.querySelectorAll is not a function

If I use this endpoint, it does not fail: https://run.mocky.io/v3/2ab904eb-23a9-4006-b68b-f112b55841f3

But in my usecase the new html fragment should be <tr>...</tr>, not a <div>.....

JS stacktrace:

Uncaught TypeError: eltOrSelector.querySelectorAll is not a function
    at findAll (htmx.js:295)
    at handleOutOfBandSwaps (htmx.js:501)
    at selectAndSwap (htmx.js:712)
    at doSwap (htmx.js:2284)
    at handleAjaxResponse (htmx.js:2358)
    at XMLHttpRequest.xhr.onload (htmx.js:2163)

Update

I could narrow down the issue to this:

devtools

This fails:

makeFragment('<tr><td>a</td></tr> <div>X</div>')

Update2

hmmm, now I know why it fails:

makeFragment

parseFromString() of Chrome is the root-cause:

parseFromString

Update3

Follow-up question: Make parseFromString() parse without validation

Update4

I created an issue, hoping someone with more creativity has an idea how to solve this: https://github.com/bigskysoftware/htmx/issues/469

1 Answers

I created a new endpoint which returns a slightly different HTML fragment:

<table>
 <tr id="row1">
  <td>2</td><td>two</td>
 </tr>
</table>

<div id="sum" hx-swap-oob="true">MAGIC</div>

<script src="https://unpkg.com/htmx.org@1.3.3/dist/htmx.js"></script>
<div id="sum">?</div>
before table
<table>
 <tr id="row1">
  <td><button 
   hx-get="https://run.mocky.io/v3/28a8e653-491c-4186-abd4-4f48f3579273"
   hx-target="#row1" hx-select="#row1">1</button>
  </td>
  <td>one</td>
 </tr>
</table>
after table

HTMX uses parseFromString() internally, and this method validates the input. If there is non valid HTML, it might truncate elements.

makeFragment() of HTMX wraps the string which it sends to parseFromString(). HTMX looks at the first element in the response. In above question it is <tr>. Thus htmx uses <table> to wrap the whole http response.

makeFragment()

The solution is the return a "normal" element to htmx (not <tr>).

Normal html fragments in htmx are exactly one element, so that it does not matter. If you use hx-swap-oob the server sends several elements to the client.

This is more or less a work-around. It would be great if htmx would handle the original string.

Related