PageSourceSearch

https://felab-unist.github.io/js/app.js?v=2

js felab-unist.github.io collected 2026-10-03 09:11:26 UTC 13,187 bytes, 363 lines download raw bytes

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.