j-j-j-jQuery. It’s on everyone’s lips, right? You love it or you hate it, or you’ve never tried it but you love it, or you’ve never tried it but you super-hate it. Yeah, we know.
Well, PJ and I launched FamSpam a bit ago and made the bold move of powering all the jabbascript with jQuery. We even wrote our own Facbeook-style lightbox library in jQuery (Facebox). So while this is a Ruby blog, indulge me for a moment as we dance with Ruby’s ugly-cool half-sister: Javascript.
whatQuery?
There are a bajillion posts about jQuery, all of which introduce you gently, so I will be brief: jQuery is all about a single namespace and kickass querying. (Get it?)
Our buddy Hpricot, you may remember, was heavily influenced by jQuery’s selector syntax. Which was, in turn, heavily influenced by CSS selectors. As such, some of this may look familiar:
$('#id').hide() $('.class').css('height', 20) $('#posts li > a').addClass('dark')
And so forth. One of the fun things is that any of those $() queries may return 0, 1, or more elements—yet the code stays the same. That’s right: our css() call would affect the height of all matched elements. Same with the addClass. But, if nothing is found, it’ll all silently fail. jAwesome!
niceQuery()
While some of the recent “jQuery vs <insert_framework_here>” blog posts might not be so nice, jQuery itself certainly is: it (mostly) easily works alongside other libraries. That means you can start dipping your toe into the jSauce while your Prototype or MooTools code doesn’t suspect a thing.
It’s easy:
jQuery.noConflict()My above examples would now be written like this:
jQuery('#id').hide() jQuery('.class').css('height', 20) jQuery('#posts li > a').addClass('dark')
The noConflict() call causes jQuery to defer ownership of $() to Moo or Proto, leaving your current js intact. How thoughtful. Check more at the comprehensive doc site.
chainQuery()
jQuery is all about chaining, in a big way. Here’s an example from FamSpam:
var person_email = $(this).parent().find('#person_email').val()
Pretty self explanatory. The find is scoped to the receiver, in this case the parent of the current element.
Another cool chain:
$('#invite_error').show().text('Please enter an email and a name.')
Hrm, we should probably put text() before show(), yeah? I love these kind of questions!
Finally, a slightly more advanced chain:
$('#facebox .body').children().hide().end(). append('<div class="loading"><img src="'+$s.loading_image+'"/></div>')
Get it? The end() reverts the most recent ‘destructive’ (read: find) operation. So we start with the .body, then find its children, then hide its children, then go back to .body and append some html. Slick, I think. Real slick.
Like I said, the doc site is super great.
ujsQuery()
Okay, here’s the segue: jQuery has unobtrusiveness built in. And it feels smooth. Real smooth.
Here, a snippet straight from FamSpam’s javascript:
$('.reset_invite_form').click(function() { $('#new_person').resetForm() $('#invite_another').hide() $('#invite_another > span').remove() $('#new_person').show() return false })
Pretty simple, right? And clear, to boot. What we do is slip this code inside of a function passed to $(document).ready(), which will be run when the, erm, document is, uh, ready.
Like this:$(document).ready(function { $('.reset_invite_form').click(function() { ... stuff ... }) })
So on and so forth. We attach Facebox to links the same way:
if ($.facebox) $('a[rel*=facebox]').facebox()
If the Facebox plugin is loaded, we find any links with a rel of “facebox” and convert them from normal links into jsery’d Facebox links. Easy as pie.
Which brings us, of course, to the segue.
spamQuery()
How are we using jQuery on FamSpam? jRails? Something custom? By hand?!
Yeah, well, by hand. We add all our behavior unobtrusively using the method detailed above. As far as I know, there’s no javascript in our html. If there is it’s on the run. For its life.
If you want to peep around, the js is (predictably) right here: http://famspam.com/javascripts/application.js.
Something to note: as of writing (1.2.2), jQuery doesn’t play nicely with Rails’ respond_to. But, hold the phone, it’s okay: a simple fix. Right here:
jQuery.ajaxSetup({ 'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript")} })
You’re now ready to rock.
Oh, one more thing before we move on: if you want some ajax-flavored will_paginate, check out this short guide. It was mentioned in another post, and now it’s mentioned here.
plugQuery()
jQuery, you see, has a wonderfully simple plugin system. We take full advantage of it by using a few choice plugins. Here’s a taste to wet your pallet.
The most essential plugin is the jQuery Form Plugin. With it, you can unobtrusively convert normal forms into ajax forms. The (obvious) advantage of this is graceful degradation, which is very kind but also very courageous.
$('#new_person').ajaxForm(function() { alert('Atta boy!') })
Seriously. Simple. And just so perfect for Rails—all the form’s attributes stay the same, including its action and method, just now it’s submitted through ajax. respond_to and jQuery are so in love it’s making me sick.
Another plugin we use is the Tablesorter. While we don’t have much tabular data on the promo or family sides of FamSpam, our admin interface is full of it. Want to sort your families by number of members? Conversations? Photos? It’s one line of code with this plugin. Sure, we’ll have to do some more complicated server-side sorting as our database grows, but this does the trick so quick right now.
$('#sorted_table').tablesorter()
See?
Another plugin we really love is the anti-aliased rounded corner plugin. Unfortunately this is not the most popular rounded corner plugin for jQuery, and that’s a shame. A damn shame. Because it’s definitely the best. We use it on the home page and other places we thought could use some class.
As usual, it’s dead simple once installed:
$('.corner').corner()
You don’t have to be so generic with it, but we like to be.
Finally, the brand new autocomplete plugin by ReinH and wycats is simple, small, and slick. See a pattern here? We’re using this on our admin site and couldn’t be happier. It speaks JSON, baby. Sign me up.
thatsitQuery()
Thanks for letting us stray for a moment from our normally dreary discourse. Got any other cool jQuery tips or treats? Leave ‘em in the comments.
Oh, a parting gift. More code to chew on: our tour.js. We use it to power the FamSpam tour. Enjoy.
I just can’t get past the Devo hats. They should have gone with a Foreigner belt or the wrap-around drumset ala Rush.
Just throwing out ideas here.
Yep, that’s pretty much how I do things: Rails + jQuery, writing the JS by hand. If you’re doing it right – as you point out – you can add all the js nice and unobtrusively in a separate file, and not pollute views with js-specific stuff. I’ve never really been convinced by RJS, either – if you’re going to use Javascript, write in Javascript! After all, jQuery made me actually want to write Javascript again. It makes many things fun again.
It also plays ball nicely with Prototype, should you be bound to that for certain things; I’m using both on one app, and switching between them dependent on the page you’re on.
Great post, Chris. ...But where are the tests?!? ;-)
Ahh, amazing. I just wrote a small rails app with jQuery and was frustrated by the responds_to stuff. Thanks!
You can do all of that with proto + lowpro, but I have to admit that JQuery looks sexier and is for sure slimmer. Thanks for yet another great post.
I fixed the rails2 thing but just placing a hidden_field with the token on the page. That way there’s no script tags and its only on the pages I need. Also if you’re doing anything other than a get request you’ll need to make sure you strip out the absolute part of the url and make it relative otherwise it trips security features in jquery. I’m actually working on a book for peepcode press all about jquery and rails. This gave me some motivation to get through it;)
I’ve been using jQuery and Rails since my first day with Rails. I couldn’t take Prototype and the messyness it created. Welcome to the club, I’m sure that now that you guys are involved with jQuery + Rails, it’s only going to get better and easier to integrate.
Just a note that there is a shortcut for the document.ready stuff:
Sorry:
$(function() { // do cool stuff });
Awesome stuff man. You inspired me a lot. I can’t wait myself tryin out jQuery. Kudos to you man! :)
Great piece. I’ve found it easy to use Rails 2.0 + jquery myself, with zero problems.
Here’s a question for you guys though (maybe something to address in a future article, idk).
How do you internally structure the js in your jQuery?
I love the selector stuff too, but I’ve felt like my js files always end up turning into code soup. I’ve been struggling to conceptualize a way to better internally structure and organize my code, or break it apart into files, or…i don’t know what.
Hey Chris,
Glad to see you finally posting about jQuery on Err proper. jQuery is a perfect companion to Rails; most people who do any kind of non-trivial JS end up writing custom stuff anyway, and the built-in Rails helpers end up being more trouble than they’re worth.
jQuery, on the other hand, is perfectly suited for progressive enhancement HTML applications; it correctly focuses on the DOM as the central programming notion of DOM scripting, which allows plugins to jump right into the mix.
Kudos!
Hi Chris,
You might want to check out Cornerz – a jQuery plugin that uses VML/Canvas. It’s very fast, small and maintains the layout. Check it here : http://www.parkerfox.co.uk/cornerz
Jonah
fogot to say – its about 10 times faster and only 4k uncompressed :)
I’ve also enjoyed mixing jQuery with rails; however, I wonder what techniques people are using to keep the javascript unobtrusive in div replacement scenarios?
To my way of thinking, it sometimes seems natural to package a chunk of markup with the behaviors it needs to operate properly. It’s particularly true when the markup operates as self contained component which may be used several places (for instance, via both partial includes and xhr div replacement).
In those situations, I prefer to call the behavioral javascript at the bottom of the markup rather than trying to ensure I add the behaviors externally (and repeatedly) on all page loads, xhr success handlers, etc. However, I usually suffer a few pangs of guilt with respect to ‘unobtrusive’ javascript.
Regular rails development seems to, at least cosmetically, avoid this dilemma by hiding the js behind helpers (does that count?). Anyone else have other techniques they prefer?
partage: Try the LiveQuery plugin. It makes what you’re talking about simple.
@Jim D.: Write your own jQuery plug-in. Here some good practices: http://www.learningjquery.com/2007/10/a-plugin-development-pattern
I just wanted to share a really killer event handling tidbit:
Generally you do something like:
jQuery(’.class’).click(function(){//whatever});
Everyone knows this can be rewritten as:
jQuery(’.class’).bind(‘click’, function(){//whatever});
But sometimes you need to unbind something:
jQuery(’.class’).unbind(‘click’, function(){//});
The problem with this is that it will unbind all the click events, not just yours. So if multiple bits of javascript have a click event handler for ’.class’, unbinding removes them all. (This is because there is no way to identify an anonymous function.)
But jQuery is so good that there is a way to handle this: Namespacing your events:
jQuery(’.class’).bind(‘click.namespace’, function(){//}); jQuery(’.class’).unbind(‘click.namespace’);
or for reinitializing an element added via ajax:
jQuery(’.class’)unbind(‘click.namespace’).bind(‘click.namespace’, function(){//});
I became a jQuery convert when I put together Make It Play, my MP3 player in a bookmarklet – jQuery can get in and out of foreign pages without causing a ruckus. The scoping is so robust I’ve even managed to install the whole thing as a Firefox extension without touching the code one bit. jQuery jForever!
jQuery rocks…
Cheers to all jQuery users there…
:)
Been enjoying jquery quite a bit over here as well.
Word to the wise: performance of selectors ($(”.mycoolclass”), $(“something.spiffy this.way comes”) and the like) is highly variable across browsers. You have been warned. Firefox 2.0, in particular, seems to suck the hardest.
Great post! I have been using jQuery on some stuff I’ve been playing with recently, and you pointed out a few things that I had missed in my travels in landQuery(). Can’t wait to keep poking.
Steven Bristol: excellent tip on the namespaced event unbinding! I’ve been hurting to do exactly that many times
what about mootools 1.2b?
The namespaced events are now also on the jQuery wiki
Thanks for this post, I’ve puttered around with prototype, mootools, and others, and this post (followed by downloading and playing with jQuery itself) made it click.
I’m astonished at how simple so many things become with jQuery.
Chris, Thanks for the post. I am enjoying jQuery. Its is ver nice. But, I am trying to use will_paginate and jQuery as it is explain in http://ozmm.org/posts/ajax_will_paginate_jq_style.html, and I am getting some very weird troubles with IE. The number or ajax calls is growing exponential each time I paginate.
Thanks for Facebox, it’s compact and useful. I wrote a Rails plugin called FaceboxRender (http://handlino.com/blog/2008/02/26/57/), just call “render_to_facebox” in will show a lightbox.
Would I be allowed to post a question here about facebox assistance without getting flamed? If not can someone direct me to a forum. I need to return a variable to facebox and having problems. Thanks
great post!
For those of you who are still using Prototype in your Rails projects (or any other project for that matter), I ported this schweet thang over to Prototype.
Check it out: http://dev.philburrows.com/svn/javascript/facebox/
I’ll write up a blog post about it soon on my blog
Great post! jQuery is great, fast and small!
Improved upon Andreas’ suggestion for handling authenticity token: http://henrik.nyh.se/2008/05/rails-authenticity-token-with-jquery
Now hyperlinked, I hope: http://henrik.nyh.se/2008/05/rails-authenticity-token-with-jquery
Chime in.