PageSourceSearch

https://bo2k.com/wp-content/themes/exalt/assets/js/main.js?ver=1.0.5

js bo2k.com collected 2026-10-02 11:04:14 UTC 12,335 bytes, 389 lines download raw bytes

1var exalt = exalt || {};
2
3// polyfill closest
4// https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
5if ( ! Element.prototype.closest ) {
6	Element.prototype.closest = function( s ) {
7		var el = this;
8
9		do {
10			if ( el.matches( s ) ) {
11				return el;
12			}
13
14			el = el.parentElement || el.parentNode;
15		} while ( el !== null && el.nodeType === 1 );
16
17		return null;
18	};
19}
20
21// polyfill forEach
22// https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill
23if ( window.NodeList && ! NodeList.prototype.forEach ) {
24	NodeList.prototype.forEach = function( callback, thisArg ) {
25		var i;
26		var len = this.length;
27
28		thisArg = thisArg || window;
29
30		for ( i = 0; i < len; i++ ) {
31			callback.call( thisArg, this[ i ], i, this );
32		}
33	};
34}
35
36/**
37 * Menu keyboard navigation
38 */
39exalt.menuAccessKeyboard = {
40
41	init: function() {
42		this.focusMenuWithChildren();
43	},
44
45	// The focusMenuWithChildren() function implements Keyboard Navigation in the Primary Menu
46	// by adding the '.focus' class to all 'li.menu-item-has-children' when the focus is on the 'a' element.
47	focusMenuWithChildren: function() {
48		// Get all the link elements within the primary menu.
49		var links, i, len,
50			menus = document.querySelectorAll( '.exalt-menu' );
51
52		if ( ! menus.length ) {
53			return false;
54		}
55
56        menus.forEach( function( menu ) {
57            
58            links = menu.getElementsByTagName( 'a' );
59
60            // Each time a menu link is focused or blurred, toggle focus.
61            for ( i = 0, len = links.length; i < len; i++ ) {
62                links[i].addEventListener( 'focus', toggleFocus, true );
63                links[i].addEventListener( 'blur', toggleFocus, true );
64            }
65
66        } );
67
68        // Sets or removes the .focus class on an element.
69        function toggleFocus() {
70            var self = this;
71
72            // Move up through the ancestors of the current link until we hit .main-navigation.
73            while ( -1 === self.className.indexOf( 'exalt-menu' ) ) {
74                // On li elements toggle the class .focus.
75                if ( 'li' === self.tagName.toLowerCase() ) {
76                    if ( -1 !== self.className.indexOf( 'focus' ) ) {
77                        self.className = self.className.replace( ' focus', '' );
78                    } else {
79                        self.className += ' focus';
80                    }
81                }
82                self = self.parentElement;
83            }
84        }
85
86	}
87};
88
89/**
90 * Search Toggle.
91 */
92exalt.searchToggle = {
93    init: function() {
94        this.addListener();
95    },
96
97    toggle: function( element ) {
98        
99		var searchBoxContainer = element.parentElement,
100            header              = document.getElementById( 'masthead' ),
101            searchBoxContainer  = searchBoxContainer.querySelector('#exalt-search-box'),
102            inputField          = searchBoxContainer.querySelector('input.search-field');
103
104		// Toggle header search class.
105		header.classList.toggle( 'hide-header-search' );
106
107        searchBoxContainer.classList.toggle( 'active' );
108
109		// Focus the input field if the search box is displayed.
110		if( searchBoxContainer.classList.contains( 'active' ) ) {
111			inputField.focus();
112		}
113
114    },
115
116	addListener: function() {
117		
118		var searchButton = document.getElementById( 'exalt-search-toggle' );
119
120		if ( ! searchButton ) {
121			return false;
122		}
123
124		var self = this;
125		
126		searchButton.addEventListener( 'click', function() {
127			self.toggle( this );
128		} );
129
130	}
131};
132
133/**
134 * Slideout Sidebar
135 */
136exalt.slideOutSidebar = {
137
138    init: function() {
139        this.addListeners();
140    },
141
142    createOverlay: function( maskId ) {
143        var mask = document.createElement( 'div' );
144        mask.setAttribute( 'class', 'exalt-overlay-mask' );
145        mask.setAttribute( 'id', maskId );
146        document.body.appendChild( mask );
147    },
148
149    removeOverlay: function( maskId ) {
150        var mask = document.getElementById( maskId );
151        mask.parentNode.removeChild( mask );
152    },
153
154    addListeners: function() {
155        var _doc = document,
156            body = _doc.body,
157            slideOutToggles = _doc.querySelectorAll( '.exalt-slideout-toggle' );
158
159            if ( ! slideOutToggles.length ) {
160                return false;
161            }
162            
163            var slideOutSidebar = _doc.getElementById( 'exalt-slideout-sidebar' );
164
165            if ( ! slideOutSidebar ) {
166                return false;
167            }
168
169            var closeBtn = slideOutSidebar.querySelector( '.exalt-slideout-toggle' ),
170            header = _doc.getElementById( 'masthead' ),
171            openBtn = header.querySelector( '.exalt-slideout-toggle' ),
172            self = this;
173
174        // Function to handle keydown events inside the mobile menu sidebar.
175        function handleSidebarKeydown(e) {
176            var focusableElements = slideOutSidebar.querySelectorAll('a, button, input, select, textarea, [tabindex]:not
176([tabindex="-1"])');
177            var firstFocusable = focusableElements[0];
178            var lastFocusable = focusableElements[focusableElements.length - 1];
179
180            if (e.key === 'Tab') {
181                if (e.shiftKey && document.activeElement === firstFocusable) {
182                    // If SHIFT+TAB and first focusable element is focused, focus last element
183                    e.preventDefault();
184                    lastFocusable.focus();
185                } else if (!e.shiftKey && document.activeElement === lastFocusable) {
186                    // If TAB and last focusable element is focused, focus first element
187                    e.preventDefault();
188                    firstFocusable.focus();
189                }
190            }
191        }
192
193        // Add keydown event listener to the mobile menu sidebar
194        slideOutSidebar.addEventListener('keydown', handleSidebarKeydown);
195
196
197        slideOutToggles.forEach( function( button ) {
198            button.addEventListener( 'click', function() {
199                if ( body.classList.contains( 'slideout-opened' ) ) {
200                    body.classList.remove( 'slideout-opened' );
201                    openBtn.focus();
202                    self.removeOverlay( 'exalt-slideout-mask' );
203                } else {
204                    body.classList.add( 'slideout-opened' );
205                    closeBtn.focus();
206                    self.createOverlay( 'exalt-slideout-mask' );
207                }
208            } );
209        } ); 
210
211        document.addEventListener( 'click', function( e ) {
212            if ( e.target && e.target.className === 'exalt-overlay-mask' ) {
213
214                var maskId = e.target.id;
215
216                body.classList.remove( 'slideout-opened' );
217                self.removeOverlay( maskId );
218            }
219        } );
220    }
221};
222
223/**
224 * Mobile Menu
225 */
226exalt.mobileMenu = {
227
228    init: function() {
229        this.addListeners();
230    },
231
232    createOverlay: function( maskId ) {
233        var mask = document.createElement( 'div' );
234        mask.setAttribute( 'class', 'exalt-overlay-mask' );
235        mask.setAttribute( 'id', maskId );
236        document.body.appendChild( mask );
237    },
238
239    removeOverlay: function( maskId ) {
240        var mask = document.getElementById( maskId );
241        mask.parentNode.removeChild( mask );
242    },
243
244    addListeners: function() {
245        var _doc = document,
246            menuToggleButtons = _doc.querySelectorAll( '.exalt-mobile-menu-toggle' ),
247            body = _doc.body,
248            mobileMenuSidebar = _doc.getElementById( 'exalt-mobile-sidebar' );
249
250        if ( ! mobileMenuSidebar ) {
251            return false;
252        }
253
254        var mobileMenuSidebarCloseBtn = mobileMenuSidebar.querySelector( '.exalt-mobile-menu-toggle' ),
255            header = _doc.getElementById( 'masthead' ),
256            mobileMenuSidebarOpenBtn = header.querySelector( '.exalt-mobile-menu-toggle' ),
257            self = this;
258
259
260        menuToggleButtons.forEach( function( button ) {
261            button.addEventListener( 'click', function() {
262                if ( body.classList.contains( 'mobile-menu-opened' ) ) {
263                    body.classList.remove( 'mobile-menu-opened' );
264                    mobileMenuSidebarOpenBtn.focus();
265                    self.removeOverlay( 'exalt-mobile-menu-mask' );
266                } else {
267                    body.classList.add( 'mobile-menu-opened' );
268                    mobileMenuSidebarCloseBtn.focus();
269                    self.createOverlay( 'exalt-mobile-menu-mask' );
270                }
271            } );
272        } );
273
274        // Function to handle keydown events inside the mobile menu sidebar.
275        function handleSidebarKeydown(e) {
276            var focusableElements = mobileMenuSidebar.querySelectorAll('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
277            var firstFocusable = focusableElements[0];
278            var lastFocusable = focusableElements[focusableElements.length - 1];
279
280            if (e.key === 'Tab') {
281                if (e.shiftKey && document.activeElement === firstFocusable) {
282                    // If SHIFT+TAB and first focusable element is focused, focus last element
283                    e.preventDefault();
284                    lastFocusable.focus();
285                } else if (!e.shiftKey && document.activeElement === lastFocusable) {
286                    // If TAB and last focusable element is focused, focus first element
287                    e.preventDefault();
288                    firstFocusable.focus();
289                }
290            }
291        }
292
293        // Add keydown event listener to the mobile menu sidebar
294        mobileMenuSidebar.addEventListener('keydown', handleSidebarKeydown);
295
296        document.addEventListener( 'click', function( e ) {
297            if ( e.target && e.target.className === 'exalt-overlay-mask' ) {
298
299                var maskId = e.target.id;
300
301                body.classList.remove( 'mobile-menu-opened' );
302                self.removeOverlay( maskId );
303            }
304        } );
305    }
306};
307
308/**
309 * Mobile Menu Toggles.
310 */
311exalt.mobileMenuToggle = {
312
313    init: function() {
314        // Do the toggle.
315        this.toggle();
316    },
317
318    performToggle: function( element ) {
319
320        var target = element.dataset.toggleTarget,
321            parentNavContainer = element.closest( '.exalt-mobile-menu' );
322            activeClass = 'toggled-on';
323
324        // Get the target element.
325        targetElement = parentNavContainer.querySelector(target);
326
327        element.classList.toggle(activeClass);
328
329        exaltToggleAttribute(element, 'aria-expanded', 'true', 'false');
330
331        targetElement.classList.toggle(activeClass);
332
333    },
334
335    toggle: function() {
336        var self = this;
337
338        document.querySelectorAll('*[data-toggle-target]').forEach( function( element ) {
339            element.addEventListener( 'click', function( event ) {
340                event.preventDefault();
341                self.performToggle( element );
342            } );
343        } );
344    }
345};
346
347/**
348 * Toggle an attribute
349 */  
350function exaltToggleAttribute( element, attribute, trueVal, falseVal ) {
351	if ( trueVal === undefined ) {
352		trueVal = true;
353	}
354	if ( falseVal === undefined ) {
355		falseVal = false;
356	}
357	if ( element.getAttribute( attribute ) !== trueVal ) {
358		element.setAttribute( attribute, trueVal );
359	} else {
360		element.setAttribute( attribute, falseVal );
361	}
362}
363
364/**
365 * Is the DOM ready?
366 *
367 * This implementation is coming from https://gomakethings.com/a-native-javascript-equivalent-of-jquerys-ready-method/
368 *
369 * @param {Function} fn Callback function to run.
370 */
371function exaltDomReady( fn ) {
372	if ( typeof fn !== 'function' ) {
373		return;
374	}
375
376	if ( document.readyState === 'interactive' || document.readyState === 'complete' ) {
377		return fn();
378	}
379
380	document.addEventListener( 'DOMContentLoaded', fn, false );
381}
382
383exaltDomReady( function() {
384    exalt.menuAccessKeyboard.init();
385    exalt.searchToggle.init();
386    exalt.mobileMenu.init();
387    exalt.mobileMenuToggle.init();
388    exalt.slideOutSidebar.init();
389} );

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.