refreshless-8.x-1.x-dev/modules/refreshless_turbo/js/behaviours.js
modules/refreshless_turbo/js/behaviours.js
(function(Drupal, drupalSettings, $) {
'use strict';
/**
* Our event namespace.
*
* @type {String}
*
* @see https://learn.jquery.com/events/event-basics/#namespacing-events
*/
const eventNamespace = 'refreshless-turbo-behaviour-manager';
/**
* RefreshLess Turbo behaviours attach and detach class.
*/
class Behaviours {
/**
* The context element to attach to; usually the <html> element.
*
* @type {HTMLElement}
*/
#context;
/**
* A Promise that resolves when all newly merged scripts have loaded.
*
* @type {Promise|null}
*/
#loadedPromise = null;
constructor(context) {
this.#context = context;
this.#bindEventHandlers();
};
/**
* Destroy this instance.
*/
destroy() {
this.#unbindEventHandlers();
}
/**
* Bind all of our event handlers.
*/
#bindEventHandlers() {
// @see https://ambientimpact.com/web/snippets/javascript-template-literal-as-object-property-name
$(this.#context).on({
[`refreshless:before-scripts-merge.${eventNamespace}`]: (event) => {
this.#beforeMergeHandler(event);
},
});
}
/**
* Unbind all of our event handlers.
*/
#unbindEventHandlers() {
$(this.#context).off(`.${eventNamespace}`);
}
/**
* 'refreshless:before-scripts-merge' event handler.
*
* @param {jQuery.Event} event
*/
#beforeMergeHandler(event) {
// Since this event handler is triggered twice, avoid overwriting the
// Promise if it exists.
if (this.#loadedPromise !== null) {
return;
}
// This needs to be registered before merging so that it's as early as
// possible in the page load cycle.
this.#loadedPromise = new Promise((resolve, reject) => {
$(this.#context).one({
[`refreshless:scripts-loaded.${eventNamespace}`]: resolve,
});
});
}
/**
* Attach behaviours.
*
* @param {Boolean} notify
* If true (the default), an attach event will be triggered.
*
* @return {Promise}
* A Promise that fulfills once Drupal.attachBehaviors() has been called.
*/
async attach(notify = true) {
// Delay attaching until all script elements have loaded.
await this.#loadedPromise;
// Set the Promise back to null so our before merge handler knows to
// create a new Promise on the next load.
this.#loadedPromise = null;
Drupal.attachBehaviors(this.#context, drupalSettings);
if (notify === true) {
const attachEvent = new CustomEvent('refreshless:attach', {
// Since we're dispatching on an abitrary context element.
bubbles: true,
});
this.#context.dispatchEvent(attachEvent);
}
return Promise.resolve();
};
/**
* Detach behaviours.
*
* @param {Boolean} notify
* If true (the default), a detach event will be triggered.
*
* @param {String} trigger
* The reason for the detach. Commonly used values:
*
* - 'unload': Triggered when the user is leaving the current page, both
* by core and RefreshLess.
*
* - 'serialize': Triggered by core's Ajax framework before form field
* values are collected and sent to the back-end.
*
* - 'move': Triggered by core's table drag on table rows.
*
* RefreshLess also provides the following:
*
* - 'refreshless:before-cache': Triggered on the page before it's about
* to be cached.
*
* - 'refreshless:cached-snapshot': Triggered on a cached snapshot that's
* about to be rendered.
*
* Defaults to 'unload' if not specified.
*
* @return {Promise}
* A Promise that fulfills when Drupal.detachBehaviors() has been called.
*/
detach(notify = true, trigger = 'unload') {
Drupal.detachBehaviors(this.#context, drupalSettings, trigger);
if (notify === true) {
const detachEvent = new CustomEvent('refreshless:detach', {
// Since we're dispatching on an abitrary context element.
bubbles: true,
detail: {
trigger: trigger,
},
});
this.#context.dispatchEvent(detachEvent);
}
return Promise.resolve();
};
}
// Merge Drupal.RefreshLess.classes into the existing Drupal global.
$.extend(true, Drupal, {RefreshLess: {classes: {
TurboBehaviours: Behaviours,
}}});
})(Drupal, drupalSettings, jQuery);
