Hello - I’m wondering what the “right” way to insert markup from the server is. I have a pretty vanilla Rails setup and I’m using unobtrusive javascript to send form data to my server (a form with data-remote=true
).
I’m trying to create a basic task list, I have a form for creating a new Task - when it’s successful, I want to append the markup from the server into my tasks list. I have my server respond with a javascript snippet (from create.js.erb
), that does something along the lines of:
document.querySelector('.tasks').innerHTML += "<%= j render @task%>"
which renders a template along the lines of:
<!--_task.html.erb -->
<div class="task" data-controller="task" data-task-id="<%=task.id %>">
<%= task.description %>
</div>
This works fine, but I’m trying to also trigger an event when a task is added (that contains the task id). Initially I added code to handle this through listening to the unobtrusive js ajax:success
event, but quickly realized that event doesn’t have any data from the server in a format that I can easily use since the server is returning JS, not JSON.
So, I realized that my Stimulus TaskController
should automatically call connect()
when my new DOM element is added. Maybe I could trigger my event there, since that has access to data attributes? For example:
// task_controller.js
export default class extends Controller {
connect() {
this.element.dispatchEvent(new CustomEvent('task:add', {bubbles: true, detail: { id: () => this.data.get('id')}}))
}
}
To my surprise however, I noticed that when innerHTML
is called, all my existing Stimulus controllers in the task list are disconnected, and then new ones (I presume) are initialized/connected. Therefore, putting my event dispatch code in the connect()
method is always resulting in n events, where n is the number of tasks in the list (rather than just the 1 event, which is what I want).
Adding Task
(5) TaskController Disconnect
TaskController Initialize
TaskController Connect
...
TaskController Initialize
TaskController Connect
I assume this is happening because innerHTML
is replacing all the child DOM nodes in the .tasks
node, rather than appending to it?
Ok I actually found an answer to my question right before I was about to submit this, but I was able to solve this by using the JS API insertAdjacentHTML
, rather than innerHTML
. So my rails create.js.erb file now looks like this:
document.querySelector('.tasks').insertAdjacentHTML('beforeend',"<%= j render @task %>")
Awesome! I’m still going to post this because it might help out someone else.
Thanks!