Stimulus Discourse

Idiomatic stimulus + server-side HTML caching?


#1

My first use-case for stimulus is refactoring a bookmarking feature on a products page where the whole product list (and most of the page) is cached server side with Russian Doll caching. So, we can’t just use inline data maps for the initial page state to indicate a user has bookmarked a product on initial load.

Previously, we had put the state of a users’ bookmark for a product in data attributes of a div just outside the products’ top-level cache statement. Then after page load, we had a bit of procedural-style jQuery that would read the special div’s data attrs and render the correct bookmark state, as well as bookmark toggle code that watches for a click, displays a spinner, and then applies the new state.

I feel like this is exactly what stimulus is made to clean up, but I’m not sure what to do with the initial page state, since it can’t be applied inline to the bookmarks HTML.

Obviously I could just keep doing what I’m doing, and read the state off a special div’s data attrs. Is there a more idiomatic way though, along the lines of data maps? “Remote data maps” or something?

Here’s an extremely simplified example.

Previously

HTML:

<div class="js-bookmark-state" data-bookmarked-products="123,456,789" />
<% cache do %>
  <% @products.each do |product| %>
    <div class="product" id="product-<%= product.id %>">
      <div class="product-name"><%= product.name %>
      <button class="bookmark-btn" data-bookmark-url="/path/to/bookmark" />
        <span class="bookmark-active fa fa-bookmark" style="display: none" />
        <span class="bookmark-inactive fa fa-bookmark-o" style="display: none" />
      </div>
    </div>
  <% end %>
<% end %>

JS:

Something that reads ‘js-bookmark-state’ to create a bookmarkedProducts array, then finds the correct bookmarked products and calls $.show() on the correct icon.

Also, watches for clicks on .bookmark-btn, and reads the response to similarly call $.toggle() on the icons if successful.

Now, using stimulus:

<% cache do %>
  <% @products.each do |product| %>
    <div class="product" id="product-<%= product.id %>">
      <div class="product-name"><%= product.name %>
      <button class="bookmark-btn" data-controller="bookmark" data-bookmark-url="/path/to/bookmark" data-program-id="<%= product.id %> />
        <span class="bookmark-active fa fa-bookmark" style="display: none" />
        <span class="bookmark-inactive fa fa-bookmark-o" style="display: none" />
      </div>
    </div>
  <% end %>
<% end %>

The stimulus Bookmark instance can know the program-id via data maps, but is the best way to track bookmarked programs via a special div?


#2

Here’s an idea: you could create a Stimulus controller for the top-level wrapper div in your first example and use that to manage the bookmarked IDs state. Then in the child controllers for each product, use either custom events or getControllerForElementAndIdentifier to request state from the parent controller for a given product (or to update state).


#3

Thanks, yes, custom events or getControllerForElementAndIdentifier seems to be the way recommended here for establishing a parent-child relationship.

I actually have a different need for parent-child controllers, which I think I’ll start a new thread about, but TL;DR experience tells me to be wary of event-based control flow. getControllerForElementAndIdentifier would work, but what I ended up doing is if a bookmark doesn’t know it’s state, it looks it up in a data attribute elsewhere on the page, but otherwise gets and sets state like a normal controller. This way I can use the bookmark controller outside the context of a list of bookmarks, which seems well encapsulated.