Thursday, December 3, 2015

A small trick to use AJAX with Rails



Reloading the whole page at every new request is not a good solution sometimes. But that's exactly what Rails does by default, as we all know. Ajax, as always, is a good solution to prevent this and Rails has its way to use Ajax.

I'm going to talk here about another way to use ajax inside rails, circunventing a small problem.

Let's assume I have a fragment like this

app/views/customer/fragment.html.erb
<div>
  <h1>Just a small test</h1>
</div>

and I what this to be displayed inside a certain area in my page whose id is "ajax-content-area". Something like this.

app/views/customer/main_customer_area.html.erb
<html>
  <body>
    <button class='btn' onclick='ajax_call_fragment();'>Click to show fragment below</button>
    <div id="ajax-content-area"></div>
  </body
</html>

Let's also assume main_customer_area.html.erb is already in my screen. It's been renderized by a call to customer#main_customer_area, according to Rails standards. Now I want to call my fragment.html.erb and place it inside the corresponding <div>. For doing so I create a route:

config/routes.rb:
get '/customer/ajax/fragment', to: 'customer#fragment'

and a method fragment

app/controller/customer.rb
def fragment
end

Now, the final piece:

app/assets/javascripts/application.js
function ajax_call_fragment() {
  var displayArea = $('#area-content-area');
  var myUrl = '/customer/ajax/area';  
  $.ajax( {
    url: myUrl,
    success: function(ret) {
      displayArea.html(ret);
    },
    error: function(ret) {
      displayArea.html("<h3>Put your error message here...</h3>");
    }
  })  
}

Now thar we are ready, when you click the button in main_customer_area, something really bad happens!

Your fragment really appears, but it comes wrapped in the main layout!

As you said nothing, Rails will proceed ad always. It will receive the ajax request for '/customer/ajax/area'; will validate the route and, as said in config/routes.rb, will invoke method fragment at Customer controller. the method says nothing, so Rails will follow its usual procedures. Will search for a view named fragment.html.erb inside app/views/customer/. It is there and then Rails will render it inside the main layout and send it back to browser. Ajax will receive it and put it inside the desired <div> object.

Everything all right, but not what you wanted in the screen. Yeah... because your fragment will be wrapped by the mais layout! You'll have it as a copy of you page inside your page. Banners, menus... all pieces inside your main layout will duplicate.

Of course you may create a new (almost) empty layout, say

app/views/layouts/emptylayout.html.erb
<%= yield %>

and rewrite your method fragment as:

app/controller/customer.rb
def fragment
  render layout: 'emptylayout'
end

This will surely work. But you'll add unnecessary complexity to yout app, creating a new layout for doing nothing.

I'll show you an easy way to achieve the same without this.

Rewrite your method fragment as

app/controller/customer.rb
def fragment
  render layout: nil
end

Yes, you may do such thing as render a nil layout. The result is exactly what you want. Rails will pick the corresponding view and pass it away, since it was told not to wrap it in a layout!

This, of course, is not a suggestion to forget all Rails support to Ajax. It's just another cool way to get things done and a new information: nil layouts are possible!

You never know when this kind of thing may be necessary.