views:

81

answers:

3

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" />
  <% end %>
</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

+1  A: 

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>
Darin Dimitrov
Thank you for your reply. But I am doing the render in Ajax context. So, no page is reloaded. How to run a Javascript in that case ?
Bragboy
In this case run the javascript in the success callback of you ajax call.
Darin Dimitrov
@Darin Dimitrov: I did exactly that and I debug and saw, the javascript stuff that I put in success method is called 'after' the rendering is done!! :(
Bragboy
Isn't this what you were are looking for? When exactly do you want to invoke this javascript function?
Darin Dimitrov
@Darin : I am really sorry. it is 'before' the rendering is done. I am actually repainting a <div> element. I want to apply the facebox() (jQuery) method to that div element once it is rendered
Bragboy
I really don't understand you. You say that you want to call the function *before* rendering is done and in your second sentence you say *once it is rendered*. So?
Darin Dimitrov
@Darin: I guess I confused you.. I have edited the question for clarity
Bragboy
+1  A: 

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.


Shripad K
I really thank you for a thorough reply. I have modified the question (put it in my code).
Bragboy
+1  A: 

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.

Shripad K
Ok done. I don't really know what you want to perform on the ajax call so i will leave that to you. you can add that code in the custom.xml.erb file. However if you feel the need to pass some variables to it, you can define instance variables in your `custom` action. They automatically become available to the custom.xml.erb file.
Shripad K
@Shripad : Thank you very much! you simply rock!!!!
Bragboy
@Bragaadeesh: You are welcome :)
Shripad K