3 Ways Email Breaks Down Nomination Workflows
Email is everywhere. It’s quick, convenient, and everyone knows how to use it. But when it becomes the primary tool…

// Configuration object for centralized settings
const TabbedNavConfig = {
DESKTOP_BREAKPOINT: 1201,
DEBOUNCE_DELAY: 250,
OPEN_FIRST_TAB: true,
CLOSE_TAB_ON_MOUSEOUT: false
};
// Custom config for tabbed hero containers
const tabbedHeroConfig = {
...TabbedNavConfig,
OPEN_FIRST_TAB: false,
CLOSE_TAB_ON_MOUSEOUT: true
};
// Function to determine the appropriate config based on container context
function getConfigForContainer(container) {
// Check if container has closest ancestor with [data-tabbed-hero="true"]
const heroAncestor = container.getAttribute('data-tabbed-hero') === 'true' ? container : container.closest('[data-tabbed-dropdown="true"]');
if (heroAncestor) {
return tabbedHeroConfig;
}
// Check for specific data-instance attributes (if you still want to support them)
// const instance = container.getAttribute('data-instance');
// if (instance === 'two') {
// // You can still have specific instance configs if needed
// return {
// ...TabbedNavConfig,
// OPEN_FIRST_TAB: false,
// CLOSE_TAB_ON_MOUSEOUT: true
// };
// }
// Default config
return TabbedNavConfig;
}
class TabbedNavigation {
constructor(container, config = TabbedNavConfig) {
this.container = container;
this.config = config;
this.activeItem = null;
this.isDesktop = window.innerWidth >= this.config.DESKTOP_BREAKPOINT;
this.hasMouseListeners = false;
this.megamenuParent = null;
this.megamenuObserver = null;
// Initialize bound handlers - these will be reused across reinitializations
this.mouseEnterHandler = this.handleMouseEnter.bind(this);
this.mouseLeaveHandler = this.handleMouseLeave.bind(this);
this.resizeHandler = this.debounce(this.handleResize.bind(this), this.config.DEBOUNCE_DELAY);
this.handleMegamenuToggle = this.handleMegamenuToggle.bind(this);
this.init();
}
init() {
// Cache DOM elements fresh each time
this.navItems = [...this.container.querySelectorAll('.dwc-tabbed-nav-list__li')];
this.elementsCache = new Map();
this.cacheElements();
this.setupAccessibility();
this.setupMegamenuObserver();
this.bindEvents();
// Set initial state
this.updateLayout();
this.setBackText()
// Show first item on page load only if desktop, not nested, and OPEN_FIRST_TAB is true
if (this.navItems.length > 0 && this.isDesktop && this.config.OPEN_FIRST_TAB) {
setTimeout(() => {
const firstItem = this.navItems[0];
// Count how many .dwc-tabbed-nav-container ancestors this.container has
let count = 0;
let current = this.container.parentElement;
while (current) {
if (current.classList?.contains('dwc-tabbed-nav-container')) {
count++;
}
current = current.parentElement;
}
const isNested = count >= 1;
if (!isNested && this.elementsCache.has(firstItem)) {
this.showTab(firstItem);
}
}, 0);
}
}
// Setup observer for megamenu parent
setupMegamenuObserver() {
// Find the .brx-has-megamenu parent
this.megamenuParent = this.container.closest('.brx-has-megamenu');
if (this.megamenuParent) {
// Set initial inert state
this.updateInertState();
// Create a MutationObserver to watch for class changes
this.megamenuObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
this.updateInertState();
}
});
});
// Start observing the megamenu parent for class changes
this.megamenuObserver.observe(this.megamenuParent, {
attributes: true,
attributeFilter: ['class']
});
}
}
// Update inert state based on megamenu open/closed state
updateInertState() {
if (!this.megamenuParent) return;
const isOpen = this.megamenuParent.classList.contains('open');
if (isOpen) {
this.container.removeAttribute('inert');
} else {
this.container.setAttribute('inert', '');
}
}
// Handle megamenu toggle (for manual triggering if needed)
handleMegamenuToggle() {
this.updateInertState();
}
// Method to clean up event listeners and reset state
cleanup() {
// Remove all event listeners
this.container.removeEventListener('click', this.handleClick);
this.container.removeEventListener('keydown', this.handleKeydown);
this.container.removeEventListener('focus', this.handleFocus);
// Remove mouse listeners if they exist
if (this.hasMouseListeners) {
this.container.removeEventListener('mouseenter', this.mouseEnterHandler, true);
this.container.removeEventListener('mouseleave', this.mouseLeaveHandler, true);
}
// Remove resize listener
window.removeEventListener('resize', this.resizeHandler);
// Clean up megamenu observer
if (this.megamenuObserver) {
this.megamenuObserver.disconnect();
this.megamenuObserver = null;
}
// Reset state
this.activeItem = null;
this.hasMouseListeners = false;
this.megamenuParent = null;
// Clear caches
if (this.elementsCache) {
this.elementsCache.clear();
}
this.navItems = [];
// Reset all tab states and accessibility attributes
const allNavItems = this.container.querySelectorAll('.dwc-tabbed-nav-list__li');
allNavItems.forEach(item => {
const button = item.querySelector('.dwc-tabbed-nav-list__li__button') ||
item.querySelector('.dwc-tabbed-nav-list__li__btn-txt');
const content = item.querySelector('.dwc-tabbed-nav-list__li__content');
// Remove classes
item.classList.remove('active');
// Reset button attributes
if (button) {
button.removeAttribute('role');
button.removeAttribute('aria-controls');
button.removeAttribute('aria-expanded');
button.removeAttribute('aria-selected');
button.removeAttribute('tabindex');
button.removeAttribute('id');
}
// Reset content attributes
if (content) {
content.removeAttribute('role');
content.removeAttribute('aria-labelledby');
content.removeAttribute('id');
content.removeAttribute('aria-hidden');
content.style.maxHeight = '';
}
// Remove data attributes
item.removeAttribute('data-tab-index');
});
// Reset nav list role
const navList = this.container.querySelector('.dwc-tabbed-nav-list');
if (navList) {
navList.removeAttribute('role');
}
// Remove inert attribute
this.container.removeAttribute('inert');
}
// Public method to reinitialize the navigation
reinitialize() {
this.cleanup();
this.isDesktop = window.innerWidth >= this.config.DESKTOP_BREAKPOINT; // Use config breakpoint
this.init();
}
// Cache DOM elements to avoid repeated queries
cacheElements() {
const uniqueIdBase = `tab-${Date.now()}`;
this.navItems.forEach((item, index) => {
const button = item.querySelector('.dwc-tabbed-nav-list__li__button') ||
item.querySelector('.dwc-tabbed-nav-list__li__btn-txt');
const content = item.querySelector('.dwc-tabbed-nav-list__li__content');
this.elementsCache.set(item, {
button,
content,
uniqueId: `${uniqueIdBase}-${index}`,
index
});
});
}
setupAccessibility() {
const navList = this.container.querySelector('.dwc-tabbed-nav-list');
if (!navList) return;
navList.setAttribute('role', 'tablist');
for (const [item, cache] of this.elementsCache) {
const { button, content, uniqueId, index } = cache;
if (button) {
button.setAttribute('role', 'tab');
button.setAttribute('aria-controls', `${uniqueId}-panel`);
button.setAttribute('aria-expanded', 'false');
button.setAttribute('aria-selected', 'false');
button.setAttribute('tabindex', '0');
button.setAttribute('id', `${uniqueId}-tab`);
}
if (content) {
content.setAttribute('role', 'tabpanel');
content.setAttribute('aria-labelledby', `${uniqueId}-tab`);
content.setAttribute('id', `${uniqueId}-panel`);
content.setAttribute('aria-hidden', 'true');
}
item.setAttribute('data-tab-index', index);
}
}
bindEvents() {
// Store bound handlers for cleanup
this.handleClick = this.handleClick.bind(this);
this.handleKeydown = this.handleKeydown.bind(this);
this.handleFocus = this.handleFocus.bind(this);
// Single event delegation for better performance
this.container.addEventListener('click', this.handleClick);
this.container.addEventListener('keydown', this.handleKeydown);
this.container.addEventListener('focus', this.handleFocus, true);
// Passive resize listener for better performance
window.addEventListener('resize', this.resizeHandler, { passive: true });
this.updateMouseListeners();
}
updateMouseListeners() {
if (this.isDesktop && !this.hasMouseListeners) {
this.container.addEventListener('mouseenter', this.mouseEnterHandler, true);
this.container.addEventListener('mouseleave', this.mouseLeaveHandler, true);
this.hasMouseListeners = true;
} else if (!this.isDesktop && this.hasMouseListeners) {
this.container.removeEventListener('mouseenter', this.mouseEnterHandler, true);
this.container.removeEventListener('mouseleave', this.mouseLeaveHandler, true);
this.hasMouseListeners = false;
}
}
// Helper method to get sibling nav items (nav items in the same container level)
getSiblingNavItems(navItem) {
// Find the direct parent container of this nav item
const navItemContainer = navItem.closest('.dwc-tabbed-nav-list');
if (!navItemContainer) return [];
// Get all nav items that are direct children of the same container
const siblingItems = [...navItemContainer.querySelectorAll('.dwc-tabbed-nav-list__li')];
// Filter to only include items that are siblings (not the navItem itself)
// and check if they belong to any TabbedNavigation instance
return siblingItems.filter(item => item !== navItem);
}
handleFocus(e) {
if (!this.isDesktop) return;
const button = e.target.closest('[role="tab"]');
if (!button) return;
const navItem = button.closest('.dwc-tabbed-nav-list__li');
if (!navItem || !this.elementsCache.has(navItem)) return;
this.handleTabInteraction(navItem, 'focus');
}
handleClick(e) {
const navItem = e.target.closest('.dwc-tabbed-nav-list__li');
if (!navItem || !this.elementsCache.has(navItem)) return;
const button = e.target.closest('.dwc-tabbed-nav-list__li__button, .dwc-tabbed-nav-list__li__btn-txt');
if (!button) return;
e.preventDefault();
this.handleTabInteraction(navItem, 'click');
}
handleKeydown(e) {
const button = e.target.closest('[role="tab"]');
if (!button) return;
const navItem = button.closest('.dwc-tabbed-nav-list__li');
if (!navItem || !this.elementsCache.has(navItem)) return;
const { index: currentIndex } = this.elementsCache.get(navItem);
switch (e.key) {
case 'Enter':
case ' ':
e.preventDefault();
this.handleTabInteraction(navItem, 'keyboard');
break;
case 'ArrowRight':
case 'ArrowDown':
e.preventDefault();
this.focusNextTab(currentIndex);
break;
case 'ArrowLeft':
case 'ArrowUp':
e.preventDefault();
this.focusPrevTab(currentIndex);
break;
case 'Home':
e.preventDefault();
this.focusTab(0);
break;
case 'End':
e.preventDefault();
this.focusTab(this.navItems.length - 1);
break;
}
}
handleMouseEnter(e) {
if (!this.isDesktop) return;
const navItem = e.target.closest('.dwc-tabbed-nav-list__li') ||
(e.target.matches('.dwc-tabbed-nav-list__li') ? e.target : null);
if (!navItem || !this.elementsCache.has(navItem)) return;
this.handleTabInteraction(navItem, 'hover');
}
handleMouseLeave(e) {
if (!this.isDesktop) return;
// Only close tabs if CLOSE_TAB_ON_MOUSEOUT is enabled
if (this.config.CLOSE_TAB_ON_MOUSEOUT) {
// Check if mouse is leaving the entire container
const rect = this.container.getBoundingClientRect();
const { clientX, clientY } = e;
// Check if mouse position is outside the container bounds
const isOutsideContainer = (
clientX < rect.left ||
clientX > rect.right ||
clientY < rect.top ||
clientY > rect.bottom
);
if (isOutsideContainer) {
this.hideAllTabs();
}
}
// If CLOSE_TAB_ON_MOUSEOUT is false, keep content visible (original behavior)
}
// Unified method to handle all tab interactions with nesting support
handleTabInteraction(navItem, interactionType) {
if (!this.elementsCache.has(navItem)) return;
if (this.isDesktop) {
// Simply hide siblings and show the target - works for all scenarios
this.hideSiblingTabs(navItem);
this.showTab(navItem);
} else {
// Mobile accordion behavior
if (interactionType === 'click' || interactionType === 'keyboard') {
this.toggleTabMobile(navItem);
}
}
}
// Hide only sibling tabs (same container level)
hideSiblingTabs(navItem) {
// console.log('Nav Item: ', navItem)
const siblings = this.getSiblingNavItems(navItem);
siblings.forEach(sibling => {
//console.log('sibling: ', sibling)
// Only hide siblings that have the active class to avoid affecting other instances
if (sibling.classList.contains('active')) {
this.hideSiblingTab(sibling);
}
});
}
// Hide a sibling tab (used when we know it's a sibling from another instance)
hideSiblingTab(navItem) {
const button = navItem.querySelector('.dwc-tabbed-nav-list__li__button') ||
navItem.querySelector('.dwc-tabbed-nav-list__li__btn-txt');
const content = navItem.querySelector('.dwc-tabbed-nav-list__li__content');
if (content && button) {
content.setAttribute('aria-hidden', 'true');
button.setAttribute('aria-expanded', 'false');
button.setAttribute('aria-selected', 'false');
navItem.classList.remove('active');
// Also handle mobile accordion collapse
if (!this.isDesktop) {
content.style.maxHeight = '0';
}
}
}
// Mobile-specific toggle behavior with proper nesting support
toggleTabMobile(navItem) {
if (!this.elementsCache.has(navItem)) return;
const isActive = navItem.classList.contains('active');
if (isActive) {
this.collapseTab(navItem);
} else {
// Close sibling tabs and open this one
this.hideSiblingTabs(navItem);
this.expandTab(navItem);
}
}
toggleTab(navItem) {
if (!this.elementsCache.has(navItem)) return;
if (this.isDesktop) {
this.handleTabInteraction(navItem, 'click');
} else {
this.toggleTabMobile(navItem);
}
}
showTab(navItem) {
if (!this.elementsCache.has(navItem)) return;
const { button, content } = this.elementsCache.get(navItem);
if (content && button) {
content.setAttribute('aria-hidden', 'false');
button.setAttribute('aria-expanded', 'true');
button.setAttribute('aria-selected', 'true');
navItem.classList.add('active');
}
this.setActiveTab(navItem);
}
hideTab(navItem) {
if (!this.elementsCache.has(navItem)) return;
const { button, content } = this.elementsCache.get(navItem);
if (content && button) {
content.setAttribute('aria-hidden', 'true');
button.setAttribute('aria-expanded', 'false');
button.setAttribute('aria-selected', 'false');
navItem.classList.remove('active');
}
// Only clear activeItem if it belongs to THIS navigation instance
// and it's the exact item being hidden
if (this.activeItem === navItem && this.navItems.includes(navItem)) {
this.activeItem = null;
}
}
expandTab(navItem) {
if (!this.elementsCache.has(navItem)) return;
// Close sibling tabs
this.hideSiblingTabs(navItem);
this.showTab(navItem);
// Smooth height animation
const { content } = this.elementsCache.get(navItem);
if (content) {
const height = content.scrollHeight;
content.style.maxHeight = `${height}px`;
}
}
collapseTab(navItem) {
if (!this.elementsCache.has(navItem)) return;
this.hideTab(navItem);
const { content } = this.elementsCache.get(navItem);
if (content) {
content.style.maxHeight = '0';
}
}
hideAllTabs() {
this.navItems.forEach(item => this.hideTab(item));
}
setActiveTab(navItem) {
// Only update aria-selected for tabs in THIS navigation instance
this.navItems.forEach(item => {
const cache = this.elementsCache.get(item);
if (cache?.button) {
cache.button.setAttribute('aria-selected', item === navItem ? 'true' : 'false');
}
});
// Only set activeItem if this navItem belongs to THIS navigation instance
if (this.navItems.includes(navItem)) {
this.activeItem = navItem;
}
}
focusTab(index) {
if (index >= 0 && index < this.navItems.length) {
const cache = this.elementsCache.get(this.navItems[index]);
if (cache?.button) {
cache.button.focus();
}
}
}
focusNextTab(currentIndex) {
const nextIndex = (currentIndex + 1) % this.navItems.length;
this.focusTab(nextIndex);
}
focusPrevTab(currentIndex) {
const prevIndex = currentIndex === 0 ? this.navItems.length - 1 : currentIndex - 1;
this.focusTab(prevIndex);
}
handleResize() {
const wasDesktop = this.isDesktop;
this.isDesktop = window.innerWidth >= this.config.DESKTOP_BREAKPOINT; // Use config breakpoint
if (wasDesktop !== this.isDesktop) {
this.updateMouseListeners();
this.updateLayout();
}
}
updateLayout() {
const currentlyActive = this.activeItem;
// Reset all states
this.hideAllTabs();
// Clear inline styles
this.navItems.forEach(item => {
const cache = this.elementsCache.get(item);
if (cache?.content) {
cache.content.style.maxHeight = '';
}
});
if (this.isDesktop) {
// Determine if this tab instance is nested
let count = 0;
let current = this.container.parentElement;
while (current) {
if (current.classList?.contains('dwc-tabbed-nav-container')) {
count++;
}
current = current.parentElement;
}
const isNested = count >= 1;
if (!isNested && this.config.OPEN_FIRST_TAB) {
if (currentlyActive) {
this.showTab(currentlyActive);
} else if (this.navItems.length > 0) {
this.showTab(this.navItems[0]);
}
}
}
}
setBackText(){
let tabElement = document.querySelector('.dwc-tabbed-nav-container');
let tabBackText = tabElement.getAttribute('data-back-text')
document.querySelectorAll('.dwc-tabbed-nav-list__li__button').forEach(tabBtn => {
const textContent = tabBtn.textContent.trim();
const tabIcon = tabBtn.querySelector('.dwc-tabbed-nav-list__li__arrow-icon');
if (tabIcon) {
tabIcon.setAttribute('data-text', textContent);
tabIcon.setAttribute('data-back-text', tabBackText);
}
});
}
// Simple debounce for resize
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Public method to destroy the instance completely
destroy() {
this.cleanup();
// Additional cleanup can be added here if needed
}
// Public method to manually update inert state (if needed)
updateInert() {
this.updateInertState();
}
}
// Simplified initialization on DOM ready
document.addEventListener('DOMContentLoaded', function() {
const tabbedNavContainers = document.querySelectorAll('.dwc-tabbed-nav-container');
tabbedNavContainers.forEach(container => {
// Skip if this container is nested inside another dwc-tabbed-nav-container
const parentNavContainer = container.parentElement.closest('.dwc-tabbed-nav-container');
if (parentNavContainer) {
return; // Skip nested containers
}
// Get the appropriate config for this container
const config = getConfigForContainer(container);
// Initialize with the determined config
new TabbedNavigation(container, config);
});
});
// For dynamic content - Updated version
window.initTabbedNav = function(container, customConfig = null) {
if (container && container.classList.contains('dwc-tabbed-nav-container')) {
// Check if this is a nested container
const parentNavContainer = container.parentElement.closest('.dwc-tabbed-nav-container');
if (parentNavContainer) {
return null; // Don't initialize nested containers
}
// Use custom config if provided, otherwise determine config automatically
const config = customConfig || getConfigForContainer(container);
return new TabbedNavigation(container, config);
}
return null;
};
/*
// TO REINITIALIZE USE THIS
// Create an instance of TabbedNavigation
const tabbedNav = new TabbedNavigation(document.querySelector('.dwc-tabbed-nav-container'));
// Call reinitialize on the instance
tabbedNav.reinitialize();
// TO USE WITH CUSTOM CONFIG
const customConfig = {
DESKTOP_BREAKPOINT: 768,
DEBOUNCE_DELAY: 300,
OPEN_FIRST_TAB: false, // This will prevent first tab from opening automatically
CLOSE_TAB_ON_MOUSEOUT: true // This will close tabs when mouse leaves container
};
const tabbedNavCustom = new TabbedNavigation(document.querySelector('.dwc-tabbed-nav-container'), customConfig);
// TO UPDATE ON URL CHANGE
function onUrlChange() {
setTimeout(function () {
const tabbedNav = new TabbedNavigation(document.querySelector('.dwc-tabbed-nav-container'));
tabbedNav.reinitialize();
}, 1500); // Delay execution by 2 seconds
}
// Listen for popstate event (history navigation)
window.addEventListener('popstate', onUrlChange);
// Listen for hashchange event (URL hash change)
window.addEventListener('hashchange', onUrlChange);
// Handle pushState and replaceState
(function (history) {
const pushState = history.pushState;
const replaceState = history.replaceState;
history.pushState = function (state) {
if (typeof history.onpushstate == "function") {
history.onpushstate({ state: state });
}
const result = pushState.apply(history, arguments);
onUrlChange(); // Call the function when pushState is used
return result;
};
history.replaceState = function (state) {
if (typeof history.onreplacestate == "function") {
history.onreplacestate({ state: state });
}
const result = replaceState.apply(history, arguments);
onUrlChange(); // Call the function when replaceState is used
return result;
};
})(window.history);
*/
console.log('%c<Jamie Sheehan Web Design Mega Menu Pro Tabbed Navigation v1.0>', 'color: #b388eb');Expert perspectives on oil logistics, workflows, and digital transformation — written by the people building the tools powering over 10B barrels a year.

Practical advice for optimizing field operations
Thought leadership on digitalizing inspection & logistics

Trends shaping the future of oil & energy tech
Email is everywhere. It’s quick, convenient, and everyone knows how to use it. But when it becomes the primary tool…

Loss control teams face monumental challenges with fragmented data, which impacts decision-making. Navarik Inspection digitally transforms this fragmented data landscape…

Vancouver, British Columbia Navarik is pleased to announce the appointment of Chris Cook as our new Chief Executive Officer, effective…

Vancouver, British Columbia - Navarik, a division of Vela Software Group (“Vela”), an operating group of Constellation Software Inc. (TSX: CSU), announces the acquisition of TIC…

Vancouver, Canada – January 22, 2025 – Navarik is excited to announce our successful migration to the cloud. This change reflects our…

Navarik is proud to announce the achievement of ISO 27001 certification for Navarik products, marking a significant milestone in the company’s…

Aligning catalog data has always been challenging within our industry. Duplicate and mismatched data across and within catalogs introduces ambiguity…

Meet Navarik’s Chief Technology Officer, Chris Cook.For this edition of our employee feature, we share Chris’ unique journey from his degrees…

Time is our most precious asset. To make every moment count, Schedulers set out on daily quests to cut time spent on repetitive, mundane tasks. With proper streamlining, it can feel…
Empowering Our Clients with Enhanced Data Security Over the past year, we have been diligently working towards acquiring the ISO 27001…