When Rails Ujs Met Chrome Frame

Rails ujs let us use other javascript frameworks (like jQuery) in our project. That’s cool.

jQuery

Here is an article about top 10 javascript frameworks by google. He use a way (an easy way) to sort these frameworks. Search “javascript frameworks” in Google. You can see that the default rails javascript frameworks prototype is not the most popular one now.

I’m working on a small project for my company by rails and jQuery. The newest rails3 use many html5 features. So I decide let user use a modern browser to use my web page. Otherwise IE6 will kill me. How to switch smoothly? I found chrome frame has beta yesterday!

Someone said that they hate the web page of xxx-browser only. But wait! I use this in my company only! Didn’t you saw those enterprise-level soft let you install their plugin?

Make an IE-only page is easy. Make an IE-except page is easier. lol


Chrome frame show my html5 page (maybe). It’s beautiful than IE. But it cannot works when I send ajax request.

For example I write a link like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    <!-- Rails html5 templete -->
<%= link_to "Add user", new_user_path, :remote => true %>

<!-- Will product -->
<a href="/users/new" data-remote="true">Add user</a>

Rails handle these link with data-remote by rails.js, It cancel click event, make a ajax request, then trigger ajax:success event back.

#!javascript
// Live is new method in jQuery 1.4+.
$('a[data-confirm]').live('click', function () { ... });

// When user click the link. jQuery will handle it and send a ajax request.
$.ajax({
url: "/users/new",
dataType: "script",
...
});

But chrome frame look like send a incorrect request to rails. Check the log of rails, I found this line:

Processing by UsersController#new as */*

The right process method is JS because the data type is script. I have test IE FF Chrome and Safari on win and mac. Only chrome frame has this issus.

I think I should fix this at client side. When I try to get navigator.userAgent by javascript. I found it has the same value as chrome stand-alone.

Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.62 Safari/533.4

But rails could detect the right user agent:

Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727) chromeframe/5.0.375.62

Oh! Javascript don’t know it’s running in chrome frame, so it can do anything. Rails know it’s processing the request from chrome frame, but it don’t know that’s a ajax request.

After a minute. I decide let rails tell javascript: “u r working for chrome frame.”

application.html.erb
1
2
3
4
5
6
<!-- Hold it in application.html.erb or ur own default layout. for this example put in navigator. -->
<% if @win_ie_with_chromeframe %>
<script>
navigator.chromeframe = true;
</script>

<% end -%>
application_controller.rb
1
2
3
4
5
6
7
8
# Detect chrome frame in application_controller.rb
before_filter :chromeframe_detect

protected
def chromeframe_detect
ua = request.user_agent
@win_ie_with_chromeframe = ua =~ /Windows NT/ and ua =~ /MSIE (\d+)/ and $1.to_i < 9 and ua =~ /chromeframe/
end

And then hack $.ajax. In fact I have three things need fix.

  • Chrome frame cannot send PUT DELETE verb.
  • Append .js to url tell rails I’m a JS request.
  • jQuery will try parse the result as JSON when dataType is script. If it got html. You will see an error: “Uncaught SyntaxError: Unexpected token <”.
application.js
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
26
27
//BUGFIX: Chromeframe miss some features.
(function($) {
$.oldAjax = $.ajax;

$.ajax = function(o) {
// I can't detect it by navigator.userAgent. So I set it on server side.
if (navigator.chromeframe) {
// Chromeframe can't send DELETE and PUT verb.
if (o.type == "DELETE") {
o.type = "POST";
o.data = "_method=delete";
} else if (o.type == "PUT") {
o.type = "POST";
o.data += "&_method=put";
}
}

// Chromeframe send incorrect ajax type make rails process the request by */*. The right way is JS. I try many way to avoid this. But I can do this with append .js to url only.
if (o.dataType == "script" && !/\.js$/.test(o.url)) {
o.url += ".js";
// Webkit try parse result to json then raise a syntax error when receive html. It appear in inspector only and user can't see that except u r a developer. The url(end of .js) has told rails how to process my request. So I set dataType as text because I need respondText only. I can parse json myself.
o.dataType = "text";
}

$.oldAjax(o);
};
});