Christopher Bloom
Frontend developer, lover of design systems, CSS architecture, and all things javascript.
Christopher Bloom
Photo by Jonas Denil on Unsplash
Photo by Fancycrave on Unsplash
// core/misc/drupal.init.es6.js
(function(domready, Drupal, drupalSettings) {
// Attach all behaviors.
domready(() => {
Drupal.attachBehaviors(document, drupalSettings);
});
})(domready, Drupal, window.drupalSettings);
// core/misc/drupal.es6.js
Drupal.attachBehaviors = function(context, settings) {
context = context || document;
settings = settings || drupalSettings;
const behaviors = Drupal.behaviors;
// Execute all of them.
Object.keys(behaviors || {}).forEach(i => {
if (typeof behaviors[i].attach === 'function') {
// Don't stop the execution of behaviors in case of an error.
try {
behaviors[i].attach(context, settings);
} catch (e) {
Drupal.throwError(e);
}
}
});
};
(function moduleNameClosure(local, alias, for, those, window, D) {
// We're safe in here ...
}(things, that, live, on, window, Drupal));
(function moduleNameClosure(D) {
D.behaviors.p2UniqueNamespace = {
attach: function drupalAttach() {
// Do stuff ...
},
};
}(Drupal));
Object.keys(Drupal.behaviors).find(b => b === 'myNamespace')
? 'DANGER! NAMESPACE EXISTS!'
: 'Your namespace is safe';
(function moduleNameClosure(D) {
D.behaviors.p2UniqueNamespace = {
attach: function drupalAttach(context) {
$('.thing', context)
.once('p2UniqueNamespace')
.addClass('flerp');
},
};
}(Drupal));
(function moduleNameClosure(D, $) {
D.behaviors.p2UniqueNamespace = {
attach() { // Enhanced object literals shorthand functions
$('.thing')
.on('click', () => doStuff()); // Arrow. Warning: scope
}, // Trailing comma = good
}; // semicolon
}(Drupal, jQuery));
(function moduleNameClosure(D, $) {
D.behaviors.p2UniqueNamespace = {
attach(context) { // "$" convention for jQuery element
$('.flerp', context).doAwesome();
// OR
$(context).find('.flerp').doAwesome();
},
};
}(Drupal, jQuery));
(function moduleNameClosure(D, $) {
D.behaviors.p2UniqueNamespace = {
attach(context, settings) {
const { bloomModule } = settings; // Destructuring
$('.flerp', context)
.doAwesome(bloomModule.awesomeLevel);
},
};
}(Drupal, jQuery));
function bloom_preprocess_html(&$vars) {
$vars['#attached']['drupalSettings']['bloomModule']['awesomeLevel'] = 'RAD';
}
(function moduleNameClosure(D, $) {
D.behaviors.p2UniqueNamespace = {
attach(context, settings) {
const { bloomModule } = settings;
const $flerp = $('.flerp', context); // Var your jQuery
// No settings or no DOM, GET OUT
if (!bloomModule || !$flerp.length) {
return;
}
$flerp.doAwesome(bloomModule.awesomeLevel);
},
};
}(Drupal, jQuery));
(function moduleNameClosure(D, $) {
D.behaviors.p2UniqueNamespace = {
attach(context, settings) {
const $header = $('.header', context);
const $menu = $header.find('.menu'); // scoped to $header
const $trigger = $menu.find('.trigger'); // scoped to $header $menu
// Single event bubbles up for many href elements
$header.on('click', 'a', function headerClick(e) {
$(e.delegateTarget).css('border', 'red'); // $header
$(this).css('color', 'blue'); // this = <a>
});
$trigger.on('click', () => $menu.toggle());
},
};
}(Drupal, jQuery));
(function moduleNameClosure(D, $) {
D.behaviors.p2UniqueNamespace = {
attach(context, settings) {
const $header = $('.header', context);
// "this" is VERY important to click()
$header.on('click', 'a', function headerClick() {
$(this).css('color', 'blue'); // this = <a>
});
// Just need to execute something, scope doesn't matter
$trigger.on('click', () => $menu.toggle());
},
};
}(Drupal, jQuery));
(function moduleNameClosure(D, $) {
// Helper function
function findAllTheThings(collection) {
return collection
.filter(({ name }) => name === 'Bill Murray')
.map(person => {
person.awesome = person.awesome * 10;
return person;
})
}
D.behaviors.p2UniqueNamespace = {
attach(context, settings) {
const { people } = settings;
if (!people) {
return;
}
$peopleDivs = findAllTheThings(settings.people)
.map(person => $(<div/>).text(person));
$('.people-holder', context).append($peopleDivs);
},
};
}(Drupal, jQuery));
// utils.js
(function utilsClosure(global) {
const onlyAwesome = collection =>
collection.filter(({ name }) => name === 'Bill Murray');
global.bloomUtils = {
onlyAwesome, // Enhanced object literals shorthand props!
};
}(window));
// bloom-module.js
(function moduleNameClosure(D, $, u) {
D.behaviors.p2UniqueNamespace = {
attach(context, settings) {
const { people } = settings;
if (!people) { return; }
$peopleDivs = u.onlyAwesome(people)
.map(person => $(<div/>).text(person));
$('.people-holder', context).append($peopleDivs);
},
};
}(Drupal, jQuery, bloomUtils));
(function moduleNameClosure(D, $) {
const state = {
menuOpen: true,
};
D.behaviors.p2UniqueNamespace = {
attach(context, settings) {
const state2Dom = (context, state) => {
$('.menu', context).toggleClass('open', state.menuOpen);
};
$('.toggle', context)
.on('click', () => state.menu = !state.menu);
state2Dom();
},
};
}(Drupal, jQuery));
(function moduleNameClosure(D, _) {
D.behaviors.p2UniqueNamespace = {
attach(context, settings) {
// get() is safe object querying
const thing =
_.get(settings, 'bloomModule.some.thing', 'default');
// assign() (available in ES6) cascades overrides of object
const defaults = { color: blue };
const carouselSettings =
_.assign({}, defaults, settings.carousel);
const days = _.range(1, 5).map(day => getResults(day));
$(window)
.on('resize', _.debounce(() => console.log('blerp')), 400));
},
};
}(Drupal, _));
(function moduleNameClosure(D) {
D.behaviors.p2UniqueNamespace = {
attach(context, settings) {
// Gross
let loop = 0;
const peopleArray = [];
for (loop; loop < settings.people.length; loop++) {
const person = settings.people[loop];
person.fullName = `${person.firstName} ${person.lastName}`;
peopleArray.push(person);
}
// Awww yisss
const betterArray = settings.people.map(person => ({
...person,
{ fullname: `${person.firstName} ${person.lastName}` },
}));
},
};
}(Drupal));
(function moduleNameClosure(D) {
D.behaviors.p2UniqueNamespace = {
attach(context, settings) {
const { murrayQuotesUri } = settings.p2;
const $widget = $('.widget', context);
if (!murrayQuotesUri || !$widget.length) { return; }
fetch(murrayQuotesUri)
.then(data => data.json())
.then(quotes => quotes.map(quote => $('<div/>').text(quote)))
.then($quotes => $widget.append($quotes));
},
};
}(Drupal))
// rando-module/js/rando-module.js
(function randoModuleClosure(D, $, u) {
D.behaviors.randoModule = {
attach(context, settings) {
// Other modules subscribe to the promise
u.quotes
.then(qs => qs.map(q => $("<div/>".text(q))))
.then($qsEl => $(".w", context).append($qsEl));
},
};
})(Drupal, jQuery, p2Utils);
If multiple behaviors share logic, move the logic to a dependent library
# p2-utils/p2-utils.libraries.yml
core:
js: js/utils.js
dependencies:
- core/drupal
- core/drupalSettings
// p2-utils/js/utils.js
(function utilsGlobalClosure(global) {
global.p2Utils = {
init(uri) {
this.quotes = fetch(uri)
.then(data => data.json());
}
};
})(window);
(function utilsModuleClosure(D, $, u) {
D.behaviors.p2Utils = {
attach(context, settings) {
const { murrayUri } = settings.p2;
if (murrayUri) {
u.init(murrayUri); // Util calls API
}
}
};
})(Drupal, jQuery, p2Utils);
# rando-module/rando-module.libraries.yml
core:
js: js/rando-module.js
dependencies:
- p2-utils/core
- core/drupal
- core/drupalSettings
// Mistaken that all functions had to be behaviors
Drupal.behaviors.randomFunction = function() { }
// Didn't understand settings arg
Drupal.behaviors.nameSpace = {
attach: function(context, settings) {
const mySetting = Drupal.settings.mySetting;
}
};
// Non-strict equality
var thing == 'something';
// .attach() already runs on $(document).ready()
$(document).ready(function() {});
// Stop using 'use strict'
'use strict';
Photo by Jeremy Thomas on Unsplash
By Christopher Bloom
Tips and best practices to write quality JavaScript in Drupal.
Frontend developer, lover of design systems, CSS architecture, and all things javascript.