Search code examples
jqueryruby-on-railserbruby-on-rails-5ujs

In first load of action, how do I pass instance variables to js.erb?


I have a ProfilesController that has the following:

if @profile.ratings
  @rating = @profile.ratings.find_by(user: current_user)
end

When the page is just loaded, before it even sends a request back to the controller, for it to respond_to, I would like to access that @rating instance variable in my app/assets/javascripts/profiles.js.erb file like so:

console.log('<%= @rating %>');

It just returns as nil now.

How do I get access to that @rating instance variable in that profile.js.erb on the first load?

Edit 1

For some clarity and more context, this is what is happening.

I have some JS powered UISliders that look like this:

enter image description here

The HTML that generates this is (along with more like it) - Profile#Show.html.erb:

        <td>
            <p>
              <button type="button" class="btn btn-danger m-r-sm slider-step-value" id="slider-step-value-speed-<%= @profile.slug %>">5</button>
              Speed
            </p>
            <div id="slider-speed" class="slider"></div>
        </td>
        <td>
            <p>
              <button type="button" class="btn btn-primary m-r-sm slider-step-value" id="slider-step-value-tackling-<%= @profile.slug %>">3</button>
              Tackling
            </p>
            <div id="slider-tackling" class="slider"></div>
        </td>

This is the JS - app/assets/javascripts/profiles.js.erb - that corresponds to that HTML:

  var sliders = $('.slider');
  var buttons = $('.slider-step-value');

  for ( var i = 0; i < sliders.length; i++ ) {
    var button = $(sliders[i]).prev('p').find('button')[0];
    var _slider_type = sliders[i].id.split('-')[1];
    console.log(_slider_type);
    console.log('<%= @rating %>');
    noUiSlider.create(sliders[i], {
      start: 5,
      step: 1,
      behaviour: 'tap',
      connect: [true, false],
      range: {
        'min':  1,
        'max':  10
      }
    });

    attachEvent(sliders[i], button);
  }

  function attachEvent(slider,button){
    slider.noUiSlider.on('update', function( values, handle ) {
      button.innerText = parseInt(values[handle]);
      var _profile_id = button.id.split('-').slice(4).join('-');
      var _rating_type = button.id.split('-')[3]
      var url = "/profiles/" + _profile_id + "/" + _rating_type + "_rating/?" + _rating_type + "=" + button.innerText

      $.ajax({
        type: "PATCH",
        url: url,
        success: function(){
          // console.log(_profile_id + "'s " + _rating_type + " was successfully updated.");
        },
        error: function(){
          // console.log(_profile_id + "'s " + _rating_type + " was NOT successfully updated.");
        }
      })
    });
  }

This is my ProfilesController#Show action:

class ProfilesController < ApplicationController
  before_action :set_profile, only: [:show, :edit, :update, :destroy, :invite_user, :speed_rating, :tackling_rating, :dribbling_rating, :passing_rating]
  authorize_resource except: [:dashboard, :speed_rating, :dribbling_rating, :tackling_rating, :passing_rating]
  skip_authorization_check only: [:dashboard, :speed_rating, :dribbling_rating, :tackling_rating, :passing_rating]

def show
    if @profile.ratings
      @rating = @profile.ratings.find_by(user: current_user)
    end
end
end

So the issue is that on the first load, what happens is the HTML loads the default values that are hardcoded in the HTML (I will eventually change this to the normal db-values, that's straightforward). Then after the JS is loaded, this noUiSlider script updates both the value of the button tag (aka the colorful box) and the position of the slider.

The way it does that is at this line:

noUiSlider.create(sliders[i], {
          start: 5,

So rather than just defaulting to 5, I want it to pull @rating from my Profiles#Show controller, so something like start: <%= @rating %>. That would automagically update both the Slider & button (aka colorful box) like I want.

The issue is on the first page load, @rating is nil/empty. So that's what I am struggling with.

Perhaps I need to send a get request, but I am unsure about how to approach this.

Edit 2

For the record, the AJAX call works and updates the DB like I expect. That happens when I move the slider, AFTER it has initially loaded though. So the issue is on the first page load only, I need the slider & button to be updated if there is a value in @rating.


Solution

  • I figured it out.

    Basically the best way for me to approach this was to add a data attribute to each of the button tags that pull from the db on initial load.

    So while my old button tag looked like this:

    <button type="button" class="btn btn-danger m-r-sm slider-step-value" id="slider-step-value-speed-<%= @profile.slug %>">5</button>
    

    My new button tag looks like this:

    <button type="button" class="btn btn-danger m-r-sm slider-step-value" id="slider-step-value-speed-<%= @profile.slug %>" data-speed-value="<%= speed_rating %>"><%= speed_rating %></button>
    

    Where speed_rating is a helper method defined in my profiles_helper.rb that looks like this:

      def speed_rating
        if @rating.speed
          @rating.speed
        else
          "5"
        end
      end
    

    Then, in my JS, all I did was select that data attribute for each button like this:

    var button = $(sliders[i]).prev('p').find('button')[0];
    var _slider_type = sliders[i].id.split('-')[1];
    var _original_value = $('button#' + button.id).data(_slider_type + "-value");
    

    The only new line was this:

    var _original_value = $('button#' + button.id).data(_slider_type + "-value");

    Then I just add that to my noUiSlider.create call, so now it looks like this:

    noUiSlider.create(sliders[i], {
      start: _original_value,
    

    As opposed to it originally looked like this:

    noUiSlider.create(sliders[i], {
       start: 5,
    

    And voila....works like magic!