mad.ly rails, jquery, flash, etc    about »

We're looking for senior developer. More Info

Posted by
Geoff Buesing

Posted on
2 November 2007 @ 10pm

Tagged
javascript, jquery, rails, rjs, ruby

Javascript + embedded Ruby templates with Rails, out-of-the-box

I recently tried using Dan Webb’s MinusMOR plugin, which allows you to write Javascript templates with embedded Ruby (a la .html.erb/.rhtml templates), but I was disappointed to find out it didn’t work with Edge Rails.

I found a random Pastie via Google that seemed to fix the plugin for Edge, and this worked okay, with the caveat that you couldn’t render partials that used the new .html.erb naming scheme (just .erb would work), and given this, and the fact that MinusMOR’s author wasn’t keeping this plugin up-to-date, I was hoping to find a better solution.

And I did find a better solution — if you’re using Edge Rails, you can create Javascript templates with embedded Ruby out-of-the-box, no plugins or monkeypatching necessary. You just need to name the template in the action.js.erb format. Example:

Controller:

# controllers/posts_controller.rb
def create
  …
  # if you’re only returning Javascript from this action,
  # you can omit the respond_to block
  respond_to do |format|
    format.js
  end
end

View:

# views/posts/create.js.erb
alert("created");

.. and the Javascript is rendered, sans layout, with a “text/javascript” Content-Type. All good so far.

JSON-escaping with “js” helper

Next thing we need is, a helper so that we can do JSON-compliant escaping (which RJS does automatically) — I took this helper straight from the MinusMOR plugin, and put it in my application helper:

# from Dan Webb’s MinusMOR plugin
def js(data)
  if data.respond_to? :to_json
    data.to_json
  else
    data.inspect.to_json
  end
end

Usage example:

# create.js.erb
alert(<%= js "url is /posts/5" %>);

Renders:

alert("url is \/posts\/5");

(Textmate users: I’m using the HTML (Rails) filetype for my js.erb files, which, while it doesn’t do any Javascript syntax highlighting, it does highlight inline Ruby syntax, and it allows me to use the ctrl-shift-> shortcut to create <%= %> blocks.)

Rendering partials with “partial” helper

MinusMOR has a ‘partial’ helper — we can use that, too — I’ve adapted it so that partials with the new .html.erb syntax can be recognized:

# from Dan Webb’s MinusMOR plugin
# enhanced with ability to detect partials with template format, i.e.: _post.html.erb
def partial(name, options={})
  old_format = self.template_format
  self.template_format = :html
  js render({ :partial => name }.merge(options))
ensure
  self.template_format = old_format
end

Usage example (syntax is jQuery):

# create.js.erb
$("#posts").append(<%= partial ‘post’, :o bject => @post %>);

Renders:

$("#posts").append("u003Cliu003Eanother postu003C/liu003E");

So, just add these two helpers to your application helper, and you should be good to go.

jQuery “selector” helper

One last helper, for jQuery users — a selector helper for creating id selectors from an object:

def selector(obj)
  js "##{dom_id(obj)}"
end

Usage example:

# update.js.erb
$(<%= selector @post %>).highlightFade();

Renders:

$("#post_5").highlightFade();

10 Comments

Posted by
jeroen
8 November 2007 @ 7pm

Nice! It would be nice to bundle this into a small plugin. I’m not using rails 2 yet, but I think I’ll be using this once it’s out.


Posted by
weepy
8 November 2007 @ 9pm

awesome stuff.

How about rolling it all up into a nice little plugin.


Posted by
Matt
8 November 2007 @ 11pm

This may be a really dumb concern.

It appears you’re suggesting that all view-specific JS be thrown into separate JS files instead of embedded in the HTML with script tags. Isn’t this in opposition to the standard best practice of reducing the number of HTTP requests?

More HTTP requests means longer load times and more work for the server.


Posted by
Branstrom
10 November 2007 @ 10am

Matt: First of all, that is not unique to this solution. It’s the same with RJS.

It’s a Good Thing to keep all JS and markup separate for the sake of easy maintenance and readability. Keep structure and behavior separate.

This stuff (JS + data ajaxed in on certain events) doesn’t really add to the initial load time, which is the primary concern when talking about the number of HTTP requests. These .js.erb requests will add up if you’ve got many of them, granted. Feel free to benchmark it…


Posted by
weepy
12 November 2007 @ 8am

You should extract all common code out of the .js.erb into your public javascript files, and then just use the calls to these functions in the .js.erb. For example.

=> .js.erb
updateMyStuff()

=> .js
updateMyStuff = function(x) {
$(‘my_div’).html(x);
}


Posted by
Andy
13 November 2007 @ 9am

Wonderful. Those little helpers from the MinusROR plugin really make this elegant.

Cheers.


Posted by
jean-sébastien
16 November 2007 @ 3am

Hi geoff,
i’m using rails 2.0 pre release. and when i launch an ajax request with query, it doesn’t work : rails take it as html formating.
So i must add ‘.js’ to my request to be considered as javascript.

i must precise that my ajax request seems to be well formatted (dataType: ’scr1pt’ beforeSend: function(xhr) {xhr.setRequestHeader(“Accept”, “text/javascript”)

can you give me your opinion?


Posted by
Geoff Buesing
16 November 2007 @ 7pm

@jean-sebastien: regarding setting request headers, I’ve been meaning to update my older post that gave those instructions — I’ve since discovered that adding the file extension (e.g., .js) to the url is an all-around better solution to request a specific format, if for no other reason than, it avoids issues with page caching and different mime types.

So that I can add the correct file extension to a url parsed from the page, I came up with a javascript function that intelligently adds an extension to a url, respecting querystring params:

function addFormatToURL(format, url) {
	var urlArray = url.split('?');
	// add extension to url, unless it's already there
	if ( urlArray[0].search('.' + format + '$') == -1 ) { urlArray[0] = urlArray[0] + '.' + format; }
	return urlArray.join('?');
}

Posted by
Geoff Buesing
16 November 2007 @ 7pm

@weepy: agreed, push as much Javascript code as you can into functions that reside in script files in /public/javascripts, and then call these functions from document.ready and js.erb files.

You’ll reap the benefits of browser caching with script files in public, and the code that’s not cached (i.e, code embedded in the page, and in js.erb files) will read at a higher level.


Posted by
jean-sébastien
3 December 2007 @ 10am

here is an additionnal helper method to use remote links “jquery way” using protect_from_forgery
I should precise that i use an additionnal class (.put .post .delete) which determines ajax method.

def remote_link_to(name, options = {}, html_options = nil, *parameters_for_method_reference)
options += (options.include?(‘?’) ? ‘&’ : ‘?’) +
“#{request_forgery_protection_token.to_s}=#{form_authenticity_token}”
html_options[:class] = html_options.has_key?(:class) ?
html_options[:class].to_s + ‘ remote’ : ‘remote’
link_to(name, options, html_options)
end