Our team was recently presented an initially edge-case issue in which a User would be displayed raw javascript when clicking the browser's back button from a page that had some sort of javascript rendering (be it Ajax, tabs, etc.). To re-create, we followed the following steps:
The previous steps would display:
(function() {
$(".job_applications").html("<li class=\"job_posting_application\">\n
...
...
...
...
);
}).call(this);
In some hit-or-miss cases, you wouldn't need to click on a tab prior to going back to the previous page, yet it would still render the raw javascript. At the end of the day, it seems the last rendered template is being cached, which is normal and expected on the browser's part, but leads into what I believe to be a larger problem.
The Rails Guides state in the section on Layouts and Rendering, specifically regarding MIME type of the template:
By default, Rails will serve the results of a rendering operation with the MIME content-type of text/html (or application/json if you use the :json option, or application/xml for the :xml option.).
Based on the Rails defaults, our controller's index action is expected to render our index.html.slim
template. However, when making a non-remote call to that page (e.g., directly navigating to the page in the browser) while tailing the server logs, we notice that it actually renders index.js.coffee
. Below is our controller action, and notice we are not explicitly responding to html or js formats, as we probably should considering the overlying features in this page:
def index
@company_id, @division_id, @job_posting_id = params[:company_id], params[:division_id], params[:job_posting_id]
# API requests are made here to instantiate @job_posting, et al.,
# but are not shown for brevity
authorize! :manage, @job_posting
@survey = @job_posting.survey
@job_applications = @job_posting.job_applications(sort_column, sort_direction)
end
Given this setup, however, index.html.slim
should be rendered based on Rails defaults. When adding a respond_to
block, it seems the caching is still in effect and the controller could care less about the presence of a respond_to
block:
def index
...
...
respond_to do |format|
format.html
format.js
end
end
Even when explicitly, and albeit smelly, telling each format to render a different template, it seems the js.coffee
template takes precedence over the html.slim
template:
def index
...
...
respond_to do |format|
format.html { render template: "users/job_posting_applications/index" }
format.js { render template: "users/job_posting_applications/ajax" }
end
end
In the above case, directly navigating to the page in the browser (in other words, not making a remote Ajax call), the server log would render ajax.js.coffee
, even though the Rails default is html unless otherwise specified.
All this being said, here are some other findings:
Started GET "/users/companies/1/divisions/18/job_postings/349421/applications" for 127.0.0.1 at 2012-10-03 19:55:26 -0400
Processing by Users::JobPostingApplicationsController#index as JSON
(you can reference the entire request shown above in this pastie)
Why it's processing as JSON is beyond me, considering we are not serving any JSON on this request, and have no specifications in routing for a default format of :json
for this route.
Additionally, when debugging the value of request.format
within this action, it returns application/json
.
Another scenario that presented is within another controller (users/company_admin_metrics#index
) that contains only an index.html.slim
template. When navigating to this page, the server log shows that it rendered users/company_admin_metrics/index.html.slim
within layouts/users
. When I create a blank js.coffee template:
$ touch app/views/users/company_admin_metrics/index.js.coffee
and directly navigate to that index page, the server log shows that it rendered users/company_admin_metrics/index.js.coffee
, which further exposes a potential issue somewhere regarding template rendering precedence.
Has anyone encountered a similar issue that may provide a potential fix for this?
Below is a minute list of the basic players into this particular question:
This request depends on requests to our job posting API through a client gem that parses JSON and returns a Ruby object, but these are not coupled with this particular app in such a way that would conflict and cause this app to have a content-type of application/json
for such a request as described above.
I think you need to append the format extension .js
to the URL in your ajax request.
What is probably happening is that the user is hitting the index
action at /job_applications
with a GET
request for html. Then the ajax is hitting the same URL/action but requesting javascript. When the user clicks the back button, the browser can't tell the difference between the two so uses the most recent (the javascript response).
Started seeing this exact issue recently in Chrome with Rails 4.2.4. Appears to be a known and hotly debated issue. Our team solved it by adding the Vary
header to xhr
requests:
class ApplicationController < ActionController::Base
after_action :set_vary_header
private
# Fix a bug/issue/by-design(?) of browsers that have a hard time understanding
# what to do about our ajax search page. This header tells browsers to not cache
# the current contents of the page because previously when someone filtered results,
# went to the listing's external site, then hit Back, they'd only see the last
# ajax response snippet, not the full listings page for their search.
#
# Heated multi-year discussion on this issue in Chrome
# https://code.google.com/p/chromium/issues/detail?id=94369
#
# And continued by the Rails team:
# https://github.com/rails/jquery-ujs/issues/318
# https://github.com/rails/jquery-rails/issues/121
def set_vary_header
if request.xhr?
response.headers["Vary"] = "accept"
end
end
end
It has been several months since this question has been asked but Ill chime in and give my two cents since I came across it.
Just as carbo said the browser can tell the difference between html and js request so when clicking the back button it sees the same url and just serves up the cached data which happens to the the js code. I had the some problem but with using push state javascript for modifying the browser history for ajax requests. The simplest solution I found was to tell the browser not to cache the request. So that when going back to the previous page its forced to reload it.
You can disable caching globally: $.ajaxSetup({ cache: false });
Or you can try specify cache false on specific ajax request.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With