Rails 4 - event to run code after a partial is rendered - javascript

I have a js.erb file that is called with ajax through a controller action. The js.erb file renders a partial. Inside the partial is a data attribute that is generated based from the model. I need a way to run code only after the partial is rendered because the code is using information in the data attribute.
items_controller.rb
def remove_tag
#item = Item.find_by_id(params[:id])
//code to remove tag from item
#item.save
respond_to do |format|
format.js
#item.reload
end
end
remove_tag.js.erb
$("#taglist").html('<%= escape_javascript(render partial: "items/tag_list", locals: {item: #item}) %>');
//the code below needs to be run after the above partial is rendered
$('#add-tag').rules("add", {
tagUniqueness: $('#taglist').data('tag-list').split(', ')
});
_tag_list.html.erb
<div id="taglist" data-tag-list="<%= item.all_tags_list %>">
//more html
</div>
The way it is now, the .rules method is using html data that was there before the partial updates it.

This type of functionality would lend itself to callbacks in javascript/jquery...
A callback function is executed after the current effect is 100% finished.
We used callbacks for executing functions after ajax loads (which had to remain asynchronous). They are highly effective if you can get them to work.
There's a great reference here:
$("#taglist").html('<%= escape_javascript(render partial: "items/tag_list", locals: {item: #item}) %>').promise().done(function(){
$('#add-tag').rules("add", {
tagUniqueness: <%= js #badge.all_tags_list.to_json %>
});
});

To convert hashes or arrays from Ruby to javascript you can use .to_json as JSON basically is a subset of javascript.
$('#add-tag').rules("add", {
tagUniqueness: <%= js #badge.all_tags_list.to_json %>
});

Related

The right way to do remote (ajax) calls in Ruby on Rails

I'm making a Single Page Application with Ruby on Rails (it's my first ruby project ever so I'm definitely missing a lot of stuff yet).
So I have a side menu with some links and the right part of the page is supposed to hold a container which is meant to be filled with some content of partial pages.
The typical menu link I have now looks this way:
<%= link_to t('my-groups'), :controller => 'dashboard', :action => 'mygroups', :remote => true %>
I have a dashboard controller, here's the simplified version of it
class DashboardController < ApplicationController
def mygroups
respond_to do |format|
format.js
end
end
I have a dashboard template with the container div in it
<div class="right_col" role="main">
<h2>This is the default content of center page</h2>
</div>
And here's the routes.rb path for it:
get 'dashboard/mygroups' => 'dashboard#mygroups'
I also have one partial page alogside with my dashboard template and it's called _mygroups.html.erb and a javascript file mygroups.js.erb which is called as my controller action
look at the screenshot of the structure
The contents of this js.erb file are:
$('.right_col').html("<%= escape_javascript(render(:partial => 'mygroups')) %>");
It all works and the partial contents appear inside the container on link click just fine.
But there are still 2 problems I couldn't google the answer for
The questions part:
1) It works with Ajax call but if I simply put this http://localhost:3000/dashboard/mygroups to my browser's navigation line and hit enter, it will give me this error
ActionController::UnknownFormat in DashboardController#mygroups
ActionController::UnknownFormat
Extracted source (around line #70):
def mygroups
respond_to do |format|
format.js end end
How can I avoid this and just redirect to index in this case?
I understand that ajax uses POST, but I tried to use post instead of get in routes.rb for this action, and it didn't work at all
2) What if I have a lot of actions for different partial pages, do I have to create a new js.erb file for each action? Can't it be done in some simplier way with just one file?
3) Is it possible to not specify controller and action on this link explicitly?
<%= link_to t('my-groups'), :controller => 'dashboard', :action => 'mygroups', :remote => true %>
I mean since it's supposed to be a POST ajax request, how come I need to display the url like this http://localhost:3000/dashboard/mygroups to a user?
Add format.html in controller like:
class DashboardController < ApplicationController
def mygroups
if request.xhr?
respond_to do |format|
format.js
end
else
redirect_to root_url
end
end
you can add url in link_to tag like:
<%= link_to t('my-groups'), '/dashboard/mygroups', :remote => true %>
Answers to you questions
When you hit the URL in browser, it sends vanilla HTTP get request(non-ajax) which your controller action is not configured to handle it. You need to add format.html and template named groups.html.erb where generally you will list all the groups, I guess.
respond_to do |format|
format.html
format.js
end
Ideally you have to create separate file for each action but if you can take something common out of different action then you can move common template code to a partial and render it either in a separate template having something special or from the controller action directly.
Yes. The rails way is to use routes helper. Run rake routes to list all available routes in your app and find relevant helpers.
I would strongly suggest to read the rails guide to understand how everything works.

Locals issue in Rails application

I am getting errors while doing an AJAX call with remote: true with locals. Here's the error:
ActionView::Template::Error (undefined local variable or method `f' for #<#<Class:0x94db890>:0x9ab41d0>):
Here is my code below:
new.html.erb
<%= form_for(#author) do |f| %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :age %><br>
<%= f.number_field :age %>
</div>
<%= link_to "Add Books" , remote: true %>
<div id = "add_book"></div>
<%= f.submit %>
Controller
def new
#author = Author.new
#books = #author.books.build
respond_to do |format|
format.html
format.js
end
end
new.js.erb
$("#add_book").append(" <%= escape_javascript(render partial: 'addbook', :locals => { :f => f }) %> ");
You are calling the new action of your controller using remote: true (an AJAX call). This will result in new.js.erb being rendered to be sent back as the response - the new.html.erb will not be processed.
Within new.js.erb you have:
$("#add_book").append(" <%= escape_javascript(render partial: 'addbook', :locals => { :f => f }) %> ");
Meaning it is attempting to render the addbook partial and set up a local variable for it, to be referenced in the partial as f, and assigning it the value (local to new.js.erb) f.
new.js.erb has not declared the local var f and thus your error.
To assist more we would need to know what the intention of this variable is - in what way does _addbook.html.erb require it?
So... the way that an erb template works is this:
Rails generates a page, in your example, this would be new by going through the new action in the controller and gathering up all the variables (eg author and books).
Then it gets the right template for the action. In the case when you first open up the new page you get the form... right? That comes from the new.html.erb template.
So rails reads through the template and runs the ruby code... replacing it with appropriate html. Then it sends the html to the browser.
By the time it's sent it to the browser, there is no longer any ruby code... just html, because the browser doesn't understand ruby, just html - so that's all that gets sent.
Now... the browser has an html form, that contains some js - this would be the add books link - it's html, that calls some js that sends a request to your rails app.
The rails app then goes through the new action, gathering the variables again, but this time, it doesn't render the html template... because it got called by js. So it grabs the js template instead.
At this point rails knows nothing about the html template. Anything that was in the html template is long gone - sent away to the browser. All it knows about is what is in the js template. It tries to run the ruby code in the template... and it's asking for f... and f doesn't exist in your js template... so it gets confused and tells you it doesn't know about this f thing.
It doesn't matter that at some point in the past it rendered a form called f, because that belonged to a way distant past request that no longer exists. The browser doesn't tell rails about f because it never knew about it (that was part of the ruby, not the html that is all that the browser ever saw). So... there is no f. ;)
In this case - I am not sure you need to pass f in as a local variable at all. If you are rendering the template that contains the form... then you don't need to pass in a form because it will already be rendering the form. Try just rendering the partial without passing in locals, and see what happens... if you get a bug, we can then work on that.

Combine All Ajax Messages thru one Action

I have a website that has a lot of AJAX forms and as it is now I have to make a new js.erb view for each one of them to just post a message that pretty much says completed but is unique to each action.
Is there a way that I can combine or forward one action to a message action in the controller so I would only need one view to handle all the JavaScript messages
Here is what I have:
Controller:
def some_action
{ do some things here }
respond_to do |format|
format.js
end
end
View:
some_action.js.erb
$('#messages').append("<%= escape_javascript(render :partial => 'flash_message') %>");
<% if #error == "True" %>
$('#flash').removeClass().addClass( "error" ).html('<%= escape_javascript(#message) %>');
<% else %>
$('#flash').removeClass().addClass( "success" ).html('<%= escape_javascript(#message) %>');
<% end %>
Would rather have one action in the controller to control all messages when no other changes are necessary.
You can abstract that functionality into instance methods, something like this
def ajax_success(message)
#message = message
render 'ajax/success'
end
def ajax_failure(message)
#message = message
render 'ajax/failure'
end
Then in your controller
def update
if (successful)
ajax_success
else
ajax_failure
end
end
You can either define these in your ApplicationController, or in a module that is included in any controllers where you wish to use them.
There are some issues here when you wish to have actions that respond to multiple content types, but for actions that should only respond to .js this should be fine.
Also, I think the standard practice for AJAX responses like this would be to return a JSON message, and then have some javascript function that could decode it and transform the page appropriately.

Iterate through array passed from controller

I'm using Ruby on Rails.
Here is my code for the view, where I make a post to the controller, passing parameter "tplangroup.id":
<div id="collapse">
<%= form_tag(tplans_collapse_tplans_path, :method => 'post', :remote => true ) do %>
<%= hidden_field_tag(:tplangroup_id, tplangroup.id) %>
<% end %>
</div>
Here is my code on the controller end, where it parses the necessary data and shoots back array "#ordered_tplans"
def collapse_tplans
#collapsed_tplangroup = Tplangroup.find(params[:tplangroup_id])
tplans_from_tplangroup = #collapsed_tplangroup.tplans
#ordered_tplans = tplans_from_tplangroup.order("favrank DESC")
return #ordered_tplans
end
Since I called :remote => true in the original form located in the view, it passes this array to a file called "collapse_tplans.js"
My question is: what is the best way/practice to parse through this array now passed to the js file, and display its contents in the view? Do I use rails code in the js file to manipulate the object? Or do I do it all in javascript/jquery? What is the best practice, and could you provide any example?
Thanks!
Really kind of depends on how you want to go about it, as with all code, there are many ways to skin a cat. I find the easiest way is to use the return ujs as an erb file (collapse_tplans.js.erb) and from there, choose the element on the page you want to attach the retuned object to, and call a standard erb or haml partial where your iterations can be done clearly.
e.g.
In collapse_tplans.js.erb
$('#my_wacky_element').append("<%= j render(:partial => 'collapse_tplans', :locals => { :ordered_tplans => #ordered_tplans }) %>");
Then in
_collapse_tplans.html.erb
<ul>
<%= ordered_tplans.each do |tplan| %>
<li><%= tplan.attribute %></li>
Here is a RailsCast on how to pass data to js from rails:
http://railscasts.com/episodes/324-passing-data-to-javascript?view=asciicast

Rails Javascript parsing using array from Controller

I am doing Ajax Call from View, then controller is fetching data from DB into an Array, now this array value I want to pass to JavaScript, so that I can update data in a table with different id's
Controller code:
def AjaxView
#var1 = Var.find(:all,:conditions => { :varname=> "one" },:select=> (params[:col]))
respond_to do |format|
format.js
end
end
AjaxView.js.erb code:
$(document).ready(function() {
$("#test").text("valuetoupdate");
});
Now when I run this code it successfully update "valuetoupdate" at id =test in view page.
Instead of this I want to update all values one by one from array #var1.
I searched more and realized that array #var1 generated in Controller will automatically get transferred to AjaxView.js.erb file. Now I have to iterate over all values, but this code doesn't work in JavaScript file:
<% for var in #var1 %>
$("#3").text(var);
<% end %>
it gives blank response
Thanks Guys,
I am able to resolve my issue, in following manner,
I moved away from for loop implementation, instead start using if else condition,
<% if #var.to_s == 'valuetoupdate' %>
{ }
<% elsif #var.to_s == 'valuetoupdate1' %>
{}
And it worked fine for me.

Categories

Resources