Skip to content

Javascript/jQuery

Javascript is a very large subject matter, and the presumption is that you understand at least the bare essentials to take the certification exam, and there are plenty of places to learn Javascript online. As such the notes for this section will just cover Drupal-related Javascript topics.

One change from Drupal 7 to Drupal 8, is that jQuery is no longer automatically loaded. This means jQuery must be manually pulled in as a dependency. There are examples of that below.

Adding Javascript Libraries

Theme and module Javascript should be loaded in using asset libraries (i.e. *.libraries.yml files where * is the name of the theme or module).

Local Javascript Libraries

Suppose you have a theme named mytheme, which needs to include the following Javascript files:

text
fancy-ui-tabs.js
fancy-ui-accordion.js
fancy-ui-tables.js (dependent on underscore and jQuery)

It would follow that you would create mytheme.libraries.yml with the following contents:

yaml
fancy-ui:
  version: 1.x
  js:
    js/fancy-ui-tabs.js: {}
    js/fancy-ui-accordion.js: {}
fancy-ui-tables:
  version: 1.x
  js:
    js/fancy-ui-tables.js: {}
  dependencies:
    - core/underscore
    - core/jquery

External Javascript Libraries

Though it can be problematic for performance and security reasons, there are times when you will need to load external Javascript into your site.

For example loading AngularJS via a CDN, from Adding stylesheets (CSS) and JavaScript (JS) to a Drupal 8 theme:

yaml
angular.angularjs:
  remote: https://github.com/angular
  version: 1.4.4
  license:
    name: MIT
    url: https://github.com/angular/angular.js/blob/master/LICENSE
    gpl-compatible: true
  js:
    https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js: { type: external, minified: true }

Attaching Javascript

Once libraries have been defined, they need to be attached where they are needed.

Attaching to All Pages

To attach Javascript to all pages:

Under the libraries: section of mytheme.info.yml:

yaml
libraries:
  - 'mytheme/global-styling'
  - 'mytheme/bootstrap-scripts'
  - 'mytheme/fancy-ui'

Attaching to a Subset of Pages

You can use hooks to add conditionally.

For example, to only load on nodes (skipping other entities), add the following function to your mytheme.theme file.

php
function mytheme_preprocess_node(&$variables) {
  $variables['#attached']['library'][] = 'mytheme/fancy-tables';
}

Or if you only want to load on the maintenance page:

js
function mytheme_preprocess_maintenance_page(&$variables) {
  $variables['#attached']['library'][] = 'mytheme/fancy-tables';
}

Drupal.behaviors

Instead of using $(document).ready(function() {}), as is common in jQuery development, it is better to make use of Drupal.behaviors as it will ensure code runs on normal page loads, ajax calls and inside BigPipe.

To enable Drupal.behaviors you must add core/drupal as a Javascript dependency in your theme/module.

yaml
my-js-lib:
  version: 1.x
  js:
    js/my-js-lib.js: {}
  dependencies:
    - core/drupal

Example from Javascript API Overview:

javascript
Drupal.behaviors.myBehavior = {
  attach: function (context, settings) {
    // Using once() to apply the myCustomBehaviour effect when you want to do just run one function.
    $(context).find('input.myCustomBehavior').once('myCustomBehavior').addClass('processed');

    // Using once() with more complexity.
    $(context).find('input.myCustom').once('mySecondBehavior').each(function () {
      if ($(this).visible()) {
          $(this).css('background', 'green');
      }
      else {
        $(this).css('background', 'yellow').show();
      }
    });
  }
};

attach() is called once the DOM has loaded for all Drupal.behaviors properties, both on the initial page load, and any subsequent ajax calls.

Closures

Since Drupal's implementation of jQuery uses jQuery.noConflict(), it is also considered good practice to wrap your custom Drupal javascript inside of a closure like this:

js
(function ($, Drupal) {
  Drupal.behaviors.myModuleBehavior = {
    ...
  };
})(jQuery, Drupal);

Translating Strings in Javascript

When necessary, string translation can be performed in Javascript using Drupal.t():

js
// Define the name parameter
const name = 'John';

// Translate the string with the embedded parameter
const translatedString = Drupal.t('Hello, @name!', {
  '@name': name
});

// Output the translated string
console.log(translatedString); // "Hello, John!" (or the translated equivalent)

Distinguish between plural and singular translations in Javascript using Drupal.formatPlural():

js
// Define the number of messages
const messageCount = 3;

const translatedString = Drupal.formatPlural(
  messageCount,
  'You have 1 new message',          // Singular form
  'You have @count new messages',    // Plural form
  {'@count': messageCount}           // Replace @count with the actual number
);

// Output the translated string
console.log(translatedString); // "You have 3 new messages" (or the translated equivalent)

Note: @count is a special placeholder and does need to be defined in your variable array, like @type does. Additionally @count should never be used in the singular string, only the plural.

Preventing Cross-site Scripting (XSS)

Any user-provided input that has not been properly sanitized previously via Twig or PHP should be passed through Drupal.checkPlain() in JavaScript.

Strict Mode

From developer.mozilla.org - Strict mode:

ECMAScript 5's strict mode is a way to opt in to a restricted variant of JavaScript, thereby implicitly opting-out of "sloppy mode". Strict mode isn't just a subset: it intentionally has different semantics from normal code. Browsers not supporting strict mode will run strict mode code with different behavior from browsers that do, so don't rely on strict mode without feature-testing for support for the relevant aspects of strict mode. Strict mode code and non-strict mode code can coexist, so scripts can opt into strict mode incrementally.

Strict mode makes several changes to normal JavaScript semantics:

  1. Eliminates some JavaScript silent errors by changing them to throw errors.
  2. Fixes mistakes that make it difficult for JavaScript engines to perform optimizations: strict mode code can sometimes be made to run faster than identical code that's not strict mode.
  3. Prohibits some syntax likely to be defined in future versions of ECMAScript.

To place javascript in strict mode add 'use strict'; to the top of the document, closure, or function. For example:

js
(function ($, Drupal) {
  'use strict';

  Drupal.behaviors.myModuleBehavior = {
    ...
  };
})(jQuery, Drupal);

Strict Mode Restrictions

From docs.microsoft.com - Strict Mode (JavaScript):

Language elementRestrictionErrorExample
VariableUsing a variable without declaring it.SCRIPT5042: Variable undefined in strict modetestvar = 4;
Read-only propertyWriting to a read-only property.SCRIPT5045: Assignment to read-only properties is not allowed in strict modevar testObj = Object.defineProperties({}, { prop1: { value: 10, writable: false // by default }, prop2: { get: function () { } } }); testObj.prop1 = 20; testObj.prop2 = 30;
Non-extensible propertyAdding a property to an object whose extensible attribute is set to false.SCRIPT5046: Cannot create property for a non-extensible objectvar testObj = new Object(); Object.preventExtensions(testObj); testObj.name = "Bob";
deleteDeleting a variable, a function, or an argument. Deleting a property whose configurable attribute is set to false.SCRIPT1045: Calling delete on <expression>is not allowed in strict modevar testvar = 15; function testFunc() {}; delete testvar; delete testFunc; Object.defineProperty(testObj, "testvar", { value: 10, configurable: false }); delete testObj.testvar;
Duplicating a propertyDefining a property more than once in an object literal.SCRIPT1046: Multiple definitions of a property not allowed in strict modevar testObj = { prop1: 10, prop2: 15, prop1: 20 };
Duplicating a parameter nameUsing a parameter name more than once in a function.SCRIPT1038: Duplicate formal parameter names not allowed in strict modefunction testFunc(param1, param1) { return 1; };
Future reserved keywordsUsing a future reserved keyword as a variable or function name.SCRIPT1050: The use of a future reserved word for an identifier is invalid. The identifier name is reserved in strict mode.- implement
- interface
- package
- private
- protected
- public
- static
- yield
OctalsAssigning an octal value to a numeric literal, or attempting to use an escape on an octal value.SCRIPT1039: Octal numeric literals and escape characters not allowed in strict modevar testoctal = 010;
var testescape = \010;
thisThe value of this is not converted to the global object when it is null or undefined.function testFunc() { return this; } var testvar = testFunc();

In non-strict mode, the value of testvar is the global object, but in strict mode the value is undefined.
eval as an identifierThe string "eval" cannot be used as an identifier (variable or function name, parameter name, and so on).var eval = 10;
Function declared inside a statement or a blockYou cannot declare a function inside a statement or a block.SCRIPT1047: In strict mode, function declarations cannot be nested inside a statement or block. They may only appear at the top level or directly inside a function body.var arr = [1, 2, 3, 4, 5]; var index = null; for (index in arr) { function myFunc() {}; }
Variable declared inside an eval functionIf a variable is declared inside an eval function, it cannot be used outside that function.SCRIPT1041: Invalid usage of 'eval' in strict modeeval("var testvar = 10"); testvar = 15;

Indirect evaluation is possible, but you still cannot use a variable declared outside the eval function.

var indirectEval = eval; indirectEval("var testvar = 10;"); document.write(testVar);

This code causes an error SCRIPT5009: 'testVar' is undefined.
Arguments as an identifierThe string "arguments" cannot be used as an identifier (variable or function name, parameter name, and so on).SCRIPT1042: Invalid usage of 'arguments' in strict modevar arguments = 10;
arguments inside a functionYou cannot change the values of members of the local arguments object.function testArgs(oneArg) { arguments[0] = 20; }

In non-strict mode, you can change the value of the oneArg parameter by changing the value of arguments[0], so that the value of both oneArg and arguments[0] is 20. In strict mode, changing the value of arguments[0] does not affect the value of oneArg, because the arguments object is merely a local copy.
arguments.calleeNot allowed.function (testInt) { if (testInt-- == 0) return; arguments.callee(testInt--); }
withNot allowed.SCRIPT1037: 'with' statements are not allowed in strict modewith (Math){ x = cos(3); y = tan(7); }

Additional Resources

Questions

What change was made to jQuery in Drupal 8 compared to Drupal 7?

In Drupal 8, jQuery is no longer automatically loaded by default, as it was in Drupal 7. This means that in Drupal 8, developers must manually pull in jQuery as a dependency when needed.

How do you include local JavaScript libraries in a Drupal theme? Provide an example.

You would create the mytheme.libraries.yml file with the following contents:

yml
my_library:
  js:
    js/my-script.js: {}
  dependencies:
    - core/jquery
What are the advantages of using Drupal.behaviors over $(document).ready() in Drupal?

The primary advantage of using Drupal.behaviors over $(document).ready() in Drupal is that Drupal.behaviors ensures JavaScript code is executed not only on the initial page load but also during AJAX requests and on pages rendered via the BigPipe module.