bootstrap_five_layouts-1.0.x-dev/modules/bootstrap_five_layouts_css_loader/js/behaviour.classHelper.js
modules/bootstrap_five_layouts_css_loader/js/behaviour.classHelper.js
/**
* @file
* Bootstrap Five Layouts CSS Loader - Class Helper Behavior
*
* Provides tab UI functionality for the help page class lists.
*/
(function (Drupal) {
'use strict';
/**
* Class Helper behavior for tab UI functionality.
*/
Drupal.behaviors.bootstrapFiveLayoutsClassHelper = {
attach: function (context) {
// Only attach once per page load
if (context !== document) {
return;
}
const tabHeader = document.getElementById('classlist-tabs-header');
if (!tabHeader) {
return;
}
// Get all tab links and panels
const tabLinks = document.querySelectorAll('#classlist-helper a[data-panel]');
const panels = document.querySelectorAll('#classlist-helper .panel');
const tabList = document.querySelector('#classlist-helper ul');
if (tabLinks.length === 0 || panels.length === 0) {
return;
}
// Add search input element via JavaScript
addSearchInput();
// Initialize search functionality
initializeSearch();
// Initialize tab functionality with accessibility
initializeTabs(tabLinks, panels, tabList);
// Handle tab clicks
tabLinks.forEach(function(link, index) {
link.addEventListener('click', function(e) {
e.preventDefault();
const targetPanel = this.getAttribute('data-panel');
showTab(targetPanel, tabLinks, panels, index);
});
// Handle keyboard navigation
link.addEventListener('keydown', function(e) {
handleKeydown(e, tabLinks, panels, index);
});
});
// Show first tab by default
if (tabLinks.length > 0) {
const firstTab = tabLinks[0].getAttribute('data-panel');
showTab(firstTab, tabLinks, panels, 0);
}
}
};
/**
* Add search input element to the page via JavaScript.
*/
function addSearchInput() {
const tabHeader = document.getElementById('classlist-tabs-header');
if (!tabHeader) {
return;
}
// Create search container
const searchContainer = document.createElement('header');
searchContainer.className = 'class-search-container';
// Create label
const label = document.createElement('label');
label.setAttribute('for', 'class-search-input');
label.textContent = Drupal.t('Live filtering across search classes');
// Create input
const input = document.createElement('input');
input.type = 'search';
input.id = 'class-search-input';
input.className = 'form-control';
input.placeholder = Drupal.t('Search classes…');
input.setAttribute('aria-describedby', 'search-help');
// Assemble the search container
searchContainer.appendChild(label);
searchContainer.appendChild(input);
// Insert after the header
tabHeader.insertAdjacentElement('afterend', searchContainer);
}
/**
* Initialize tab functionality by setting up initial state with accessibility.
*/
function initializeTabs(tabLinks, panels, tabList) {
// Set up ARIA attributes for tablist
if (tabList) {
tabList.setAttribute('role', 'tablist');
tabList.setAttribute('aria-label', 'Bootstrap class categories');
}
// Hide all panels initially and set up ARIA attributes
panels.forEach(function(panel, index) {
panel.style.display = 'none';
panel.setAttribute('role', 'tabpanel');
panel.setAttribute('aria-hidden', 'true');
panel.setAttribute('aria-labelledby', tabLinks[index].id || 'tab-' + index);
panel.querySelector('h2').style.display ='none';
});
// Add active class management and ARIA attributes
tabLinks.forEach(function(link, index) {
link.classList.add('tab-link');
link.setAttribute('role', 'tab');
link.setAttribute('aria-selected', 'false');
link.setAttribute('aria-controls', panels[index].id);
link.setAttribute('tabindex', '-1');
// Add unique ID if not present
if (!link.id) {
link.id = 'tab-' + index;
}
});
}
/**
* Show the specified tab and hide others with proper accessibility.
*/
function showTab(targetPanelId, tabLinks, panels, activeIndex) {
// Hide all panels
panels.forEach(function(panel, index) {
panel.style.display = 'none';
panel.setAttribute('aria-hidden', 'true');
});
// Remove active class and ARIA states from all links
tabLinks.forEach(function(link, index) {
link.classList.remove('active');
link.setAttribute('aria-selected', 'false');
link.setAttribute('tabindex', '-1');
});
// Show target panel
const targetPanel = document.getElementById(targetPanelId);
if (targetPanel) {
targetPanel.style.display = 'block';
targetPanel.setAttribute('aria-hidden', 'false');
}
// Add active class and ARIA states to clicked link
const activeLink = document.querySelector(`a[data-panel="${targetPanelId}"]`);
if (activeLink) {
activeLink.classList.add('active');
activeLink.setAttribute('aria-selected', 'true');
activeLink.setAttribute('tabindex', '0');
// Focus the active tab for keyboard users
activeLink.focus();
// Announce panel change to screen readers
const panelTitle = targetPanel ? targetPanel.querySelector('h3') : null;
const panelName = panelTitle ? panelTitle.textContent : Drupal.t('Panel');
Drupal.announce(Drupal.t('Switched to @panel panel', {'@panel': panelName}));
}
}
/**
* Handle keyboard navigation for tabs.
*/
function handleKeydown(e, tabLinks, panels, currentIndex) {
let targetIndex = currentIndex;
switch (e.key) {
case 'ArrowRight':
case 'ArrowDown':
e.preventDefault();
targetIndex = (currentIndex + 1) % tabLinks.length;
break;
case 'ArrowLeft':
case 'ArrowUp':
e.preventDefault();
targetIndex = currentIndex === 0 ? tabLinks.length - 1 : currentIndex - 1;
break;
case 'Home':
e.preventDefault();
targetIndex = 0;
break;
case 'End':
e.preventDefault();
targetIndex = tabLinks.length - 1;
break;
case 'Enter':
case ' ':
e.preventDefault();
const targetPanel = tabLinks[currentIndex].getAttribute('data-panel');
showTab(targetPanel, tabLinks, panels, currentIndex);
return;
default:
return; // Let other keys work normally
}
// Focus the target tab
tabLinks[targetIndex].focus();
}
/**
* Initialize search functionality for filtering classes.
*/
function initializeSearch() {
const searchInput = document.getElementById('class-search-input');
if (!searchInput) {
return;
}
// Add event listener for search input
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase().trim();
filterClasses(searchTerm);
});
// Add keyboard shortcuts
searchInput.addEventListener('keydown', function(e) {
// Escape key clears search
if (e.key === 'Escape') {
this.value = '';
filterClasses('');
this.focus();
}
});
}
/**
* Filter classes in all panels based on search term.
*/
function filterClasses(searchTerm) {
const panels = document.querySelectorAll('#classlist-helper .panel');
let totalVisibleCount = 0;
panels.forEach(function(panel) {
const table = panel.querySelector('table');
if (!table) {
return;
}
const tbody = table.querySelector('tbody');
if (!tbody) {
return;
}
const rows = tbody.querySelectorAll('tr');
let visibleCount = 0;
rows.forEach(function(row) {
const cells = row.querySelectorAll('td');
let isVisible = false;
// Check if any cell content matches the search term
cells.forEach(function(cell) {
const cellText = cell.textContent.toLowerCase();
if (cellText.includes(searchTerm)) {
isVisible = true;
}
});
// Show/hide row based on search match
if (isVisible || searchTerm === '') {
row.style.display = '';
visibleCount++;
} else {
row.style.display = 'none';
}
});
// Update panel visibility and add "no results" message if needed
updatePanelVisibility(panel, visibleCount, searchTerm);
// Update the count in the corresponding tab header
updateTabCount(panel, visibleCount, searchTerm);
totalVisibleCount += visibleCount;
});
// Announce search results to screen readers
if (searchTerm !== '') {
if (totalVisibleCount === 0) {
Drupal.announce(Drupal.t('No classes found matching "@search"', {'@search': searchTerm}));
} else {
Drupal.announce(Drupal.t('@count classes found matching "@search"', {
'@count': totalVisibleCount,
'@search': searchTerm
}));
}
} else {
Drupal.announce(Drupal.t('Search cleared, showing all classes'));
}
}
/**
* Update panel visibility and show/hide "no results" message.
*/
function updatePanelVisibility(panel, visibleCount, searchTerm) {
const table = panel.querySelector('table');
const existingNoResults = panel.querySelector('.no-results-message');
// Remove existing "no results" message
if (existingNoResults) {
existingNoResults.remove();
}
// Show/hide table based on results
if (visibleCount === 0 && searchTerm !== '') {
table.style.display = 'none';
// Add "no results" message
const noResultsDiv = document.createElement('div');
noResultsDiv.className = 'no-results-message alert alert-info';
noResultsDiv.innerHTML = '<strong>' + Drupal.t('Nothing with the pharse has been found.') + '</strong>';
// Insert after the panel title
const title = panel.querySelector('h2');
if (title) {
title.insertAdjacentElement('afterend', noResultsDiv);
}
} else {
table.style.display = '';
}
}
/**
* Update the count displayed in the tab header for the given panel.
*/
function updateTabCount(panel, visibleCount, searchTerm) {
const panelId = panel.id;
if (!panelId) {
return;
}
// Find the corresponding tab link
const tabLink = document.querySelector(`a[data-panel="${panelId}"]`);
if (!tabLink) {
return;
}
// Find the count span within the tab link
const countSpan = tabLink.querySelector('.count');
if (!countSpan) {
return;
}
// Store original count if not already stored
if (!countSpan.hasAttribute('data-original-count')) {
countSpan.setAttribute('data-original-count', countSpan.textContent);
}
// Update the count display
if (searchTerm === '') {
// Show original count when no filter is applied
countSpan.textContent = countSpan.getAttribute('data-original-count');
} else {
// Show filtered count
countSpan.textContent = visibleCount;
}
}
})(Drupal);
