PageSourceSearch

https://flagman.co.jp/common/js/modaal.min.js

js flagman.co.jp collected 2026-10-07 12:19:14 UTC 47,962 bytes, 1,370 lines download raw bytes

1/*!
2	Modaal - accessible modals - v0.4.4
3	by Humaan, for all humans.
4	http://humaan.com
5 */
6/**
7	Modaal jQuery Plugin : Accessible Modals
8	==== General Options ===
9	type (string) 					: ajax, inline, image, iframe, confirm. Defaults to 'inline'
10	content_source (stribg)			: Accepts a string value for your target element, such as '#my-content'. This allows for when trigger element is
11										an `<a href="#">` link. Not to be confused with the already existing `source` event.
12	animation (string) 				: Fade, expand, down, up. Defaults to 'fade'
13	after_callback_delay (integer)	: Specify a delay value for the after open callbacks. This is necessary because with the bundled animations
14										have a set duration in the bundled CSS. Specify a delay of the same amount as the animation duration in so
15										more accurately fire the after open/close callbacks. Defaults 350, does not apply if animation is 'none',
16										after open callbacks are dispatched immediately
17	is_locked (boolean)				: Set this to true to disable closing the modal via keypress or clicking the background. Beware that if
18										type != 'confirm' there will be no interface to dismiss the modal if is_locked = true, you'd have to
19										programmatically arrange to dismiss the modal. Confirm modals are always locked regardless of this option
20										Defaults to false
21	hide_close (boolean)			: Set this to true to hide the close modal button. Key press and overlay click will still close the modal.
22										This method is best used when you want to put a custom close button inside the modal container space.
23	background (string)				: Background overlay style. Defaults to '#000'
24	overlay_opacity (float) 		: Background overlay transparency. Defaults to 0.8
25	overlay_close (boolean)			: Set this to false if you want to disable click to close on overlay background.
26	accessible_title (string)		: Accessible title. Default 'Dialog Window'
27	start_open (boolean)			: Set this to true to launch the Modaal window immediately on page open
28	fullscreen (boolean)			: Set this to true to make the modaal fill the entire screen, false will default to own width/height attributes.
29	custom_class (string)			: Fill in this string with a custom class that will be applied to the outer most modal wrapper.
30	width (integer)					: Desired width of the modal. Required for iframe type. Defaults to undefined //TODO
31	height (integer)				: Desired height of the modal. Required for iframe type. Defaults to undefined //TODO
32	background_scroll (boolean)		: Set this to true to enable the page to scroll behind the open modal.
33    should_open (boolean|function)  : Boolean or closure that returns a boolean to determine whether to open the modal or not.
34	close_text						: String for close button text. Available for localisation and alternative languages to be used.
35	close_aria_label				: String for close button aria-label attribute (value that screen readers will read out). Available for localisation and alternative languages to be used.
36	=== Events ===
37	before_open (function) 			: Callback function executed before modal is opened
38	after_open (function)			: Callback function executed after modal is opened
39	before_close (function)			: Callback function executed before modal is closed
40	after_close (function)			: Callback function executed after modal is closed
41	source (function(element, src))	: Callback function executed on the default source, it is intended to transform the
42										source (href in an AJAX modal or iframe). The function passes in the triggering element
43										as well as the default source depending of the modal type. The default output of the
44										function is an untransformed default source.
45	=== Confirm Options & Events ===
46	confirm_button_text (string)	: Text on the confirm button. Defaults to 'Confirm'
47	confirm_cancel_button_text (string) : Text on the confirm modal cancel button. Defaults to 'Cancel'
48	confirm_title (string)			: Title for confirm modal. Default 'Confirm Title'
49	confirm_content (string)		: HTML content for confirm message
50	confirm_callback (function)		: Callback function for when the confirm button is pressed as opposed to cancel
51	confirm_cancel_callback (function) : Callback function for when the cancel button is pressed
52	=== Gallery Options & Events ===
53	gallery_active_class (string)	: Active class applied to the currently active image or image slide in a gallery 'gallery_active_item'
54	outer_controls (boolean)		: Set to true to put the next/prev controls outside the Modaal wrapper, at the edges of the browser window.
55	before_image_change (function)	: Callback function executed before the image slide changes in a gallery modal. Default function( current_item, incoming_item )
56	after_image_change (function)	: Callback function executed after the image slide changes in a gallery modal. Default function ( current_item )
57	=== AJAX Options & Events ===
58	loading_content (string)		: HTML content for loading message. Default 'Loading &hellip;'
59	loading_class (string)			: Class name to be applied while content is loaded via AJAX. Default 'is_loading'
60	ajax_error_class (string)		: Class name to be applied when content has failed to load. Default is 'modaal-error'
61	ajax_success (function)		 	: Callback for when AJAX content is loaded in
62	=== SOCIAL CONTENT ===
vendor: 7,288 bytes, lines 63-303
63	instagram_id (string)			: Unique photo ID for an Instagram photo.
64*/
65( function( $ ) {
66
67	var modaal_loading_spinner = '<div class="modaal-loading-spinner"><div><div></div></div><div><div></div></div><div><div></div></div><div><div></div></div><div><div></div></div><div><div></div></div><div><div></div></div><div><div></div></div></div>'
68	
69	var Modaal = {
70		init : function(options, elem) {
71			var self = this;
72
73			self.dom = $('body');
74
75			self.$elem = $(elem);
76			self.options = $.extend({}, $.fn.modaal.options, self.$elem.data(), options);
77			self.xhr = null;
78
79			// set up the scope
80			self.scope = {
81				is_open: false,
82				id: 'modaal_' + ( new Date().getTime() ) + ( Math.random().toString(16).substring(2) ),
83				source: self.options.content_source ? self.options.content_source : self.$elem.attr('href')
84			};
85
86			// add scope attribute to trigger element
87			self.$elem.attr('data-modaal-scope', self.scope.id);
88
89			// private options
90			self.private_options = {
91				active_class: 'is_active'
92			};
93
94			self.lastFocus = null;
95			
96			// if is_locked
97			if ( self.options.is_locked || self.options.type == 'confirm' || self.options.hide_close ) {
98				self.scope.close_btn = '';
99			} else {
100				self.scope.close_btn = '<button type="button" class="modaal-close" id="modaal-close" aria-label="' + self.options.close_aria_label + '"><span>' + self.options.close_text + '</span></button>';
101			}
102
103			// reset animation_speed
104			if (self.options.animation === 'none' ){
105				self.options.animation_speed = 0;
106				self.options.after_callback_delay = 0;
107			}
108
109			// On click to open modal
110			$(elem).on('click.Modaal', function(e) {
111				e.preventDefault();
112				self.create_modaal(self, e);
113			});
114
115			// Define next/prev buttons
116			if (self.options.outer_controls === true) {
117				var mod_class = 'outer';
118			} else {
119				var mod_class = 'inner';
120			}
121			self.scope.prev_btn = '<button type="button" class="modaal-gallery-control modaal-gallery-prev modaal-gallery-prev-' + mod_class + '" id="modaal-gallery-prev" aria-label="Previous image (use left arrow to change)"><span>Previous Image</span></button>';
122			self.scope.next_btn = '<button type="button" class="modaal-gallery-control modaal-gallery-next modaal-gallery-next-' + mod_class + '" id="modaal-gallery-next" aria-label="Next image (use right arrow to change)"><span>Next Image</span></button>';
123
124			// Check for start_open
125			if (self.options.start_open === true ){
126				self.create_modaal( self );
127			}
128		},
129
130		// Initial create to determine which content type it requires
131		// ----------------------------------------------------------------
132		create_modaal : function(self, e) {
133			var self = this;
134			var source;
135
136			// Save last active state before modal
137			self.lastFocus = self.$elem;
138
139			if ( self.options.should_open === false || ( typeof self.options.should_open === 'function' && self.options.should_open() === false ) ) {
140				return;
141			}
142
143			// CB: before_open
144			self.options.before_open.call(self, e);
145
146			switch (self.options.type) {
147				case 'inline':
148					self.create_basic();
149					break;
150
151				case 'ajax':
152					source = self.options.source( self.$elem, self.scope.source );
153					self.fetch_ajax( source );
154					break;
155
156				case 'confirm':
157					self.options.is_locked = true;
158					self.create_confirm();
159					break;
160
161				case 'image':
162					self.create_image();
163					break;
164
165				case 'iframe':
166					source = self.options.source( self.$elem, self.scope.source );
167					self.create_iframe( source );
168					break;
169
170				case 'video':
171					self.create_video(self.scope.source);
172					break;
173
174				case 'instagram':
175					self.create_instagram();
176					break;
177			}
178
179			// call events to be watched (click, tab, keyup, keydown etc.)
180			self.watch_events();
181		},
182
183		// Watching Modal
184		// ----------------------------------------------------------------
185		watch_events : function() {
186			var self = this;
187
188			self.dom.off('click.Modaal keyup.Modaal keydown.Modaal');
189
190			// Body keydown
191			self.dom.on('keydown.Modaal', function(e) {
192				var key = e.keyCode;
193				var target = e.target;
194
195				// look for tab change and reset focus to modal window
196				// done in keydown so the check fires repeatedly when you hold the tab key down
197				if (key == 9 && self.scope.is_open) {
198					if (!$.contains(document.getElementById(self.scope.id), target) ) {
199						$('#' + self.scope.id).find('*[tabindex="0"]').focus();
200					}
201				}
202			});
203
204			// Body keyup
205			self.dom.on('keyup.Modaal', function(e) {
206				var key = e.keyCode;
207				var target = e.target;
208
209				if ( (e.shiftKey && e.keyCode == 9) && self.scope.is_open) {
210					// Watch for shift + tab key press. if open shift focus to close button.
211					if (!$.contains(document.getElementById(self.scope.id), target) ) {
212						$('#' + self.scope.id).find('.modaal-close').focus();
213					}
214				}
215
216				if ( !self.options.is_locked ){
217					// On escape key press close modal
218					if (key == 27 && self.scope.is_open ) {
219						if ( $(document.activeElement).is('input:not(:checkbox):not(:radio)') ) {
220							return false;
221						}
222
223						self.modaal_close();
224						return;
225					}
226				}
227
228				// is gallery open and images length is > 1
229				if ( self.options.type == 'image' ) {
230					// arrow left for back
231					if (key == 37 && self.scope.is_open && (!$('#' + self.scope.id + ' .modaal-gallery-prev').hasClass('is_hidden')) ) {
232						self.gallery_update('prev');
233					}
234					// arrow right for next
235					if (key == 39 && self.scope.is_open && (!$('#' + self.scope.id + ' .modaal-gallery-next').hasClass('is_hidden')) ) {
236						self.gallery_update('next');
237					}
238					return;
239				}
240			});
241
242			// Body click/touch
243			self.dom.on('click.Modaal', function(e) {
244				var trigger = $(e.target);
245
246				// General Controls: If it's not locked allow greedy close
247				if ( !self.options.is_locked ){
248					if ( (self.options.overlay_close && trigger.is('.modaal-inner-wrapper')) || trigger.is('.modaal-close') || trigger.closest('.modaal-close').length ) {
249						self.modaal_close();
250						return;
251					}
252				}
253
254				//Confirm Controls
255				if ( trigger.is('.modaal-confirm-btn' ) ){
256					// if 'OK' button is clicked, run confirm_callback()
257					if ( trigger.is('.modaal-ok') ) {
258						self.options.confirm_callback.call(self, self.lastFocus);
259					}
260
261					if ( trigger.is('.modaal-cancel') ) {
262						self.options.confirm_cancel_callback.call(self, self.lastFocus);
263					}
264					self.modaal_close();
265					return;
266				}
267
268				// Gallery Controls
269				if ( trigger.is( '.modaal-gallery-control' ) ){
270					// it not active, don't do nuthin!
271					if ( trigger.hasClass('is_hidden') ) {
272						return;
273					}
274
275					// trigger previous
276					if ( trigger.is('.modaal-gallery-prev') ) {
277						self.gallery_update('prev');
278					}
279					// trigger next
280					if ( trigger.is('.modaal-gallery-next') ) {
281						self.gallery_update('next');
282					}
283					return;
284				}
285			});
286		},
287
288		// Append markup into DOM
289		build_modal : function(content) {
290			var self = this;
291
292			// if is instagram
293			var igClass = '';
294			if ( self.options.type == 'instagram' ) {
295				igClass = ' modaal-instagram';
296			}
297
298			var wrap_class = (self.options.type == 'video') ? 'modaal-video-wrap' : 'modaal-content';
299
300			/*
301				modaal-start_none : fully hidden via display:none;
302				modaal-start_fade : hidden via opacity:0
303				modaal-start_slidedown : ...
vendor: 35,354 bytes, lines 303-1370
303
304			*/
305			var animation_class;
306			switch ( self.options.animation ) {
307				case 'fade' :
308					animation_class = ' modaal-start_fade';
309					break;
310				case 'slide-down' :
311					animation_class = ' modaal-start_slidedown';
312					break;
313				default :
314					animation_class = ' modaal-start_none'
315			}
316
317			// fullscreen check
318			var fullscreen_class = '';
319			if ( self.options.fullscreen ) {
320				fullscreen_class = ' modaal-fullscreen';
321			}
322
323			// custom class check
324			if ( self.options.custom_class !== '' || typeof(self.options.custom_class) !== 'undefined' ) {
325				self.options.custom_class = ' ' + self.options.custom_class;
326			}
327
328			// if width and heights exists and is typeof number
329			var dimensionsStyle = '';
330			if ( self.options.width && self.options.height && typeof self.options.width == 'number' && typeof self.options.height == 'number' ) {
331				// if width and height exist, and they are both numbers
332				dimensionsStyle = ' style="max-width:' + self.options.width + 'px;height:' + self.options.height + 'px;overflow:auto;"';
333			} else if ( self.options.width && typeof self.options.width == 'number' ) {
334				// if only width
335				dimensionsStyle = ' style="max-width:' + self.options.width + 'px;"';
336			} else if ( self.options.height && typeof self.options.height == 'number' ) {
337				// if only height
338				dimensionsStyle = ' style="height:' + self.options.height + 'px;overflow:auto;"';
339			}
340
341			// Reset dimensions style (width and height) for certain types
342			if ( self.options.type == 'image' || self.options.type == 'video' || self.options.type == 'instagram' || self.options.fullscreen ) {
343				dimensionsStyle = '';
344			}
345
346			// if is touch
347			// this is a bug fix for iOS to allow regular click events on div elements.
348			var touchTrigger = '';
349			if ( self.is_touch() ) {
350				touchTrigger = ' style="cursor:pointer;"'
351			}
352
353			var build_markup = '<div class="modaal-wrapper modaal-' + self.options.type + animation_class + igClass + fullscreen_class + self.options.custom_class + '" id="' + self.scope.id + '"><div class="modaal-outer-wrapper"><div class="modaal-inner-wrapper"' + touchTrigger + '>';
354
355					// hide if video
356					if (self.options.type != 'video') {
357						build_markup += '<div class="modaal-container"' + dimensionsStyle + '>';
358					}
359
360					// add the guts of the content
361					build_markup +=	'<div class="' + wrap_class + ' modaal-focus" aria-hidden="false" aria-label="' + self.options.accessible_title + ' - ' + self.options.close_aria_label + '" role="dialog">';
362
363							// If it's inline type, we want to clone content instead of dropping it straight in
364							if (self.options.type == 'inline') {
365								build_markup += '<div class="modaal-content-container" role="document"></div>';
366							} else {
367								// Drop in the content if it's not inline
368								build_markup +=	content;
369							}
370
371					// close wrap_class
372					build_markup += '</div>' + self.scope.close_btn;
373
374					// hide if video
375					if (self.options.type != 'video') {
376						build_markup += '</div>';
377					}
378
379			// close off modaal-inner-wrapper
380			build_markup +=	'</div>';
381			
382			// If type is image AND outer_controls is true: add gallery next and previous controls.
383			if (self.options.type == 'image' && self.options.outer_controls === true) {
384				build_markup += self.scope.prev_btn + self.scope.next_btn;
385			}
386
387			// close off modaal-wrapper
388			build_markup +=	'</div></div>';
389
390			// append ajax modal markup to dom
391			if ($('#' + self.scope.id + '_overlay').length < 1) {
392				self.dom.append(build_markup);
393			}
394
395			// if inline, clone content into space
396			if (self.options.type == 'inline') {
397				content.appendTo('#' + self.scope.id + ' .modaal-content-container');
398			}
399
400			// Trigger overlay show (which triggers modal show)
401			self.modaal_overlay('show');
402		},
403
404		// Create Basic Inline Modal
405		// ----------------------------------------------------------------
406		create_basic : function() {
407			var self = this;
408			var target = $(self.scope.source);
409			var content = '';
410
411			if (target.length) {
412				content = target.contents().detach();
413				target.empty();
414			} else {
415				content = 'Content could not be loaded. Please check the source and try again.';
416			}
417
418			// now push content into markup
419			self.build_modal(content);
420		},
421
422		// Create Instagram Modal
423		// ----------------------------------------------------------------
424		create_instagram : function() {
425			var self = this;
426			var id = self.options.instagram_id;
427			var content = '';
428
429			var error_msg = 'Instagram photo couldn\'t be loaded, please check the embed code and try again.';
430
431			self.build_modal('<div class="modaal-content-container' + ( self.options.loading_class != '' ? ' ' + self.options.loading_class : '' ) + '">' + self.options.loading_content + '</div>' );
432
433			// ID exists, is not empty null or undefined.
434			if ( id != '' && id !== null && id !== undefined ) {
435				// set up oembed url
436				var ig_url = 'https://api.instagram.com/oembed?url=http://instagr.am/p/' + id + '/';
437
438				$.ajax({
439					url: ig_url,
440					dataType: "jsonp",
441					cache: false,
442					success: function (data) {
443						
444						// Create temp dom element from which we'll clone into the modaal instance. This is required to bypass the unusual small thumb issue instagram oembed was serving up
445						self.dom.append('<div id="temp-ig" style="width:0;height:0;overflow:hidden;">' + data.html + '</div>');
446						
447						// Check if it has loaded once before.
448						// This is to stop the Embeds.process from throwing and error the first time it's being loaded.
449						// private_options are individual to a modaal_scope so will not work across multiple scopes when checking if true, only that one item.
450						if ( self.dom.attr('data-igloaded') ) {
451							window.instgrm.Embeds.process();
452						} else {
453							// first time it's loaded, let's set a new private option to use next time it's opened.
454							self.dom.attr('data-igloaded', 'true');
455						}
456
457						// now set location for new content
458						// timeout is required as well to bypass the unusual small thumb issue instagram oembed was serving up
459						var target = '#' + self.scope.id + ' .modaal-content-container';
460						if ( $(target).length > 0) {
461							setTimeout(function() {
462								$('#temp-ig').contents().clone().appendTo( target );
463								$('#temp-ig').remove();
464							}, 1000);
465						}
466						
467					},
468					error: function() {
469						content = error_msg;
470
471						// now set location for new content
472						var target = $('#' + self.scope.id + ' .modaal-content-container');
473						if ( target.length > 0) {
474							target.removeClass( self.options.loading_class ).addClass( self.options.ajax_error_class );
475							target.html(content);
476						}
477					}
478				});
479
480			} else {
481				content = error_msg;
482			}
483
484			return false;
485		},
486
487		// Fetch Ajax Data
488		// ----------------------------------------------------------------
489		fetch_ajax : function(url) {
490			var self = this;
491			var content = '';
492
493			// If no accessible title, set it to 'Dialog Window'
494			if ( self.options.accessible_title == null ) {
495				self.options.accessible_title = 'Dialog Window'
496			}
497
498			if ( self.xhr !== null ){
499				self.xhr.abort();
500				self.xhr = null;
501			}
502
503			self.build_modal('<div class="modaal-content-container' + ( self.options.loading_class != '' ? ' ' + self.options.loading_class : '' ) + '">' + self.options.loading_content + '</div>' );
504
505			self.xhr = $.ajax(url, {
506				success: function(data) {
507					// content fetch is successful so push it into markup
508					var target = $('#' + self.scope.id).find('.modaal-content-container');
509					if ( target.length > 0){
510						target.removeClass( self.options.loading_class );
511						target.html( data );
512
513						self.options.ajax_success.call(self, target);
514					}
515				},
516				error: function( xhr ) {
517					// There were some errors so return an error message
518					if ( xhr.statusText == 'abort' ){
519						return;
520					}
521
522					var target = $('#' + self.scope.id + ' .modaal-content-container');
523					if ( target.length > 0){
524						target.removeClass( self.options.loading_class ).addClass( self.options.ajax_error_class );
525						target.html( 'Content could not be loaded. Please check the source and try again.' );
526					}
527				}
528			});
529		},
530
531		// Create Confirm Modal
532		// ----------------------------------------------------------------
533		create_confirm : function() {
534			var self = this;
535			var content;
536
537			content = '<div class="modaal-content-container">' +
538					'<h1 id="modaal-title">' + self.options.confirm_title + '</h1>' +
539					'<div class="modaal-confirm-content">' + self.options.confirm_content + '</div>' +
540						'<div class="modaal-confirm-wrap">' +
541							'<button type="button" class="modaal-confirm-btn modaal-ok" aria-label="Confirm">' + self.options.confirm_button_text + '</button>' +
542							'<button type="button" class="modaal-confirm-btn modaal-cancel" aria-label="Cancel">' + self.options.confirm_cancel_button_text + '</button>' +
543						'</div>' +
544					'</div>' +
545				'</div>';
546
547			// now push content into markup
548			self.build_modal(content);
549		},
550
551		// Create Image/Gallery Modal
552		// ----------------------------------------------------------------
553		create_image : function() {
554			var self = this;
555			var content;
556
557			var modaal_image_markup = '';
558			var gallery_total;
559			
560			// If has group attribute
561			if ( self.$elem.is('[data-group]') || self.$elem.is('[rel]') ) {
562
563				// find gallery groups
564				var use_group = self.$elem.is('[data-group]');
565				var gallery_group = use_group ? self.$elem.attr('data-group') : self.$elem.attr('rel');
566				var gallery_group_items = use_group ? $('[data-group="' + gallery_group + '"]') : $('[rel="' + gallery_group + '"]');
567
568				// remove any previous active attribute to any in the group
569				gallery_group_items.removeAttr('data-gallery-active', 'is_active');
570				// add active attribute to the item clicked
571				self.$elem.attr('data-gallery-active', 'is_active');
572
573				// how many in the grouping are there (-1 to connect with each function starting with 0)
574				gallery_total = gallery_group_items.length - 1;
575
576				// prepare array for gallery data
577				var gallery = [];
578
579				// start preparing markup
580				modaal_image_markup = '<div class="modaal-gallery-item-wrap">';
581
582				// loop each grouping item and push it into our gallery array
583				gallery_group_items.each(function(i, item) {
584					// setup default content
585					var img_src = '';
586					var img_alt = '';
587					var img_description = '';
588					var img_active = false;
589					var img_src_error = false;
590
591					var data_modaal_desc = item.getAttribute('data-modaal-desc');
592					var data_item_active = item.getAttribute('data-gallery-active');
593
594					// if item has inline custom source, use that instead of href. Fall back to href if available.
595					if ( $(item).attr('data-modaal-content-source') ) {
596						img_src = $(item).attr('data-modaal-content-source');
597					} else if ( $(item).attr('href') ) {
598						img_src = $(item).attr('href');
599					} else if ( $(item).attr('src') ) {
600						img_src = $(item).attr('src');
601					} else {
602						img_src = 'trigger requires href or data-modaal-content-source attribute';
603						img_src_error = true;
604					}
605
606					// Does it have a modaal description
607					if ( data_modaal_desc != '' && data_modaal_desc !== null && data_modaal_desc !== undefined ) {
608						img_alt = data_modaal_desc;
609						img_description = '<div class="modaal-gallery-label"><span class="modaal-accessible-hide">Image ' + (i+1) + ' - </span>' + data_modaal_desc.replace(/</g, "&lt;").replace(/>/g, "&gt;") + '</div>'
610					} else {
611						img_description = '<div class="modaal-gallery-label"><span class="modaal-accessible-hide">Image ' + (i+1) + '</span></div>';
612					}
613
614					// is it the active item
615					if ( data_item_active ) {
616						img_active = true
617					}
618
619					// set new object for values we want
620					var gallery_item = {
621						'url': img_src,
622						'alt': img_alt,
623						'rawdesc': data_modaal_desc,
624						'desc': img_description,
625						'active': img_active,
626						'src_error': img_src_error
627					};
628
629					// push object into gallery array
630					gallery.push( gallery_item );
631				});
632
633				// now loop through all items in the gallery and build up the markup
634				for (var i = 0; i < gallery.length; i++) {
635					// Set default active class, then check if array item active is true and update string for class
636					var is_active = '';
637					var aria_label = gallery[i].rawdesc ? 'Image: ' + gallery[i].rawdesc : 'Image ' + i + ' no description';
638
639					if ( gallery[i].active ) {
640						is_active = ' ' + self.private_options.active_class;
641					}
642
643					// if gallery item has source error, output message rather than undefined image
644					var image_output = gallery[i].src_error ? gallery[i].url : '<img src="' + gallery[i].url + '" alt=" " style="width:100%">';
645
646					// for each item build up the markup
647					modaal_image_markup += '<div class="modaal-gallery-item gallery-item-' + i + is_active + '" aria-label="' + aria_label + '">' +
648						image_output + gallery[i].desc +
649					'</div>';
650				}
651
652				// Close off the markup for the gallery
653				modaal_image_markup += '</div>';
654
655				// Add next and previous buttons if outside
656				if (self.options.outer_controls != true) {
657					modaal_image_markup += self.scope.prev_btn + self.scope.next_btn;
658				}
659			} else {
660				// This is only a single gallery item so let's grab the necessary values
661
662				// define the source, check if content_source option exists, and use that or fall back to href.
663				var this_img_src;
664				var img_src_error = false;
665				if ( self.$elem.attr('data-modaal-content-source') ) {
666					this_img_src = self.$elem.attr('data-modaal-content-source');
667				} else if ( self.$elem.attr('href') ) {
668					this_img_src = self.$elem.attr('href');
669				} else if ( self.$elem.attr('src') ) {
670					this_img_src = self.$elem.attr('src');
671				} else {
672					this_img_src = 'trigger requires href or data-modaal-content-source attribute';
673					img_src_error = true;
674				}
675
676				var this_img_alt_txt = '';
677				var this_img_alt = '';
678				var aria_label = '';
679
680				if ( self.$elem.attr('data-modaal-desc') ) {
681					aria_label = self.$elem.attr('data-modaal-desc');
682					this_img_alt_txt = self.$elem.attr('data-modaal-desc');
683					this_img_alt = '<div class="modaal-gallery-label"><span class="modaal-accessible-hide">Image - </span>' + this_img_alt_txt.replace(/</g, "&lt;").replace(/>/g, "&gt;") + '</div>';
684				} else {
685					aria_label = "Image with no description";
686				}
687
688				// if image item has source error, output message rather than undefined image
689				var image_output = img_src_error ? this_img_src : '<img src="' + this_img_src + '" alt=" " style="width:100%">';
690
691				// build up the html
692				modaal_image_markup = '<div class="modaal-gallery-item is_active" aria-label="' + aria_label + '">' +
693					image_output + this_img_alt +
694				'</div>';
695			}
696
697			// Update content variable
698			content = modaal_image_markup;
699
700			// now push content into markup
701			self.build_modal(content);
702
703			// setup next & prev buttons
704			if ( $('.modaal-gallery-item.is_active').is('.gallery-item-0') ) {
705				$('.modaal-gallery-prev').hide();
706			}
707			if ( $('.modaal-gallery-item.is_active').is('.gallery-item-' + gallery_total) ) {
708				$('.modaal-gallery-next').hide();
709			}
710		},
711
712		// Gallery Change Image
713		// ----------------------------------------------------------------
714		gallery_update : function(direction) {
715			var self = this;
716			var this_gallery = $('#' + self.scope.id);
717			var this_gallery_item = this_gallery.find('.modaal-gallery-item');
718			var this_gallery_total = this_gallery_item.length - 1;
719
720			// if single item, don't proceed
721			if ( this_gallery_total == 0 ) {
722				return false;
723			}
724
725			var prev_btn = this_gallery.find('.modaal-gallery-prev'),
726				next_btn = this_gallery.find('.modaal-gallery-next');
727
728			var duration = 250;
729
730			var new_img_w = 0,
731				new_img_h = 0;
732
733			// CB: Before image change
734			var current_item = this_gallery.find( '.modaal-gallery-item.' + self.private_options.active_class ),
735				incoming_item = ( direction == 'next' ? current_item.next( '.modaal-gallery-item' ) : current_item.prev( '.modaal-gallery-item' ) );
736			self.options.before_image_change.call(self, current_item, incoming_item);
737
738			// stop change if at start of end
739			if ( direction == 'prev' && this_gallery.find('.gallery-item-0').hasClass('is_active') ) {
740				return false;
741			} else if ( direction == 'next' && this_gallery.find('.gallery-item-' + this_gallery_total).hasClass('is_active') ) {
742				return false;
743			}
744
745
746			// lock dimensions
747			current_item.stop().animate({
748				opacity: 0
749			}, duration, function(){
750				// Move to appropriate image
751				incoming_item.addClass('is_next').css({
752					'position': 'absolute',
753					'display': 'block',
754					'opacity': 0
755				});
756
757				// Collect doc width
758				var doc_width = $(document).width();
759				var width_threshold = doc_width > 1140 ? 280 : 50;
760
761				// start toggle to 'is_next'
762				new_img_w = this_gallery.find('.modaal-gallery-item.is_next').width();
763				new_img_h = this_gallery.find('.modaal-gallery-item.is_next').height();
764
765				var new_natural_w = this_gallery.find('.modaal-gallery-item.is_next img').prop('naturalWidth');
766				var new_natural_h = this_gallery.find('.modaal-gallery-item.is_next img').prop('naturalHeight');
767
768				// if new image is wider than doc width
769				if ( new_natural_w > (doc_width - width_threshold) ) {
770					// set new width just below doc width
771					new_img_w = doc_width - width_threshold;
772
773					// Set temp widths so we can calulate the correct height;
774					this_gallery.find('.modaal-gallery-item.is_next').css({ 'width': new_img_w });
775					this_gallery.find('.modaal-gallery-item.is_next img').css({ 'width': new_img_w });
776
777					// Set new height variable
778					new_img_h = this_gallery.find('.modaal-gallery-item.is_next').find('img').height();
779				} else {
780					// new img is not wider than screen, so let's set the new dimensions
781					new_img_w = new_natural_w;
782					new_img_h = new_natural_h;
783				}
784
785				// resize gallery region
786				this_gallery.find('.modaal-gallery-item-wrap').stop().animate({
787					'width': new_img_w,
788					'height': new_img_h
789				}, duration, function() {
790					// hide old active image
791					current_item.removeClass(self.private_options.active_class + ' ' + self.options.gallery_active_class).removeAttr('style');
792					current_item.find('img').removeAttr('style');
793
794					// show new image
795					incoming_item.addClass(self.private_options.active_class + ' ' + self.options.gallery_active_class).removeClass('is_next').css('position','');
796
797					// animate in new image (now has the normal is_active class
798					incoming_item.stop().animate({
799						opacity: 1
800					}, duration, function(){
801						$(this).removeAttr('style').css({
802							'width': '100%'
803						});
804						$(this).find('img').css('width', '100%');
805
806						// remove dimension lock
807						this_gallery.find('.modaal-gallery-item-wrap').removeAttr('style');
808
809						// CB: After image change
810						self.options.after_image_change.call( self, incoming_item );
811					});
812
813					// Focus on the new gallery item
814					this_gallery.find('.modaal-gallery-item').removeAttr('tabindex');
815					this_gallery.find('.modaal-gallery-item.' + self.private_options.active_class + '').attr('tabindex', '0').focus();
816
817					// hide/show next/prev
818					if ( this_gallery.find('.modaal-gallery-item.' + self.private_options.active_class).is('.gallery-item-0') ) {
819						prev_btn.stop().animate({
820							opacity: 0
821						}, 150, function(){
822							$(this).hide();
823						});
824					} else {
825						prev_btn.stop().css({
826							'display': 'block',
827							'opacity': prev_btn.css('opacity')
828						}).animate({
829							opacity: 1
830						}, 150);
831					}
832					if ( this_gallery.find('.modaal-gallery-item.' + self.private_options.active_class).is('.gallery-item-' + this_gallery_total) ) {
833						next_btn.stop().animate({
834							opacity: 0
835						}, 150, function(){
836							$(this).hide();
837						});
838					} else {
839						next_btn.stop().css({
840							'display': 'block',
841							'opacity': prev_btn.css('opacity')
842						}).animate({
843							opacity: 1
844						}, 150);
845					}
846				});
847			});
848		},
849
850		// Create Video Modal
851		// ----------------------------------------------------------------
852		create_video : function(url) {
853			var self = this;
854			var content;
855
856			// video markup
857			content = '<iframe src="' + url + '" class="modaal-video-frame" frameborder="0" allowfullscreen></iframe>';
858
859			// now push content into markup
860			self.build_modal('<div class="modaal-video-container">' + content + '</div>');
861		},
862
863		// Create iFrame Modal
864		// ----------------------------------------------------------------
865		create_iframe : function(url) {
866			var self = this;
867			var content;
868
869			if ( self.options.width !== null || self.options.width !== undefined || self.options.height !== null || self.options.height !== undefined ) {
870				// video markup
871				content = '<iframe src="' + url + '" class="modaal-iframe-elem" frameborder="0" allowfullscreen></iframe>';
872			} else {
873				content = '<div class="modaal-content-container">Please specify a width and height for your iframe</div>';
874			}
875
876			// now push content into markup
877			self.build_modal(content);
878		},
879
880		// Open Modaal
881		// ----------------------------------------------------------------
882		modaal_open : function() {
883			var self = this;
884			var modal_wrapper = $( '#' + self.scope.id );
885			var animation_type = self.options.animation;
886
887			if (animation_type === 'none' ){
888				modal_wrapper.removeClass('modaal-start_none');
889				self.options.after_open.call(self, modal_wrapper);
890			}
891
892			// Open with fade
893			if (animation_type === 'fade') {
894				modal_wrapper.removeClass('modaal-start_fade');
895			}
896
897			// Open with slide down
898			if (animation_type === 'slide-down') {
899				modal_wrapper.removeClass('modaal-start_slide_down');
900			}
901
902			var focusTarget = modal_wrapper;
903
904			// Switch focusTarget tabindex (switch from other modal if exists)
905			$('.modaal-wrapper *[tabindex=0]').removeAttr('tabindex');
906
907			if ( self.options.type == 'image' ) {
908				focusTarget = $('#' + self.scope.id).find('.modaal-gallery-item.' + self.private_options.active_class);
909
910			} else if ( modal_wrapper.find('.modaal-iframe-elem').length ) {
911				focusTarget = modal_wrapper.find('.modaal-iframe-elem');
912
913			} else if ( modal_wrapper.find('.modaal-video-wrap').length ) {
914				focusTarget = modal_wrapper.find('.modaal-video-wrap');
915
916			} else {
917				focusTarget = modal_wrapper.find('.modaal-focus');
918
919			}
920
921			// now set the focus
922			focusTarget.attr('tabindex', '0').focus();
923
924			// Run after_open
925			if (animation_type !== 'none') {
926				// CB: after_open
927				setTimeout(function() {
928					self.options.after_open.call(self, modal_wrapper)
929				}, self.options.after_callback_delay);
930			}
931		},
932
933		// Close Modal
934		// ----------------------------------------------------------------
935		modaal_close : function() {
936			var self = this;
937			var modal_wrapper = $( '#' + self.scope.id );
938
939			// CB: before_close
940			self.options.before_close.call(self, modal_wrapper);
941
942			if (self.xhr !== null){
943				self.xhr.abort();
944				self.xhr = null;
945			}
946
947			// Now we close the modal
948			if (self.options.animation === 'none' ){
949				modal_wrapper.addClass('modaal-start_none');
950			}
951
952			// Close with fade
953			if (self.options.animation === 'fade') {
954				modal_wrapper.addClass('modaal-start_fade');
955			}
956
957			// Close with slide up (using initial slide down)
958			if (self.options.animation === 'slide-down') {
959				modal_wrapper.addClass('modaal-start_slide_down');
960			}
961
962			// CB: after_close and remove
963			setTimeout(function() {
964				// clone inline content back to origin place
965				if (self.options.type == 'inline') {
966					$('#' + self.scope.id + ' .modaal-content-container').contents().detach().appendTo( self.scope.source )
967				}
968				// remove markup from dom
969				modal_wrapper.remove();
970				// CB: after_close
971				self.options.after_close.call(self);
972				// scope is now closed
973				self.scope.is_open = false;
974
975			}, self.options.after_callback_delay);
976
977			// Call overlay hide
978			self.modaal_overlay('hide');
979
980			// Roll back to last focus state before modal open. If was closed programmatically, this might not be set
981			if (self.lastFocus != null) {
982				self.lastFocus.focus();
983			}
984		},
985
986		// Overlay control (accepts action for show or hide)
987		// ----------------------------------------------------------------
988		modaal_overlay : function(action) {
989			var self = this;
990
991			if (action == 'show') {
992				// Modal is open so update scope
993				self.scope.is_open = true;
994
995				// set body to overflow hidden if background_scroll is false
996				if (! self.options.background_scroll) {
997					self.dom.addClass('modaal-noscroll');
998				}
999
1000				// append modaal overlay
1001				if ($('#' + self.scope.id + '_overlay').length < 1) {
1002					self.dom.append('<div class="modaal-overlay" id="' + self.scope.id + '_overlay"></div>');
1003				}
1004
1005				// now show
1006				$('#' + self.scope.id + '_overlay').css('background', self.options.background).stop().animate({
1007					opacity: self.options.overlay_opacity
1008				}, self.options.animation_speed, function(){
1009					// now open the modal
1010					self.modaal_open();
1011				});
1012
1013			} else if (action == 'hide') {
1014
1015				// now hide the overlay
1016				$('#' + self.scope.id + '_overlay').stop().animate({
1017					opacity: 0
1018				}, self.options.animation_speed, function(){
1019					// remove overlay from dom
1020					$(this).remove();
1021
1022					// remove body overflow lock
1023					self.dom.removeClass('modaal-noscroll');
1024				});
1025			}
1026		},
1027
1028		// Check if is touch
1029		// ----------------------------------------------------------------
1030		is_touch : function() {
1031			return 'ontouchstart' in window || navigator.maxTouchPoints;
1032		}
1033	};
1034
1035	// Define default object to store
1036	var modaal_existing_selectors = [];
1037
1038	// Declare the modaal jQuery method
1039	// ------------------------------------------------------------
1040	$.fn.modaal = function(options) {
1041		return this.each(function (i) {
1042			var existing_modaal = $(this).data('modaal');
1043
1044			if ( existing_modaal ){
1045				// Checking for string value, used for methods
1046				if (typeof(options) == 'string'){
1047					switch (options) {
1048						case 'open':
1049 							// create the modal
1050 							existing_modaal.create_modaal(existing_modaal);
1051							break;
1052						case 'close':
1053							existing_modaal.modaal_close();
1054							break;
1055					}
1056				}
1057			} else {
1058				// Not a string, so let's setup the modal ready to use
1059				var modaal = Object.create(Modaal);
1060				modaal.init(options, this);
1061				$.data(this, "modaal", modaal);
1062
1063				// push this select into existing selectors array which is referenced during modaal_dom_observer
1064				modaal_existing_selectors.push({
1065					'element': $(this).attr('class'),
1066					'options': options
1067				});
1068			}
1069		});
1070	};
1071
1072	// Default options
1073	// ------------------------------------------------------------
1074	$.fn.modaal.options = {
1075
1076		//General
1077		type: 'inline',
1078		content_source: null,
1079		animation: 'fade',
1080		animation_speed: 300,
1081		after_callback_delay: 350,
1082		is_locked: false,
1083		hide_close: false,
1084		background: '#000',
1085		overlay_opacity: '0.8',
1086		overlay_close: true,
1087		accessible_title: 'Dialog Window',
1088		start_open: false,
1089		fullscreen: false,
1090		custom_class: '',
1091		background_scroll: false,
1092		should_open: true,
1093		close_text: 'Close',
1094		close_aria_label: 'Close (Press escape to close)',
1095		width: null,
1096		height: null,
1097
1098		//Events
1099		before_open: function(){},
1100		after_open: function(){},
1101		before_close: function(){},
1102		after_close: function(){},
1103		source: function( element, src ){
1104			return src;
1105		},
1106
1107		//Confirm Modal
1108		confirm_button_text: 'Confirm', // text on confirm button
1109		confirm_cancel_button_text: 'Cancel',
1110		confirm_title: 'Confirm Title', // title for confirm modal
1111		confirm_content: '<p>This is the default confirm dialog content. Replace me through the options</p>', // html for confirm message
1112		confirm_callback: function() {},
1113		confirm_cancel_callback: function() {},
1114
1115
1116		//Gallery Modal
1117		gallery_active_class: 'gallery_active_item',
1118		outer_controls:	false,
1119		before_image_change: function( current_item, incoming_item ) {},
1120		after_image_change: function( current_item ) {},
1121
1122		//Ajax Modal
1123		loading_content: modaal_loading_spinner,
1124		loading_class: 'is_loading',
1125		ajax_error_class: 'modaal-error',
1126		ajax_success: function(){},
1127
1128		//Instagram
1129		instagram_id: null
1130	};
1131
1132	// Check and Set Inline Options
1133	// ------------------------------------------------------------
1134	function modaal_inline_options(self) {
1135
1136		// new empty options
1137		var options = {};
1138		var inline_options = false;
1139
1140		// option: type
1141		if ( self.attr('data-modaal-type') ) {
1142			inline_options = true;
1143			options.type = self.attr('data-modaal-type');
1144		}
1145
1146		// option: type
1147		if ( self.attr('data-modaal-content-source') ) {
1148			inline_options = true;
1149			options.content_source = self.attr('data-modaal-content-source');
1150		}
1151
1152		// option: animation
1153		if ( self.attr('data-modaal-animation') ) {
1154			inline_options = true;
1155			options.animation = self.attr('data-modaal-animation');
1156		}
1157
1158		// option: animation_speed
1159		if ( self.attr('data-modaal-animation-speed') ) {
1160			inline_options = true;
1161			options.animation_speed = self.attr('data-modaal-animation-speed');
1162		}
1163
1164		// option: after_callback_delay
1165		if ( self.attr('data-modaal-after-callback-delay') ) {
1166			inline_options = true;
1167			options.after_callback_delay = self.attr('data-modaal-after-callback-delay');
1168		}
1169
1170		// option: is_locked
1171		if ( self.attr('data-modaal-is-locked') ) {
1172			inline_options = true;
1173			options.is_locked = (self.attr('data-modaal-is-locked') === 'true' ? true : false);
1174		}
1175
1176		// option: hide_close
1177		if ( self.attr('data-modaal-hide-close') ) {
1178			inline_options = true;
1179			options.hide_close = (self.attr('data-modaal-hide-close') === 'true' ? true : false);
1180		}
1181
1182		// option: background
1183		if ( self.attr('data-modaal-background') ) {
1184			inline_options = true;
1185			options.background = self.attr('data-modaal-background');
1186		}
1187
1188		// option: overlay_opacity
1189		if ( self.attr('data-modaal-overlay-opacity') ) {
1190			inline_options = true;
1191			options.overlay_opacity = self.attr('data-modaal-overlay-opacity');
1192		}
1193
1194		// option: overlay_close
1195		if ( self.attr('data-modaal-overlay-close') ) {
1196			inline_options = true;
1197			options.overlay_close = (self.attr('data-modaal-overlay-close') === 'false' ? false : true);
1198		}
1199
1200		// option: accessible_title
1201		if ( self.attr('data-modaal-accessible-title') ) {
1202			inline_options = true;
1203			options.accessible_title = self.attr('data-modaal-accessible-title');
1204		}
1205
1206		// option: start_open
1207		if ( self.attr('data-modaal-start-open') ) {
1208			inline_options = true;
1209			options.start_open = (self.attr('data-modaal-start-open') === 'true' ? true : false);
1210		}
1211
1212		// option: fullscreen
1213		if ( self.attr('data-modaal-fullscreen') ) {
1214			inline_options = true;
1215			options.fullscreen = (self.attr('data-modaal-fullscreen') === 'true' ? true : false);
1216		}
1217
1218		// option: custom_class
1219		if ( self.attr('data-modaal-custom-class') ) {
1220			inline_options = true;
1221			options.custom_class = self.attr('data-modaal-custom-class');
1222		}
1223
1224		// option: close_text
1225		if ( self.attr('data-modaal-close-text') ) {
1226			inline_options = true;
1227			options.close_text = self.attr('data-modaal-close-text');
1228		}
1229
1230		// option: close_aria_label
1231		if ( self.attr('data-modaal-close-aria-label') ) {
1232			inline_options = true;
1233			options.close_aria_label = self.attr('data-modaal-close-aria-label');
1234		}
1235
1236		// option: background_scroll
1237		if ( self.attr('data-modaal-background-scroll') ) {
1238			inline_options = true;
1239			options.background_scroll = (self.attr('data-modaal-background-scroll') === 'true' ? true : false);
1240		}
1241
1242		// option: width
1243		if ( self.attr('data-modaal-width') ) {
1244			inline_options = true;
1245			options.width = parseInt( self.attr('data-modaal-width') );
1246		}
1247
1248		// option: height
1249		if ( self.attr('data-modaal-height') ) {
1250			inline_options = true;
1251			options.height = parseInt( self.attr('data-modaal-height') );
1252		}
1253
1254		// option: confirm_button_text
1255		if ( self.attr('data-modaal-confirm-button-text') ) {
1256			inline_options = true;
1257			options.confirm_button_text = self.attr('data-modaal-confirm-button-text');
1258		}
1259
1260		// option: confirm_cancel_button_text
1261		if ( self.attr('data-modaal-confirm-cancel-button-text') ) {
1262			inline_options = true;
1263			options.confirm_cancel_button_text = self.attr('data-modaal-confirm-cancel-button-text');
1264		}
1265
1266		// option: confirm_title
1267		if ( self.attr('data-modaal-confirm-title') ) {
1268			inline_options = true;
1269			options.confirm_title = self.attr('data-modaal-confirm-title');
1270		}
1271
1272		// option: confirm_content
1273		if ( self.attr('data-modaal-confirm-content') ) {
1274			inline_options = true;
1275			options.confirm_content = self.attr('data-modaal-confirm-content');
1276		}
1277
1278		// option: gallery_active_class
1279		if ( self.attr('data-modaal-gallery-active-class') ) {
1280			inline_options = true;
1281			options.gallery_active_class = self.attr('data-modaal-gallery-active-class');
1282		}
1283
1284		// option: loading_content
1285		if ( self.attr('data-modaal-loading-content') ) {
1286			inline_options = true;
1287			options.loading_content = self.attr('data-modaal-loading-content');
1288		}
1289
1290		// option: loading_class
1291		if ( self.attr('data-modaal-loading-class') ) {
1292			inline_options = true;
1293			options.loading_class = self.attr('data-modaal-loading-class');
1294		}
1295
1296		// option: ajax_error_class
1297		if ( self.attr('data-modaal-ajax-error-class') ) {
1298			inline_options = true;
1299			options.ajax_error_class = self.attr('data-modaal-ajax-error-class');
1300		}
1301
1302		// option: start_open
1303		if ( self.attr('data-modaal-instagram-id') ) {
1304			inline_options = true;
1305			options.instagram_id = self.attr('data-modaal-instagram-id');
1306		}
1307
1308		// now set it up for the trigger, but only if inline_options is true
1309		if ( inline_options ) {
1310			self.modaal(options);
1311		}
1312	};
1313
1314	// On body load (or now, if already loaded), init any modaals defined inline
1315	// Ensure this is done after $.fn.modaal and default options are declared
1316	// ----------------------------------------------------------------
1317	$(function(){
1318
1319		var single_modaal = $('.modaal');
1320
1321		// Check for existing modaal elements
1322		if ( single_modaal.length ) {
1323			single_modaal.each(function() {
1324				var self = $(this);
1325				modaal_inline_options(self);
1326			});
1327		}
1328
1329		// Obvserve DOM mutations for newly added triggers
1330		var modaal_dom_observer = new MutationObserver(function(mutations) {
1331			mutations.forEach(function(mutation) {
1332				if (mutation.addedNodes && mutation.addedNodes.length > 0) {
1333					// element added to DOM
1334					var findElement = [].some.call(mutation.addedNodes, function(el) {
1335						var elm = $(el);
1336						if ( elm.is('a') || elm.is('button') ) {
1337							
1338							if ( elm.hasClass('modaal') ) {
1339								// is inline Modaal, initialise options
1340								modaal_inline_options(elm);
1341							} else {
1342								// is not inline modaal. Check for existing selector
1343								modaal_existing_selectors.forEach(function(modaalSelector) {
1344									if ( modaalSelector.element == elm.attr('class') ) {
1345										$(elm).modaal( modaalSelector.options );
1346										return false;
1347									}
1348								});
1349							}
1350
1351						}
1352					});
1353				}
1354			});
1355		});
1356		var observer_config = {
1357			subtree: true,
1358			attributes: true,
1359			childList: true,
1360			characterData: true
1361		};
1362
1363		// pass in the target node, as well as the observer options
1364		setTimeout(function() {
1365			modaal_dom_observer.observe(document.body, observer_config);
1366		}, 500);
1367
1368	});
1369
1370} ( jQuery, window, document ) );

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.