RJS Minus R

UPDATE: I’ve released a happy-clappy, touch feely version that doesn’t override .rjs and instead gives you the .ejs extension. It’s called MinusMOR.

I like RJS and it’s approach of sending executable JavaScript responses back to Ajax requests. It seems ugly but it’s actually a life saver for making really complex UI updates easily and once I got to using it in real projects it definitely saved me vast amounts of time for anything more than trivial. The thing is that if I did want to do anything more than trivial (as in a simple conditional statement) the whole ‘write Ruby generate JavaScript’ thing fell down and I ended up writing these nasty Ruby-JavaScript Frankenstein’s monsters as I’m sure many of you have:

page << 'if (someClientSideVariable) {'
page['a'].replace_html :partial => 'thing'
page << '} else {'
page['b'].replace_html :partial => 'thong'
page << '}'

Why do I need to write the RJS templates in this ugly style? When RJS first came out I was quite impressed with the Ruby to JavaScript translation stuff but it’s limitations (and that of lots of other similar attempts at ‘compiling down to JavaScript’ like Google Web Toolkit) shine through after the lightest of uses. Now, in that example above, I know I could make it cleaner so it’s not quite fair but still for me (or anyone else that can write JS) it’s just another hurdle to jump through and, in my experience, it’s never been long before I’ve needed to resort to the old page << business. Also, why bother learning another API when you already know Prototype? Or, for that matter, what if you don’t want to use Prototype but do want to use RJS….

With this in mind, I’ve written a tiny weeny plugin to help out. It’s called MinusR and it simply changes .rjs templates so you can write JavaScript and use ERB in them:

if (someClientSideVariable) {
  $('a').update(<%=js render(:partial => 'thing') %>);
} else {
  $('b').update(<%=js render(:partial => 'thong') %>);
}

MinusR gives you the js helper that calls to_json on any value passed to it so you can drop nicely escaped and formatted data into your script as before. Everyone’s a winner. Well, not quite, I could be going out on a limb here but I really think this is the better way. Go on, give it a go. A bit of javaScript won’t hurt you….

The only problem to solve now is to get it highlighting nicely in Textmate….anyone?

UPDATE: Luke has written a little bit about Minus R.

26 Comments (Closed)

I think I’ve put pure JS + Erb into Rhtml views/partials, in the past.

Does using the MinusR plugin disable RJS for normal usage?

Dr NicDr Nic at 17.11.06 / 13PM

I may be misunderstanding how this works, but if it redefines the way .rjs templates are executed won’t that mean that any existing RJS templates in a given project will cease to work? That could cause problems if people try to reuse existing applications as part of their Rails apps.

If that’s the case, it would probably be better to grab a new file extension (.ejs or something) for this alternative form of embedded Ruby / JavaScript.

Simon WillisonSimon Willison at 17.11.06 / 13PM

Perhaps have a look at the Rails bundle to see how it combines ERb highlighting with HTML, then apply those principles to RJS templates in the Rails bundle (the scope for RJS templates might need changing too).

Luke RedpathLuke Redpath at 17.11.06 / 14PM

Simon: Yeah, it does redefine them completely. I did consider using a different extension but I couldn’t think of one that made more sense than .rjs so I used that. I was going to go with .prjs (Proper Ruby JavaScript) but then I thought, balls to it, I’ll override .rjs. It’s simpler that way. The whole concept of evaluating returned JavaScript in Rails is under the banner of RJS.

This is primarily for my use and I’m now pretty much of the opinion that Ruby-to-JavaScript is nasty so I’m making a statement here!

Love the JavaScript.

Dr Nic: Do you mean you’ve found another way to do this?

DanDan at 17.11.06 / 14PM

Dr Nic: Ah, just re-read what you said. Gotcha, yeah, you could just use the .rhtml extension. You need to set the content type manually then too.

DanDan at 17.11.06 / 14PM

Interesting concept, Dan. Have you considered .erjs (for “Embedded Ruby JavaScript”) as an extension? Makes sense to me.

Also, do you see any benefit to a syntax like this?


if (someClientSideVariable) {
  <%= page['a'].replace_html :partial => 'thing' %>
} else {
  <%= page['a'].replace_html :partial => 'thong' %>
}

Essentially you’re turning traditional .RJS inside-out.

Bryan HelmkampBryan Helmkamp at 17.11.06 / 15PM

What about the extension js? As in, JavaScript.

Dr NicDr Nic at 17.11.06 / 15PM

Very nice. Something about all the “write javascript in Foo” frameworks bothers me…even if it is ruby.

Rob SanheimRob Sanheim at 17.11.06 / 19PM

I’m new to Rails and haven’t started using RJS yet… so I just assumed this was how it already worked. From your examples, it seems like a big improvement. For the most part I’d rather write JavaScript for the browser than hide it under a Ruby layer.

But I’m leery of commandeering the .rjs filename extension that’s already in the core. Seems like that makes adoption of your plugin an all-or-nothing proposition. Consider this a newbie’s vote to change it.

Erik OstromErik Ostrom at 17.11.06 / 20PM

I vote for js, too. Bryan’s example looks rather nice, too! RJS methods could then serve only as helpers in ERB blocks.

MislavMislav at 17.11.06 / 20PM

This looks excellent; like you, I think the concept of getting Rails to render JS for AJAX requests is cool, but think completely abstracting the JS to Ruby is going too far. I think Bryan’s idea is a very cool one though – I’d love to see that implemented.

Jon LeightonJon Leighton at 17.11.06 / 21PM

The biggest problem with using another extension is that it stops being the default for respond_to and various other things that default to using rhtml, rxml and rjs and I’m just not into compromising on this. I can’t see a use case in mixing the two different styles in one application and if your using an old application your best sticking with what you have.

And, on another note, I really think, after working with it, that the Ruby-to-JavaScript aspect is a blatantly bad idea and I don’t like relegating what I think is a better method to another weird extension. In my applications, I want this to be the absolute default.

Of course, you could just take my code an change it if you like.

DanDan at 17.11.06 / 23PM

I just want to chime in on Dan’s side, I think using .rjs for this is the way to go. Consider that .rhtml is HTML with embedded Ruby in it, it only make sense for .rjs to be Javascript with embedded Ruby, and not Ruby that creates Javascript. That sort of functionality would make more sense in a Helper module, akin to the way other helper methods generate HTML.

OzziOzzi at 18.11.06 / 14PM

You are right. Screw backwards compatibility! :)

I never used RJS anyway. I can’t stand someone writing my JavaScript except for myself 8)

MislavMislav at 18.11.06 / 21PM

Love the experimentation. I’m a very happy RJS user myself, but I also do write JavaScript by hand for application.js when I have to. So I don’t see it like an all or nothing trade at all.

80%+ of my Ajax is the trivial stuff that RJS excels at. Update this div with a rerender of this template, then run this effect on that element. Automating that behind RJS makes perfect sense to me. Just like Active Record automates 80%+ of the SQL writing for queries, but still allows you to do your own thing when you must.

Anyway, what I mostly wanted to say is don’t think of it as all or nothing. And by that extension, I think it’s a bad idea to hijack .rjs. What about .ejs? ERb-based JavaScript. That way you can combine the two and live happily ever after.

Keep playing.

DHHDHH at 19.11.06 / 06AM

Dan, you need to HTML-escape the ERB tags in that second code sample, otherwise they don’t render in Safari or IE, which makes the MinusR example look like plain vanilla JavaScript.

Chris MearChris Mear at 19.11.06 / 15PM

I may be completely missing it, but how do you test for server side/ruby variables in the rjs now?

Would something like this work with this plugin?

<% if @some_var %> $(‘a’).update(<%=js render :partial => ‘thing’ %>); <% else %> if (someClientVar) { alert(“what”); } else { alert(“how”); } <% end %>

Jason SeiferJason Seifer at 19.11.06 / 20PM

DHH: You’re right. It works as well as a glue layer and is great for triggering simple updates and stuff and I definitely think you shouldn’t be putting anything approaching serious logic in an RJS template. I guess it’s just a matter of what makes you most comfortable when writing this glue layer which is totally a matter of taste. I do find that I tend to off-road with page << for really simple things like checking the existence of elements etc that don’t tend to fit elsewhere and, as someone comfortable with JS, it’s less ugly.

On the point of all or nothing, I am being a bit militant about it but I’m happy personally with only using JS in RJS templates (and I think a few other people will) and I really value it being the default for respond_to etc so I like it the way it is. I’m aware of the downsides, like the fact that Im alias_method_chaining some of the internals which is never a perfect solution but I can handle it of things change and yeah, it’s just a ten minute experiment. I think I’ll be using it in my future projects though. I’ve really gotten on with it so far. Perhaps I should make an liberal, MOR version as well? MinusMOR? :)

Chris: Cheers! Updated now. I always forget to do that.

Jason: Yeah, that looks about right.

DanDan at 20.11.06 / 00AM

I use RJS a lot, but for simple ajax stuff, it makes a lot of sense to use render :update and do it in the controller (ie, just replacing a div with a partial and highlighting something.. why have a 2 line rjs file?) – More complicated JS stuff… does tend to delve into the depths of raw javascript.

so that said, i don’t see a huge problem with hijacking .rjs for my own purposes… But i still think it would be a good idea to pick a different extension anyway. That way if people DO want to use both, they can. I use .mab instead of .rhtml, but it should would be inconvenient if markaby hijacked .rhtml :)

jtjt at 23.11.06 / 06AM

When I work with Markaby I can have use both *.mab files and *.rhtml files in the same application. (In the case of conflict *.mab takes precedence).

Couldn’t we do the same and have both *.ejs and *.rjs files both being used by respond_to format.rjs (and with *.ejs taking precedence).

William FiskWilliam Fisk at 23.11.06 / 07AM

As other readers noted, I would like the ability to do both. I just ended up doing something like,
page << <<-END
if (someClientSideVariable) {
  $('a').update(#{render(:partial => 'thing')});
} else {
  $('b').update(#{render(:partial => 'thong')});
}
END

Not as pretty, but... pretty simple.

KevinBKevinB at 23.11.06 / 15PM

Ah boo! You’re have some backbone! Stand up for your JavaScript rights! :)

Okay, I’ll do a MinusMOR version that uses .ejs soon for you lilly livered liberals.

DanDan at 23.11.06 / 15PM

I agree with William, isn’t possible to create a new extension (.ejs) in order to preserve the .rjs template ?

Ok… i read just now your last post Dan. So, thanks for this feature you’re going to add ! :)

Sandro

Sandro PaganottiSandro Paganotti at 24.11.06 / 14PM

My Prototype Javascript TextMate language syntax supports ruby ERB.

http://subtlegradient.com/articles/2006/07/17/textmate-javascript-prototype-script-aculo-us

Thomas AylottThomas Aylott at 15.01.07 / 16PM

Thomas: Nice work, mate. That’s damn useful.

DanDan at 16.01.07 / 09AM

About This Article