So far the biggest slice of the development cake for this website was developing widgets - 3 little bears - a custom CSS component is called Fab in the PCSS file, in the top right corner of the site. Those are complex because several, diverse scopes are involved. In the back-end it's programmatically loaded View's exposed filter as Search widget, Website contact form as entity form as well as Sign in (class to extend) and Sign up form (entity form).
See more about Drupal/PHP code in the next segment. Here's a few notes for JavaScript code that was much needed here as-a-heroine:
- Search widget tiny code.
- Custom "fancy" form elements, inputs and buttons etc. that make interaction flow, label floating and default and x close icons.
- Custom collapsible widget that can be applied elsewhere too, uses data attributes for global logic.
Image
- Active class on Site logo's (SVG) path, loading spinner/logic - see these below in Common elements logic in JS code toggle.
- A specific JS code to make videos lazy-loaded. It seems like a good idea at the moment, in order to try to improve a page loading time and considering the fact that videos are stored as Media local videos, so not iFrame and no any player (such as videojs could be) yet - only a bare HTML5 video tag.
- Intersection Observer to follow sections (those are big) being in view and animating (fade in currently). See here.
- jQuery was intentionally skipped and around 600 lines of code in question, in total, were written pure vanilla style, actually according to the most modern Web APIs Why? Because skipping jQuery makes this code way more generic and "tune-able" into any of modern frameworks like Angular, React, Vue etc. and it seems it may have a bigger longevity/compatibility even with Drupal which is still including jQuery in the core at this point.
/* Common elements logic in JS */
Code
/**
* @file
* Ph TailwindCss theme scripts.
*/
(function(Drupal) {
'use strict';
document.onreadystatechange = () => {
let timeout;
if (document.readyState == 'interactive') {
// Always a good idea to clear timeout.
if (typeof timeout === 'number') {
clearTimeout(timeout);
}
} else if (document.readyState == 'complete') {
// Remove blur from the <main> element.
document.querySelector('main').classList.remove('blur-sm');
// Loading spinner animation handling.
const spinner = document.querySelector('[data-spinner="main"]');
if (spinner) {
spinner.classList.add('animate__fadeOut');
timeout = setTimeout((s) => {
s.remove();
}, 1500, spinner);
}
}
};
Drupal.behaviors.phDefault = {
attach: function(context, settings) {
if (settings.path.isFront) {
// Fill star "*" path in logo svg when on front page (miming "is-active" class).
const logo = document.getElementById('site-logo');
if (logo) {
const paths = logo.children[0].children;
if (paths[1]) {
paths[1].setAttribute('fill', '#ef4444b3');
}
}
}
}
};
})(Drupal);
/* Search widget JS */
Code
/**
* @file
* Ph* search widget.
*
* @ingroup Ph* core scripts.
*/
(function(Drupal) {
'use strict';
Drupal.behaviors.phSearch = {
attach: function(context, settings) {
const searchInputs = [].slice.call(context.querySelectorAll(
'[data-collapsible-search-target]'));
searchInputs.forEach(input => {
const collapsibleId = input.dataset.collapsibleSearchTarget;
if (collapsibleId && context.querySelector(
'[data-collapsible-search=' + collapsibleId + ']')) {
const toggle = context.querySelector(
'[data-collapsible-search=' + collapsibleId + ']');
if (toggle) {
let timeout;
toggle.addEventListener('click', event => {
event.preventDefault();
// Do out custom "grow" animation here.
// It works with the following Tailwind classes.
input.classList.toggle('scale-x-0');
input.classList.toggle('scale-x-100');
input.classList.toggle('invisible');
input.classList.toggle('visible');
// Take care of the siblings, being part of the same wrapper in DOM.
if (Drupal.behaviors.phDefault) {
const collapsibleParentId = event.currentTarget.dataset
.collapsibleParent;
let parent = collapsibleParentId ? context.querySelector(
'[data-collapsible-parent-id="' +
collapsibleParentId + '"]') : event.currentTarget
.parentNode.parentNode;
Drupal.behaviors.phDefault.siblings(event.currentTarget,
parent, '[data-collapsible]', {
'class': 'is-active',
'op': 'remove'
});
}
// Drupal's "is-active" class.
event.currentTarget.parentNode.classList.toggle(
'is-active');
// Focus on this search input after a while.
if (input.classList.contains('scale-x-100')) {
timeout = setTimeout((element) => {
element.focus();
}, 600, input);
} else {
// Always a good idea to clear timeout.
if (typeof timeout === 'number') {
clearTimeout(timeout);
}
}
});
// Always a good idea to clear timeout.
if (typeof timeout === 'number') {
clearTimeout(timeout);
}
}
}
});
}
};
})(Drupal);