PageSourceSearch

https://nhi.edu/wp-content/plugins/pinnacle-promo-banner/assets/js/promo-banner-frontend.js?ver=1.0.0

js nhi.edu collected 2026-10-03 22:11:54 UTC 18,634 bytes, 468 lines download raw bytes

1/**
2 * Promo Banner Frontend JavaScript
3 * 
4 * This file handles all frontend functionality for the promotional banners
5 * including animations, interactions, and tracking.
6 */
7
8(function($) {
9    'use strict';
10    
11    // Promo Banner Class
12    class PromoBanner {
13        constructor(element) {
14            this.element = element;
15            this.bannerId = element.data('banner-id');
16            this.position = element.data('position');
17            this.isExpanded = false;
18            this.isAnimating = false;
19            
20            this.init();
21        }
22        
23        init() {
24            this.bindEvents();
25            this.setupAccessibility();
26            
27            // Show the banner first, then handle expand by default
28            this.show();
29        }
30        
31        bindEvents() {
32            const self = this;
33            
34            // Toggle banner
35            this.element.find('.promo-banner-toggle').on('click', function(e) {
36                e.preventDefault();
37                self.toggle();
38            });
39            
40            // Close banner
41            this.element.find('.promo-banner-close').on('click', function(e) {
42                e.preventDefault();
43                self.close();
44            });
45            
46            // Close on outside click
47            $(document).on('click', function(e) {
48                if (!$(e.target).closest('.promo-banner').length) {
49                    self.close();
50                }
51            });
52            
53            // Close on escape key
54            $(document).on('keydown', function(e) {
55                if (e.keyCode === 27) { // ESC key
56                    self.close();
57                }
58            });
59            
60            // Handle window resize
61            $(window).on('resize', function() {
62                self.handleResize();
63            });
64        }
65        
66        setupAccessibility() {
67            // Set ARIA attributes
68            this.element.attr('role', 'dialog');
69            this.element.attr('aria-label', 'Promotional Banner');
70            this.element.attr('aria-hidden', 'true');
71            
72            // Set focus management
73            this.element.find('.promo-banner-toggle').attr('aria-expanded', 'false');
74            this.element.find('.promo-banner-toggle').attr('aria-controls', 'promo-banner-content-' + this.bannerId);
75            
76            // Create content ID for ARIA
77            this.element.find('.promo-banner-content').attr('id', 'promo-banner-content-' + this.bannerId);
78        }
79        
80        handleExpandByDefault() {
81            const expandByDefault = this.element.data('expand-by-default') === 'yes' || this.element.data('expand-by-default') === true;
82            const delay = this.element.data('delay') || 0;
83            const scrollPercentage = this.element.data('scroll-percentage') || 0;
84            
85            // Only auto-expand if no other triggers are set and expand by default is enabled
86            if (expandByDefault && delay === 0 && scrollPercentage === 0) {
87                // For expand by default banners, clear any previous user closed cookie
88                // so they always expand on page load
89                this.clearCookie('promo_banner_' + this.bannerId + '_user_closed');
90                
91                // Handle expansion based on device type
92                setTimeout(() => {
93                    if (this.isMobile()) {
94                        // On mobile, show the modal instead of expanding the banner
95                        this.showMobileModal();
96                    } else {
97                        // On desktop, expand the banner normally
98                        this.expand();
99                    }
100                }, 100); // Small delay to ensure smooth animation
101            }
102        }
103        
104        setupDelay() {
105            const delay = this.element.data('delay') || 0;
106            const expandByDefault = this.element.data('expand-by-default') === 'yes';
107            
108            if (delay > 0) {
109                setTimeout(() => {
110                    this.show();
111                    // If expand by default is enabled, handle expansion based on device type
112                    if (expandByDefault) {
113                        setTimeout(() => {
114                            if (this.isMobile()) {
115                                // On mobile, show the modal instead of expanding the banner
116                                this.showMobileModal();
117                            } else {
118                                // On desktop, expand the banner normally
119                                this.expand();
120                            }
121                        }, 100); // Small delay to ensure smooth animation
122                    }
123                }, delay * 1000);
124            } else {
125                this.show();
126                // If expand by default is enabled, handle expansion based on device type
127                if (expandByDefault) {
128                    setTimeout(() => {
129                        if (this.isMobile()) {
130                            // On mobile, show the modal instead of expanding the banner
131                            this.showMobileModal();
132                        } else {
133                            // On desktop, expand the banner normally
134                            this.expand();
135                        }
136                    }, 100); // Small delay to ensure smooth animation
137                }
138            }
139        }
140        
141        show() {
142            if (this.isExpanded || this.isAnimating) return;
143            
144            this.element.addClass('promo-banner-visible');
145            this.element.attr('aria-hidden', 'false');
146            
147            // Handle expand by default functionality
148            this.handleExpandByDefault();
149        }
150        
151        toggle() {
152            if (this.isAnimating) return;
153            
154            // Check if mobile
155            if (this.isMobile()) {
156                this.showMobileModal();
157            } else {
158                if (this.isExpanded) {
159                    this.close();
160                } else {
161                    this.expand();
162                }
163            }
164        }
165        
166        // Check if mobile
167        isMobile() {
168            return $(window).width() <= 768;
169        }
170        
171        // Show mobile modal
172        showMobileModal() {
173            const modalId = '#' + this.element.attr('id') + '-modal';
174            const $modal = $(modalId);
175            
176            if ($modal.length) {
177                $modal.addClass('active');
178                $('body').addClass('modal-open');
179                
180                // Bind modal close events if not already bound
181                this.bindModalEvents($modal);
182            }
183        }
184        
185        // Bind modal events
186        bindModalEvents($modal) {
187            const self = this;
188            
189            // Close modal on close button click
190            $modal.find('.promo-banner-close').off('click.modal').on('click.modal', function(e) {
191                e.preventDefault();
192                self.closeMobileModal($modal);
193            });
194            
195            // Close modal on overlay click
196            $modal.off('click.modal').on('click.modal', function(e) {
197                if (e.target === this) {
198                    self.closeMobileModal($modal);
199                }
200            });
201            
202            // Close modal on escape key
203            $(document).off('keydown.modal').on('keydown.modal', function(e) {
204                if (e.keyCode === 27) { // ESC key
205                    self.closeMobileModal($modal);
206                }
207            });
208        }
209        
210        // Close mobile modal
211        closeMobileModal($modal) {
212            $modal.removeClass('active');
213            $('body').removeClass('modal-open');
214            
215            // Remember user preference if modal was shown by default
216            const expandByDefault = this.element.data('expand-by-default') === 'yes';
217            if (expandByDefault) {
218                this.setCookie('promo_banner_' + this.bannerId + '_user_closed', 'true', 1); // Remember for 1 day
219            }
220        }
221        
222        expand() {
223            if (this.isExpanded || this.isAnimating) {
224                return;
225            }
226            
227            this.isAnimating = true;
228            this.element.addClass('promo-banner-expanded');
229            this.element.find('.promo-banner-toggle').attr('aria-expanded', 'true');
230            
231            // Update aria-hidden when expanded
232            this.element.attr('aria-hidden', 'false');
233            
234            // Focus management
235            setTimeout(() => {
236                this.element.find('.promo-banner-content').focus();
237            }, 300);
238            
239            this.isExpanded = true;
240            this.isAnimating = false;
241        }
242        
243        close() {
244            if (!this.isExpanded || this.isAnimating) return;
245            
246            this.isAnimating = true;
247            this.element.removeClass('promo-banner-expanded');
248            this.element.find('.promo-banner-toggle').attr('aria-expanded', 'false');
249            
250            // Remember user preference if banner was expanded by default
251            const expandByDefault = this.element.data('expand-by-default') === 'yes';
252            if (expandByDefault) {
253                this.setCookie('promo_banner_' + this.bannerId + '_user_closed', 'true', 1); // Remember for 1 day
254            }
255            
256            // Focus management - blur the button first, then set aria-hidden
257            this.element.find('.promo-banner-toggle').blur();
258            
259            setTimeout(() => {
260                // Update aria-hidden after focus is removed
261                this.element.attr('aria-hidden', 'true');
262                this.isExpanded = false;
263                this.isAnimating = false;
264            }, 100); // Small delay to ensure blur completes
265        }
266        
267        handleResize() {
268            // Note: Position classes are now maintained across all screen sizes
269            // CSS media queries handle responsive positioning without removing position classes
270            
271            // Close mobile modal if switching to desktop
272            if (!this.isMobile()) {
273                const modalId = '#' + this.element.attr('id') + '-modal';
274                const $modal = $(modalId);
275                if ($modal.hasClass('active')) {
276                    this.closeMobileModal($modal);
277                    
278                    // If expand by default is enabled and we're switching to desktop, expand the banner
279                    const expandByDefault = this.element.data('expand-by-default') === 'yes';
280                    const userClosed = this.getCookie('promo_banner_' + this.bannerId + '_user_closed');
281                    if (expandByDefault && !userClosed && this.element.hasClass('promo-banner-visible')) {
282                        setTimeout(() => {
283                            this.expand();
284                        }, 100);
285                    }
286                }
287            } else {
288                // If switching to mobile and expand by default is enabled, show modal instead
289                const expandByDefault = this.element.data('expand-by-default') === 'yes';
290                const userClosed = this.getCookie('promo_banner_' + this.bannerId + '_user_closed');
291                if (expandByDefault && !userClosed && this.element.hasClass('promo-banner-visible') && this.isExpanded) {
292                    this.close(); // Close the expanded banner
293                    setTimeout(() => {
294                        this.showMobileModal(); // Show the modal instead
295                    }, 100);
296                }
297            }
298        }
299        
300        getCookie(name) {
301            const nameEQ = name + '=';
302            const ca = document.cookie.split(';');
303            for (let i = 0; i < ca.length; i++) {
304                let c = ca[i];
305                while (c.charAt(0) === ' ') c = c.substring(1, c.length);
306                if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
307            }
308            return null;
309        }
310        
311        setCookie(name, value, days) {
312            let expires = '';
313            if (days) {
314                const date = new Date();
315                date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
316                expires = '; expires=' + date.toUTCString();
317            }
318            document.cookie = name + '=' + (value || '') + expires + '; path=/';
319        }
320        
321        clearCookie(name) {
322            document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
323        }
324    }
325    
326    // Initialize banners when DOM is ready
327    $(document).ready(function() {
328        // Initialize all promo banners
329        $('.promo-banner').each(function() {
330            const $banner = $(this);
331            
332            // Check if this banner already has the PromoBanner class functionality
333            // by looking for the data attributes that the PromoBanner class sets
334            if ($banner.data('banner-id') && $banner.find('.promo-banner-toggle').length) {
335                // Use the new PromoBanner class
336                new PromoBanner($banner);
337            } else {
338                // Use legacy functionality for backwards compatibility
339                const $toggle = $banner.find('.promo-banner-toggle');
340                const $modal = $('#' + $banner.attr('id') + '-modal');
341                const $modalClose = $modal.find('.promo-banner-close');
342                
343                // Check if mobile
344                function isMobile() {
345                    return $(window).width() <= 768;
346                }
347
348                // Toggle banner
349                $toggle.on('click.legacy', function(e) {
350                    e.preventDefault();
351
352                    if (isMobile()) {
353                        // Show modal on mobile
354                        $modal.addClass('active');
355                        $('body').addClass('modal-open');
356                    } else {
357                        // Show sliding content on desktop
358                        $banner.toggleClass('promo-banner-expanded');
359                    }
360                });
361
362                // Close modal (mobile)
363                $modalClose.on('click.legacy', function(e) {
364                    e.preventDefault();
365                    $modal.removeClass('active');
366                    $('body').removeClass('modal-open');
367                });
368                
369                // Close on outside click (desktop)
370                $(document).on('click.legacy', function(e) {
371                    if (!$(e.target).closest('.promo-banner').length) {
372                        $banner.removeClass('promo-banner-expanded');
373                    }
374                });
375
376                // Close modal on overlay click (mobile)
377                $modal.on('click.legacy', function(e) {
378                    if (e.target === this) {
379                        $modal.removeClass('active');
380                        $('body').removeClass('modal-open');
381                    }
382                });
383
384                // Close on escape key
385                $(document).on('keydown.legacy', function(e) {
386                    if (e.keyCode === 27) { // ESC key
387                        if (isMobile() && $modal.hasClass('active')) {
388                            // Close modal on mobile
389                            $modal.removeClass('active');
390                            $('body').removeClass('modal-open');
391                        } else {
392                            // Close banner on desktop
393                            $banner.removeClass('promo-banner-expanded');
394                        }
395                    }
396                });
397
398                // Handle window resize - close modal if switching to de
398sktop
399                $(window).on('resize.legacy', function() {
400                    if (!isMobile() && $modal.hasClass('active')) {
401                        $modal.removeClass('active');
402                        $('body').removeClass('modal-open');
403                    }
404                });
405            }
406        });
407        
408        // Handle dynamic content loading
409        if (typeof MutationObserver !== 'undefined') {
410            const observer = new MutationObserver(function(mutations) {
411                mutations.forEach(function(mutation) {
412                    if (mutation.type === 'childList') {
413                        mutation.addedNodes.forEach(function(node) {
414                            if (node.nodeType === 1 && $(node).hasClass('promo-banner')) {
415                                const $banner = $(node);
416                                
417                                // Check if this banner already has the PromoBanner class functionality
418                                if ($banner.data('banner-id') && $banner.find('.promo-banner-toggle').length) {
419                                    // Use the new PromoBanner class
420                                    new PromoBanner($banner);
421                                }
422                                // Note: Legacy functionality for dynamically added content
423                                // would need to be handled separately if needed
424                            }
425                        });
426                    }
427                });
428            });
429            
430            observer.observe(document.body, {
431                childList: true,
432                subtree: true
433            });
434        }
435    });
436    
437    // Utility functions
438    window.PromoBanner = {
439        // Public method to show banner by ID
440        show: function(bannerId) {
441            const banner = $('#promo-banner-' + bannerId);
442            if (banner.length) {
443                const promoBanner = banner.data('promo-banner-instance');
444                if (promoBanner) {
445                    promoBanner.expand();
446                }
447            }
448        },
449        
450        // Public method to hide banner by ID
451        hide: function(bannerId) {
452            const banner = $('#promo-banner-' + bannerId);
453            if (banner.length) {
454                const promoBanner = banner.data('promo-banner-instance');
455                if (promoBanner) {
456                    promoBanner.close();
457                }
458            }
459        },
460        
461        // Public method to get banner statistics (placeholder for future implementation)
462        getStats: function(bannerId) {
463            // This function is reserved for future analytics implementation
464            return null;
465        }
466    };
467    
468})(jQuery);

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.