PageSourceSearch

https://machias.edu/wp-content/themes/umaine/assets/js/vendor/modal.js?ver=1.0.1

js machias.edu collected 2026-09-24 11:17:36 UTC 21,065 bytes, 787 lines download raw bytes

1/*! University of Maine - v0.0.1
2 * http://10up.com/
3 * Copyright (c) 2022
4 * Licensed GPLv2+
5 */
6// jshint ignore: start
7/**
8 * Name: Accessible Modal Plugin
9 * Contributors: Tim Wright ([email protected], @csskarma)
10 * License: MIT, https://opensource.org/licenses/MIT
11 * Converted from a jQuery plugin originally written by @scottohara: https://github.com/scottaohara/accessible-components
12 */
13
14( function() {
15	
16		'use strict';
17	
18		// Define global TenUp object if it doesn't exist
19		if ( 'object' !== typeof window.TenUp ) {
20			window.TenUp = {};
21		}
22	
23	
24		// Polyfill for el.matches
25		if (!Element.prototype.matches) {
26			Element.prototype.matches =
27			Element.prototype.matchesSelector ||
28			Element.prototype.mozMatchesSelector ||
29			Element.prototype.msMatchesSelector ||
30			Element.prototype.oMatchesSelector ||
31			Element.prototype.webkitMatchesSelector ||
32			function(s) {
33				var matches = (this.document || this.ownerDocument).querySelectorAll(s),
34				i = matches.length;
35				while (--i >= 0 && matches.item(i) !== this) {}
36				return i > -1;
37			};
38		}
39	
40		/*
41		 * Cross-browser way to deal with class management
42		 */
43	
44		TenUp.hasClass = function ( el, cls ) {
45			return el.className && new RegExp("(\\s|^)" + cls + "(\\s|$)").test( el.className );
46		};
47	
48		/*
49		 * Cross-browser way to add a class
50		 */
51	
52		TenUp.addClass = function ( el, cls ) {
53	
54			if ( el.classList ) {
55				el.classList.add(cls);
56			} else if (!TenUp.hasClass(el, cls)) {
57				el.className += " " + cls;
58			}
59	
60		};
61	
62		/*
63		 * Cross-browser way to remove a class
64		 */
65	
66		TenUp.removeClass = function ( el, cls ) {
67			if ( el.classList ) {
68				el.classList.remove( cls );
69			} else if( TenUp.hasClass( el, cls ) ) {
70				var reg = new RegExp( '(\\s|^)' + cls + '(\\s|$)' );
71				el.className = el.className.replace( reg, ' ' );
72			}
73		};
74	
75		// Caching and setting up some variables
76	
77		var i;
78	
79		// use this defualt title if the attr isn't set
80		var safetyModalTitle  = "Dialog Window";
81	
82	
83	
84	
85	
86	
87		// initialize all the modals
88		TenUp.modal = function ( options, callback ) {
89	
90			// Define the default values
91			var defaults = {
92				'target': '.a11y-modal'
93			};
94			var defaults_count = defaults.length;
95			var opt;
96	
97			// Map all default settings to user defined options
98			for ( opt = 0; opt < defaults_count; opt = opt + 1) {
99				if( typeof options[opt] === "undefined" ) {
100					options[opt] = defaults[opt];
101				}
102			}
103	
104			var el = document.querySelector( options.target );
105	
106			if ( ! el ) {
107				return;
108			}
109	
110			var id = el.id;
111			var self = document.getElementById( id );
112	
113			var modalTrigger = options.trigger;
114			var modal = '.a11y-modal';
115			var modalDoc = '.modal';
116			var modalTitle = '[data-modal-title]';
117			var modalClose = '[data-modal-close]';
118			var bodyElements = 'a11y-hide-if-modal-open';
119			var genModalClose = document.createElement( 'button' );
120			var html = document.body;
121			var modallisting = document.querySelectorAll( modal );
122			var modallistingCount = modallisting.length;
123	
124			// build out the fallback button
125			genModalClose.setAttribute( 'type', 'button' );
126			genModalClose.setAttribute( 'data-modal-close', 'true' );
127			TenUp.addClass( genModalClose, 'modal__outro__close' );
128	
129			genModalClose.innerHTML = '<span umaine-modal-aria-hidden="true">x</span>';
130			// setup modals properly
131			var setup_a11y_modal = function () {
132	
133				// setup each modal instance to have the
134				// appropriate attributes. These attributes
135				// are applied to what would be considered the
136				// modal container, or 'overlay'
137	
138				var findTitle = self.querySelector( modalTitle );
139				var findHeading = self.querySelector( '[data-modal-title]' );
140				var modalDocVar = self.querySelectorAll( modalDoc );
141				var modalDocVarCount = modalDocVar.length;
142				var modalObj;
143				var thisLabel;
144				var j;
145	
146				// first check to see what sort of dialog this should be
147				// if a data-modal-alert attribute is set to true, then
148				// this is meant to be an alert dialog, so set the role
149				// to 'alertdialog'. If it's not set, it's mean to be
150				// a normal dialog. So set the role to just 'dialog'
151	
152				if ( self.getAttribute( 'data-modal-alert' ) === 'true' ) {
153					self.setAttribute( 'role', 'alertdialog' );
154				} else {
155					self.setAttribute( 'role', 'dialog' );
156				}
157	
158				// we will need to set focus to the modal content
159				// container for focus trapping reasons, so we
160				// need this to have a tabindex
161	
162				self.setAttribute( 'tabindex', '-1' );
163				self.querySelector( modalDoc ).setAttribute( 'tabindex', '-1' );
164	
165				// check to see if an aria-label was set on the modal
166				// if not, then start running checks to apply an aria-labelledby
167	
168				if ( !self.getAttribute( 'aria-label' ) ) {
169	
170					// if the modal window has a child modalTitle set,
171					// then add an aria-labelledby attribute to the dialog,
172					// pointing to that element.
173	
174					if ( findTitle ) {
175	
176						thisLabel = findTitle.getAttribute( 'id' );
177	
178					} //if
179	
180					// in the event that a modalTitle wasn't manually set,
181					// then we should look to see if there's a heading element
182					// present at all, and then make THAT the source for the
183					// aria-labelledby
184	
185					else if ( findHeading ) {
186	
187						// does the heading we found have an id already?
188						// let's check
189	
190						if ( findHeading.setAttribute( 'id' ) ) {
191	
192							thisLabel = findHeading.setAttribute( 'id' );
193	
194						} else { // if it doesn't, then generate one
195	
196							thisLabel = self.setAttribute( 'id' ) + '_title';
197	
198							findHeading.setAttribute( 'id', thisLabel );
199	
200						} // else
201	
202					} // else/if
203	
204					self.setAttribute( 'aria-labelledby', thisLabel );
205	
206				} // if
207	
208				// setup each modal content area (the component that
209				// contains the actual content)
210	
211				for ( j = 0; j < modalDocVarCount; j = j + 1 ) {
212	
213					modalObj = modalDocVar[j];
214	
215					// important for older versions of NVDA to accurately
216					// understand a modal's content
217	
218					modalObj.setAttribute( 'role', 'document' );
219	
220					// Modals need a close button, and it should be the last
221					// element in the modal.
222	
223					// If a modal doesn't have a close button, create it.
224	
225					if ( typeof modalObj.querySelector( modalClose ) === 'undefined' ) {
226	
227						if ( typeof modalObj.querySelector( '.modal__outro' ) === 'undefined' ) {
228	
229							modalObj.querySelector( '.modal__outro' ).appendChild( genModalClose );
230	
231						} else {
232	
233							modalObj.appendChild( genModalClose );
234	
235						} // if/else
236	
237					} // if
238	
239					// Set aria-label and control attributes to the close trigger.
240	
241					modalObj.querySelector( modalClose ).setAttribute( 'aria-label', 'Close Mod
241al' );
242					modalObj.querySelector( modalClose ).setAttribute( 'aria-controls', modalObj.parentNode.getAttribute( 'id' ) );
243	
244				} // end for loop
245	
246			};
247	
248			// setup modal triggers
249			// the following applies needed aria-attributes
250			// to the modal triggers, as well as doing a
251			// final check to ensure that the modal window
252			// has appropriate labeling
253	
254			var setup_a11y_modal_triggers = function () {
255	
256				var modalTriggerEl = document.querySelectorAll( modalTrigger );
257				var modalTriggerCount = modalTriggerEl.length;
258				var grabTarget;
259				var modalTarget;
260				var modalObj;
261				var m;
262	
263				for ( m = 0; m < modalTriggerCount; m = m + 1 ) {
264	
265					modalObj = modalTriggerEl[m];
266	
267					// if the trigger is a link, we need to give it a
268					// button role.
269	
270					if ( modalObj.getAttribute( 'href' ) ) {
271	
272						modalObj.setAttribute( 'role', 'button' );
273	
274					}
275	
276					// The triggers need to point to the modals they control via
277					// the aria-controls attribute. So run a check to see if the
278					// attribute exists on the button.
279					//
280					// It's likely that it WON'T exist, as the optimal method for
281					// the minimum mark-up is to use a data-modal-open attribute
282					// instead. The reason for this is that in situations without
283					// JavaScript, we don't want partial ARIA hooks, as that can
284					// create confusion for ATs that would expect certain
285					// functionality that wouldn't be available due to lack of JS.
286	
287					if ( !modalObj.getAttribute( 'aria-controls' ) ) {
288	
289						// make sure that the trigger actually triggers something.
290						// if it there's no data-modal-open attribute set, then
291						// pull the target from the href
292	
293						if ( modalObj.getAttribute( 'data-modal-open' ) ) {
294	
295							grabTarget = modalObj.getAttribute( 'data-modal-open' );
296							modalObj.setAttribute( 'aria-controls', grabTarget );
297	
298						}
299						// if there's no data-modal-open, pull the target from
300						// from the href
301	
302						else if ( modalObj.getAttribute( 'href' ) ) {
303	
304							grabTarget = modalObj.getAttribute( 'href' ).split( '#' )[1];
305							modalObj.setAttribute( 'aria-controls', grabTarget );
306	
307						}
308	
309						// if neither of the above are set, then this just won't work
310	
311						else {
312							// No target set. A target is set by setting the value of an aria-controls attribute, which if absent, can be generated by the trigger's href URI, or a data-modal-open attribute to the value of the modal window ID you are attempting to open.
313							return false;
314						}
315	
316					} // end if aria-controls
317	
318					// now that the aria-controls is set, point to the modal's target
319					// so we can run the next if
320	
321					modalTarget = document.querySelector( '#' + modalObj.getAttribute( 'aria-controls' ) );
322	
323					// finally a last check to see if the trigger is meant to launch
324					// an alert dialog modal. If the alertdialog role wasn't set during
325					// the initial setup function, then look to see if the 'data-modal-alert'
326					// attribute is present on the trigger, and if so, apply the alertdialog
327					// role to the modal on trigger activation.
328	
329					if ( modalObj.getAttribute( 'data-modal-alert' ) === 'true' && modalTarget.getAttribute( 'role' ) !== 'alertdialog' ) {
330						modalTarget.setAttribute( 'role', 'alertdialog' );
331					}
332	
333				} // for loop
334			};
335	
336			// Place modal window(s) as the first child(ren)
337			// of the body element so tabbing backwards can
338			// move focus into the browser's address bar
339	
340			var organize_dom = function () {
341	
342				var body = document.body;
343				var modalEl = document.querySelectorAll( modal );
344				var modalElCount = modalEl.length;
345				var k;
346	
347				// place all the modal dialogs at the top of the DOM, as the
348				// first children of BODY. This will allow for backwards tabbing
349				// into the browser's address bar, where as if the modals were
350				// not located at the top of the DOM, keyboard focus would be
351				// completely trapped within the modal window.
352	
353				for( k = 0; k < modalElCount; k = k + 1 ) {
354					body.insertBefore( modalEl[k], body.firstChild );
355				}
356	
357				// for all direct children of the BODY element, add a class
358				// to target during open/close
359				TenUp.addClass( body.querySelector( '*:not(.a11y-modal)' ), bodyElements );
360	
361			};
362	
363			var open_a11y_modal = function ( e ) {
364	
365				// setup vars
366	
367				var openTarget = e.target;
368				var modalTarget = document.getElementById( openTarget.getAttribute( 'aria-controls' ) );
369	
370				// Check to see if the modal has either an aria-label or labelledby attribute
371				// if not, that means that the modal didn't have a manually set aria-label,
372				// nor does the modal have any sort of heading element to draw a title from.
373				// In this instance, pull the safetyModalTitle var in as an aria-label
374	
375				if ( !modalTarget.getAttribute( 'aria-labelledby' ) && !modalTarget.getAttribute( 'aria-label' ) ) {
376	
377					// Last ditch effort to allow control over what the aria-label will be.
378					// If the data-set-modal-title attribute is set to the modal trigger,
379					// its value will be set as the modal's aria-label
380	
381					if ( openTarget.getAttribute( 'data-set-modal-title' ) ) {
382						safetyModalTitle = openTarget.getAttribute( 'data-set-modal-title' );
383					}
384	
385					// set an aria-label to the modal
386					modalTarget.setAttribute( 'aria-label', safetyModalTitle );
387	
388				} // if
389	
390				// traps focus while the modal is open
391	
392				trap_focus();
393	
394				// if modal trigger is an <a>, make sure that URI isn't
395				// updated and more importantly that the document doesn't
396				// auto-jump to the DOM location of the modal window.
397	
398				e.preventDefault();
399	
400				// set that modal be visible, controlled by the
401				// umaine-modal-aria-hidden attribute and CSS
402				// then shift focus to it
403	
404				modalTarget.setAttribute( 'umaine-modal-aria-hidden', 'false' );
405	
406				// add a class to the HTML, to allow for a CSS hook
407				// to help restrict document scroll while the modal
408				// is open
409	
410				TenUp.addClass( html, 'modal-is-open' );
411	
412				// Hide main document content from screen readers by
413				// applying an umaine-modal-aria-hidden attribute to all direct
414				// siblings of the modal windows. (var bodyElements)
415	
416				document.querySelector( '.' + bodyElements ).setAttribute( 'umaine-modal-aria-hidden', 'true' );
417	
418				// finally, apply focus to the newly opened modal window
419	
420				modalTarget.querySelector( modalDoc ).focus();
421	
422			};
423	
424			// Bind to both the button click and the escape key to
425			// close the modal window  but only if isModalOpen is set to true
426	
427			var close_a11y_modal = function ( e ) {
428	
429				e.preventDefault();
430	
431				var returnFocus = document.querySelectorAll( '[aria-controls="' + self.getAttribute( 'id' ) + '"]');
432				var returnFocusCount = returnFocus.length;
433	
434				returnFocus = returnFocus[returnFocusCount - 1];
435	
436				TenUp.removeClass( html, 'modal-is-open' );
437				self.setAttribute( 'umaine-modal-aria-hidden', 'true' );
438	
439				// remove the umaine-modal-aria-hidden that was applied during modal open
440	
441				document.querySelector( '.' + bodyElements ).removeAttribute( 'umaine-modal-aria-hidden' );
442	
443				returnFocus.focus();
444	
445			};
446	
447			// keyboard controls specific to the modal dialog windows
448	
449			var keytrolls_a11y_modal_trigger = function ( e ) {
450	
451				var keyCode = e.keyCode || e.which;
452	
453				switch ( keyCode ) {
454	
455					// space & enter
456	
457					case 32:
458					case 13:
459						e.stopPropagation();
460						e.target.click();
461						break;
462	
463				} // switch
464	
465			};
466	
467			// trap focus within the modal window, because otherwise
468			// users can tab to obscured elements, and that's just
469			// bad UX.
470	
471			var trap_focus = function () {
472	
473				// I'm open to better solutions for trapping focus within the modal with it's open
474				var all_nodes = document.querySelectorAll( "*" );
475				var trapArea = self.querySelector( modalDoc );
476				var nodeCount =  all_nodes.length;
477				var j;
478	
479	
480				for ( j = 0; j < nodeCount; j = j + 1 ) {
481					all_nodes.item( j ).addEventListener( "focus", function( e ) {
482	
483						if ( TenUp.hasClass( html, 'modal-is-open' ) && !trapArea.contains( e.target ) ) {
484	
485							e.stopPropagation();
486							trapArea.focus();
487	
488						}
489					}, false );
490				}
491	
492			}; // end trap_focus
493	
494			/*
495			 * SETUP FUNCTIONS
496			 */
497	
498			organize_dom();
499			setup_a11y_modal();
500			setup_a11y_modal_triggers();
501	
502			/*
503			 * EVENT BINDINGS
504			 */
505	
506			// close the modal is the overlay is clicked
507	
508			self.addEventListener( "click", function( e ) {
509	
510				if ( e.target === self.querySelector( modalDoc ).parentNode ) {
511					e.stopPropagation();
512					close_a11y_modal( e );
513				}
514			}, false );
515	
516			// close the modal on ESC
517	
518			self.addEventListener("keydown", function( e ) {
519	
520				if( e.keyCode == 27 && TenUp.hasClass( html, 'modal-is-open' ) ) {
521					close_a11y_modal( e );
522				}
523			}, false);
524	
525			// close the modal if you click the close button
526	
527			self.querySelector( modalClose ).addEventListener('click', function( e ) {
528				close_a11y_modal( e );
529			}, false );
530	
531			// open the modal when the trigger is clicked
532	
533			document.addEventListener( "click", function( e ) {
534	
535				if ( e.target.matches( modalTrigger ) ) {
536					e.stopPropagation();
537					open_a11y_modal( e );
538				}
539	
540			}, false );
541	
542			// making sure the modal triggers open with <enter> and <space> (making it act like a button, if it's a link)
543	
544			document.addEventListener( "keydown", function( e ) {
545				if (e.target.matches( modalTrigger )) {
546					e.stopPropagation();
547					keytrolls_a11y_modal_trigger( e );
548				}
549			}, false );
550	
551			// accept callback functions, because why not?
552	
553			if ( typeof callback === 'function' ) {
554				callback.call();
555			}
556	
557		}; // end: a11y_modal: function
558	
559	} )();
560	
561;(function ( w, doc ) {
562	
563	  // Enable strict mode
564	  'use strict';
565	
566	  ( function hashClick() {
567	
568		// wait for everything to be loaded
569		w.onload = function() {
570	
571		  // is there a #hash in the url?
572		  if ( w.location.hash ) {
573	
574			var hashValue = w.location.hash;
575			var element = doc.querySelector("a[href='" + hashValue + "']");
576			var data = doc.querySelector("[data-href='" + hashValue + "']");
577	
578			if( element ) { element.click(); }
579			if( data ) { data.click(); }
580	
581		  } // End if
582	
583		}; // End window.onload
584	
585	  })();
586	
587	})( this, this.document );
588
589// jshint ignore: start
590/**
591 * University of Maine
592 * http://10up.com/
593 *
594 * Copyright (c) 2017 10up
595 * Licensed under the GPLv2+ license.
596 */
597
598var UMaine = UMaine || {};
599
600UMaine.modal = ( function( window, TenUp, undefined ) {
601	var cache = {
602		modal: document.getElementById( 'gallery-modal' ),
603		modalCaption: document.getElementById( 'modal-caption' )
604	};
605
606	/**
607	 * Attach events
608	 */
609	var init = function() {
610		/**
611		 * Bail if TenUp is not defined
612		 */
613
614		if ( 'undefined' === typeof TenUp ) {
615			return;
616		}
617
618		TenUp.modal({
619			'target': '#gallery-modal',
620			'trigger': '.gallery a img'
621		}, function() {
622			setUpGalleries();
623		});
624	};
625
626	/**
627	 * Prepares functions in case multiple galleries are set
628	 */
629	var setUpGalleries = function() {
630		var galleries = document.querySelectorAll( '.gallery' );
631
632		if ( 0 === galleries.length ) {
633			return;
634		}
635
636		Array.prototype.forEach.call( galleries, setUpGallery );
637	};
638
639	/**
640	 * Updates links and image attributes to make modal open.
641	 * @param {*} gallery 
642	 * @param {*} index 
643	 */
644	var setUpGallery = function( gallery, index ) {
645		if ( 'undefined' === typeof gallery ) {
646			return;
647		}
648
649		cache.modal.setAttribute( 'aria-labelledby', 'Image Lightbox' );
650
651		var links = gallery.querySelectorAll( 'a' );
652
653		if ( 0 === links.length ) {
654			return;
655		}
656
657		Array.prototype.forEach.call( links, changeLinkTarget );
658	}
659
660	/**
661	 * Changes gallery link target to point to the modal
662	 * Saves image url in a data attr
663	 * @param {*} link 
664	 * @param {*} index 
665	 */
666	var changeLinkTarget = function( link, index ) {
667		if ( 'undefined' === typeof link ) {
668			return;
669		}
670
671		var imageUrl = link.getAttribute( 'href' ),
672			image    = link.querySelector( 'img' );
673
674		/**
675		 * Change link only if it points to an image
676		 */
677		var regex = /\.(gif|jpe?g|tiff|png|webp)$/;
678
679		if ( ! regex.test( imageUrl ) ) {
680			return;
681		}
682
683		link.setAttribute( 'href', '#gallery-modal' );
684		image.setAttribute( 'data-image', imageUrl );
685		image.setAttribute( 'aria-controls', 'gallery-modal' );
686
687		link.addEventListener( 'click', populateModal );
688	};
689
690	/**
691	 * Loads image inside modal.
692	 * @param {*} event 
693	 */
694	var populateModal = function( event ) {
695		if ( ! event.target.matches( '.gallery a img' ) ) {
696			return;
697		}
698		
699		var image = event.target,
700			imageUrl = image.getAttribute( 'data-image' ),
701			link = image.parentElement,
702			modalContent,
703			newImage,
704			closeButton;
705
706		if ( 'undefined' === typeof cache.modal ) {
707			return;
708		}
709
710		setUpCaption( link );
711
712		modalContent = cache.modal.querySelector( '.modal__content' );
713		closeButton  = cache.modal.querySelector( '.modal__outro__close' );
714
715		if ( 'undefined' === typeof modalContent ) {
716			return;
717		}
718
719		newImage = new Image();
720		newImage.src = imageUrl;
721		newImage.classList.add( 'modal-image' );
722
723		modalContent.appendChild( newImage );
724		closeButton.addEventListener( 'click', emptyModal );
725		// close the modal on ESC
726		document.addEventListener( 'keydown', function( event ) {
727			if( event.keyCode == 27 ) {
728				event.stopPropagation();
729				emptyModal();
730			}
731		}, false);
732
733		cache.modal.addEventListener( "click", function( event ) {	
734			if ( ! event.target.matches( '#gallery-modal' ) ) {
735				return;
736			}
737
738			event.stopPropagation();
739			emptyModal();
740		}, false );
741	};
742
743	/**
744	 * Remove image to prevent duplicates inside modal.
745	 */
746	var emptyModal = function() {
747		var images = document.querySelectorAll( '.modal-image' );
748
749		if ( 0 === images.length ) {
750			return;
751		}
752
753		Array.prototype.forEach.call( images, function( image ) {
754			image.parentNode.removeChild( image );
755		} );
756
757		cache.modalCaption.innerText = '';
758	};
759
760	/**
761	 * Looks for a caption and adds it to the bottom of the modal
762	 * @param {*} link 
763	 */
764	var setUpCaption = function( link ) {
765		if ( 'undefined' === typeof link ) {
766			return;
767		}
768
769		var parent = link.parentElement,
770			captionEl = parent.nextElementSibling;
771
772		if ( ! captionEl ) {
773			return;
774		}
775
776		cache.modalCaption.innerText = captionEl.innerText;
777	};
778
779	return {
780		init: init
781	};
782} )( window, TenUp );
783
784( function( window, undefined ) {
785	"use strict";
786	UMaine.modal.init();
787} )( this );

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.