History Buttons with AJAX and Ruby on Rails
Thu Feb 21 12:56:50 -0800 2008
If you are using Ruby on Rails with AJAX to update parts or whole pages of your site, you will hit the history problem soon enough, luckily, with Really Simple History and Rails’ RJS templates and helpers, it becomes quite trivial to handle.
First a Quick Primer
So a normal web site (and therefore a “normal” rails site) uses many different complete web pages sent to the client browser. Each one is sent as a separate cycle of action and delivered to the client browser that then reads the page and loads it up. This is one of those things like ‘rain falls’ and ‘the sun comes up in the east’ etc…
But AJAX changes those rules. Actually, it changed those rules some years ago… but there you go.
What you can do with an AJAX page is load up one page, have it on your screen and then update various parts of the web page in situ. This means you only need to send down your page once, saving on downloads and network traffic.
The problem with this is that web browsers loose track of what you are doing because there is no page to load and so their history trail gets all mucked up.
So how do we fix this? Well, with Really Simple History you can, and easily.
What we do is when you visit a page, we insert into the history a hash of that page and the javascript you need to get your AJAX app to display it.
This example is going to show a simple example of going between two pages, one is called “Products” and the other is called “Users”. Products has the url ’/products/’ and users has the url ’/users/’. Both are get requests for simplicity.
Getting and Installing Really Simple History
First stop is to get RSH from the downloads page, the most recent version at the time of this blog post is RSH0.6.
Then, unzip it and put “rsh.js” in your /javascripts/ folder and “blank.html” in your /public/ folder. When you put the blank.html page there, you will over write the one that ships with Rails, this is fine.
RSH also includes a minified version in ‘rsh.compressed.js’ which you can put in your javascripts directory and replace the rsh.js file, but while debugging stuff, and getting this working, use the non minified version, it makes it easier (possible?) to see what went wrong.
Modify application.html.erb
First you have to include the rsh javascript file in your application.html.erb file (or whatever your layout template is) by doing:
<%= javascript_include_tag "rsh" %> |
Now you need to put the required javascript into application.html.erb so that your app fires up the history pages when your page loads… do this by putting the following javascript into your
section of your web template, make sure it is after all your javascript include tags:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<script type="text/javascript"> window.dhtmlHistory.create({ toJSON: function(o) { return Object.toJSON(o); }, fromJSON: function(s) { return s.evalJSON(); } }); var pageListener = function(newLocation, historyData) { eval(historyData); }; window.onload = function() { dhtmlHistory.initialize(); dhtmlHistory.addListener(pageListener); } </script> |
The above links your page into the RSH framework. The important bit is the var pageListener, as this is what fires when you use your back and forward buttons after populating the history. In our case, we just want to call eval on whatever we store in the history cache and allow it to execute as Javascript.
Adding history magic to your RJS template
Now when we go to the products and users page, we are using javascript to render them, our controllers look something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# ProductsController: class ProductsController def index @products = Product.find(:all) respond_to do |wants| wants.js wants.html end end end # UsersController class UsersController def index @users = User.find(:all) respond_to do |wants| wants.js wants.html end end end |
When the user navigates to the Users index page via a AJAX call using link_to_remote or the like, Rails will see it is a Javascript request and then render index.js.rjs which might look like this:
1 2 |
# app/views/users/index.js.rjs page.replace_html('MainContent', :partial => users) |
Which replaces the Main Content div with the users partial (automatically passing to it the @users instance variable)
So what we want to do here is add into the RJS template a call to add in the history link. We can do this like so:
1 2 3 |
# app/views/users/index.js.rjs page.replace_html('MainContent', :partial => 'users') page << %[dhtmlHistory.add("users", "new Ajax.Request('/users/', {asynchronous:false, evalScripts:true, method:'get'});")] |
Then in the products RJS template we do the same thing (but for the products page instead):
1 2 3 |
# app/views/products/index.js.rjs page.replace_html('MainContent', :partial => 'products') page << %[dhtmlHistory.add("products", "new Ajax.Request('/products/', {asynchronous:false, evalScripts:true, method:'get'});")] |
That is it!
Testing it out
Now, go to your home page and click an AJAX link to pull up your products page, you will see in the browser URL address bar the following: http://127.0.0.1:3000/#products and your products page should show. Then click (from your products page) on an AJAX link to get to your users page, and the page should load and then you should see http://127.0.0.1:3000/#users
Now, hit the back button and after a short lag, your products page should reappear! Click the forward button and your users page will appear.
What is happening is that the RSH framework is looking in the history and finding the previous page you went to, it is then pulling up the javascript associated with that page and executing it, in this case, a get request to the server, just as if you had clicked on an AJAX link. The Rails server then handles this as any other request and sends back a response, which calls up your RJS template and renders it to the browser…
Simple hey?
Of course there are many other things you can do, you don’t have to store get requests, you can story any arbitrary javascript in the value part of the dhtmlHistory.add call, also, I tend to make a helper or two in my application_helper.rb file to handle making the AJAX. Like this:
1 2 3 4 5 6 7 |
module ApplicationHelper def add_simple_history(name, url) page << %[dhtmlHistory.add("#{name}", "new Ajax.Request('#{url}', {asynchronous:false, evalScripts:true, method:'get'});")] end end |
Which then makes your RJS template look like this:
1 2 3 |
# app/views/products/index.js.rjs page.replace_html('MainContent', :partial => 'products') page.add_simple_history('products', '/products/') |
Much nicer.
blogLater
Mikel




Wed Feb 27 02:50:55 -0800 2008
Great Post !!! Wonderful timing for me, I knew I had to start using RSH but didn’t know enough RoR to make it call come together. Much appreciated!
One question. What happens when you’re using an UJS package like Low Pro? It doesn’t seem to append the ”#” to the URL in that case.
TIA.
GP
Wed Feb 27 09:48:12 -0800 2008
@Grayson – Not totally sure what problem you are running into, but the # marker that has the section you are in is fired off by the window.onload function that is inside your application.html.erb file, as LowPro is an unobtrusive Javascript extender for Prototype, you probably have a conflict here.
Figure out how to get that window.onload command loaded via LowPro, and you should be fine.
Mikel
Fri Apr 18 15:58:31 -0700 2008
Thanks for that..to much how to do’s for php out there. Will give it a try tomorrow..
was about to scrap the whole ajax thing…
Jeff
Wed Jul 23 04:51:49 -0700 2008
just an idea [code] def link_to_remote(args) (args2||= {}).merge! :onmouseup => “dhtmlHistory.add(this.firstChild.nodeValue.replace(/\\W/, ’_’), this.readAttribute(‘onclick’).replace(/return false;$/, ’’))” super(args) end [/code] t put in application helper for example
Thu Apr 09 06:13:24 -0700 2009
I enjoy seeing how other forward thinkers continue to develop solutions. I have also created a solution that involves a new HTML vista la tanta Micro format and doesn’t require polling, two features I thought were particularly appealing. If you’re interested in a method for handling the infamous back/forward buttons in Ajax applications the full article can be read here.http://positionabsolute.net/blog/2007/07/javascript-history-service.php
Wed May 27 18:36:34 -0700 2009
Actually I am not much familiar with rails. But in the web application i am working, we use just .html.erb file and render them as partial for the ajax requests. We use an ajax.html.erb layout to render all the ajax requests. So i don’t see many rjs files in my web app. How do I need to modify my application to use RHS?
respond_to do |format| format.html do render :layout => ‘ajax’ if request.xhr? end format.xml { render :xml => @reference } end
Pls help me with using RJS in my application
Sun Aug 02 04:35:06 -0700 2009
I am integrating RSH in my R-o-R ajax application but have issues with json scripts conflicting with the jquery libs. and plugins. After getting that resolve I am stuck where R-o-R complains about blank.html not found.
As per this blog, I have place blank.html under the public folder, yet when loading my home page I get the following rail template error.
no route matches /home/blank.html {:method=>:get}
Not sure why is the blank.html not getting located by rail. Has someone encountered this problem and got it fixed? If yes please share the solution.