1
votes

I have my js.erb file called remotely from my Rails 5.0.4 app.

<% if @admin_invitation.present? && @admin_invitation.valid? %>
<%my_view = j (render partial: "shared/invitation_card",:user => @admin_invitation.invited_id, :email => @admin_invitation.invited_email, :type => "admin" ) %>
$(".invites.admins .row").prepend("<%=my_view %>");
<% end %>

App.refresh( "<%= j(render 'shared/alerts') %>" );

It works as expected when i hardcode the values in the partial, but when i feed them in as locals from this js.erb file, they dont come through (nil). Worth noting that I render the partial from a a non-remote haml view and it works just fine.

Here are the ways of rendering i have tried (I know some are outdated, but just for a sanity check)

render partial: "shared/invitation_card", :locals=> { :email => "[email protected]", :type => "admin" }
render partial: "shared/invitation_card", object: "[email protected]", as: "email"
render partial: "shared/invitation_card", email: "[email protected]", type:"admin"
render partial: "shared/invitation_card", locals: { email: "[email protected]", type: "admin" }

Anyone else have issues with rendering haml partials from a js.erb in rails 5?

EDIT:

Here is the controller method calling the js.erb above.

def create
    if params[:invited_status] == "temporary_invitation_user"
      result = CreateGroupInvitation.call(invitation_model: GroupFollowInvitation, group_id: params[:group_id], inviter: current_user, invited_email: params[:invited_email], notification: "created_group_follow_email_invitation")
    else
      result = CreateGroupInvitation.call(invitation_model: GroupFollowInvitation, group_id: params[:group_id], inviter: current_user, invited_id: params[:invited_id] , notification: "created_group_follow_invitation")
    end

    if result.success?
      @invitation = result.invitation
      @invited = params[:invited_id] == "null" ? params[:invited_email] : User.find(params[:invited_id])
      flash.now[:success] = "Your follow invitation was sent"
    else
      if result.error_message.present?
        flash.now[:info] = result.error_message
      else
        flash.now[:error] = "An error happend"
      end
    end

    respond_to do |format|
      format.js { render layout: false }
    end
  end
2

2 Answers

1
votes

You did not specify how you are calling js.erb, but the best approach I suggest is the following.

STEP 1 - BUILDING THE VIEW

Either start by including a link, form or button with the remote: true option in your view

for example

<%= form_with(model: @invitation) do |f| %>
...
<% end %>

STEP 2 - UNDERSTANDING THE ROUTING

The resources routes in route.rb for :invitations generates the following routes:

new_invitation GET  /invitations/new(.:format) invitations#new                           
               POST /invitations(.:format)     invitations#create                                      

and your form in your view invitations/new.html.erb on submit will perform a POST request to the url /invitations.

EXPLANATION OF AJAX

In webdevelopment you can do 2 types of requests between Server and Client, Synchronous or asynchronous requests. In a Synchronous request, the client/browser will perform an HTTP request to the backend server, which will answer with a response. The browser will stop and start loading until he does receive the full response.

In a asynchronous request, the request and response cycle will not include the browser stopping to load.

Your javascript file is just one file included in the client/browser asset pipeline, which are all the js, css, images and fonts that are downloaded by the browser when the user go to visit your page at http://yourwebsite.xyz and performs a get request to that url. Your server performs a response that include all those information.

So that js file can not find your variable which is from your server application or from a query in your database. The two things are separated, one is your server and the other is the client.

If you want to have that variable and add it to the client/browser dynamically and asynchronously, then you need to use AJAX.

A get request needs to be performed from your client to your backend server, the response will include the @invitation variable in the json format

STEP 3 - BUILDING YOUR CONTROLLER

The Rails router receives a POST request at /invitations and redirects the request to the controller invitations action create. The parameters are passed through the ActionController::Parameters object params.

The Server running Ruby on Rails responds to the request with 3 different formats: HTML to render the view JS to execute script code on the front end. The JS file invitations/create.js.erb was already downloaded by the client through the asset pipeline, this is just a command to execute it. JSON to send the @invitation object in json format, it will be available at /invitations.json

# app/controllers/invitations_controller.rb
# ......
def create
  @invitation = Invitation.new(params[:invitation])

  respond_to do |format|
    if @invitation.save
      format.html { redirect_to @invitation, notice: 'Invitation was successfully created.' }
      format.js
      format.json { render json: @invitation, status: :created, location: @invitation }
    else
      format.html { render action: "new" }
      format.json { render json: @invitation.errors, status: :unprocessable_entity }
    end
  end
end

STEP 4 - APPENDING RESULT ASYNCHRONOUSLY TO THE PAGE

In invitations/create.js.erb you can include your js code and you can use the instance variable from the invitations#create action

$("<%= escape_javascript(render @invitation) %>").appendTo("#invitations");

more info about working with AJAX and Rails

1
votes

I found the answer. It is simple actually

render partial 'view' locals: {k1: v1, k2:v2} does not give you a local hash. It simply defines the local variables k1 and k2 for use in your partial. You do not need to extract them from 'locals' hash.

render 'view' locals: {k1: v1, k2:v2} does give you a local hash with k1, v2 inside. They are not defined until you extract them. This inconsistency threw me off. my code was prepared for them to be inside the locals hash.