Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails 3.1 is returning raw Javascript code when back button is used

Tags:

My Basic Goal

A Rails app that can load new content via Ajax while using HTML5 to maintain the browser's history state. I have a super basic version of the app running on Heroku. I've also set up a public GitHub repository so that all the code mentioned below can be looked at in more detail.

The Problem

I successfully implemented the basic app using the HTML5 browser history API (at one point I also made a version using the History.js jQuery plugin). Everything works as it should until the user clicks a non-ajax link and then hits the back button. For instance, if the user clicked item 2, the app successfully loads the new content and changes the browser state. If they hit the back button, it also correctly reloads the previous content and changes the browser state back to match. But if the user then browses to another webpage (any other page on the internet) then clicks their browser's back button, the Rails app renders raw javascript instead of the original page.

Everything works up until that last step, when the controller starts returning the raw javascript from show.js.erb instead of show.html.erb. I've tried changing several settings in the controller, but I can't figure out what specifically is causing this. I do know that if I remove the history.pushState call in show.js.erb, the problem stops (though obviously I lose all the browser history functionality I was looking for in the first place).

Any ideas?

Bonus: Excruciating Detail

This is how it all works (I think!).

The user clicks on a link, which looks like this under the hood:

<%= link_to number_to_human(volume.number), volume_path(volume), :remote => true %>

This sends a javascript request to the controller, which looks like this:

def show
  @volume = Volume.find(params[:id])

  respond_to do |format|
    format.html # show.html.erb
    format.js # show.js.erb
    format.json { render json: @volume }
  end
end

This passes the request to show.js.erb which looks like this:

var stateObject = {"html": "<%= escape_javascript render(@volume) %>"};
history.pushState(stateObject,'',"<%= escape_javascript volume_path(@volume) %>");
$('#volumes').fadeTo(0, 0);
$('#volumes').html(stateObject.html).fadeTo('slow', 1.0);

Which then replaces the content of a div tag on the original page and uses pushState to create a state object and change the page URL.

The user can then click the back button and javascript attached to the window.onpopstate event in show.html.erb will reload the appropriate content:

    if(history.pushState) {
    window.onpopstate = function(event) {  
        if (event.state != null) {
            $('#volumes').html(event.state.html);
        }
    }
}
like image 329
Simon Avatar asked Oct 12 '11 01:10

Simon


2 Answers

I'm very new to RoR so I'm not sure if my answer would be the best, but I ran into the same problem and managed to fix it. So here's my two cents.

It seems showing raw ajax response happens because of browser cache. I fixed the problem by adding the following header to prevent the response from begin cached.

response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
like image 55
Joon Avatar answered Oct 22 '22 02:10

Joon


What Joon said above definitely worked. I just added an extra check to see if the request was from ajax before I added the headers. Thanks Joon!

if request.xhr?
    response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
    response.headers["Pragma"] = "no-cache"
    response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
like image 23
thehammer Avatar answered Oct 22 '22 01:10

thehammer