PageSourceSearch

https://sonc.org/wp-content/plugins/modal-block/dist/modal.js?ver=1767982553

js sonc.org collected 2026-09-24 10:04:29 UTC 12,883 bytes, 365 lines download raw bytes

1/**
2 * Modal jquery code
3 * 11/13/2021 - do not scroll to first focusable field
4 * 8/2/2021 - add code to allow timer modal to optionally display only once via the use of a cookie
5 * 8/17/2021 - do not allow timer modal to display if another modal already open
6 * 11/1/2021 - when setting focus on first element do not scroll
7 * 2/15/2022 - disable close modal on escape if disabled in the frontend
8 * 4/5/2022 - Do not scroll when returning focus on modal close 
9 * 5/5/2022 - Allow for modals to call other modals
10 * 6/28/2022 - Allow for checking url for text before triggering modal
11 * 11/22/2022 - custom events for open and close
12 * 1/11/2023 - Provide ability to manually trigger initialization of modals on page. Also only initialize modals when document is ready.
13 */
14
15    
16var bodModalBlock = function(){
17	"use strict";
18	
19	var $ = jQuery.noConflict();
20
21	const focusableElements =
22	'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
23
24	var bodModalCount = 0; // global count of modals
25	var bodModalActive = false;
26	var bodModals = [];
27
28	var initElements = function(){
29	
30		bodModalCount = 0; // global count of modals
31		bodModalActive = false;
32		bodModals = [];
33
34		// if we have already inited this type of element
35		$('.wp-block-bod-modal-block').each(function(){
36			// for the identified modal we add an instance of the class BodModal 
37			// to the data element  
38			// $(this).data('bod-block-popup', new BodModal(this));
39			bodModals.push(new BodModal(this));
40
41		});
42	};
43
44	var checkForCookie = function(cookieName){
45		let name = cookieName + "=";
46		let decodedCookie = decodeURIComponent(document.cookie);
47		let ca = decodedCookie.split(';');
48		for(let i = 0; i <ca.length; i++) {
49		let c = ca[i];
50		while (c.charAt(0) == ' ') {
51			c = c.substring(1);
52		}
53		if (c.indexOf(name) == 0) {
54			return c.substring(name.length, c.length);
55		}
56		}
57		return "";
58	}
59
60
61	// class used to manipulate the modal, there will be a seperate instance of the 
62	// class for each modal on the page
63	// container - DOM object for modal 
64	var BodModal = function (container) {
65		// convert passed in DOM modal container to jquery object so we can apply jquery methods
66		this.$container = $(container);
67		this.$trigger = this.$container.find('.bod-block-popup-trigger');
68		this.$overlay = this.$container.find('.bod-block-popup-overlay');
69
70		// Markup the aria labels
71
72		bodModalCount ++; // get modal number to append to get unique id
73		this.$container.find('.bod-block-popup-wrap').attr('aria-labelledby','bodModalAriaTitle' + bodModalCount);
74		this.$container.find('.bod-modal-title').attr('id' , 'bodModalAriaTitle' + bodModalCount);
75		this.$container.find('.bod-block-popup-wrap').attr('aria-describedby','bodModalAriaContent' + bodModalCount);
76		this.$container.find('.bod-modal-content').attr('id' , 'bodModalAriaContent' + bodModalCount);
77
78
79		// bind our functions to this so we use current rather than event version of this
80		
81		this.show = this.show.bind(this);
82		this.afterShow = this.afterShow.bind(this);
83		this.hide = this.hide.bind(this);
84		this.keyPress = this.keyPress.bind(this);
85
86		// we need a timer for fading in and out the modal and for show on page load
87
88		this.timer = null;
89
90		// assign trigger event depending upon trigger type which is 
91		// stored has a class
92
93		if (this.$trigger.hasClass('type_selector')) {
94			var triggerSelector = this.$trigger.attr('data-selector');
95			if (triggerSelector) {
96				$('.' + triggerSelector).css('cursor','pointer').on('click', this.show); // attach click event
97			}
98		} else if (this.$trigger.hasClass('type_load')) {
99			// we have show on page load
100			
101			// need to load the data-once attribute which will be either no , or yes
102
103			var loadOnce = this.$trigger.attr('data-once');
104			var triggerTimer = true;
105			if (loadOnce) {			
106				if (loadOnce === 'yes') {
107						let modalId = this.$trigger.attr('data-id');
108						if (!modalId) modalId= "";
109						let modalOnce = checkForCookie ("bodModalOnce" + modalId);
110						if (modalOnce) triggerTimer = false;
111				}								
112			}
113
114			// check if we need to check for text in url to enable modal
115			var loadurl = this.$trigger.attr('data-urltrig');
116			if (loadurl) {
117				if (window.location.href.indexOf(loadurl)===-1) triggerTimer = false;
118			}
119
120			if (triggerTimer) {
121				// we need to extract the delay amd set a timeout using it
122				var loadDelay = this.$trigger.attr('data-delay');
123				isNaN(parseInt(loadDelay)) ? loadDelay = 1000 : loadDelay = parseInt(loadDelay);
124
125				// if we only want to show the modal once we exact the UID provided when the modal created (optional), and the number of days we wait until we show the 
126				// modal again (this is added to the cookie)
127				if (loadOnce) {
128					let modalId = this.$trigger.attr('data-id');
129					if (!modalId) modalId= "";
130					let noShowDays = this.$trigger.attr('data-days');
131					isNaN(parseInt(noShowDays)) ? noShowDays = 30 : noShowDays = parseInt(noShowDays);
132					this.timer = setTimeout(this.show, loadDelay, loadOnce, modalId , noShowDays);
133				} else {
134					this.timer = setTimeout(this.show, loadDelay);
135				}
136			}
137
138		} else { // must be button, text, or image trigger
139			this.$trigger.on('click', this.show); // attach click event
140		}
141
142		// setup the overlay and content wrap, put them into a jquery object and 
143		// attach the hide event for the click
144
145		// check to see if we have disabled the close on overlay click in the frontend
146		var disableOverlayClose = this.$overlay.attr('data-disabled-overlay-close');
147		if (!disableOverlayClose) {
148			this.$overlay.on('click' , this.hide);
149		} else {
150			if (disableOverlayClose === 'false') {
151				this.$overlay.on('click' , this.hide);				
152			}
153		}
154
155		// check to see if we have disabled the close on escape key press in the frontend
156		var disableEscapeClose = this.$overlay.attr('data-disabled-escape-close');
157		if (!disableEscapeClose) {
158			this.disableEscapeClose = false;
159		} else {
160			if (disableEscapeClose === 'true') {
161				this.disableEscapeClose = true;			
162			} else {
163				this.disableEscapeClose = false;
164			}
165		}
166
167		this.$modalWrap = this.$container.find('.bod-block-popup-wrap');
168		this.$modal = this.$container.find('.bod-block-popup');
169		this.$modalcloser = this.$container.find('.bod-block-popup-closer')
170		.on('click' , this.hide);
171		this.$titlecloser = this.$container.find('.bod-block-title-closer')
172		.on('click' , this.hide);
173		this.$btncloser = this.$container.find('.bod-block-close-btn .bod-btn')
174		.on('click' , this.hide);
175		this.$modalToModal = this.$container.find('.bod-modal-to-modal')
176		.on('click' , this.hide);
177		this.$escCloser = $(document).on('keydown' , this.keyPress);
178
179		// Javascript modal wrap
180
181		this.modalWrap = container.querySelector('.bod-block-popup-wrap'); 
182
183		// capture first, all and last focusable elements so we can enforce modal focus trap for ADA
184		this.focusableContent = this.modalWrap.querySelectorAll(focusableElements);
185		if (this.focusableContent) {
186			this.firstFocusableElement = this.focusableContent[0]; // get first element to be focused inside modal
187			this.lastFocusableElement = this.focusableContent[this.focusableContent.length - 1]; // get last element to be focused inside modal
188		}
189		this.modalOpen = false;
190
191	}
192
193	BodModal.prototype = {
194		show: function(loadOnce , modalId, noShowDays ){
195
196			$(document).trigger("bod-modal-before-open",this.$modalWrap);
197
198			// if we try and show a modal but one is already open then return without showing the new one
199			if (this.$trigger.hasClass('type_load') && bodModalActive === true) {
200				return;
201			} 
202
203			bodModalActive = true;
204
205			if (loadOnce) {
206				// check if we need to set a cookie to stop modal being shown mmore than once
207				if (loadOnce !== 'no') {
208					// flag set to say we only show once so set the cookie
209					const d = new Date();
210					d.setTime(d.getTime() + (noShowDays*24*60*60*1000));
211					let expires = "expires="+ d.toUTCString();
212					if (modalId) {
213						var cookie = "bodModalOnce" + modalId;
214					} else {
215						var cookie = "bodModalOnce";
216					}
217					document.cookie = cookie + "=" + loadOnce + ";" + expires + ";path=/";
218				}
219			}
220
221			// start to clearing any timeouts
222
223			clearTimeout(this.timer);
224
225			// when we show the modal we move it to the body DOM so that
226			// nothing else impacts it
227			this.$overlay.appendTo(document.body).show();
228			this.$modalWrap.appendTo(document.body).show();
229
230			// even though we have hit it with a show the opacticy still makes it
231			// invisible. We wait to ensure the modal is ready then then add
232			// an active class with triggers the opacity transition
233			
234			this.timer = setTimeout(this.afterShow, 25);
235		},
236		afterShow: function(){
237			clearTimeout(this.timer);
238
239			// Lets check what transition we are going to use
240
241			if (this.transition = this.$modal.attr('data-transition')) {
242				if (this.transition == 'left') {
243					this.$modal.addClass('slide-right');
244				} else if (this.transition == 'right') {
245					this.$modal.addClass('slide-left');
246				} else if (this.transition == 'bottom') {
247					this.$modal.addClass('slide-up');
248				} else if (this.transition == 'top') {
249					this.$modal.addClass('slide-down');
250				} else {
251					this.$modal.addClass('fade');
252				};
253			} else {
254				this.$modal.addClass('fade');
255			};
256
257			this.$overlay.addClass('active');
258			this.$modalWrap.addClass('active');
259			this.$modalWrap.attr('aria-modal','true');
260
261			this.modalOpen = true;
262			this.triggerElement = document.activeElement;
263			if (this.firstFocusableElement) {
264				this.firstFocusableElement.focus({
265					preventScroll: true
266				});
267			} else {
268				document.activeElement.blur();
269			}
270			$(document).trigger("bod-modal-after-open",this.$modalWrap);
271
272		},
273		hide: function() {
274			if ($(document).trigger("bod-modal-before-close",this.$modalWrap).data('cancel')){
275				return;
276			};
277			
278			this.$overlay.removeClass('active');
279			this.$modalWrap.removeClass('active');
280			// add the overlay and modal wrap back to the container and hide it
281			this.$overlay.appendTo(this.$container).hide();
282			this.$modalWrap.appendTo(this.$container).hide();
283			this.$modalWrap.attr('aria-modal','false'); 
284			bodModalActive = false;
285			this.modalOpen = false;
286			$(document).trigger("bod-modal-after-close",this.$modalWrap);
287
288			// return focus to element that was active before modal called
289			// but do not scroll to it
290			if (this.triggerElement) this.triggerElement.focus(
291				{
292					preventScroll: true
293				}
294			);
295		},	
296		keyPress: function(e) {
297			if ( e.keyCode === 27 && !this.disableEscapeClose) { // ESC
298				this.hide();
299			}
300
301			// here we do the ADA focus trap but only if the modal is open
302
303			if (this.modalOpen) {
304
305				// code to enforce focus trap for ADA
306
307				let isTabPressed = e.key === 'Tab' || e.keyCode === 9;
308
309				// if keypress is not tab do nothing
310				if (!isTabPressed) {
311					return;
312				}
313		
314				// Do we have at least one element in the modal that can receive focus 
315				if (this.firstFocusableElement) {
316					if (e.shiftKey) { // if shift key pressed for shift + tab combination
317						if (document.activeElement === this.firstFocusableElement) {
318							this.lastFocusableElement.focus();
318 // add focus for the last focusable element
319							e.preventDefault();
320						}
321					} else { // if tab key is pressed
322						if (document.activeElement === this.lastFocusableElement) { // if focused has reached to last focusable element then focus first focusable element after pressing tab
323							this.firstFocusableElement.focus(); // add focus for the first focusable element
324							e.preventDefault();
325						}
326					}
327				} else {
328					// if we are here the modal is open and the tab key has been pressed 
329					// but there are no elements in the modal that can receive focus.
330					// Therefore we do not want to provide focus to any element outside 
331					// the modal.
332					e.preventDefault();
333				}
334			}
335		},
336	}
337
338	return {
339		initModal : initElements
340	}
341	
342};
343
344var bodModal = bodModalBlock();
345jQuery(document).ready(function( $ ) {
346
347	bodModal.initModal();
348
349});
350
351// This is an example for the custom events, this one returns cancel=true so modal will not close
352//(function($){
353//$(document).on("bod-modal-before-close", function(e, modalData){
354//	$(this).data('cancel', true);
355
356//	this.$modalData = $(modalData);
357//	this.$modalData.addClass('testbeforeclose');
358//	alert("added before close");
359//});
360//})(jQuery);
361
362// This is an example of how to manually trigger initialization 10 secs after page is ready
363//jQuery(document).ready(function( $ ) {
364//	setTimeout(bodModal.initModal,10000);
365//});

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.