Warning message

Documentation is currently being migrated into the new system. Some pages might be temporarily missing, and some guides might appear empty. Thank you for your patience while we are improving Drupal.org documentation.

Managing JavaScript in Drupal 7

Last updated on
15 October 2016

Drupal 7 has introduced several new techniques that allow you far greater flexibility and control in the scripts you can have on your Drupal site's pages.

Adding JavaScript

In Drupal 7, there are four primary methods of adding JavaScript to Drupal.

Method 1: Module/Theme .info file

JavaScript files can be added to a module and/or theme .info files. When adding JavaScript using this method, it will be included on every page. This allows Javascript to be aggregated in an optimal way, and is the preferred method of adding Javascript that most visitors will need on most/all pages on a typical site visit. Example:

scripts[] = somescript.js

Method 2: drupal_add_js()

JavaScript can be added in any function using drupal_add_js(). See the rest of this page and the documentation page for the various options available when calling this function. Example:

drupal_add_js('path/to/somescript.js');

Method 3: #attached

Drupal 7 makes heavy use of render arrays. Any element of a render array can have the #attached property, and this property can be used to attach JavaScript to that element. Example:

$element['#attached']['js'][] = array(
  'type' => 'file',
  'data' => 'path/to/somescript.js',
);

Note that when possible, this method is preferred over drupal_add_js(), as the reference to the file is cached, whereas drupal_add_js() requires a function call to add the script to the page. In some instances, such as hook_block_view(), drupal_add_js() will not be called if the block is cached, and therefore #attached must be used to ensure your code is loaded with the block.

Method 4: Libraries

Drupal Libraries provide a standardized method of adding collections of JavaScript and CSS. Libraries can, for example, be used to integrate jQuery plugins with Drupal. If you have a set of JavaScript and/or CSS that could be considered a package, it can be provided as a library to other modules by implementing hook_library(). It can then be included by using either ['#attached']['library'] or drupal_add_library(). This is the preferred way to integrate with JavaScript and CSS which might be used by other modules.

An example of a system provided library can be found in system.module, which defines the Vertical Tabs library. This library includes one JavaScript file and one CSS file. The library is defined as follows:

function system_library() {
  ...
  // Vertical Tabs.
  $libraries['vertical-tabs'] = array(
    'title' => 'Vertical Tabs',
    'website' => 'http://drupal.org/node/323112',
    'version' => '1.0',
    'js' => array(
      'misc/vertical-tabs.js' => array(),
    ),
    'css' => array(
      'misc/vertical-tabs.css' => array(),
    ),
  );
  ...
  return $libraries;
}

The library can then be added using one of the following methods.
drupal_add_library() example:

// Module name: system
// Library name: vertical-tabs
drupal_add_library('system', 'vertical-tabs');

#attached example:

// Module name: system
// Library name: vertical-tabs
$element['#attached']['library'][] = array('system', 'vertical-tabs');

Do not directly add library files

drupal_add_js() and [#attached]['js'] should not be used for JavaScript which is part of a library (as all core JavaScript libraries are). Use drupal_add_library(), or ['#attached']['library'] instead.

Weighted JavaScript

When adding JavaScript, you can add a weighted "group" value to each script so you can control the order in which JavaScript is outputted on the page. The following constants are available to be used as values when using the "group" value:

  • JS_LIBRARY: Any libraries, settings, or jQuery plugins.
  • JS_DEFAULT: Any module-layer JavaScript.
  • JS_THEME: Any theme-layer JavaScript

Groups will be added to the page in the above order. All files in the JS_LIBRARY group will be added first, followed by files in the JS_DEFAULT group, and finally files in the JS_THEME group. JavaScript files added in modules are automatically added to the JS_DEFAULT group, and JavaScript files added in themes are automatically added to the JS_THEME group.

You can also use the "weight" property to fine-tune the output order of JavaScript files. The weight will affect files that have the same 'scope', 'group', and 'every_page' levels.

drupal_add_js() example:

drupal_add_js(
  'jQuery(document).ready(function () { alert("Hello!"); });',
  array(
    'type' => 'inline',
    'scope' => 'footer',
    'group' => JS_THEME,
    'weight' => 5,
  )
);

#attached example:

$element['#attached']['js'][] = array(
  'type' => 'inline',
  'data' => 'jQuery(document).ready(function () { alert("Hello!"); });',
  'scope' => 'footer',
  'group' => JS_THEME,
  'weight' => 5,
);

External JavaScript

External scripts can be included as follows.
drupal_add_js() example:

drupal_add_js('http://example.com/example.js', 'external');

#attached example:

$element['#attached']['js'][] = array(
  'type' => 'external',
  'data' => 'http://example.com/example.js',
);

Passing values from PHP to Javascript with "Drupal.settings"

You can easily make variables from PHP available to Javascript on the front end with Drupal.settings.

drupal_add_js() example:

  drupal_add_js(array('myModule' => array('key' => 'value')), 'setting');

#attached example:

$element['#attached']['js'][] = array(
  'type' => 'setting',
  'data' => array('myModule' => array('key' => 'value')),
);

This will be available in Javascript as:

  if (Drupal.settings.myModule.key === 'value') {
    alert('Got it!');
  }

NB: Drupal.settings may not be available until after your javascript has been attached to the page, which can result in undefined errors if it's being immediately called.

Overriding JavaScript

hook_js_alter() allows you to modify the path referenced by one of the scripts added by core or another module. An obvious example is if you want to use a newer version of jQuery than what comes with core:

function hook_js_alter(&$javascript) { 
  $javascript['misc/jquery.js']['data'] = drupal_get_path('module', 'jquery_update') . '/jquery.js'; // Swap out jQuery to use an updated version of the library
}

Behaviors

Behavior handling has changed in Drupal 7, with modules now required to explicitly define their attach handler, and optionally specify a detach handler.

Instead of the settings being a global object, settings are now passed to your handlers directly, after the context.

In Drupal 6, Drupal.behaviors was structured as follows:

Drupal.behaviors.exampleModule = function (context) {
  // Code to be run on page load, and
  // on ajax load added here
}

In Drupal 7, Drupal.behaviors is structured as follows:

(function ($) {
  Drupal.behaviors.exampleModule = {
    attach: function (context, settings) {
      // Code to be run on page load, and
      // on ajax load added here
    }
  };
}(jQuery));

These behaviors are instigated as you declare them in JavaScript so to clarify, there is no need for defining behaviors within a theme's or module's PHP file.

Additional References

Lullabot published a comprehensive article regarding Drupal behaviors.

Using jQuery

jQuery is now namespaced to avoid conflicts with other Javascript libraries such as Prototype. All your code that expects to use jQuery as $ should be wrapped in an outer context like so.

(function ($) {
  // All your code here
}(jQuery));

If you don't, you may see the error Uncaught TypeError: Property '$' of object [object DOMWindow] is not a function or similar.

jQuery $.once()

Drupal.behaviors will often be called multiple times on a page. For example, every time a form performs some Ajax operation, all Drupal behaviors will be executed again after page load, in order to attach any relevant JavaScript to the newly loaded elements. This can have the undesired affect of applying JavaScript to elements each time Drupal behaviors are executed, resulting in the same code being applied multiple times. To ensure that the JavaScript is applied only once, we can use the jQuery $.once() function. This function will ensure that the code inside the function is not executed if it has already been executed for the given element.

Using jQuery $.once() (integrated into D7 core), the developer experience of applying these effects is improved. Note that there is also the $.removeOnce() method that will only take effect on elements that have already applied the behaviors.

Drupal.behaviors.mybehavior = {
  attach: function (context, settings) {
    $('#some_element', context).once('mybehavior', function () {
      // Code here will only be applied to $('#some_element')
      // a single time.
    });
  }
};

For best-practice examples, it's helpful to look at (comparatively) simple examples such as misc/collapse.js to see how it's done.

See also: