PageSourceSearch

https://saudiembassy.sa/_catalogs/masterpage/public/js/nds-includes.js

js saudiembassy.sa collected 2026-10-02 16:35:25 UTC 15,153 bytes, 356 lines download raw bytes

1/**
2 * NDS Partials Loader
3 * Fetches shared HTML partials and injects them into placeholder elements.
4 * After all partials are loaded, dispatches 'nds-partials-loaded' event
5 * and triggers NDS component re-initialization.
6 *
7 * Also provides fallback handlers for NDS.Mainnav features (dropdowns,
8 * digital stamp, mobile toggle) since that module caches DOM refs at
9 * parse time before partials are fetched.
10 */
11(function () {
12    'use strict';
13
14    // Determine base path from <base> tag or current page depth
15    var base = document.querySelector('base');
16    var basePath = base ? base.getAttribute('href') : './';
17    if (basePath.charAt(basePath.length - 1) !== '/') basePath += '/';
18
19    var partials = [
20        { id: 'nds-topbar-placeholder',         file: '_partials/topbar.html' },
21        { id: 'nds-mainnav-placeholder',         file: '_partials/mainnav.html' },
22        { id: 'nds-mainnav-embassy-placeholder', file: '_partials/mainnav-embassy.html' },
23        { id: 'nds-sidemenu-placeholder',        file: '_partials/sidemenu.html' },
24        { id: 'nds-footer-placeholder',          file: '_partials/footer.html' },
25        { id: 'nds-cookie-placeholder',          file: '_partials/cookie-popup.html' },
26        // { id: 'nds-accessibility-placeholder',   file: '_partials/accessibility-panel.html' },
27        { id: 'nds-user-feedback-placeholder',   file: '_partials/user-feedback.html' },
28        { id: 'nds-hero-main-placeholder',       file: '_partials/hero-main.html' },
29        { id: 'nds-hero-sub-placeholder',        file: '_partials/hero-sub.html' },
30        { id: 'nds-embassies-finder-placeholder', file: '_partials/embassies-finder.html' },
31        { id: 'nds-embassy-map-placeholder',     file: '_partials/embassy-map.html' }
32    ];
33
34    var promises = [];
35
36    partials.forEach(function (p) {
37        var el = document.getElementById(p.id);
38        if (!el) return;
39
40        var promise = fetch(basePath + p.file + '?v=' + Date.now())
41            .then(function (res) {
42                if (!res.ok) throw new Error(p.file + ': ' + res.status);
43                return res.text();
44            })
45            .then(function (html) {
46                el.innerHTML = html;
47                var scripts = el.querySelectorAll('script');
48                scripts.forEach(function (orig) {
49                    var s = document.createElement('script');
50                    if (orig.type) s.type = orig.type;
51                    if (orig.src) { s.src = orig.src; }
52                    else { s.textContent = orig.textContent; }
53                    orig.parentNode.replaceChild(s, orig);
54                });
55            })
56            .catch(function (err) {
57                console.warn('NDS partial load failed:', err.message);
58            });
59
60        promises.push(promise);
61    });
62
63    var partialsReady = Promise.all(promises);
64    var domReady = new Promise(function (resolve) {
65        if (document.readyState === 'loading') {
66            document.addEventListener('DOMContentLoaded', resolve);
67        } else { resolve(); }
68    });
69
70    Promise.all([partialsReady, domReady]).then(function () {
71        setActiveSidemenu();
72        setHeroSubData();
73        ensureFontLoaded();
74
75        document.dispatchEvent(new CustomEvent('nds-partials-loaded'));
76
77        // Re-initialize NDS components
78        if (typeof NDS !== 'undefined') {
79            if (NDS.Init && NDS.Init.reinitialize) {
80                NDS.Init.reinitialize();
81            } else if (NDS.init) {
82                NDS.init();
83            }
84            if (NDS.TimeDate && NDS.TimeDate.init) {
85                try { NDS.TimeDate.init(); } catch (e) { /* ignore */ }
86            }
87        }
88
89        revealNavElements();
90        initDigitalStampToggle();
91        initNavDropdowns();
92        initMobileNavToggle();
93        initMinimalNavMode();
94    });
95
96    // ─── Minimal nav mode (toggle body.nds-minimal based on viewport) ──
97    function initMinimalNavMode() {
98        var mql = window.matchMedia('(max-width: 960px)');
99        function update() {
100            document.body.classList.toggle('nds-minimal', mql.matches);
101        }
102        if (mql.addEventListener) {
103            mql.addEventListener('change', update);
104        } else if (mql.addListener) {
105            mql.addListener(update);
106        }
107        update();
108    }
109
110    // ─── Font loading fallback ────────────────────────────────────
111    function ensureFontLoaded() {
112        function markLoaded() {
113            var current = (document.body.getAttribute('data-font-loaded') || '').trim();
114            if (current.indexOf('hgi-stroke-rounded') === -1) {
115                document.body.setAttribute('data-font-loaded', (current + ' hgi-stroke-rounded').trim());
116            }
117        }
118        var current = document.body.getAttribute('data-font-loaded') || '';
119        if (current.indexOf('hgi-stroke-rounded') !== -1) return;
120        if (document.fonts && document.fonts.ready) {
121            document.fonts.ready.then(function () {
122                try { if (document.fonts.check('1em "hgi-stroke-rounded"')) markLoaded(); }
123                catch (e) {}
124            });
125        }
126        setTimeout(markLoaded, 3000);
127    }
128
129    // ─── Sidemenu active state ────────────────────────────────────
130    function setActiveSidemenu() {
131        var path = window.location.pathname;
132        var links = document.querySelectorAll('.nds-sidemenu a[href]');
133        links.forEach(function (a) {
134            var href = a.getAttribute('href');
135            if (path.indexOf(href) !== -1 || path.endsWith(href)) {
136                var li = a.closest('li');
137                if (li) li.setAttribute('data-state', 'active');
138                var parent = a.closest('.nds-sidemenu-group');
139                while (parent) {
140                    var toggle = parent.querySelector('.nds-sidemenu-toggle');
141                    if (toggle) toggle.setAttribute('aria-expanded', 'true');
142                    var content = parent.querySelector('.nds-sidemenu-content');
143                    if (content) content.style.display = 'block';
144                    parent = parent.parentElement ? parent.parentElement.closest('.nds-sidemenu-group') : null;
145                }
146            }
147        });
148    }
149
150    // ─── Hero sub data ────────────────────────────────────────────
151    function setHeroSubData() {
152        var heroSub = document.getElementById('nds-hero-sub-placeholder');
153        if (!heroSub) return;
154        var page = document.querySelector('[data-page-title]');
155        if (!page) return;
156        var title = page.getAttribute('data-page-title');
157        var desc = page.getAttribute('data-page-description');
158        if (title) {
159            var h1 = heroSub.querySelector('.nds-section-title');
160            if (h1) h1.textContent = title;
161        }
162        if (desc) {
163            var p = heroSub.querySelector('.nds-section-description');
164            if (p) p.textContent = desc;
165        }
166    }
167
168    // ─── Reveal hidden nav elements ───────────────────────────────
169    function revealNavElements() {
170        var selectors = [
171            '.nds-digitalStamp-tab[hidden]',
172            '.nds-nav-minimal[hidden]',
173            '.nds-collapse[hidden]',
174            '.nds-nav-primary[hidden]',
175            '.nds-nav-actions[hidden]'
176        ];
177        selectors.forEach(function (sel) {
178            document.querySelectorAll(sel).forEach(function (el) {
179                el.removeAttribute('hidden');
180            });
181        });
182    }
183
184    // ─── Digital stamp toggle ─────────────────────────────────────
185    function initDigitalStampToggle() {
186        var tab = document.querySelector('.nds-digitalStamp-tab');
187        var stamp = document.getElementById('nds-digitalStamp');
188        if (!tab || !stamp) return;
189        if (tab.hasAttribute('data-nds-bound')) return;
190        tab.setAttribute('data-nds-bound', '');
191
192        tab.addEventListener('click', function () {
193            var isOpen = hasState(stamp, 'open');
194            if (isOpen) {
195                stamp.setAttribute('data-state', 'open closing');
196                tab.setAttribute('aria-expanded', 'false');
197                removeStateToken(tab, 'expanded');
198                setTimeout(function () {
199                    stamp.removeAttribute('data-state');
200                    stamp.style.display = 'none';
201                }, 350);
202            } else {
203                closeAllDropdowns();
204                stamp.removeAttribute('hidden');
205                stamp.style.display = '';
206                stamp.setAttribute('data-state', 'open opening');
207                tab.setAttribute('aria-expanded', 'true');
208                addStateToken(tab, 'expanded');
209                setTimeout(function () {
210                    stamp.setAttribute('data-state', 'open opened');
211                }, 350);
212            }
213        });
214    }
215
216    // ─── Nav dropdown handler (replaces NDS.Mainnav dropdown logic) ──
217    function initNavDropdowns() {
218        // Use document-level event delegation (same pattern as NDS.Mainnav)
219        document.addEventListener('click', function (e) {
220            // Click on a nav dropdown trigger
221            var trigger = e.target.closest('.nds-main-nav .nds-dropdown > .nds-nav-link');
222            if (trigger) {
223                e.preventDefault();
224                var dd = trigger.closest('.nds-dropdown');
225                var isOpen = hasState(dd, 'open');
226
227                // Close all other open dropdowns first
228                closeAllDropdowns(dd);
229
230                if (isOpen) {
231                    closeDropdown(dd);
232                } else {
233                    openDropdown(dd);
234                }
235                return;
236            }
237
238            // Click on hamburger toggler
239            if (e.target.closest('.nds-mainNav-toggler')) {
240                e.preventDefault();
241                return; // handled by initMobileNavToggle
242            }
243
244            // Click outside — close all open dropdowns
245            var openDDs = document.querySelectorAll('.nds-main-nav .nds-dropdown[data-state~="open"]');
246            openDDs.forEach(function (dd) {
247                var menu = dd.querySelector('.nds-dropdown-menu');
248                if (!dd.contains(e.target) && (!menu || !menu.contains(e.target))) {
249                    closeDropdown(dd);
250                }
251            });
252
253            // Close mobile nav if clicking outside
254            var collapse = document.getElementById('ndsNavCollapse');
255            if (collapse && hasState(collapse, 'open')) {
256                var nav = document.querySelector('.nds-main-nav');
257                if (nav && !nav.contains(e.target)) {
258                    closeMobileNav(collapse);
259                }
260            }
261        });
262
263        // Escape key closes dropdowns
264        document.addEventListener('keydown', function (e) {
265            if (e.key === 'Escape') {
266                closeAllDropdowns();
267                var collapse = document.getElementById('ndsNavCollapse');
268                if (collapse && hasState(collapse, 'open')) {
269                    closeMobileNav(collapse);
270                }
271            }
272        });
273    }
274
275    function openDropdown(dd) {
276        var link = dd.querySelector('.nds-nav-link');
277        dd.setAttribute('data-state', 'open opening');
278        if (link) addStateToken(link, 'active');
279        setTimeout(function () {
280            dd.setAttribute('data-state', 'open opened');
281        }, 300);
282    }
283
284    function closeDropdown(dd) {
285        var link = dd.querySelector('.nds-nav-link');
286        dd.setAttribute('data-state', 'open closing');
287        if (link) removeStateToken(link, 'active');
288        setTimeout(function () {
289            dd.removeAttribute('data-state');
290        }, 300);
291    }
292
293    function closeAllDropdowns(except) {
294        var openDDs = document.querySelectorAll('.nds-main-nav .nds-dropdown[data-state~="open"]');
295        openDDs.forEach(function (dd) {
296            if (dd !== except) closeDropdown(dd);
297        });
298    }
299
300    // ─── Mobile nav toggle (hamburger) ────────────────────────────
301    function initMobileNavToggle() {
302        var toggler = document.querySelector('.nds-mainNav-toggler button');
303        var collapse = document.getElementById('ndsNavCollapse');
304        if (!toggler || !collapse) return;
305        if (toggler.hasAttribute('data-nds-bound')) return;
306        toggler.setAttribute('data-nds-bound', '');
307
308        toggler.addEventListener('click', function (e) {
309            e.preventDefault();
310            var isOpen = hasState(collapse, 'open');
311            if (isOpen) {
312                closeMobileNav(collapse);
313                toggler.setAttribute('aria-expanded', 'false');
314            } else {
315                collapse.style.display = '';
316                collapse.setAttribute('data-state', 'open opening');
317                toggler.setAttribute('aria-expanded', 'true');
318                setTimeout(function () {
319                    collapse.setAttribute('data-state', 'open opened');
320                }, 300);
321            }
322        });
323    }
324
325    function closeMobileNav(collapse) {
326        collapse.setAttribute('data-state', 'open closing');
327        var toggler = document.querySelector('.nds-mainNav-toggler button');
328        if (toggler) toggler.setAttribute('aria-expanded', 'false');
329        setTimeout(function () {
330            collapse.removeAttribute('data-state');
331        }, 300);
332    }
333
334    // ─── State helpers ────────────────────────────────────────────
335    function hasState(el, token) {
336        var state = (el.getAttribute('data-state') || '').trim();
337        return state.split(/\s+/).indexOf(token) !== -1;
338    }
339
340    function addStateToken(el, token) {
341        var current = (el.getAttribute('data-state') || '').trim();
342        if (current.split(/\s+/).indexOf(token) === -1) {
343            el.setAttribute('data-state', (current + ' ' + token).trim());
344        }
345    }
346
347    function removeStateToken(el, token) {
348        var current = (el.getAttribute('data-state') || '').trim();
349        var parts = current.split(/\s+/).filter(function (t) { return t !== token; });
350        if (parts.length) {
351            el.setAttribute('data-state', parts.join(' '));
352        } else {
353            el.removeAttribute('data-state');
354        }
355    }
356})();

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.