Hotwire Discussion

DOM changes from backend in a turbo_frame_tag are not triggering value changes callbacks

Hi

I have a sample application and I want to trigger a stimulus method controller upon some DOM changes.
Those changes are sent from the backend with action-cable
after_save { broadcast_replace_later_to :exchanges }

On the frontend I have
#views/exchanges/index.html.erb

    <div class="mt-10" data-controller="hello">
      <dl class="space-y-10 md:space-y-0 md:grid md:grid-cols-1 md:gap-x-8 md:gap-y-10">
        <%= turbo_stream_from :exchanges %>
        <%= turbo_frame_tag 'exchanges' do %>
          <%= render @exchanges %>
        <% end %>
      </dl>
    </div>

and my partial
#views/exchanges/_exchange.html.erb

<%= turbo_stream_from :exchanges %>
<div class="relative border-gray-200 border-b-8" id="<%= dom_id(exchange) %>" data-hello-target="slide" data-hello-index-value="<%= exchange.price %>">
  <dt>
    <div class="absolute flex items-center justify-center h-12 w-12 rounded-md bg-indigo-500 text-white">
      <!-- Heroicon name: outline/globe-alt -->
      <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
      </svg>
    </div>
    <p class="ml-16 text-lg leading-6 font-medium text-gray-900"><%= exchange.name %></p>
  </dt>
  <dd class="mt-2 ml-16 text-base text-gray-500">
    with 1000 reais you can buy <%= exchange.convert_to_btc(1000) %> bitcoins
  </dd>
</div>

the changes on backend are successfully streamed to the view but Im trying to make my stimulus controller aware of it and react to those changes in a callback
#javascript/controllers/hello_controller.js

import { Controller } from "stimulus"

export default class extends Controller {
  static values = { index: Number }
  static targets = [ "slide" ]
  connect() {
    console.log("connect called")
  }

  initialize() {
    console.log("initialize called")
  }

  indexValueChanged() {
     //THIS does not get called after changes on the data-hello-index-value
    console.log("indexValueChanged called")
  }
}

My hope was DOM changes could trigger indexValueChanged on my stimulus controller and I could implement some sort of animation from there but for some reason the ValueChanged callback doesnt get triggered.
Maybe I have misunderstood the purpose of the value properties api. But any clarification on this will be appreciated.

tks.

initialize or connect are triggered when the controller is added to the DOM.

Look into mutation observers. see these threads:

1 Like

The roll-your-own mutation observer thing still irks me beyond belief. But yes, like tleish said, that’s your only option at present.

Also, I wouldn’t include another turbo_stream_from tag in your partial. The one call in your index page is enough.

@strobilomyces - Why does this irk you?

@tleish As I see it, listening for DOM changes seems like an obvious basic use case. Having to write mutation observer code feels less like extending Stimulus and more like a step away from rolling my own framework. It’s undocumented and seems like a departure from the rest of the concepts, which are simple both in syntax and implementation. I see people confused about this almost as much as the link_to/button_to mix-up (a common issues sticky thread might not be a bad idea, come to think of it).

ty for your response.
I saw stimulus uses mutation observes to implement the valueChanged callbacks so I thought any changes to the observed values would trigger the callback, something similiar to a 2-way-databinding and it seems not to be the case.
Thanks for the links I will take a look and see if I can come up with a solution based on that.

@strobilomyces I can certainly understand some of the frustration.

Some frameworks out there have javascript methods to cover any and all scenarios. The tradeoff being those frameworks are larger. For me, the small stimulus library is one of it’s strenghts.

I personally like the ability to only use and customize mutationObservers in some controllers. In 25+ stimulus controllers for one project we have, only 5 need a mutationObserver. I feel concerned a global stimulus observeMutation feature could result in poor performance on large pages.

@tleish I don’t feel like the code would be a particularly large addition to the framework for the functionality it offers. I could see this being a non-trivial sticking point for people who maybe aren’t so versed in Javascript/Typescript, yet are capable of learning Stimulus on its own. Viewing some of the comments on this forum, people seem to have their hands full enough with the basic concepts without having to deal with something like this.

As for performance, I see your point. Maybe it could be invoked rather than enabled by default.

In any event, something should be documented on this :slight_smile:

@strobilomyces Agreed, documentation could use some help and Hotwire comes with a learning curve.

Ok I’ve found the major source of confusion.
The problem is the value attributes are children of the div that declares the stimulus controller

<div class="mt-10" data-controller="hello">
  <turbo-frame id="exchanges">
    <!-- MUTATIONS HERE WONT TRIGGER CALLBACKS ON HELLO CONTROLLER-->
    <div class="relative border-gray-200 border-b-8" id="exchange_6" data-hello-target="slide" data-hello-index-value="44444">
      <dt>
        <div class="absolute flex items-center justify-center h-12 w-12 rounded-md bg-indigo-500 text-white">
          <!-- Heroicon name: outline/globe-alt -->
          <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"></path>
          </svg>
        </div>
        <p class="ml-16 text-lg leading-6 font-medium text-gray-900">bit_nuvem</p>
      </dt>
      <dd class="mt-2 ml-16 text-base text-gray-500">
        with 1000 reais you can buy 0.005728000183296006 bitcoins
      </dd>
    </div>
  </turbo-frame>
</div>

So I have 2 possible solutions: create the controllers on children div and adapt my controller logic or create a custom observer to serve as a bridge between the controller and the data changes from the back-end.
I think I will try the first one as it seems simpler RN
Thanks everyone.