The Lowdown on jQuery in Drupal - Part Two
Wow - one really does have to keep one's finger on the pulse with this jQuery business. Since Part One of this post there have been not one but three further releases of jQuery, so the current version is now 1.2.6. And I am happy to report that since then I have gotten involved in the effort to bring the jQuery Update module up to date, which entails not only trying to keep up with this moving target but also ironing out the bugs that have been popping up in all sorts of unexpected places due to the major API changes that came with version 1.2. Squashing a bug in one place sometimes makes a new one pop up in an entirely different module, which makes testing quite challenging, but there are many people committed to getting this out the door and I think the end may be in sight.
There is already a D6 version of jQuery Update, having been ported by webchick, so this module will most likely continue to be the bridge between the two disparate release cycles, but one would hope that eventually the process of updating and checking for backward compatibility will get smoother and smoother. One thing that will certainly help in this regard is the adoption by contrib module developers of best practices in writing any jQuery for their modules. Of course, we're never going to be able to predict which methods and selector expressions will become deprecated, but if we at least keep our code in line with a broad "Drupal way" of doing things, then it will be easier to fix bugs as the same patch might work for several modules.
Nitty-gritty example section
Warning: Technical jargon doth lie hereinA good example of the kind of best practices I'm talking about is the new approach to attaching behaviours in Drupal 6. When most of us learn jQuery for the first time, we learn to put all our code inside the $(document).ready function, like this:
$(document).ready(function(){
This ensures that our code will get run as soon as the DOM has loaded, manipulating elements and binding behaviours to events as per our instructions. However, as of Drupal 6, we don't need to include the $(document).ready() function in our jQuery code at all. Instead we put all our code inside a function that we assign as a property of Drupal.behaviors. Say what? Ok, I'll back up a little. In our misc/ directory in our Drupal install, we not only have the jquery.js file but we also have drupal.js (and several other js files, controlling functionality specific to certain pages or admin features, e.g. upload.js), which governs the general use of jQuery in Drupal. It declares the Drupal JavaScript object for the purpose of containing properties that need to be accessed and used by other js files. For example, the Drupal.settings property is used for passing an array of module settings to the js code for that module. You do this simply by calling drupal_add_js() in your php code and making sure the second parameter is passed in as "setting", e.g.:
// do some fancy stuff
});
drupal_add_js(array('mymodule' => $array_of_settings), 'setting');
In your js file, then, you can access these settings at Drupal.settings.mymodule. You have thus extended the Drupal.settings object with the mymodule property, which is itself an object containing all your settings.
Another property of the Drupal object is the behaviors object, i.e. Drupal.behaviors, and when we want our module to add new jQuery behaviours, we simply extend this object. The entire jQuery code for your module could be structured like this:
Drupal.behaviors.myModuleBehavior = function (context) {
But, you may wonder, all that does is declare a function - how does it even get called? Well, that's all looked after in drupal.js. It has a $(document).ready function which calls the Drupal.attachBehaviors function, which in turn cycles through the Drupal.behaviors object calling every one of its properties, these all being functions declared by various modules as above, and passing in the document as the context. The reason for doing it this way is that if your jQuery code makes AJAX calls which result in new DOM elements being added to the page, you might want your behaviours (e.g. hiding all h3 elements or whatever) to be attached to that new content as well. But since it didn't exist when the DOM was loaded and Drupal.attachBehaviors ran it doesn't have any behaviours attached. With the above set-up, though, all you need to do is call Drupal.behaviors.myModuleBehavior(newcontext), where newcontext would be the new, AJAX-delivered content, thus ensuring that the behaviours don't get attached to the whole document all over again. See here for the full example of how to use this code.
//do some fancy stuff
};
We have nedjo to thank for this elegant solution to a classic problem and it is in fact not exclusive to Drupal 6. The jstools package in Drupal 5 uses this exact pattern to control the behaviours of its modules - collapsiblock, tabs, jscalendar, etc.
</technical jargon>
The above illustrates the trend towards standard ways of doing jQuery in Drupal, which as I said should really help when it comes to updating for different versions. As a further step toward that goal, stella has initiated a plan to add js code to coder module, so we'll be able to check that we're complying with standards at the level of individual lines of code as well.
Beyond the nitty-gritty
One grand prize that awaits us if we manage to get jQuery Update up to date, is the UI plugin (see the great new website here, with lots of demos), which I mentioned in Part One, and for which there is a D6 Drupal module that provides it. There has been an attempt to provide this for Drupal 5 without using jQuery Update: the jQuery UI Backport module from starbow, which namespaces jQuery 1.2.1 and jQuery UI 1.0 so that they can be included in Drupal 5 without conflicting with the old jQuery version already there. But starbow himself admits that this is not the cleanest solution and that jQuery Update is really the way to go - which, though I agree with this, still seems a shame as it must have been quite a challenge to figure out all that namespacing! Anyway, like I said - and perhaps I'm being naïvely optimistic - it looks like we might be nearly there.
But before we get carried away thinking about all the lovely draggables and droppables we will then be able to add to our modules, it's important to ask whether they are really essential to them. Because, of course, including them will immediately add two module dependencies. If the UI-based functionality is solely for the purpose of enhancing your module's admin interface, then this would seem a little extravagant, to me at least. Especially when you consider the fact that things like drag and drop simply aren't expected by D5 users, whereas in D6 they're the norm (but only because the code for providing them is already in core).
AHAH and Drag&Drop: getting jQuery goodness without writing any jQuery
Which brings us finally to two wonderful new pieces of js code that are available for Drupal 6 module developers to make use of: ahah.js and tabledrag.js. The former is a re-working of the code used in the D5 AHAH Forms Framework module, which allows you to create forms that can have new elements added to them dynamically using AHAH (AJAX's less sophisticated cousin). This is an extremely useful piece of functionality which I was delighted to be able to make use of in the D6 version of Quick Tabs, it being an absolutely ideal candidate for such a feature: when creating a Quick Tabs block, simply click a button to instantly add a new tab. One thing I would say about this though, is that while it's great that the jQuery code for this is already there for you, this is really only half the battle when it comes to creating AHAH forms - the other big challenge lies in setting up the AHAH callback to return the new version of your form without breaching FAPI security.
The same is not true of tabledrag, the delightfully simple to use piece of core functionality that allows you to add the drag&drop re-arranging feature you've probably seen on the blocks page of D6 to your own module's admin interface. To learn how to use it, you need look no further than api.drupal.org, where the level of documentation for the drupal_add_tabledrag() function is nothing short of spectacular (relatively speaking of course ;-) But I will leave you with one more excellent resource that will help you get your head around that tricky AHAH stuff: quicksketch's very thorough drupaldojo screencast on AHAH which actually covers D&D briefly as well.
Well, this post turned out to be way longer than I had intended, but hopefully not too rambling. I think the subject of jQuery is only going to get more and more relevant to Drupal developers in general as the relationship between the two projects deepens and matures. It really does seem to be a perfect match.













Awesome stuff, really. I
Awesome stuff, really. I especially digged the part about extending the Drupal.settings object; I always wondered if there was a better way of loading jQuery behaviors within Drupal...
Once thing that still annoys me though is that I can't find a central documentation for jQuery/Drupal on api.drupal.org: there is the Javascript quickstart guide (http://api.drupal.org/api/file/developer/topics/javascript_startup_guide...) but it's, obviously, a quickstart guide. Where should we look for this kind of features; I know for example that there is now a Drupa.t() function, but only because I read the changelogs and articles around Drupal 6. Any advice, oh great jQuery guru ?
Thanks Ronan :-) ... but
Thanks Ronan :-)
... but I'm certainly no guru. Though the documentation is definitely sparse where jQuery is concerned, most of what has been done has been discussed and explained in specific issues so it just takes a bit longer to find full answers. For example, here's an explanation of JavaScript theming in the Theme Developer's Guide, but if you want an in-depth explanation of what the thinking is behind this and specifically on the Drupal.t() js function, then you'll want to read the discussion among the real gurus on this thread. Which I really ought to do, because I still haven't got my head around that part yet. And maybe I should see if I can help fill in some of the gaps on the javascript startup guide because it looks like the beginnings of a pretty useful piece of documentation...
I think I, as so many other
I think I, as so many other users of Drupal, need to take my member role more seriously. This jQuery startup guide may be a good starting point, but I definitely need to contribute to documentation.
Following your posts about jQuery, would you mind me geeking a bit around jQuery UI on this blog; I won't manage to match your articles but I got into some funny developments that could well illustrate what this library can do for our community...