Search code examples
javascriptruby-on-railsturbolinks

Update CSS class based on value in model after Ajax request


I have a view where I display a list of episodes. I'm using a partial for that:

    <div class="episode-list">
      <% if user_signed_in? %>
        <%= render partial: "shared_partials/episode_with_image_compact", collection: @recent_episodes, as: :episode %>
    <% end %>
    </div>

In that partial I have a button_to that sends a request to the backend to mark an episode as seen:

          <%= button_to manual_new_scrobble_path, {method: :post, form_class: 'manual-scrobble-btn', class: "button is-small #{'listened' if episode.has_play}", remote: true, params: {scrobble: {user_id: current_user.id, episode_id: episode.id}}} do %>
            <%= scrobble_icon(episode) %>
          <% end %>

The scrobble_icon function just sets a class based on the status of an episode like this:

  def scrobble_icon(episode)
    if episode.has_play
      icon('fas', 'check', class: 'listened')
    else
      icon('fas', 'check', class: 'unlistened')
    end
  end

I'm using remote: true so it doesn't result in a page load. When I was doing other frontend frameworks for SPA there was always this two-way-binding by default where the value would update based on the status of the "store" / model.

I've now read multiple articles about TurboLinks and server generated javascript but I still can't figure out what's the correct solution to the problem.

What I want:

  • Click on button
  • Request is successful and writes value to DB
  • Button now has the class "listened"

What actually happens:

  • Click on button
  • Request is successful and writes value to DB
  • Button looks the same
  • I reload the page, button now has the class "listened"

A lot of answers to similar problems talk about jQuery which is:

1) not in Rails 6 any more
2) not something I want to use in 2019

Thank you!


Solution

  • In the end what I had to do was to add a class as mentioned in arieljuod's answer but the building block I didn't understand in my previous reply is the following.

    In your app/javascript/packs/application.js (or a custom one if you prefer) you have to set up a listener on the ajax:success event that gets emmited by Rails as mentioned in the documentation.

    Then we have to check for our unique id in the event to differentiate events coming from other sources. We then add / remove the classes and everything works as expected.

    So far it seems to work fine, if there's something I missed please let me know!

    document.addEventListener('ajax:success', function (event) {
        let detail = event.detail;
        let data = detail[0], status = detail[1], xhr = detail[2];
    
        // Check if our event is from an manual episode scrobble button
        let button = [...event.target.children].find(element => element.id === 'scrobble_btn_episode' + data.episode_id);
    
        // If find() is not successful it's undefined
        if (typeof button !== 'undefined') {
            // We set the classes on our button
            button.classList.add("listened");
            button.classList.remove("unlistened");
    
            // The button has a child, we also set the classes there for the check mark
            let id = button.getElementsByClassName('fa-check');
            id[0].classList.add("listened");
            id[0].classList.remove("unlistened");
        }
    });