I've been having a lot of fun with preprocess functions in Drupal 6.x, particularly when theming Views. It's a great system, but my colleague came across a nice "gotcha" this morning.
We have been adding JavaScript in Views preprocess functions if we want to provide tidy jQuery interfaces for Views output. This works great, so we thought nothing of doing the same for pages, which is where we came unstuck.
To understand why, you need to understand the process order of preprocess functions, as defined here:
http://drupal.org/node/223430
Following the logic in the above link, the preprocess function order for a page is something like this:
- template_preprocess
- template_preprocess_page
- yourmodule_preprocess_page
- yourtheme_preprocess_page
We also need to understand how drupal_add_js() works. It builds an array called $javascript
and each time it is called it adds another item to that array - one item for each time it is called. Just before the page is rendered, Drupal calls a function called drupal_get_js() which renders a HTML line for each item in the $javascript
array that drupal_add_js() built.
The key thing is drupal_get_js() is called only once - in template_preprocess_page(), in this line:
<?php
$variables['scripts'] = drupal_get_js();
?>
Consequently, if you add JavaScript using a drupal_add_js() call in yourmodule_preprocess_page() or yourtheme_preprocess_page() then it *will* be added to the $javascript
variable, but it will be too late, because it will be added *after* drupal_get_js() has been called to render the mark-up for the JavaScript to be presented on the page.
The solution?
You can either put your JavaScript elsewhere, somewhere which will render before template_preprocess_page() - a hook_menu() callback function would be a good candidate, for example, if this is possible. Or you can still use a drupal_add_js() in your module's page preprocess function, but you must rebuild the 'scripts' variable, like so:
<?php
$base = drupal_get_path('module', 'yourmodule');
drupal_add_js($base . '/js/nav-effect.js');
$variables['scripts'] = drupal_get_js();
?>
4 comments
Thanks, this saved the day
Thanks, this saved the day
Contradiction?
Reading this blog post I'm a bit confused. On the one had you say that if you use drupal_add_js() in your theme_preprocess_page() it won't won't work because drupal_get_js() has already been called. But as a solution you say to just rebuild $variables['scripts'] ... but that shouldn't work either since drupal_get_js() has already been called and won't be called again to render the HTML for the scripts.
Clarification
You can call drupal_get_js() any time. Drupal only calls it once, but *you* can call it whenever you need it.
If you want to invoke a drupal_add_js() *after* Drupal has called drupal_get_js() in the system page preprocess, you can call drupal_add_js() - which will still add your JavaScript to the list, then call drupal_get_js() again for the list of JavaScripts, now containing the one you just added.
Hope that clears it up...
Great post
This really helped point me in the right direction. I was still having trouble getting drupal_get_js() to regenerate properly in my page preprocess function. Through trial and error I came to realize that, if you're not adding your JS to the "header" region of the page, you have to pass the alternate region as an argument to drupal_get_js(). Likewise, you need to make sure you're rebuilding the correct page template variable. For example, I was adding JS to the footer section of the page, so in my preprocess function I needed to do:
$variables['closure'] = drupal_get_js('footer');
If you look at the default theme function for the closure, you can see this is exactly what Drupal does in order to pull in the footer JS.
Thanks again for the excellent post.
Post new comment