Emulating RJS with Merb/JQuery/Haml

Dec 21, 2008

Ok, Merb is great. Rails is equally as great. RJS is great. JQuery and Haml are just outstanding. But I found that Merb not doing RJS-like behavior a little daunting in the current application that I'm working on. Of course, Merb does javascript, and you can send Ajax requests, but it's not quite as easy to accomplish RJS-type interactions with the current page. I did quite a bit of googling, and all I ever came up with was a very interesting presentation from Yehuda Katz about using JQuery with Merb. It didn't completely answer my question about getting RJS functionality in a Merb app. So, I set about trying to actually figure something out on my own!

Let's say I have an action called mark_as_complete that I can call for several items on a page, have it make a database call, then update the item without having to leave the page. The first step is to make the form for each item submit via AJAX instead of a regular HTTP POST request. Thanks to Ryan Bates' RailsCast on JQuery for help on that. All I have to do is give the form a class of remote, then this function will cause it to be submitted via AJAX (as well as any form on the site with the same class name):

// function to send the jQuery form object via AJAX
jQuery.fn.submitWithAjax = function() {
  this.submit(function() {
    // be sure to add the '/2008/12/21/emulatingrjswithmerbslashjqueryslashhaml/.js' part so that it knows this is format:js
    $.post(this.action + '/2008/12/21/emulatingrjswithmerbslashjqueryslashhaml/.js', $(this).serialize(), null, "script");
    return false;
  return this;

$(document).ready(function() {
  // once the page has completely loaded,
  // make forms with the class "remote" submit via AJAX

Now that's done, I need to figure out how to get the RJS stuff to fire correctly. I've already set the format of the request as .js, so the controller knows it's an AJAX request. With Merb, you have to add the provides :js line to your action so that it knows to accept js requests.

def mark_as_complete(id)
  provides :js
  # do work, son
  # set instance variable to indicate success/failure
  if stuff_done
    @complete = true

Now for the funky part. Getting my Haml template to update the page. Well, if you just name a view mark_as_complete.js.haml, then the application will use it. The hard part was figuring out what to do in the file. I could call javascript in the file and get that to work (alert(), for example), but the following bit of code did NOT work:

- if @complete


'); - else alert("An error occurred processing your request.");

Nothing happened and I couldn't figure out why. The javascript was being run, but the ruby code inside it wasn't being evaluated. Turns out it's because I'm using Haml. I suspect that if I were using erb, then I could surround my ruby code with output blocks(<%= %>) and it would work(though I haven't tested it).

After experimenting with all sorts of variations on the above code, I finally decided to try and put the javascript code into Haml ruby output blocks(so the ruby code would be evaluated). Low and behold, the following actually worked, despite being ugly and less readable.

- if @complete
  = "$('#div_name_#{@record.id}').html('complete');"
  = "$('#notes_#{@record.id}').replaceWith('


');" - else alert("An error occurred processing your request.");

Notice that the alert() call isn't in an output block because it doesn't have any ruby code that needs to be evaluated.

This solution worked for me, but I'm sure there is a better way to accomplish this. If anyone has a better approach, please leave it in the comments.

Tagged: rubyhamljquerymerbrjstutorial