Rails 3 UJS driver events - ruby-on-rails

According to Simone Carletti blog post, Rails 3 ajax helpers have changed a lot. We are supposed to write more javascript with rails 3 than we used to with rails 2.
I tried to figure out how to show up an ajax loading gif -while an ajax query is running- in the "rails 3 way". I came up with this kind of code, which uses javascript events sent by the Rails 3 UJS driver. This example uses prototype:
<div id="wait" style="display:none">
<img src="/images/ajax-loader.gif"> Please wait...
</div>
<div>
<%= link_to 'Get', 'finished', :id => "mylink", :remote => true %>
</div>
<%= javascript_tag do %>
Event.observe('mylink', 'ajax:before', function(event) {
$('wait').show();
});
Event.observe('mylink', 'ajax:complete', function(event) {
$('wait').hide();
});
<% end %>
This works well, but I wish it was possible to write these ajax events "triggers" with the help of the prototype and scriptaculous helpers, just like when we use link_to_function for example:
<%=
link_to_function("toggle visibility") do |page|
page.toggle "wait"
end
%>
Is there a way to do that, or are we supposed to write ajax events "triggers" in javascript directly, either prototype or jquery?
Best regards,
Philippe Lang

The idea of UJS is to move the javascript code out of the views into separate js files. The way you're doing it is defeating that goal. Instead, I believe you should have a js file with a "dom:loaded" or similar handler that sets up handlers for rails callbacks and other events.
Something like (using prototype):
(function () {
$(document).on('dom:loaded', function (event) {
$$('a[data-remote=true]').each(function (link) {
link.on('ajax:complete', function (request) {
// do something
});
});
});
}());
This way all javascripts are separated from the view, which is the idea of unobtrusive javascript.

After looking at rails source code, I came up with this solution:
def javascript_event_tag(name, event, &block)
content = "Event.observe('#{name}', '#{event}', function() {"
content = content + update_page(&block)
content = content + "});"
content_tag(:script, javascript_cdata_section(content))
end
This makes it easier to react to UJS events:
<div id="wait" style="display:none">
<img src="/images/ajax-loader.gif"> Please wait...
</div>
<%= link_to 'ajax call', 'code_on_controller', :id => "mylink", :remote => true %>
<%=
javascript_event_tag('mylink', 'ajax:before') do |page|
page.show 'wait'
end
%>
<%=
javascript_event_tag('mylink', 'ajax:complete') do |page|
page.hide 'wait'
end
%>
Instead of having to write raw prototype or jquery code, you can use rails helpers.

Related

Rails ajax forms + flash messages

Are there any good tutorials or gems that handle Rails Flash messages via Ajax?
In my .js.erb files, I've used something like the following:
<% if #event.errors.any? %>
<% #event.errors.keys.each do |key| %>
$( "#event_<%= j key.to_s %>" ).addClass("errorField");
<% end %>
$("#flashMSG").css("display", "block");
if(!$(".flashError").length){
$("<div class='flashError'>").html("Sorry, please fill in required fields.").appendTo(".noticeContainer");
}
<% else %>
$(document).bind("ajax:success", function(e, data, status, xhr) {
$("#flashMSG").slideUp(400).delay(400).slideDown();
if(!$(".flashSuccess").length){
$("<div class='flashSuccess'>").html("Event Updated.").appendTo(".noticeContainer");
}
});
<% end %>
Just wondering if there's a better way to do this or automate it similar to how Rails just handles it with a standard form submission.
How do you handle Rail's flash with Ajax requests?
and the gist:
https://gist.github.com/linjunpop/3410235

Accessing Rails variable in CoffeeScript

I want to use the current user's through my coffee script. But if I try to use current_user in my CoffeeScript, I get the undefined variable current_user in console of browser.
Is there any way to access it in coffee script? What I exactly wish to do is as follows:
current_user.updated_at < Date.now().getTime()
I need it to implement this in my Rails app.
For raw javascript + erb
<script type="text/javascript">
var my_foo = <%= some_ruby_expression %>
</script>
e.g.
<script type="text/javascript">
var user_name = "<%= current_user.name %>";
</script>
except var keyword, this is a valid coffeescript code, I believe.
Btw, is it really what you are asking for? - I don't know -))
UPDATE
current_user is a ruby/active_record object. You might assign it to a javascript variable, but can't use as you did in ruby.
But below snippet might give you some idea. I've created a rails project, and scaffolded a page model.
<p id="notice"><%= notice %></p>
<p>
<strong>Title:</strong>
<%= #page.title %>
</p>
<p>
<strong>Email:</strong>
<%= #page.email %>
</p>
<p>
<strong>Comments:</strong>
<%= #page.comments %>
</p>
<script type="text/javascript">
var mypage = '<%= raw #page.to_json %>'; // attention to single quotes
console.log(mypage);
</script>
<%= link_to 'Edit', edit_page_path(#page) %> |
<%= link_to 'Back', pages_path %>
Then you can parse and use it as an ordinary javascript object. Below picture show, how I did it.
UPDATE #2
do it in coffeescript file.
window.onload = ->
myvar = '<%= raw #page.to_json %>'
myvarAsObj = JSON.parse(myvar)
do_something_with myvarAsObj
return
Coffee script is turned into javascript and runs on the browser. What you want to do is render current_user.updated_at into a node in HTML, then use coffee script to extract that value.
Javascript files are (more or less) static files which will be executed by the browser. I don't know if you ever deployed an rails app in production. You'll run the command bin/rake assets:precompile which will generate one (or more) massive javascript file(s). So how would you find out the current user at this time? there's no HTTP request at all, so how could there be a user?
So to do this, you could either write the current user to a HTML node and then read it again with javascript (coffeescript) (which in my opinion is the best way) or you could print a script tag into your view containing what you want from your user.

Render template having Jquery datatables in .js.erb file

I am currently working on a project where I am implementing several reports. The report filters are remotely submitted to my action and the return results are displayed in Datatable with searching,sorting and pagination.
I have a drg.js.erb file which is having code like this :
var html = "<%= escape_javascript(render(partial: 'drg_datatable',formats: [:html],locals: {result: #result})) %>";
$("#datatable-result").append(html);
The partial _drg_datatable.html.erb is having datatable implemented like this. Below is my _drg_datatable.html.erb file :
<% if result %>
<table id="results" class="table table-striped table-bordered display">
<% case params[:view] %>
<% when "ahfs" %>
<%= datatable_ahfs_result(result) %>
<% when "drg_code" %>
<%= datatable_drg_result(result) %>
<% when "inpharmics_id" %>
<%= datatable_inpharmics_id_result(result) %>
<% when "provider" %>
<%= datatable_provider_result(result) %>
<% else %>
<% end %>
</table>
<% end %>
The problem I am facing is that when I render the partial _drg_datatable.html.erb using .js.erb file it creates the table but escapes the javascript to add sorting,pagination and other cool features we get in Jquery Datatables. Can someone point me how should I go about doing this ? I have tried to render the partial is .js.erb without writing escape_javascript but then the partial does not get rendered at all.
You must explicitly call the datatable js function in your drg.js.erb in order to "datatablize" your table. Ex:
var html = "<%= escape_javascript(render(partial: 'drg_datatable',formats: [:html],locals: {result: #result})) %>";
$("#datatable-result").append(html);
$('#results').dataTable();
I suppose you have something like:
$(document).ready(function(){
$('a selector of yours').dataTable();
});
somewhere in your application's javascripts. This runs once, after document load and applies to elements existent to your dom. Since now you are adding a new table you have to "re assign" the datatable behavior...
#grotori: Your solution gave me a hint of fixing it. I renamed my datatable id with a name which was not used in the application anywhere. I removed the initial implementation of the datatable in the partial and modified the code to render the partial first and than apply datatable to it. Here is what I did :
var html = "<%= escape_javascript(render(partial: 'drg_datatable',formats: [:html],locals: {result: #result})) %>";
$("#datatable-result").html(html);
jQuery(function() {
$("#drg-results").dataTable({
"sDom": "<'row-fluid'<'span4'l><'span7 pull-right'f>r>t<'row-fluid'<'span4'i><'span7 pull-right'p>>",
"sPaginationType": "bootstrap",
"sScrollX": "100%",
"bDestroy": true,
"bProcessing": true,
"bScrollCollapse": true
});
});
Hope this helps other trying to achieve the same thing.

Ajax call in rails 3.2.3 with will_paginate gem

Im trying to implement an Ajax call with the will_paginate gem, I found this guide http://ramblinglabs.com/blog/2011/11/rails-3-1-will_paginate-and-ajax which seemed like a simple solution, though it includes coffeescript which i am not familiar with, so if anyone has a different solution then please advise..
My code is as follows
My View
<div class="container">
<div class="row">
<div id="userRecipes">
<%= render partial: 'userrecipes' %>
</div>
</div><!--/row-->
My partial (userrecipes)
<% #recipes.each do |r| %>
<div class="span3">
<div class="thumbnail">
<%= image_tag r.avatar.url(:myrecipes) %>
</div>
<h4><%= link_to r.dish_name, r %></h4>
<hr>
<p><%= truncate r.description, :length => 90 %></p>
<p><%= link_to "Edit Recipe", edit_recipe_path(r.id) %></p>
<p><%= link_to "Delete Recipe", recipe_path(r.id), :confirm => "Are you sure?", :method => :delete %></p>
<p><%= link_to "Add to favorites", {:controller => 'favourites', :action => 'create', :recipe_id => r.id}, {:method => :post } %></p>
</div><!--/span3-->
<% end %>
<%= will_paginate #recipes %>
updated userrecipes.js.erb file
$('#userRecipes').html('<%= escape_javascript(render partial: 'userrecipes') %>');
$.setAjaxPagination();
Coffeescript
$ ->
$.setAjaxPagination = ->
$('.pagination a').click (event) ->
event.preventDefault()
loading = $ '<div id="loading" style="display: none;"><span><img src="/assets/loading.gif" alt="cargando..."/></span></div>'
$('.other_images').prepend loading
loading.fadeIn()
$.ajax type: 'GET', url: $(#).attr('href'), dataType: 'script', success: (-> loading.fadeOut -> loading.remove())
false
$.setAjaxPagination()
When i click on the next anchor tag to show the next set of results the page stays as it is and no new content appears
When using the console to see if there are any errors i can see any, the output is
GET http://localhost:3000/my_recipes?page=2&_=1355055997639
Am i missing something here? or is there an issue with my userrecipes.js.erb file because in other Ajax examples i have seen thy are using escape_javascript when rendering the partial?
Edit
Whilst inspecting the response in the console it is also showing that the new recipes to be loaded are being loaded but nothing is happening in the view
Any pointers appreciated
Thanks
Why not try a simpler approach, create a new helper (ex. app/helpers/will_paginate_helper.rb) with the following content:
module WillPaginateHelper
class WillPaginateJSLinkRenderer < WillPaginate::ActionView::LinkRenderer
def prepare(collection, options, template)
options[:params] ||= {}
options[:params]['_'] = nil
super(collection, options, template)
end
protected
def link(text, target, attributes = {})
if target.is_a? Fixnum
attributes[:rel] = rel_value(target)
target = url(target)
end
#template.link_to(target, attributes.merge(remote: true)) do
text.to_s.html_safe
end
end
end
def js_will_paginate(collection, options = {})
will_paginate(collection, options.merge(:renderer => WillPaginateHelper::WillPaginateJSLinkRenderer))
end
end
Then in your view use this tag for ajax pagination:
<%= js_will_paginate #recipes %>
Remember that the pagination links will include existing params of the url, you can exclude these as shown below. This is standard will paginate functionality:
<%= js_will_paginate #recipes, :params => { :my_excluded_param => nil } %>
Hope that solves your problem.
Explanation:
Will_paginate allows you to create your own custom renderer. The WillPaginateJSLinkRenderer is such a custom renderer and this class could be defined anywhere, it doesn't have to be defined inside the helper module. The custom renderer extends the standard renderer (LinkRenderer) and redefines only two methods.
The prepare method is overriden to explicitly remove the cache buster parameter since will_paginate creates the page urls with all parameters that were present when rendering the page and we do not want to reuse the cache buster parameter.
The link method is a copy paste from the original LinkRenderer source code but creates a link with remote: true to make it a JS resquest.
Finally the js_will_paginate method is a standard view helper method to call the normal will_paginate view helper method but adds our custom renderer to the options so that it will be used instead of the normal renderer.
Just in case someone is looking for Rails 4 solution. I liked the answer from Pierre Pretorius, but it failed for me on 4.1.1 with "method undefined" for link_to_function on the line:
#template.link_to_function(text.to_s.html_safe, ajax_call, attributes)
I just replaced the line to:
#template.link_to(text.to_s.html_safe, '#', attributes.merge(:onclick => "#{ajax_call} event.preventDefault();"))
I hope it may help someone.
You are not escaping javascripts in your js.erb, it should be the problem.
$('#userRecipes').html('<%= escape_javascript(render partial: 'userrecipes') %>');
$.setAjaxPagination();
The ajax_will_paginate is a superb method, but unfortunately it was messing up the UI of the paginate. I had employed another javascript method for more flexibility. And I call this method on pageRender.
function checkForAjaxPaginate(controller,make_ajax){
var href = "";
$(".pagination").find("ul").each(function(){
$(this).find("li").each(function(){
$(this).find("a").each(function(){
href = $(this).attr("href");
if(href.indexOf(controller+".js") != -1){
if(make_ajax){
$(this).attr("data-remote","true");
}
else{
$(this).attr("href",href.replace(".js",""));
}
}
});
});
});
}
For more flexibility, you just pass the action name in the "controller" variable and pass true to make_ajax if you want to convert it to ajax. All I am doing is to check if the href link is ".js", and if make_ajax is true, then adding an attr "data-remote=true", which in our ROR app makes it ajax. if make_ajax is not true then am just removing the ".js" so the code does not mess.
I hope this helps
Pierre Pretorius' answer is great, but I had to do a little more to make it work for me that wasn't already clear. Maybe this will be helpful to other beginners.
In addition to making that helper and using the js_will_paginate tag I did this:
After moving the content I was trying to paginate into a partial (_recipes.html.erb in the case above), I also created a show.js.erb file (in this same view alongside the partial) with text similar to this:
$('#recipes').html('<%= escape_javascript(render partial: 'recipes') %>');
Then I had to move the js_will_paginate tag to the partial as well at the bottom (so it would be updated as I paginated through or clicked next/previous). Then I had to wrap the render partial helper in the div id you're targeting in the js file, so in this example:
<div id="recipes">
<%= render partial: "recipes" %>
</div>
This may have been very clear to others, but I wasn't sure which steps from the OP had to remain and which were unnecessary. These steps worked for me. You don't need to add respond_to js in the controller as the helper takes care of that or have a coffeescript file.

How can I change my regular filter form using a submit, to use AJAX?

I have a table of venues. The venues index page by default lists all the venue records and lets you filter them by area and or type using a form on the same page with a submit button and a page reload.
All the venues found are then displayed and the form resets itself.
How can I go about having the list of venues update without a page reload just by clicking on the differant filter options? So theres no submit button and the form keeps its altered state.
The venues controller
def index
if
#venues = Venue.with_type(params[:venuetypes]).with_area(params[:areas]).order("average_rating DESC").all
else
#venues = Venue.all
end
#venues = #venues.paginate :per_page => 15, :page => params[:page]
end
The venues index.html.erb This is already using jQuery-UI to get better looking checkboxes
<div class="filter_options_container">
<%= form_tag '', :method => :get, :id => 'filter_form' do %>
<fieldset class="filter_form_fieldset venuetypes">
<% Venuetype.all.each do |v| %>
<p class="venuetype_check"><%= check_box_tag 'venuetypes[]', v.id, false, :id => "venuetype-#{v.id}" %>
<label for="venuetype-<%= v.id %>"><%= v.name %></label></p>
<% end %>
</fieldset>
<fieldset class="filter_form_fieldset areas">
<% Area.all.each do |a| %>
<p class="area_check"><%= check_box_tag 'areas[]', a.id, false, :id => "area-#{a.id}" %>
<label for="area-<%= a.id %>"><p1><%= a.name %></p1></label></p>
<% end %>
</fieldset>
<div class="filter_form_button">
<p2><input type="submit" value="Filter"/></p2>
</div>
<% end %>
</div>
<div class="venue_partials_container">
<%= render :partial => 'venue', :collection => #venues %>
<div class="clearall"></div>
<div class="paginate_container">
<div class="paginate">
<%= will_paginate #venues %>
</div>
</div>
</div>
Thanks very much for any help its much appreciated!
You probably want to use jquery to do that.
The simplest will be to use jquery to automatically send the form when a filter is clicked, there is still a page reload but the visitor do not have to click on the submit button.
The code will be something like :
$(".checkbox-which-send-form-when-clicked").change(function(e){
$(this).parents("form:first").submit();
});
Please read documentation on jquery website
Another solution which will not make the page reloaded each time a checkbox is clicked is to use Ajax request to send the form and get the result. A good tutorial could be found here
So I don't know Ruby at all, but I know the architeccture you need to implement using jQuery. First you need to setup an endpoint that will return JSON to the client. When a user creates, modifies or just plain submits the filter form you will post or get the JSON endpoint. Its your option, you can retrieve it as each field is changed by the user or just when the submit button is clicked, it is entirely up to you how interactive you want to make it.
You would use the jQuery $.ajax functions to retrieve the JSON. I reccomend either the $.post or $.get to retrieve the updated data.jQuery AJAX Shorthand functions
You can also setup a click event handler for your form submit button. It might look like this, it is important to use the e.preventDefault because this keeps the entire page from posting back.
$("form :submit").click(function(e){
e.preventDefault();
$.getJSON([your AJAX url], [data from filter], function(result){
//update table using templates
});
});
In the $getJSON callback I reccomend using the jQuery templates or some other templating merge functionality like Resig's micro templates.
I wrote a whole series of blog posts on doing exactly this sort of thing in ASP.NET. It should translate pretty well for you to Ruby, just replace the ASP.NET stuff with your Ruby server-side and you should be on your way.
My Thin ASP.NET Series

Resources