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 …' 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, "<").replace(/>/g, ">") + '</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, "<").replace(/>/g, ">") + '</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.