Can't get hotwire to work for my use case. Looking for ideas

All the rails casts and drifting ruby vids I have watched make using hotwire look super easy. But they all share the same use case of make records that are unordered or else have a default order.

My use case requires records with an explicit order stored in an order attribute. The records are rendered in that order, and creating new records isn’t trivial since you can create a record at any position and everything else gets shifted, etc.

I have a link which loads the form at any position with the proper order value already set. I have managed to get turbo frames to work for that as well as dismissing the form once it is submitted, but I can’t figure out how to load the new record into the list because I can’t seem to break out of the turbo_frame the form is in to insert the new record.

The markup is like this:

<turbo-frame id="record_1">
  ...
</turbo-frame>

<turbo-frame id="new_record_after_1">
  <form ...>
</turbo-frame>

<turbo-frame id="record_2">
  ...
</turbo-frame>

<turbo-frame id="new_record_after_2">
  <form ...>
</turbo-frame>

<turbo-frame id="record_2">
  ...
</turbo-frame>

...

As you can see, since the forms are in a different turbo frames and the submission responses will only operate within them, I can’t insert the new record. Even if turbo frames would get replaced outside the initial context, I don’t have a turbo frame that would match.

I have tried using turbo streams as well, which I couldn’t get to work either since the entire list needs to be replaced to adjust ordering and I couldn’t figure out how to get the broadcasts to work, and honestly that approach is a little overkill for what I am doing.

I had all of this working fine with just stimulus, so if I need to I can go back to that, but I figured it was worth a shot to remove some of the complexity I had in my js. Any advice/helps would be awesome. Thank you!

I might use a single turbo-frame to load the form, and then in the turbo-frame response include a turbo-stream that positions the response in your list.

1 Like

There are so many ways you can do this, but perhaps the most elegant way would be to create your own stream action and handle this particular need in that.

Also remember that you don’t need to target the frame the form is within any more - you can target anything - use target=“dom_id” for an id and targets=“classnames” for selecting by class.

import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
  initialize() {
    this.handleAddon = this.handleAddon.bind(this)
  }

  connect() {
    document.addEventListener( "turbo:before-stream-render", this.handleAddon)
  }

  disconnect() {
    document.removeEventListener( "turbo:before-stream-render", this.handleAddon )
  }

  handleAddon(event) {
    this.performAction(event.target)
  }

  performAction(onStream) {
    const actionFunction = this.extraAction(onStream.action)
    if (actionFunction) {
      actionFunction.bind(onStream)()
      event.preventDefault()
      return(false)
    }
    return(true)
  }

  extraAction(action) {
    const ExtraActions = {
        insert_after: async function insert_after() {
/*
          Your javascript here to insert after the relevent target - e are the target or targets referenced in the stream
          this.targetElements.forEach((e => {
            .... this.templateContent contains your stream contents
          }))

*/
        }
    }
    return ExtraActions[action]
  }

}
1 Like

I would be interested to know which solution you have adopted: whether it be that suggested by @tleish or @defsdoor or otherwise, and the reasons/costs/benefits of doing so.

I ended up putting the entire list of items into a wrapping turbo-frame and making the form target that frame, then I render the entire list back out when it submits. This was necessary anyway because things must be shifted downward when new items are inserted, and links and ids need to be updated in that process.

1 Like

You may also find this discussion to help: