marcamillion
marcamillion

Reputation: 33775

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.

Upvotes: 0

Views: 563

Answers (3)

marcamillion
marcamillion

Reputation: 33775

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!

Upvotes: 1

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230461

If you need to access that variable from profiles.js.erb, put its calculation into profiles action of the corresponding controller. Currently, it's calculated in another action and, naturally, not available in profiles.

Upvotes: 0

Devin Howard
Devin Howard

Reputation: 705

Your question doesn't make a lot of sense to me, because you've said you're trying to render @rating, but then you wrote code that renders '<%= @profile.ratings %>'. Also, the if statement you shared suggests that sometimes that field is nil. I'll see what I can do but I think you need to post more information.

I'm imagining you have a page like this:

<%# profile.html.erb %>
<div id="profiles">
  ...
  <button data-remote="true" >Load ratings</button>
</div>

and then when you click this button, it sends a request to your controller with :js format, which loads your profile.js.erb.

There must be a controller action displaying any text. If you're looking at an HTML page from rails, there is some controller action somewhere. You need to add the @rating instance variable to that controller action, and render it in the corresponding view.

Could you post:

  1. What route (url) you're accessing
  2. What controller that route is rendered by
  3. What view (if any) that route is rendered by

Upvotes: 0

Related Questions