_hyperscript icon indicating copy to clipboard operation
_hyperscript copied to clipboard

Event handling doesn't work after putting HTML from a server

Open osadasami opened this issue 3 years ago • 10 comments

I send request to the server. Server renders the same HTML but with blocks in another positions. I get that HTML with hyperscript and replace current HTML on the page. I expect that I still can use arrows to trigger event that sends request to the server. But events doesn't work with the new HTML. In order to make it work I need to refresh the page

It would be great if Hyperscript could connect to new HTML that fetched from server side and listen events

<ul>
  <% data.each do |block| %>
    <li>
      <%= block['pos'] %>
      <input
        type="text"
        value="<%= block['content'] %>"
        _="
          on keydown[key is 'ArrowUp' and metaKey]
            log 'Move up'
            fetch <%= move_up_block_path(block['id']) %>
              {
                method: 'post',
                headers: {'X-CSRF-Token': document.getElementsByName('csrf-token')[0].content}
              }
            log it
            put it into #children
          end
        "
      >
    </li>
  <% end %>
</ul>

osadasami avatar Apr 25 '21 15:04 osadasami

Try _hyperscript.processNode(theNewElement) -- it's a part of the public API, but it's not conveniently documented. That should let hyperscript know about your new elements

benpate avatar Apr 26 '21 02:04 benpate

Try _hyperscript.processNode(theNewElement) -- it's a part of the public API, but it's not conveniently documented. That should let hyperscript know about your new elements

Thank you! It works. New code looks like this

on keydown[key is 'ArrowUp' and metaKey]
  log 'Move up'
  fetch <%= move_up_block_path(block['id']) %>
    {
      method: 'post',
      headers: {'X-CSRF-Token': document.getElementsByName('csrf-token')[0].content}
    }
  put it into #children
  put first of <input[value=`${me.value}`]/> into el
  call el.focus()
  call _hyperscript.processNode(#children)
end

osadasami avatar Apr 26 '21 02:04 osadasami

I want to clean this code up for the next release. I think we should consider processing nodes any time we add content to the dom, or at least add a command for doing so.

Ideally this:

on keydown[key is 'ArrowUp' and metaKey]
  log 'Move up'
  fetch <%= move_up_block_path(block['id']) %>
    {
      method: 'post',
      headers: {'X-CSRF-Token': document.getElementsByName('csrf-token')[0].content}
    }
  put it into #children
  put first of <input[value=`${me.value}`]/> into el
  call el.focus()
  call _hyperscript.processNode(#children)
end

should look like this:

on keydown[key is 'ArrowUp' and metaKey]
  log 'Move up'
  fetch <%= move_up_block_path(block['id']) %> using POST 
     with  headers 
       X-CSRF-Token: <name='csrf-token'/>.content
  put it into #children
  focus() the  first of <input[value=`${my value}`]/> 
end

We should also handle htmx attributes somehow here.

1cg avatar Apr 26 '21 13:04 1cg

I think it would also be nice to have an event triggered after a fetch of hyperscript, similar to how HTMX' htmx:afterSwap works

zilti avatar May 03 '21 13:05 zilti

This would be helpful, but it also starts down the road of replicating a lot of what htmx already does. This might be a good thing, but it would also result in a lot of duplicated code. I'd propose that we hook into the htmx's existing swap functions instead -- something like a swap function that knows how to use htmx.swap()

fetch /my-server then swap it into #destination

Alternatively, we could add a setting to hyperscript to use htmx.swap features instead of default actions.

benpate avatar Jun 19 '21 17:06 benpate

I think we should consider processing nodes any time we add content to the dom

I think this would be best. I think having to manually process a node that contains hyperscript is clunky.

or at least add a command for doing so.

In this case, I'd propose activate, initialize, or process (in that order of preference). E.g.

set newElement to '<button _="on click do some hyperscript">Click me</button>'
append newElement to the body then activate it

iamjameswalters avatar Apr 19 '22 21:04 iamjameswalters

It would be a nice feature, because now it's not even obvious how to make a clone of html element.

You can do:

<button _="on click put me as HTML after me">Test</button>

but generated items are not handled by hyperscript and you need to do something like this:

<div id="copy">
   <button _="on click put me as HTML after me _hyperscript.processNode(#copy)">Test</button>
</div>

which looks strange.

ustitc avatar Apr 26 '22 19:04 ustitc

It's super easy to make your own conversions. Here's an example that I made for another project. You might just make it and then publish it. We're still talking about ways to collect everyone's independent extensions outside of the main core.

benpate avatar Apr 26 '22 23:04 benpate

How can I trigger hyperscript when new elems are "managed" by third party lib? (a virtualized list lib https://clusterize.js.org/)

I'm pushing some new elems using ws like this

ws.send([
    <div _="on load log 'loadeded'" class="newelem">
      sthsth
    </div>,
  ])

they are handled by socket owner

<div
      hx-ext="ws"
      ws-connect="/ws"
      _=" on htmx:wsBeforeMessage 
         set elems to JSON.parse(event.detail.message)
         $logsi.append(elems)
         go to bottom of the #contentArea unless $scrolling
         for elem in .newelem 
             log 'lelo'
             log elem
             add .hover:bg-red-600 .bg-black to elem
             remove .newelem from elem
             put 'on click log 2' into elem.classes
             add @_='on click log 1' to elem
             add @classes='remove bar:1s, add foo:1s' to elem
            _hyperscript.processNode(elem)

         "
    >

they show up fine but hyperscript parts doesn't run. Tried using processNode mentioned above, didn't help.

cakir-enes avatar Oct 26 '23 21:10 cakir-enes

Another way of handling this is to call processNode on a containing element when its children are mutated:

<slot data-notification
  _="
    on mutation of childList
      call _hyperscript.processNode(me)
  "
></slot>

This slot can then be dynamically updated with content from a server response:

<slot data-notification
  _="
    on slotnotification(html)
      set my.innerHTML to html

    on mutation of childList
      call _hyperscript.processNode(me)
  "
></slot>

<button
    _="
      fetch '/some/endpoint'
        -- ...
      catch e
        -- send the markup to the notification element
        send slotnotification(html: e.responseText) 
          to the first <[data-notification]/>
    "
>
    do the thing
</button>

larrybotha avatar Apr 03 '24 12:04 larrybotha