1// Main Application JavaScript 2const App = { 3 currentPage: 'home', 4 5 async init() { 6 console.log('Initializing App...'); 7 8 // Load components 9 await this.loadComponents(); 10 11 // Initialize navigation 12 this.setupNavigation(); 13 14 // Initialize mobile menu 15 this.setupMobileMenu(); 16 17 // Handle initial URL 18 this.handleInitialRoute(); 19 20 // Handle browser back/forward 21 window.addEventListener('popstate', (e) => { 22 this.handleRoute(); 23 }); 24 25 // Handle hash changes (when user types in address bar) 26 window.addEventListener('hashchange', (e) => { 27 this.handleRoute(); 28 }); 29 }, 30 31 async loadComponents() { 32 try { 33 // Load header 34 const headerResponse = await fetch('./components/header.html'); 35 const headerHTML = await headerResponse.text(); 36 document.getElementById('header-container').innerHTML = headerHTML; 37 38 // Load footer 39 const footerResponse = await fetch('./components/footer.html'); 40 const footerHTML = await footerResponse.text(); 41 document.getElementById('footer-container').innerHTML = footerHTML; 42 43 console.log('Components loaded successfully'); 44 } catch (error) { 45 console.error('Error loading components:', error); 46 } 47 }, 48 49 // Hashes the router owns. Anything else (e.g. "#research-areas")
49is an 50 // in-page anchor and must be left alone: routing it would 404 and fall back 51 // to home, which resets the scroll position. 52 pages: ['home', 'members', 'publications', 'research', 'projects', 53 'activities', 'journal-club', 'news', 'join-us'], 54 55 isPageHash(hash) { 56 return this.pages.includes(hash); 57 }, 58 59 handleInitialRoute() { 60 const hash = window.location.hash.slice(1); // Remove # 61 62 // Clean up hash - remove trailing slashes and handle members/xxx format 63 const cleanHash = hash.replace(/\/$/, ''); // Remove trailing slash 64 65 if (cleanHash.startsWith('members/')) { 66 const parts = cleanHash.split('/'); 67 const memberId = parts[1]; 68 if (memberId) { 69 // Load members page first, then navigate to profile 70 // Use updateHistory: false to avoid duplicate history entries 71 this.loadPage('members', false).then(() => { 72 setTimeout(() => { 73 this.loadProfile(memberId); 74 }, 100); 75 }); 76 } else { 77 // Just members/ without ID - load members page 78 this.loadPage('members', false); 79 } 80 } else if (cleanHash.startsWith('profile/')) { 81 const parts = cleanHash.split('/'); 82 const memberId = parts[1]; 83 // Load members page first, then navigate to profile 84 this.loadPage('members', false).then(() => { 85 setTimeout(() => { 86 this.loadProfile(memberId); 87 }, 100); 88 }); 89 } else if (this.isPageHash(cleanHash)) { 90 // Don't create duplicate history entry for initial page load 91 this.loadPage(cleanHash, false); 92 } else if (cleanHash) { 93 // Unknown hash: treat it as an in-page anchor on the home page. 94 this.loadPage('home', false); 95 } else { 96 this.loadPage('home', false); 97 } 98 }, 99 100 // handleRoute() í¨ìë ìì 101 handleRoute() { 102 const hash = window.location.hash.slice(1); 103 104 // Clean up hash - remove trailing slashes 105 const cleanHash = hash.replace(/\/$/, ''); 106 107 if (cleanHash.startsWith('members/')) { 108 const parts = cleanHash.split('/'); 109 const memberId = parts[1]; 110 if (memberId) { 111 this.loadProfile(memberId); 112 } else { 113 this.loadPage('members', false); 114 } 115 } else if (cleanHash.startsWith('profile/')) { 116 const parts = cleanHash.split('/'); 117 const memberId = parts[1]; 118 this.loadProfile(memberId); 119 } else if (this.isPageHash(cleanHash)) { 120 this.loadPage(cleanHash, false); 121 } else if (cleanHash) { 122 // In-page anchor, not a route. Leave the current page and scroll alone. 123 return; 124 } else { 125 this.loadPage('home', false); 126 } 127 }, 128 129 setupNavigation() { 130 // Handle all navigation links 131 document.addEventListener('click', async (e) => { 132 // Check if clicked element or its parent is a nav-link 133 const navLink = e.target.closest('.nav-link'); 134 135 if (navLink && navLink.dataset.tab) { 136 e.preventDefault(); 137 138 const targetPage = navLink.dataset.tab; 139 console.log('Navigation clicked:', targetPage); 140 141 // Always load the page when navigation is clicked 142 await this.loadPage(targetPage); 143 144 // Close mobile menu if open 145 const mobileMenu = document.getElementById('mobile-menu'); 146 if (mobileMenu && !mobileMenu.classList.contains('hidden')) { 147 mobileMenu.classList.add('hidden'); 148 } 149 } 150 }); 151 }, 152 153 setupMobileMenu() { 154 const menuButton = document.getElementById('mobile-menu-button'); 155 const mobileMenu = document.getElementById('mobile-menu'); 156 157 if (menuButton && mobileMenu) { 158 menuButton.addEventListener('click', () => { 159 mobileMenu.classList.toggle('hidden'); 160 }); 161 } 162 }, 163 164 async loadPage(page, updateHistory = true) { 165 console.log(`Loading page: ${page}`); 166 167 // Update current page 168 this.currentPage = page; 169 170 // Update active nav states 171 this.updateActiveNav(page); 172 173 try { 174 const response = await fetch(`./pages/${page}.html`); 175 if (!response.ok) { 176 throw new Error(`Page not found: ${page}`); 177 } 178 179 const html = await response.text(); 180 const mainContent = document.getElementById('main-content'); 181 if (mainContent) { 182 mainContent.innerHTML = html; 183 184 // Scroll to top 185 window.scrollTo(0, 0); 186 187 // IMPORTANT: Wait for DOM to be fully rendered before initializing page 188 // Use requestAnimationFrame to ensure DOM is painted 189 requestAnimationFrame(() => { 190 requestAnimationFrame(() => { 191 this.initializePage(page); 192 }); 193 }); 194 195 // Update browser history 196 if (updateHistory) { 197 history.pushState({ page }, '', `#${page}`); 198 } 199 200 // Update page title 201 const titles = { 202 'home': 'Home', 203 'members': 'Members', 204 'activities': 'Activities', 205 'publications': 'Publications', 206 'research': 'Research', 207 'projects': 'Research Projects',
208 'journal-club': 'Journal Club', 209 'news': 'News & Announcements', 210 'join-us': 'Contact Us' 211 }; 212 document.title = `${titles[page] || page} | UNIST Financial Engineering Lab`; 213 } 214 } catch (error) { 215 console.error(`Error loading page ${page}:`, error); 216 // Load home page as fallback 217 if (page !== 'home') { 218 this.loadPage('home'); 219 } 220 } 221 }, 222 223 async loadProfile(memberId) { 224 console.log(`Loading profile: ${memberId}`); 225 226 try { 227 // Load the profile template 228 const response = await fetch('./pages/profile.html'); 229 if (!response.ok) { 230 throw new Error('Failed to load profile template'); 231 } 232 233 const html = await response.text(); 234 const mainContent = document.getElementById('main-content'); 235 if (mainContent) { 236 mainContent.innerHTML = html; 237 window.scrollTo(0, 0); 238 239 // Update URL with proper history management 240 // Only push state if we're not already at this URL 241 const currentHash = window.location.hash.slice(1); 242 const targetHash = `members/${memberId}`; 243 244 if (currentHash !== targetHash) { 245 history.pushState({ profile: memberId }, '', `#${targetHash}`); 246 } 247 248 // Initialize profile with member data 249 requestAnimationFrame(() => { 250 requestAnimationFrame(() => { 251 if (typeof ProfileManager !== 'undefined') { 252 ProfileManager.init(memberId); 253 } 254 }); 255 }); 256 } 257 } catch (error) { 258 console.error('Error loading profile:', error); 259 this.loadPage('members'); 260 } 261 }, 262 263 updateActiveNav(activePage) { 264 // Remove all active classes 265 document.querySelectorAll('.nav-link').forEach(link => { 266 link.classList.remove('active', 'text-brand-navy', 'font-semibold'); 267 link.classList.add('text-gray-600'); 268 }); 269 270 // Add active class to current page links 271 document.querySelectorAll(`.nav-link[data-tab="${activePage}"]`).forEach(link => { 272 link.classList.add('active', 'text-brand-navy', 'font-semibold'); 273 link.classList.remove('text-gray-600'); 274 }); 275 }, 276 277 initializePage(page) { 278 console.log(`Initializing ${page} page functionality`); 279 280 switch(page) { 281 case 'home': 282 // Initialize news carousel for home page 283 const initCarousel = () => { 284 console.log('Attempting to initialize carousel...'); 285 286 if (typeof NewsCarousel === 'undefined') { 287 console.error('NewsCarousel module not loaded!'); 288 return; 289 } 290 291 const carouselElement = document.getElementById('news-carousel'); 292 if (carouselElement) { 293 console.log('Found carousel, resetting and initializing...'); 294 NewsCarousel.reset(); 295 296 Promise.resolve().then(() => { 297 NewsCarousel.init().then(() => { 298 console.log('Carousel initialized successfully'); 299 }).catch(err => { 300 console.error('Carousel init failed:', err); 301 }); 302 }); 303 } else { 304 console.log('Carousel element not found, retrying in 500ms...'); 305 setTimeout(initCarousel, 500); 306 } 307 }; 308 309 setTimeout(initCarousel, 300); 310 break; 311 312 case 'publications': 313 if (typeof PublicationsManager !== 'undefined') { 314 PublicationsManager.init(); 315 } 316 break; 317 318 case 'research': 319 if (typeof ResearchManager !== 'undefined') { 320 ResearchManager.init(); 321 } 322 break; 323 324 case 'members': 325 if (typeof MembersManager !== 'undefined') { 326 MembersManager.init(); 327 } 328 break; 329 330 case 'activities': 331 if (typeof ActivitiesManager !== 'undefined') { 332 ActivitiesManager.init(); 333 } 334 break; 335 336 case 'projects': 337 if (typeof ProjectsManager !== 'undefined') { 338 ProjectsManager.init(); 339 } 340 break; 341
342 case 'journal-club': 343 if (typeof JournalClubManager !== 'undefined') { 344 JournalClubManager.init(); 345 } 346 break; 347 348 case 'news': 349 if (typeof NewsManager !== 'undefined') { 350 NewsManager.init(); 351 } 352 break; 353 } 354 } 355}; 356 357// Initialize app when DOM is ready 358document.addEventListener('DOMContentLoaded', () => { 359 App.init(); 360}); 361 362// Make App globally available 363window.App = App;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.