A Better Way to Add Javascript and CSS with hook_library()
When I was first learning Drupal, I often used drupal_add_js() and drupal_add_css() whenever I needed files added to the rendered page. I remember hearing about hook_libray() (and seeing it often in contrib modules), but I scoffed at it's use since drupal_add_js() and drupal_add_css() typically did the job just fine. When I finally came to my senses and looked into hook_library(), I realized that this gem of a function was one of Drupal 7's best new features. I made the switch, why should you?
While drupal_add_js() and drupal_add_css() are great (and necessary) functions that quickly allow you to ensure files are included with the page request, they're really meant to be one-off solutions. But what if you ever get into a situation where more than one file needs to be added (ie multiple js/css files), or you need to add the same set of files in more than one place? You'll quickly find yourself repeating the same code and thus potentially creating more work if those files ever need to be replaced or requirements change.
What hook_library() offers is the ability to define (or package) a library as a set of Javascript and/or CSS files, then you can use drupal_add_library() without having to make sure each file is added manually. Think of it as saying, "Whenever this library is added, make sure these files/settings are included".
hook_library() in Practice
Let's say I have one Javascript file and one CSS file that need to be loaded. I could do the following:
- /**
- * Implements hook_library().
- */
- function mymodule_library() {
- // I usually get get the path to my module in advance for ease of use later
- $module_path = drupal_get_path('module', 'mymodule');
- $items = array();
- $items['awesomenesss'] = array(
- 'title' => t('Awesomenesss'),
- 'version' => '1.0',
- 'js' => array(
- $module_path . '/js/awesomeness.js' => array(),
- ),
- 'css' => array(
- $module_path . '/css/awesomeness.js' => array(),
- ),
- );
- return $items;
- }
Then, if I want to make sure it's added to a page, I just use hook_add_library() like so:
- /**
- * My page callback.
- */
- function mymodule_page() {
- // First param is module name, second is library name.
- drupal_add_library('mymodule', 'awesomenesss');
- return 'Awesome!';
- }
What's awesome about hook_library() is that it has the same flexibility of drupal_add_js() and drupal_add_css() in that you can also add inline code, external files (ie from a CDN), or javascript settings. Let's see that in action:
- /**
- * Implements hook_library().
- */
- function mymodule_library() {
- // I usually get get the path to my module in advance for ease of use later
- $module_path = drupal_get_path('module', 'mymodule');
- $items = array();
- // Awesomenesss is just a fictional example, you could use whatever name you like.
- $items['awesomenesss'] = array(
- 'title' => t('Awesomenesss'),
- // Version is required
- 'version' => '1.0',
- // js is an array of javascript files to add.
- 'js' => array(
- // see drupal_add_js() for more info about what goes here.
- $module_path . '/js/awesomeness.js' => array(),
- // Just as with drupal_add_js(), we can add settings here as well.
- array(
- 'data' => array('mymodule' => array('awesomeness' => array('level_of_awesome' => 50))),
- 'type' => 'setting',
- ),
- ),
- // css is also an array of files to add.
- 'css' => array(
- // see drupal_add_css for more info about what goes here.
- $module_path . '/css/awesomeness.css' => array(),
- ),
- // Inline css??? why not.
- array(
- 'data' => '.awesomestuff {text-decoration: blink;}',
- 'type' => 'inline',
- ),
- );
- return $items;
- }
While the same functionality could be achieved calling those functions manually, hook_library() offers a more structured way to package any required js/css files as a library that can easily be included.
But wait, there's more!
Imagine you've got a module that defines a piece of content that can be rendered in multiple locations...in fact, you might not even know where your content will show. You just define it, then it could show up in a block, on a page, as content of a node, etc. With the magical power of the #attached FAPI property, you could attach your library to any piece of content (built as a renderable array), and bypass the drupal_add_* calls altogether like so:
- /**
- * My page callback.
- */
- function mymodule_page() {
- $output = array();
- // Definitely need more awesome! Let's override the default and add below.
- $setting = array();
- $setting['mymodule']['awesomenesss']['level_of_awesome'] = 100;
- $output['awesome_list'] = array(
- '#theme' => 'item_list',
- '#title' => t('Awesome List'),
- '#items' => array(),
- // Here, we use #attached to add our library.
- '#attached' => array(
- 'library' => array(
- array('mymodule', 'awesomenesss'),
- ),
- // We could also manually add js/css files/settings here as well if needed like so
- 'js' => array(
- array('data' => $setting, 'type' => 'setting'),
- ),
- ),
- );
- return $output;
- }
Now, no matter where that content is rendered (in this case it's just a page, but I could do the same in a theme function, block definition, etc.), the needed files will be added. If the "library" ever needs to be updated, we now just update our hook_library() definition once and go back to enjoying life.
Convinced?
Hopefully you'll see how this can greatly help your development as it gives you a more structured way to make sure files are included.
One last note is that hook_library() also offers the ability to add dependencies to your library. In short, your library could make sure that one or more other libraries are loaded whenever it's included. This often comes in handy if I'm adding a jQuery plugin that should depend on core's jQuery UI libraries (ie datepicker, dialog, mouse, etc).
As always, this is just something I've learned over the past couple of years and I hope it helps someone else. If you have any tips, suggestions, or improvements I'd love to hear them.
Tags