How to call a javascript post render in Rails?
This is the facebox popup content
<div class="form_container">
<% form_remote_tag :url => { :action => 'custom', :d=>params[:day], :h=>params[:hour] },
:class => 'general-form',
:update => 'grid['+params[:day]+']['+params[:hour]+']',
:success => 'handle_success('+params[:day]+','+params[:hour]+')' do -%>
<table width="300px" style="border:none">
<% @availability_hash.each_key do |availability_id| %>
#some view related stuff
<% end %>
</table>
<input type="submit" class="btn" value="Re-assign Coaches" />
<% en开发者_运维技巧d %>
</div>
This is the controller method
def custom
master_scheduler
h = params[:h].to_i
d = params[:d].to_i
render(:partial => "grid_item" , :locals => {:day=>d, :hour=>h})
end
This is the partial, (_grid_item.html.erb)
<div class ="left_inner_element">
<div class="l_upper_element">
<div class="coaches_committed"><%= data[:committed_coaches] %></div>
<div class="coaches_available"><%= data[:available_coaches] %></div>
</div>
<div class="l_lower_element"><%= data[:classes] %> : <%= data[:avg_attendance] %>
</div>
</div>
<a id="link[<%= day %>][<%= hour %>]" rel="facebox" href="coach_selector_popup?(bla bla)" >
<div class ="right_inner_element right_inner_color_<%= data[:color_code] %>">
#some ui stuff
</div>
</a>
This is the script used in the main page
<script type="text/javascript">
jQuery(document).ready(function($) {
jQuery('a[rel*=facebox]').facebox()
$.facebox.settings.opacity = 0.5
})
function handle_success(d,h){
jQuery(document).trigger('close.facebox');
var link_div = document.getElementById('link['+d+']['+h+']');
jQuery(link_div).facebox();
}
</script>
<div class="master_view">
<table class="master_scheduler_table" id="master_scheduler_table" >
<% HOURS_IN_A_DAY.each do |hour| %>
<tr>
<td><%= time(hour*2) %></td>
<% DAYS_IN_A_WEEK.each do |day| %>
<td id="grid[<%= day %>][<%= hour %>]" >
<%= render(:partial => "grid_item" , :locals => {:day=>day, :hour=>hour, :data=>@data[day][hour]}) %>
</td>
<% end %>
</tr>
<% end %>
</table>
</div>
After this rendering is done, I would like to invoke a javscript. How to do that? I am using a form_remote_tag from the view.
The render partial is done in Ajax context, no page reloads will happen. The form_remote_tag has a :success method where I can call a javascript. Unfortunately, the javascript that I put in the success method is getting called 'before' the rendering is done. But, I need to invoke a javascript 'after' the rendering is complete.
I have simply put an alert message for clarity in Question. Actually I am calling a facebox() method on the DIV element that I am rendering. The facebox() (jQuery's) method should be applied to the DIV element after it is rendered only then it will be effective.
Edit: I have added the complete code. If you can see, I am calling the javascript method handle_success once the Ajax returns. (I apologize for putting the :success method inside the partial). The handle_success is called before the actual rendering completes. That should be 'after' it completes
Here you go:
<!-- IN HTML.ERB FILE -->
<div class="form_container">
<% form_tag({:action => :custom, :d => params[:day], :h => params[:hour]}, {:class => "general-form"}) do %>
<table width="300px" style="border:none">
<% @availability_hash.each_key do |availability_id| %>
<tr>
<td width="50%"><b><%= CoachAvailability.find(availability_id).coach_availability_template.coach.display_name %></b> (X/8)</td>
<td><%= select_tag('template['+availability_id.to_s+']', options_for_select([["Available", 0],["Scheduled", 1]], :selected => CoachAvailability.find(availability_id).status )) %></td>
</tr>
<% end %>
</table>
<%= submit_tag "Re-assign Coaches", :class => "btn" %>
<% end -%>
//In your JS file
$(".general-form").live('submit', function() {
$.post(this.action, $(this).serialize(), "_method=post");
return false;
})
#In your controller
def custom
master_scheduler
h = params[:h].to_i
d = params[:d].to_i
@day = d
@hour = h
respond_to do |format|
format.xml
end
end
<!-- IN custom.xml.erb -->
<taconite>
<append select="#div_changed_content">
What ever changes you want to make. Your code is bit confusing. So i won't go deep into what should come here. You know what changes you want to make. :)
Maybe you wanted to add this? How did you get data[:commited_coaches] and data[:available_coaches] without passing it through the partial??
Anyways if you want to pass it now you can do so from the controller itself. By creating a @data_commited_coaches and @data_available_coaches and storing the above in that.
<div class ="left_inner_element">
<div class="l_upper_element">
<div class="coaches_committed"><%= data[:committed_coaches] %></div>
<div class="coaches_available"><%= data[:available_coaches] %></div>
</div>
<div class="l_lower_element"><%= data[:classes] %> : <%= data[:avg_attendance] %>
</div>
</div>
<a id="link[<%= day %>][<%= hour %>]" rel="facebox" href="coach_selector_popup?(bla bla)" >
<div class ="right_inner_element right_inner_color_<%= data[:color_code] %>">
#some ui stuff
</div>
</a>
</append>
<eval>
// You can call the handle_success here: If i guessed right, this is what you want to do.
handle_success(<%= @day %>,<%= @hour %>);
</eval>
</taconite>
I am skeptical about you having added the &hour
and &date
. I would suggest you remove it if you detect firebug throwing an exception as it might not be valid xml. If you do encounter a XML Parsing error in firebug (see the XML tab when you do a AJAX Post in the firebug console) just edit your question with the error. I'll look into it. Also can you explain to me this bit of code:
:update => 'grid['+params[:day]+']['+params[:hour]+']'
? Which element on the page are you updating?
Also as you can see, the custom.xml.erb contains a <taconite>
tag. It has a <append>
tag with an id of div_changed_content
. You will have to create a div tag with that id in your .html.erb
file for it to work.
The rendering is done on the server side and once the whole page is composed the resulting HTML is returned to the client. So you cannot execute client scripts in this case. To invoke some javascript once the page is loaded you could use the onload
event handler:
<script type="text/javascript">
window.onload = function() {
// put your code here
};
</script>
or if you use jQuery:
<script type="text/javascript">
$(function() {
// put your code here
});
</script>
or if you use Prototype:
<script type="text/javascript">
document.observe("dom:loaded", function() {
// put your code here
});
</script>
You got it a bit wrong there:
"The submit tag has a :success method where I can call a javascript. Unfortunately, the javascript that I put in the success method is getting called 'before' the rendering is done."
The :success
method is for when the submit happens successfully. Not for any event that takes place after the submit! You need a success method to be executed after the partial has been rendered and not when the submit button is clicked! If you are familiar with jquery i would suggest you go the unobtrusive way. I never relied on built-in helpers coz of its lack of flexibility and also i hate to use inline javascript.. you won't be able to cache it. I would have all my javascript in one minified js file which can be cached once than the code spread everywhere. Anyways i am just digressing from the main point.
Secondly, you should not render a partial for an ajax call. That will always refresh your page. Instead you should create a .js.erb
or a .xml.erb
file for that particular controller action.
Since you have not given what exactly you are rendering in your partial i will instead provide you with a general answer. Please share with us your partial code so that we know what exactly you want. Also provide the selector id/class
of the HTML tag that you want to modify on success(after rendering that is..).
If you want a better way of going about it i will give you the config that i use for ajax:
In your application.js:
$.ajaxSetup({
'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/xml")}
});
$(document).ready(function() {
// All non-GET requests will add the authenticity token
// If not already present in the data packet
$("body").bind('ajaxSend', function(elm, xhr, s) {
if (s.type == "GET") return;
if (s.data && s.data.match(new RegExp("\\b" + window._auth_token_name + "="))) return;
if (s.data) {
s.data = s.data + "&";
} else {
s.data = "";
// if there was no data, $ didn't set the content-type
xhr.setRequestHeader("Content-Type", s.contentType);
}
s.data = s.data + encodeURIComponent(window._auth_token_name) + "=" + encodeURIComponent(window._auth_token);
});
});
In IE and SAFARI the accept headers default to text/html instead of text/xml or text/javascript. To correct that, add this to your application.controller.rb; Takes care of all your cross browser issues:
def correct_safari_and_ie_accept_headers
ajax_request_types = [ 'text/javascript', 'application/json', 'text/xml']
request.accepts.sort!{ |x, y| ajax_request_types.include?(y.to_s) ? 1 : -1 } if request.xhr?
end
In your layout your_layout.html.erb
add this to the header:
<% if protect_against_forgery? %>
<script type="text/javascript" charset="utf-8">
//<![CDATA[
window._auth_token_name = "#{request_forgery_protection_token}";
window._auth_token = "#{form_authenticity_token}";
//]]>
</script>
<% end %>
<%= javascript_include_tag 'jquery.taconite.js','application.js', :cache => true %>
I suggest you get the JQuery taconite plugin! Its just too good if you want to deal with ajax in your app! It can perform multiple DOM modifications at once.
JQuery taconite by Malsup
Will explain how to use this in a bit.
Now add this to your application.js file:
$("form").live('submit', function() {
$.post(this.action, $(this).serialize(), "_method=post");
return false;
})
Now in your action where you used to render your partial render(:partial => "grid_item")
, change the action to this
def your_action_here
# CODE GOES HERE:
# @instance_variable_to_store_stuff = some_stuff
respond_to do |format|
format.html {redirect_to :action => :index}
format.xml #EXTREMELY IMPORTANT. THIS CORRESPONDS TO your_action_here.xml.erb
end
end
Now in your views/your_controller/
create a your_action_here.xml.erb
file. In this file you can add the javascript code that is executed after the rendering takes place. This action will respond in two ways. If the user has javascript disabled in his browser it will respond with html data if not it will default to xml data. Now this is where taconite comes into picture. The beauty of taconite is that it converts all your jquery based DOM modifiers to XML compliant markup.
Instead of doing $("#div").append('<div>YO!</div>')
you can do:
<append select="#div">
<div>YO!</div>
</append>
Also you can do multiple DOM modifications! Forget chaining a 100 different modifiers and cluttering your view code. It feels more natural to use with rails helpers as its simple markup. One of the main reasons I stopped using .js.erb
files.
Say in your views you have a div tag <div>
, with an id of "div"
In your_action_here.xml.erb,
<taconite>
<append select="#div">
<%=h @instance_variable_to_store_stuff %>
</append>
</taconite>
If you want to do multiple DOM updates you can do this for example:
<taconite>
<replace select="#div">
<%=h @instance_variable_to_store_stuff %>
<replace>
<append select="#someotherelement_id">
<div>I got appended to #someotherelement_id</div>
</append>
<slideUp select=".someelementwithaclass" />
<slideDown select=".someelementwithanotherclass" />
</taconite>
You can also run javascript inside your your_action_here.xml.erb with a eval tag as follows:
<taconite>
<append select="#div">
<%=h @instance_variable_to_store_stuff %>
</append>
<eval>
alert("HO! HO! HO!");
setTimeout(($("#flash").effect("highlight", {}, 3000).slideUp("slow")), 5000);
$("form")[0].reset();
</eval>
</taconite>
One thing that you ought to keep in mind is that all xml tags should be closed and none should be left open. Strictly xhtml compliant.
I forgot to mention: Taconite also provides you with debug logging in firebug. A sweet feature if you want to track changes made from the ajax request during development.
精彩评论