Using Javascript in Drupal
Closures
Since Drupal's implementation of jQuery uses jQuery.noConflict(), it is considered good practice to wrap your custom Drupal javascript inside of a closure like this:
(function ($, Drupal) {
Drupal.behaviors.myModuleBehavior = {
...
};
})(jQuery, Drupal);
More info here E.g.:
(function ($, Drupal) {
Drupal.behaviors.my_custom_behavior = {
attach: function (context, settings) {
// Code to be run on page load or refresh
console.log('Hello, World!');
}
};
})(jQuery, Drupal);
Add some global JS to the theme
In booktheme.info.yml
you need to specify the key to your theme library. Specifically the booktheme/global
library. This refers to a key global
in the booktheme.libraries.yml file. Here is the booktheme.info.yml
file:
name: Book Theme
type: theme
base theme: classy
description: A flexible theme with a responsive, mobile-first layout.
package: d9book
core_version_requirement: ^10 || ^11
libraries:
- booktheme/global
regions:
header: 'Header'
primary_menu: 'Primary menu'
secondary_menu: 'Secondary menu'
page_top: 'Page top'
page_bottom: 'Page bottom'
featured: 'Featured'
breadcrumb: 'Breadcrumb'
content: 'Content'
sidebar_first: 'Sidebar first'
sidebar_second: 'Sidebar second'
footer: 'Footer'
Then in the booktheme.libraries.yml
file you need a key global
and under that, refer to the file: booktheme.js
in the js
folder of the theme. Here is the booktheme.libraries.yml
file:
# Main theme library.
global:
js:
js/booktheme.js: {}
css:
base:
css/base/elements.css: {}
component:
css/components/block.css: {}
css/components/breadcrumb.css: {}
css/components/field.css: {}
css/components/form.css: {}
css/components/header.css: {}
css/components/menu.css: {}
css/components/messages.css: {}
css/components/node.css: {}
css/components/sidebar.css: {}
css/components/table.css: {}
css/components/tabs.css: {}
css/components/buttons.css: {}
layout:
css/layouts/layout.css: {}
theme:
css/theme/print.css: { media: print }
Then in the booktheme.js
file you can add your javascript. Here is the booktheme.js
file:
/**
* @file
* Book Theme behaviors.
*/
(function ($, Drupal) {
'use strict';
/**
* Behavior description.
*/
Drupal.behaviors.booktheme = {
attach: function (context, settings) {
console.log('It works!');
}
};
} (jQuery, Drupal));
You can add any key to your main libraries.yml file and then add it to the theme.info.yml file. The name is up to you. Notice here how I added a key global-stuff
to the selwyn.libraries.yml
file below. I also added a dependency to jQuery as jQuery is no longer loaded automatically on every page in Drupal:
global-stuff:
js:
js/selwyn.js: {}
dependencies:
- core/jquery
base:
version: VERSION
css:
component:
css/components/action-links.css:
weight: -10
...
progress:
version: VERSION
css:
component:
css/components/progress.css:
weight: -10
search-results:
version: VERSION
css:
component:
css/components/search-results.css: { }
user:
version: VERSION
css:
component:
css/components/user.css:
weight: -10
and then in my selwyn.info.yml
file I added the key selwyn/global-stuff
to the libraries key:
name: selwyn
type: theme
'base theme': stable9
starterkit: true
version: VERSION
libraries:
- selwyn/base
- selwyn/global-stuff
- selwyn/messages
- core/normalize
libraries-extend:
user/drupal.user:
- selwyn/user
core/drupal.dropbutton:
- selwyn/dropbutton
core/drupal.dialog:
- selwyn/dialog
file/drupal.file:
- selwyn/file
core/drupal.progress:
- selwyn/progress
core_version_requirement: ^10
generator: 'starterkit_theme:10.1.5'
Add JS to a module
In your module folder, add your js file. e.g. here in web/modules/general/js/jsplay.js
:
(function ($, Drupal) {
'use strict';
/**
* Behavior description.
*/
Drupal.behaviors.logitworks = {
attach: function (context, settings) {
console.log('It really works!');
}
};
} (jQuery, Drupal));
In your general.libraries.yml
file, add the key jsplay
and if you need jQuery, add it as a dependency
# Custom module library for general purposes.
jsplay:
version: 1.x
js:
js/jsplay.js: {}
dependencies:
- core/jquery
Then to get the js to load on any page content, add this to your module file:
/**
* Implements hook_preprocess_HOOK().
*/
function general_preprocess_page(&$variables) {
$variables['#attached']['library'][] = 'general/jsplay';
}
More at Adding Assets to a Drupal module via libraries
Standard JS IIFE (immediately invoked function expression)
(function (Drupal, $) {
"use strict";
// Our code here.
console.log('Yep - more stuff working.')
}) (Drupal, jQuery);
Click to enable a dropdown menu
(function (Drupal, $) {
"use strict";
Drupal.behaviors.selwynMenuTweaker = {
attach: function (context, settings) {
$(context).find('#df-user-account').on('click', function() {
flipNav();
});
function flipNav() {
$('#df-account-dropdown').toggle(400);
console.log('selwynclickedme');
}
}
};
})(Drupal, jQuery);
Cycle through some elements and add some text or css to them
(function (Drupal, $) {
Drupal.behaviors.andtestthis = {
attach: function (context, settings) {
$('#noah').append(" and test this") ;
}
};
}) (Drupal, jQuery);
or using native js forEach
:
(function (Drupal, $) {
Drupal.behaviors.logitworks = {
attach: function (context, settings) {
const noah_elements = document.querySelectorAll('#noah');
noah_elements.forEach(element => {
// Do something with each element
element.append('test');
});
const elements = document.querySelectorAll('.yomama');
elements.forEach(element => {
element.style.backgroundColor = 'red';
});
}
};
}) (Drupal, jQuery);
Add a quick function to run when the page is ready
(function($) {
$(document).ready(function() {
// Your code here.
console.log('Yep - more stuff working.');
});
})(jQuery);
Using Drupal.behaviors
:
(function($) {
// Define a namespace for your JavaScript code.
Drupal.behaviors.myModule = {
// This function is called when the document is ready.
attach: function(context, settings) {
// Add a message to the page.
$('body').append('<p>Hello, world!</p>');
}
};
})(jQuery);
Dropdown list
!function (document, Drupal, $) {
'use strict';
Drupal.behaviors.dropDownList = {
attach: function attach(context) {
$(document).on('click', '.js-header-dropdown__link', function () {
// Close all popups but this.
$('.js-header-dropdown__link')
.not(this)
.removeClass('is-dropdown')
.next($('.js-header-dropdown'))
.removeClass('is-open');
// Enable popup.
$(this)
.toggleClass('is-dropdown')
.next($('.js-header-dropdown'))
.toggleClass('is-open');
});
// Close popup when clicking outside container.
$(document).on('click', function (e) {
if (!$(e.target).closest('.js-header-dropdown__link').length)
$('.js-header-dropdown__link')
.removeClass('is-dropdown')
.next($('.js-header-dropdown'))
.removeClass('is-open');
});
// Close popup when hitting `esc` key.
$(document).keydown(function(e) {
if (e.keyCode == 27) {
$('.js-header-dropdown__link')
.removeClass('is-dropdown')
.next($('.js-header-dropdown'))
.removeClass('is-open');
}
});
}
};
}(document, Drupal, jQuery);
Asset library overview
These are collections of css and js files
Namespaced: theme_name/library_name
There are 3 ways to use asset libraries:
- Info file
- Preprocess function
{{ attach_library('classy/node') }}
Here is an example of an asset library in use:
In burger.info.yml
for the theme:
name: "Hamburger Theme"
description: "interesting description for the theme"
type: theme
core: 9.x
base theme: classy
libraries:
- burger/global-styling
So to add css in that library:
global-styling:
css:
layout:
css/custom.css: {}
theme:
css/custom.css: {}
and make a css
folder in the theme folder with a file called custom.css:
* {
color: red;
}
Don't forget to flush caches.
Attaching a library to specific nodes
To only load on nodes (skipping other entities), add the following function to your .theme
file.
function mytheme_preprocess_node(&$variables) {
$variables['#attached']['library'][] = 'mytheme/fancy-tables';
}
Add Javascript to a project using asset libraries
In this project at web/themes/custom/txglobal/txglobal.libraries.yml
we are trying to load some js in the file js/globe.js
only on specific pages. The asset library is defined below (see map:) and in the template file, we specify which assets to include. That causes globe.js
to be loaded when that template is used.
global:
version: VERSION
css:
base:
css/txglobal.css: {}
js:
js/txglobal.js: {}
map:
version: 1.x,
js:
js/globe.js: {}
In the node--map.html.twig
file at web/themes/custom/txglobal/templates/content/node--map.html.twig
we attach the library txglobal.libraries.yml
from above using:
{{ attach_library('txglobal/map') }}
Retrieve values from select fields on a form and update other fields
(function ($, Drupal) {
/**
* Handle controlling the node add/edit for the staff profile content type.
*
*/
Drupal.behaviors.areasOfExpertiseImprovements = {
attach: function () {
// The select field for the staff profile type.
const typeSelect = $('.staff-profile-form #edit-field-staff-profile-type');
// The select field for the primary title.
const cccPrimaryTitleSelect = $('#edit-field-staff-profile-title');
...
/**
* Handle the areas of expertise field.
*/
const updateAreasOfExpertise = () => {
// Get the value of the primary title select field as an integer.
const cccPrimaryTitle = parseInt(cccPrimaryTitleSelect.val(),10);
// Get the value of the type select (taxonomy) field as an integer.
const typeSelectVal = parseInt(typeSelect.val(),10);
if (typeSelectVal === 2574) {
handlePI(); // Type = PI.
}
// Type = Other Staff.
else if (typeSelectVal === 2575) {
// Array of primary title tid's that are considered valid for Other Staff.
const validCccPrimaryTitles = [2477, 2478, 2558, 2651];
if (validCccPrimaryTitles.includes(cccPrimaryTitle)) {
handleOtherStaffWithCccPrimaryTitle();
} else {
handleOtherStaff();
}
}
};
...
// Update field on the initial form load.
updateAreasOfExpertise();
// Update field on the Staff Profile type change.
typeSelect.on('change', function () {
resetAllAreasOfExpertise(true);
updateAreasOfExpertise();
});
// Update fields on the Primary Title change.
cccPrimaryTitleSelect.on('change', function () {
updateAreasOfExpertise();
});
}
};
})(jQuery, Drupal);
ESLint
ESLint can be used to make sure JavaScript code is consistent, free from syntax errors, leaking variables and that it can be properly minified.
ESLint is a Node.js module and is integrated into a number of IDEs. (See installation and usage instructions.) One of the fastest ways to install all necessary ESLint dependencies is using eslint-config-drupal.
The following configuration files ship with Drupal and can be used from there: .eslintrc.json
and .eslintignore
.
In a Drupal folder these configuration files will be automatically detected and used by ESLint when it is invoked from within the code base.
Installation
# Before running these commands, install node.js, npm, and npx
npm install eslint --save-dev
npm i eslint-config-drupal
npx eslint modules/custom/
npx eslint themes/custom/