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);
Set all cards to the same height
The code in file drupal/web/sites/abc/themes/custom/uswds_base_abc/js/paragraphHeightNormalization.js
will normalize the height of all paragraphs within a specific container, ensuring they all have the same height based on the tallest card. This is useful when users can put different content into each paragraph and you want them to be consistent.
The .field--name-field-clp2-requirements
class targets the block field that holds the paragraphs.
The .paragraph--type--requirements-container
class targets each individual paragraph within that container.
((Drupal) => {
Drupal.behaviors.paragraphHeightNormalization = {
// Exclude execution on mobile or tablet devices (width < 1024px)
if (window.innerWidth < 1024) {
return;
}
attach: (context) => {
const paragraphContainers = context.querySelectorAll('.field--name-field-clp2-requirements');
paragraphContainers.forEach(container => {
const paragraphs = container.querySelectorAll('.paragraph--type--requirements-container');
// Find maximum height
const maxHeight = Array.from(paragraphs).reduce((max, paragraph) => {
return Math.max(max, paragraph.offsetHeight);
}, 0);
// Apply max height
paragraphs.forEach(paragraph => {
paragraph.style.height = `${maxHeight}px`;
});
});
}
};
})(Drupal);
Don't forget to add the library to your theme's uswds_base_abc.libraries.yml
file:
paragraph-height-normalization:
version: 1.x
js:
js/paragraphHeightNormalization.js: {}
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/