Section 1 Title

Content...

Section 2 Title

Content...

Use code with caution. Html JavaScript Explanation (Internal SPA Navigation Script in dns-security-page.html) // SCRIPT FOR THIS PAGE'S INTERNAL SECTION NAVIGATION document.addEventListener('DOMContentLoaded', function() { // --- Initialization and Setup --- const pagePath = window.location.pathname; const pageName = pagePath.substring(pagePath.lastIndexOf('/') + 1) || 'dns-security-page.html'; // For logging console.log(`[Content Page - ${pageName}] DOMContentLoaded. Initializing internal SPA-like navigation.`); // 1. Get DOM Elements for Navigation and Content const internalNavContainer = document.querySelector('body > nav.main-nav'); const contentDisplayAreaForSections = document.querySelector('body > main#content-display-area'); // Critical Check: If these main containers aren't found, nothing else will work. if (!internalNavContainer) { console.error(`[Content Page - ${pageName}] CRITICAL: This page's internal 'nav.main-nav' (body > nav.main-nav) not found!`); return; // Stop script execution } if (!contentDisplayAreaForSections) { console.error(`[Content Page - ${pageName}] CRITICAL: This page's 'main#content-display-area' (body > main#content-display-area) not found!`); return; // Stop script execution } // Elements within the navigation container const internalNavToggles = internalNavContainer.querySelectorAll('.nav-toggle'); // All clickable toggles/links in nav const internalAllLinksWithDataContentId = internalNavContainer.querySelectorAll('a[data-content-id]'); // Links that directly map to content sections // Content sections (direct children of the content display area) const internalContentSections = contentDisplayAreaForSections.querySelectorAll(':scope > .content-section'); // Top Back/Next buttons (IDs should be unique within this page) const internalTopBackBtn = document.getElementById('top-nav-back'); const internalTopNextBtn = document.getElementById('top-nav-next'); // Logging to verify elements are found console.log(`[Content Page - ${pageName}] Internal Top Back Button:`, internalTopBackBtn); console.log(`[Content Page - ${pageName}] Internal Top Next Button:`, internalTopNextBtn); console.log(`[Content Page - ${pageName}] Found internal nav links with data-content-id:`, internalAllLinksWithDataContentId.length); console.log(`[Content Page - ${pageName}] Found internal content sections:`, internalContentSections.length); // Force display of top nav buttons if they are found (for debugging visibility) if(internalTopBackBtn) internalTopBackBtn.style.display = 'inline-block'; if(internalTopNextBtn) internalTopNextBtn.style.display = 'inline-block'; // 2. Handle cases where no navigable content or links are found if (internalAllLinksWithDataContentId.length === 0 || internalContentSections.length === 0) { console.warn(`[Content Page - ${pageName}] No internal navigation links with 'data-content-id' OR no '.content-section' divs found. Internal Back/Next will be disabled.`); if(internalTopBackBtn) internalTopBackBtn.disabled = true; if(internalTopNextBtn) internalTopNextBtn.disabled = true; contentDisplayAreaForSections.querySelectorAll('.content-nav button').forEach(btn => btn.disabled = true); // Disable in-section buttons // Fallback: If sections exist but no links, try to show the first section if (internalContentSections.length > 0 && internalContentSections[0]) { const firstSection = internalContentSections[0]; if (firstSection) { console.log(`[Content Page - ${pageName}] No nav links found, attempting to show first section: #${firstSection.id}`); firstSection.style.display = 'block'; // FORCE display firstSection.classList.add('active'); // Add active class // Update buttons for this single shown section const backBtn = firstSection.querySelector('.content-nav .btn-back'); const nextBtn = firstSection.querySelector('.content-nav .btn-next'); if(backBtn) backBtn.disabled = true; if(nextBtn) nextBtn.disabled = true; } } return; // Stop further navigation setup } // 3. Prepare Navigation Data Structures // internalAllContentIds: An ordered array of section IDs based on the order of links in the nav. // This determines the sequence for Back/Next. const internalAllContentIds = Array.from(internalAllLinksWithDataContentId).map(link => link.dataset.contentId); // internalAvailableSectionIds: A Set for quick lookup to check if a section ID actually exists in the DOM. const internalAvailableSectionIds = new Set(Array.from(internalContentSections).map(section => section.id)); console.log(`[Content Page - ${pageName}] Internal Content IDs for nav sequence:`, JSON.stringify(internalAllContentIds)); console.log(`[Content Page - ${pageName}] Available internal section IDs in DOM:`, Array.from(internalAvailableSectionIds)); // Store original text of dropdown toggles (those with data-original-text) internalNavToggles.forEach(toggle => { if (toggle.dataset.originalText) { // Only if it's a dropdown parent toggle.dataset.originalText = toggle.textContent.trim(); } }); // --- Core Functions --- // Function to close all open submenus in the internal navigation function internalCloseAllSubmenus() { internalNavContainer.querySelectorAll('.submenu.show').forEach(openSubmenu => { openSubmenu.classList.remove('show'); }); } // Attaching click listeners to navigation elements internalAllLinksWithDataContentId.forEach(link => { link.addEventListener('click', function(event) { event.preventDefault(); const contentIdFromLink = this.dataset.contentId; // console.log(`[CP - ${pageName}] Nav link clicked for section: ${contentIdFromLink}`); if (internalAvailableSectionIds.has(contentIdFromLink)) { internalShowContent(contentIdFromLink); } else { console.error(`[CP - ${pageName}] Nav link for "${contentIdFromLink}", but section not in internalAvailableSectionIds.`); } internalCloseAllSubmenus(); }); }); // Add listeners for dropdown toggles that DON'T have data-content-id but have submenus internalNavContainer.querySelectorAll('.nav-toggle:not([data-content-id])').forEach(toggle => { const submenu = toggle.nextElementSibling; if (submenu && submenu.classList.contains('submenu')) { toggle.addEventListener('click', function(event) { event.preventDefault(); const isOpen = submenu.classList.contains('show'); internalCloseAllSubmenus(); // Close others first if (!isOpen) submenu.classList.add('show'); // Then open this one if it wasn't already }); } }); // Attach listeners to Top Back/Next buttons (if they exist) if (internalTopBackBtn) { internalTopBackBtn.addEventListener('click', function() { if (!this.disabled) internalNavigateContent(-1); }); } if (internalTopNextBtn) { internalTopNextBtn.addEventListener('click', function() { if (!this.disabled) internalNavigateContent(1); }); } // Event delegation for in-section Back/Next buttons contentDisplayAreaForSections.addEventListener('click', function(event) { const targetButton = event.target; // Ensure the button is within a .content-nav of an *active* section if (targetButton.closest('.content-section.active .content-nav')) { if (targetButton.classList.contains('btn-back') && !targetButton.disabled) { internalNavigateContent(-1); } else if (targetButton.classList.contains('btn-next') && !targetButton.disabled) { internalNavigateContent(1); } } }); // Function to show a specific content section and hide others function internalShowContent(contentId) { console.log(`[CP - ${pageName}] internalShowContent - START - Target ID: "${contentId}"`); const sectionToShow = document.getElementById(contentId); // Validate that the section exists and is in our list of known available sections if (!sectionToShow || !internalAvailableSectionIds.has(contentId)) { console.error(`[CP - ${pageName}] ERROR in internalShowContent: Section element with ID "#${contentId}" NOT FOUND or not in available set.`); // Fallback: Hide all sections and disable nav buttons internalContentSections.forEach(s => { s.classList.remove('active'); s.style.display = 'none'; }); if(internalTopBackBtn) internalTopBackBtn.disabled = true; if(internalTopNextBtn) internalTopNextBtn.disabled = true; contentDisplayAreaForSections.querySelectorAll('.content-nav button').forEach(btn => btn.disabled = true); return; } console.log(`[CP - ${pageName}] internalShowContent: Found section element to show:`, sectionToShow); // 1. Hide all sections internalContentSections.forEach(section => { section.classList.remove('active'); section.style.display = 'none'; // Explicitly hide }); console.log(`[CP - ${pageName}] internalShowContent: All sections hidden.`); // 2. Reset nav link active states and dropdown parent texts internalNavToggles.forEach(toggle => { const originalText = toggle.dataset.originalText; if (originalText) toggle.textContent = originalText; // Reset text if it's a dropdown parent toggle.classList.remove('active'); }); internalAllLinksWithDataContentId.forEach(link => link.classList.remove('active')); // Deactivate all specific content links // 3. Show the target section console.log(`[CP - ${pageName}] internalShowContent: Setting display:block and adding .active to #${contentId}`); sectionToShow.style.display = 'block'; // FORCE it to be block sectionToShow.classList.add('active'); // Add .active class // Verify styles immediately after modification const computedStyle = window.getComputedStyle(sectionToShow); console.log(`[CP - ${pageName}] internalShowContent: AFTER style change, computed display for #${contentId}: ${computedStyle.display}. ClassList: ${sectionToShow.classList}`); if (!sectionToShow.classList.contains('active')) { console.error(`[CP - ${pageName}] internalShowContent: FAILED to add 'active' class to #${contentId}`); } if (computedStyle.display !== 'block') { console.error(`[CP - ${pageName}] internalShowContent: Computed display for #${contentId} is NOT 'block', it is '${computedStyle.display}'. CSS override likely.`); } // 4. Update active state for the clicked navigation link and its parent (if in submenu) const newActiveLink = internalNavContainer.querySelector(`a[data-content-id="${contentId}"]`); if (newActiveLink) { newActiveLink.classList.add('active'); const parentSubmenu = newActiveLink.closest('.submenu'); if (parentSubmenu) { const parentToggle = parentSubmenu.previousElementSibling; // The tag that toggles the submenu if (parentToggle && parentToggle.classList.contains('nav-toggle') && !parentToggle.dataset.contentId) { // Ensure it's a dropdown parent parentToggle.classList.add('active'); parentToggle.textContent = newActiveLink.textContent.trim(); // Update parent text } } // If it's a top-level link that itself has data-content-id, it's already handled by newActiveLink.classList.add('active') } // 5. Update the enabled/disabled state of Back/Next buttons internalUpdateNavigationButtons(contentId); // 6. Update URL hash (use replaceState for internal tabs to not mess with parent history) if (window.location.hash !== `#${contentId}`) { history.replaceState(null, '', `#${contentId}`); } // 7. Scroll this iframe's window to the top try { window.scrollTo({ top: 0, behavior: 'auto' }); } catch(e) { /* ignore if in weird context */ } // 8. Handle quiz reset if the shown section is the quiz if (contentId === 'quiz' && typeof resetQuiz === 'function') { console.log(`[CP - ${pageName}] Resetting internal quiz for section: ${contentId}`); resetQuiz(); } console.log(`[CP - ${pageName}] internalShowContent - END - Section "${contentId}" should be visible.`); } // Function to update the disabled state of navigation buttons function internalUpdateNavigationButtons(currentContentId) { const currentIndex = currentContentId ? internalAllContentIds.indexOf(currentContentId) : -1; const isFound = currentIndex !== -1; const isFirst = currentIndex === 0; const isLast = currentIndex >= internalAllContentIds.length - 1; // console.log(`[CP - ${pageName}] Update internal buttons: ID=${currentContentId}, Index=${currentIndex}, First=${isFirst}, Last=${isLast}, TotalIDs: ${internalAllContentIds.length}`); if (internalTopBackBtn) internalTopBackBtn.disabled = !isFound || isFirst; if (internalTopNextBtn) internalTopNextBtn.disabled = !isFound || isLast; // Update buttons within each content section internalContentSections.forEach(section => { const backBtn = section.querySelector('.content-nav .btn-back'); const nextBtn = section.querySelector('.content-nav .btn-next'); const isActiveSectionCurrently = section.id === currentContentId && isFound; if (backBtn) backBtn.disabled = !isActiveSectionCurrently || isFirst; if (nextBtn) nextBtn.disabled = !isActiveSectionCurrently || isLast; }); } // Function to navigate to the previous or next internal section function internalNavigateContent(direction) { const currentActiveInternalSection = contentDisplayAreaForSections.querySelector(':scope > .content-section.active'); let startIndex = -1; if (currentActiveInternalSection) { startIndex = internalAllContentIds.indexOf(currentActiveInternalSection.id); } else if (direction > 0) { // If no section is active and user clicks "next" startIndex = -1; // This will make nextIndex = 0 } else { // No active section and user clicks "back" return; } if ((startIndex === 0 && direction < 0) || (startIndex >= internalAllContentIds.length - 1 && direction > 0)) { return; // Already at bounds } let nextIndex = startIndex + direction; if (nextIndex >= 0 && nextIndex < internalAllContentIds.length) { const potentialNextId = internalAllContentIds[nextIndex]; if (internalAvailableSectionIds.has(potentialNextId) && document.getElementById(potentialNextId)) { internalShowContent(potentialNextId); } else { console.warn(`[CP - ${pageName}] Navigate: Next ID ${potentialNextId} not in available DOM sections or not in nav sequence.`); } } } // Quiz Logic Placeholder (ensure these functions are defined if you have a quiz) const quizForm = document.getElementById('quizForm'); const quizSubmitButton = document.getElementById('quizSubmit'); function handleQuizSubmit() { console.log(`[CP - ${pageName}] Quiz submitted.`); if(quizSubmitButton) quizSubmitButton.disabled = true; if(quizForm) quizForm.classList.add('submitted'); /* Add actual grading logic */ } function resetQuiz() { console.log(`[CP - ${pageName}] Quiz reset.`); if(quizForm) quizForm.reset(); if(quizSubmitButton) quizSubmitButton.disabled = false; if(quizForm) quizForm.classList.remove('submitted'); /* Add actual reset logic */ } if (quizForm && quizSubmitButton) { quizForm.addEventListener('submit', function(event) { event.preventDefault(); handleQuizSubmit(); }); } // Close submenus if clicking outside the nav document.body.addEventListener('click', function(event) { if (internalNavContainer && event.target.closest('nav.main-nav') !== internalNavContainer && !event.target.closest('.submenu')) { internalCloseAllSubmenus(); } }); // Initial load logic for internal sections function internalInitialLoad() { console.log(`[CP - ${pageName}] Internal Initial Load. Current hash: ${window.location.hash}`); const hash = window.location.hash.substring(1); // Get section ID from URL hash let contentIdToShow = null; if (hash && internalAllContentIds.includes(hash) && internalAvailableSectionIds.has(hash)) { contentIdToShow = hash; // A valid section is specified in the hash } else { // Default to the first section ID found in the navigation links that also exists in the DOM contentIdToShow = internalAllContentIds.find(id => internalAvailableSectionIds.has(id)) || null; if (hash && contentIdToShow) { // If there was an invalid hash, but we found a valid default console.warn(`[CP - ${pageName}] Invalid/unknown internal hash "${hash}". Defaulting to ${contentIdToShow}. Updating hash.`); history.replaceState(null, '', `#${contentIdToShow}`); // Update hash to reflect default } else if (hash) { // Hash was present but no valid section for it, and no valid default from nav console.warn(`[CP - ${pageName}] Invalid/unknown internal hash "${hash}". No valid default nav link target found.`); } } if (contentIdToShow) { console.log(`[CP - ${pageName}] Initial load will attempt to show: ${contentIdToShow}`); internalShowContent(contentIdToShow); } else if (internalContentSections.length > 0 && internalContentSections[0]) { // Last resort: if no nav links or hash match, but sections exist, show the first one. const firstSection = internalContentSections[0]; console.warn(`[CP - ${pageName}] No contentId to show from nav or hash. Showing first detected DOM section: ${firstSection.id}`); internalContentSections.forEach(s => {s.style.display = 'none'; s.classList.remove('active');}); firstSection.classList.add('active'); firstSection.style.display = 'block'; internalUpdateNavigationButtons(firstSection.id); // Update buttons for this section if (window.location.hash !== `#${firstSection.id}`) { // Update hash if not already matching history.replaceState(null, '', `#${firstSection.id}`); } } else { console.error(`[CP - ${pageName}] No valid internal content section to display initially. All content may remain hidden.`); if(internalTopBackBtn) internalTopBackBtn.disabled = true; if(internalTopNextBtn) internalTopNextBtn.disabled = true; } } // Listen for hash changes within this iframe (e.g., if parent sets iframe.src with a new hash) window.addEventListener('hashchange', () => { console.log(`[CP - ${pageName}] Internal hashchange event. Reloading internal content for hash: ${window.location.hash}`); internalInitialLoad(); }); internalInitialLoad(); // Call initial load sequence });