/static/modaal/js/modaal.js
https://github.com/leoiceo/OpenSA · JavaScript · 1390 lines · 848 code · 227 blank · 315 comment · 230 complexity · 518947ca01725ec15a39b3f5282fb858 MD5 · raw file
- /*!
- Modaal - accessible modals - v0.4.3
- by Humaan, for all humans.
- http://humaan.com
- */
- /**
- Modaal jQuery Plugin : Accessible Modals
- ==== General Options ===
- type (string) : ajax, inline, image, iframe, confirm. Defaults to 'inline'
- content_source (stribg) : Accepts a string value for your target element, such as '#my-content'. This allows for when trigger element is
- an `<a href="#">` link. Not to be confused with the already existing `source` event.
- animation (string) : Fade, expand, down, up. Defaults to 'fade'
- after_callback_delay (integer) : Specify a delay value for the after open callbacks. This is necessary because with the bundled animations
- have a set duration in the bundled CSS. Specify a delay of the same amount as the animation duration in so
- more accurately fire the after open/close callbacks. Defaults 350, does not apply if animation is 'none',
- after open callbacks are dispatched immediately
- is_locked (boolean) : Set this to true to disable closing the modal via keypress or clicking the background. Beware that if
- type != 'confirm' there will be no interface to dismiss the modal if is_locked = true, you'd have to
- programmatically arrange to dismiss the modal. Confirm modals are always locked regardless of this option
- Defaults to false
- hide_close (boolean) : Set this to true to hide the close modal button. Key press and overlay click will still close the modal.
- This method is best used when you want to put a custom close button inside the modal container space.
- background (string) : Background overlay style. Defaults to '#000'
- overlay_opacity (float) : Background overlay transparency. Defaults to 0.8
- overlay_close (boolean) : Set this to false if you want to disable click to close on overlay background.
- accessible_title (string) : Accessible title. Default 'Dialog Window'
- start_open (boolean) : Set this to true to launch the Modaal window immediately on page open
- fullscreen (boolean) : Set this to true to make the modaal fill the entire screen, false will default to own width/height attributes.
- custom_class (string) : Fill in this string with a custom class that will be applied to the outer most modal wrapper.
- width (integer) : Desired width of the modal. Required for iframe type. Defaults to undefined //TODO
- height (integer) : Desired height of the modal. Required for iframe type. Defaults to undefined //TODO
- background_scroll (boolean) : Set this to true to enable the page to scroll behind the open modal.
- should_open (boolean|function) : Boolean or closure that returns a boolean to determine whether to open the modal or not.
- close_text : String for close button text. Available for localisation and alternative languages to be used.
- 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.
- === Events ===
- before_open (function) : Callback function executed before modal is opened
- after_open (function) : Callback function executed after modal is opened
- before_close (function) : Callback function executed before modal is closed
- after_close (function) : Callback function executed after modal is closed
- source (function(element, src)) : Callback function executed on the default source, it is intended to transform the
- source (href in an AJAX modal or iframe). The function passes in the triggering element
- as well as the default source depending of the modal type. The default output of the
- function is an untransformed default source.
- === Confirm Options & Events ===
- confirm_button_text (string) : Text on the confirm button. Defaults to 'Confirm'
- confirm_cancel_button_text (string) : Text on the confirm modal cancel button. Defaults to 'Cancel'
- confirm_title (string) : Title for confirm modal. Default 'Confirm Title'
- confirm_content (string) : HTML content for confirm message
- confirm_callback (function) : Callback function for when the confirm button is pressed as opposed to cancel
- confirm_cancel_callback (function) : Callback function for when the cancel button is pressed
- === Gallery Options & Events ===
- gallery_active_class (string) : Active class applied to the currently active image or image slide in a gallery 'gallery_active_item'
- outer_controls (boolean) : Set to true to put the next/prev controls outside the Modaal wrapper, at the edges of the browser window.
- before_image_change (function) : Callback function executed before the image slide changes in a gallery modal. Default function( current_item, incoming_item )
- after_image_change (function) : Callback function executed after the image slide changes in a gallery modal. Default function ( current_item )
- === AJAX Options & Events ===
- loading_content (string) : HTML content for loading message. Default 'Loading …'
- loading_class (string) : Class name to be applied while content is loaded via AJAX. Default 'is_loading'
- ajax_error_class (string) : Class name to be applied when content has failed to load. Default is 'modaal-error'
- ajax_success (function) : Callback for when AJAX content is loaded in
- === SOCIAL CONTENT ===
- instagram_id (string) : Unique photo ID for an Instagram photo.
- */
- ( function( $ ) {
- 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>'
-
- var Modaal = {
- init : function(options, elem) {
- var self = this;
- self.dom = $('body');
- self.$elem = $(elem);
- self.options = $.extend({}, $.fn.modaal.options, self.$elem.data(), options);
- self.xhr = null;
- // set up the scope
- self.scope = {
- is_open: false,
- id: 'modaal_' + ( new Date().getTime() ) + ( Math.random().toString(16).substring(2) ),
- source: self.options.content_source ? self.options.content_source : self.$elem.attr('href')
- };
- // add scope attribute to trigger element
- self.$elem.attr('data-modaal-scope', self.scope.id);
- // private options
- self.private_options = {
- active_class: 'is_active'
- };
- self.lastFocus = null;
-
- // if is_locked
- if ( self.options.is_locked || self.options.type == 'confirm' || self.options.hide_close ) {
- self.scope.close_btn = '';
- } else {
- 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>';
- }
- // reset animation_speed
- if (self.options.animation === 'none' ){
- self.options.animation_speed = 0;
- self.options.after_callback_delay = 0;
- }
- // On click to open modal
- $(elem).on('click.Modaal', function(e) {
- e.preventDefault();
- self.create_modaal(self, e);
- });
- // Define next/prev buttons
- if (self.options.outer_controls === true) {
- var mod_class = 'outer';
- } else {
- var mod_class = 'inner';
- }
- 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>';
- 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>';
- // Check for start_open
- if (self.options.start_open === true ){
- self.create_modaal( self );
- }
- },
- // Initial create to determine which content type it requires
- // ----------------------------------------------------------------
- create_modaal : function(self, e) {
- var self = this;
- var source;
- // Save last active state before modal
- self.lastFocus = self.$elem;
- if ( self.options.should_open === false || ( typeof self.options.should_open === 'function' && self.options.should_open() === false ) ) {
- return;
- }
- // CB: before_open
- self.options.before_open.call(self, e);
- switch (self.options.type) {
- case 'inline':
- self.create_basic();
- break;
- case 'ajax':
- source = self.options.source( self.$elem, self.scope.source );
- self.fetch_ajax( source );
- break;
- case 'confirm':
- self.options.is_locked = true;
- self.create_confirm();
- break;
- case 'image':
- self.create_image();
- break;
- case 'iframe':
- source = self.options.source( self.$elem, self.scope.source );
- self.create_iframe( source );
- break;
- case 'video':
- self.create_video(self.scope.source);
- break;
- case 'instagram':
- self.create_instagram();
- break;
- }
- // call events to be watched (click, tab, keyup, keydown etc.)
- self.watch_events();
- },
- // Watching Modal
- // ----------------------------------------------------------------
- watch_events : function() {
- var self = this;
- self.dom.off('click.Modaal keyup.Modaal keydown.Modaal');
- // Body keydown
- self.dom.on('keydown.Modaal', function(e) {
- var key = e.keyCode;
- var target = e.target;
- // look for tab change and reset focus to modal window
- // done in keydown so the check fires repeatedly when you hold the tab key down
- if (key == 9 && self.scope.is_open) {
- if (!$.contains(document.getElementById(self.scope.id), target) ) {
- $('#' + self.scope.id).find('*[tabindex="0"]').focus();
- }
- }
- });
- // Body keyup
- self.dom.on('keyup.Modaal', function(e) {
- var key = e.keyCode;
- var target = e.target;
- if ( (e.shiftKey && e.keyCode == 9) && self.scope.is_open) {
- // Watch for shift + tab key press. if open shift focus to close button.
- if (!$.contains(document.getElementById(self.scope.id), target) ) {
- $('#' + self.scope.id).find('.modaal-close').focus();
- }
- }
- if ( !self.options.is_locked ){
- // On escape key press close modal
- if (key == 27 && self.scope.is_open ) {
- if ( $(document.activeElement).is('input:not(:checkbox):not(:radio)') ) {
- return false;
- }
- self.modaal_close();
- return;
- }
- }
- // is gallery open and images length is > 1
- if ( self.options.type == 'image' ) {
- // arrow left for back
- if (key == 37 && self.scope.is_open && (!$('#' + self.scope.id + ' .modaal-gallery-prev').hasClass('is_hidden')) ) {
- self.gallery_update('prev');
- }
- // arrow right for next
- if (key == 39 && self.scope.is_open && (!$('#' + self.scope.id + ' .modaal-gallery-next').hasClass('is_hidden')) ) {
- self.gallery_update('next');
- }
- return;
- }
- });
- // Body click/touch
- self.dom.on('click.Modaal', function(e) {
- var trigger = $(e.target);
- // General Controls: If it's not locked allow greedy close
- if ( !self.options.is_locked ){
- if ( (self.options.overlay_close && trigger.is('.modaal-inner-wrapper')) || trigger.is('.modaal-close') || trigger.closest('.modaal-close').length ) {
- self.modaal_close();
- return;
- }
- }
- //Confirm Controls
- if ( trigger.is('.modaal-confirm-btn' ) ){
- // if 'OK' button is clicked, run confirm_callback()
- if ( trigger.is('.modaal-ok') ) {
- self.options.confirm_callback.call(self, self.lastFocus);
- }
- if ( trigger.is('.modaal-cancel') ) {
- self.options.confirm_cancel_callback.call(self, self.lastFocus);
- }
- self.modaal_close();
- return;
- }
- // Gallery Controls
- if ( trigger.is( '.modaal-gallery-control' ) ){
- // it not active, don't do nuthin!
- if ( trigger.hasClass('is_hidden') ) {
- return;
- }
- // trigger previous
- if ( trigger.is('.modaal-gallery-prev') ) {
- self.gallery_update('prev');
- }
- // trigger next
- if ( trigger.is('.modaal-gallery-next') ) {
- self.gallery_update('next');
- }
- return;
- }
- });
- },
- // Append markup into DOM
- build_modal : function(content) {
- var self = this;
- // if is instagram
- var igClass = '';
- if ( self.options.type == 'instagram' ) {
- igClass = ' modaal-instagram';
- }
- var wrap_class = (self.options.type == 'video') ? 'modaal-video-wrap' : 'modaal-content';
- /*
- modaal-start_none : fully hidden via display:none;
- modaal-start_fade : hidden via opacity:0
- modaal-start_slidedown : ...
- */
- var animation_class;
- switch ( self.options.animation ) {
- case 'fade' :
- animation_class = ' modaal-start_fade';
- break;
- case 'slide-down' :
- animation_class = ' modaal-start_slidedown';
- break;
- default :
- animation_class = ' modaal-start_none'
- }
- // fullscreen check
- var fullscreen_class = '';
- if ( self.options.fullscreen ) {
- fullscreen_class = ' modaal-fullscreen';
- }
- // custom class check
- if ( self.options.custom_class !== '' || typeof(self.options.custom_class) !== 'undefined' ) {
- self.options.custom_class = ' ' + self.options.custom_class;
- }
- // if width and heights exists and is typeof number
- var dimensionsStyle = '';
- if ( self.options.width && self.options.height && typeof self.options.width == 'number' && typeof self.options.height == 'number' ) {
- // if width and height exist, and they are both numbers
- dimensionsStyle = ' style="max-width:' + self.options.width + 'px;height:' + self.options.height + 'px;overflow:auto;"';
- } else if ( self.options.width && typeof self.options.width == 'number' ) {
- // if only width
- dimensionsStyle = ' style="max-width:' + self.options.width + 'px;"';
- } else if ( self.options.height && typeof self.options.height == 'number' ) {
- // if only height
- dimensionsStyle = ' style="height:' + self.options.height + 'px;overflow:auto;"';
- }
- // Reset dimensions style (width and height) for certain types
- if ( self.options.type == 'image' || self.options.type == 'video' || self.options.type == 'instagram' || self.options.fullscreen ) {
- dimensionsStyle = '';
- }
- // if is touch
- // this is a bug fix for iOS to allow regular click events on div elements.
- var touchTrigger = '';
- if ( self.is_touch() ) {
- touchTrigger = ' style="cursor:pointer;"'
- }
- 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 + '>';
- // hide if video
- if (self.options.type != 'video') {
- build_markup += '<div class="modaal-container"' + dimensionsStyle + '>';
- }
- // add the guts of the content
- build_markup += '<div class="' + wrap_class + ' modaal-focus" aria-hidden="false" aria-label="' + self.options.accessible_title + ' - ' + self.options.close_aria_label + '" role="dialog">';
- // If it's inline type, we want to clone content instead of dropping it straight in
- if (self.options.type == 'inline') {
- build_markup += '<div class="modaal-content-container" role="document"></div>';
- } else {
- // Drop in the content if it's not inline
- build_markup += content;
- }
- // close wrap_class
- build_markup += '</div>' + self.scope.close_btn;
- // hide if video
- if (self.options.type != 'video') {
- build_markup += '</div>';
- }
- // close off modaal-inner-wrapper
- build_markup += '</div>';
-
- // If type is image AND outer_controls is true: add gallery next and previous controls.
- if (self.options.type == 'image' && self.options.outer_controls === true) {
- build_markup += self.scope.prev_btn + self.scope.next_btn;
- }
- // close off modaal-wrapper
- build_markup += '</div></div>';
- // append ajax modal markup to dom
- if ($('#' + self.scope.id + '_overlay').length < 1) {
- self.dom.append(build_markup);
- }
- // if inline, clone content into space
- if (self.options.type == 'inline') {
- content.appendTo('#' + self.scope.id + ' .modaal-content-container');
- }
- // Trigger overlay show (which triggers modal show)
- self.modaal_overlay('show');
- },
- // Create Basic Inline Modal
- // ----------------------------------------------------------------
- create_basic : function() {
- var self = this;
- var target = $(self.scope.source);
- var content = '';
- if (target.length) {
- content = target.contents().detach();
- target.empty();
- } else {
- content = 'Content could not be loaded. Please check the source and try again.';
- }
- // now push content into markup
- self.build_modal(content);
- },
- // Create Instagram Modal
- // ----------------------------------------------------------------
- create_instagram : function() {
- var self = this;
- var id = self.options.instagram_id;
- var content = '';
- var error_msg = 'Instagram photo couldn\'t be loaded, please check the embed code and try again.';
- self.build_modal('<div class="modaal-content-container' + ( self.options.loading_class != '' ? ' ' + self.options.loading_class : '' ) + '">' + self.options.loading_content + '</div>' );
- // ID exists, is not empty null or undefined.
- if ( id != '' && id !== null && id !== undefined ) {
- // set up oembed url
- var ig_url = 'https://api.instagram.com/oembed?url=http://instagr.am/p/' + id + '/';
- $.ajax({
- url: ig_url,
- dataType: "jsonp",
- cache: false,
- success: function (data) {
-
- // 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
- self.dom.append('<div id="temp-ig" style="width:0;height:0;overflow:hidden;">' + data.html + '</div>');
-
- // Check if it has loaded once before.
- // This is to stop the Embeds.process from throwing and error the first time it's being loaded.
- // private_options are individual to a modaal_scope so will not work across multiple scopes when checking if true, only that one item.
- if ( self.dom.attr('data-igloaded') ) {
- window.instgrm.Embeds.process();
- } else {
- // first time it's loaded, let's set a new private option to use next time it's opened.
- self.dom.attr('data-igloaded', 'true');
- }
- // now set location for new content
- // timeout is required as well to bypass the unusual small thumb issue instagram oembed was serving up
- var target = '#' + self.scope.id + ' .modaal-content-container';
- if ( $(target).length > 0) {
- setTimeout(function() {
- $('#temp-ig').contents().clone().appendTo( target );
- $('#temp-ig').remove();
- }, 1000);
- }
-
- },
- error: function() {
- content = error_msg;
- // now set location for new content
- var target = $('#' + self.scope.id + ' .modaal-content-container');
- if ( target.length > 0) {
- target.removeClass( self.options.loading_class ).addClass( self.options.ajax_error_class );
- target.html(content);
- }
- }
- });
- } else {
- content = error_msg;
- }
- return false;
- },
- // Fetch Ajax Data
- // ----------------------------------------------------------------
- fetch_ajax : function(url) {
- var self = this;
- var content = '';
- // If no accessible title, set it to 'Dialog Window'
- if ( self.options.accessible_title == null ) {
- self.options.accessible_title = 'Dialog Window'
- }
- if ( self.xhr !== null ){
- self.xhr.abort();
- self.xhr = null;
- }
- self.build_modal('<div class="modaal-content-container' + ( self.options.loading_class != '' ? ' ' + self.options.loading_class : '' ) + '">' + self.options.loading_content + '</div>' );
- self.xhr = $.ajax(url, {
- success: function(data) {
- // content fetch is successful so push it into markup
- var target = $('#' + self.scope.id).find('.modaal-content-container');
- if ( target.length > 0){
- target.removeClass( self.options.loading_class );
- target.html( data );
- self.options.ajax_success.call(self, target);
- }
- },
- error: function( xhr ) {
- // There were some errors so return an error message
- if ( xhr.statusText == 'abort' ){
- return;
- }
- var target = $('#' + self.scope.id + ' .modaal-content-container');
- if ( target.length > 0){
- target.removeClass( self.options.loading_class ).addClass( self.options.ajax_error_class );
- target.html( 'Content could not be loaded. Please check the source and try again.' );
- }
- }
- });
- },
- // Create Confirm Modal
- // ----------------------------------------------------------------
- create_confirm : function() {
- var self = this;
- var content;
- content = '<div class="modaal-content-container">' +
- '<h1 id="modaal-title">' + self.options.confirm_title + '</h1>' +
- '<div class="modaal-confirm-content">' + self.options.confirm_content + '</div>' +
- '<div class="modaal-confirm-wrap">' +
- '<button type="button" class="modaal-confirm-btn modaal-ok" aria-label="Confirm">' + self.options.confirm_button_text + '</button>' +
- '<button type="button" class="modaal-confirm-btn modaal-cancel" aria-label="Cancel">' + self.options.confirm_cancel_button_text + '</button>' +
- '</div>' +
- '</div>' +
- '</div>';
- // now push content into markup
- self.build_modal(content);
- },
- // Create Image/Gallery Modal
- // ----------------------------------------------------------------
- create_image : function() {
- var self = this;
- var content;
- var modaal_image_markup = '';
- var gallery_total;
-
- // If has group attribute
- if ( self.$elem.is('[data-group]') || self.$elem.is('[rel]') ) {
- // find gallery groups
- var use_group = self.$elem.is('[data-group]');
- var gallery_group = use_group ? self.$elem.attr('data-group') : self.$elem.attr('rel');
- var gallery_group_items = use_group ? $('[data-group="' + gallery_group + '"]') : $('[rel="' + gallery_group + '"]');
- // remove any previous active attribute to any in the group
- gallery_group_items.removeAttr('data-gallery-active', 'is_active');
- // add active attribute to the item clicked
- self.$elem.attr('data-gallery-active', 'is_active');
- // how many in the grouping are there (-1 to connect with each function starting with 0)
- gallery_total = gallery_group_items.length - 1;
- // prepare array for gallery data
- var gallery = [];
- // start preparing markup
- modaal_image_markup = '<div class="modaal-gallery-item-wrap">';
- // loop each grouping item and push it into our gallery array
- gallery_group_items.each(function(i, item) {
- // setup default content
- var img_src = '';
- var img_alt = '';
- var img_description = '';
- var img_active = false;
- var img_src_error = false;
- var data_modaal_desc = item.getAttribute('data-modaal-desc');
- var data_item_active = item.getAttribute('data-gallery-active');
- // if item has inline custom source, use that instead of href. Fall back to href if available.
- if ( $(item).attr('data-modaal-content-source') ) {
- img_src = $(item).attr('data-modaal-content-source');
- } else if ( $(item).attr('href') ) {
- img_src = $(item).attr('href');
- } else if ( $(item).attr('src') ) {
- img_src = $(item).attr('src');
- } else {
- img_src = 'trigger requires href or data-modaal-content-source attribute';
- img_src_error = true;
- }
- // Does it have a modaal description
- if ( data_modaal_desc != '' && data_modaal_desc !== null && data_modaal_desc !== undefined ) {
- img_alt = data_modaal_desc;
- img_description = '<div class="modaal-gallery-label"><span class="modaal-accessible-hide">Image ' + (i+1) + ' - </span>' + data_modaal_desc + '</div>'
- } else {
- img_description = '<div class="modaal-gallery-label"><span class="modaal-accessible-hide">Image ' + (i+1) + '</span></div>';
- }
- // is it the active item
- if ( data_item_active ) {
- img_active = true
- }
- // set new object for values we want
- var gallery_item = {
- 'url': img_src,
- 'alt': img_alt,
- 'rawdesc': data_modaal_desc,
- 'desc': img_description,
- 'active': img_active,
- 'src_error': img_src_error
- };
- // push object into gallery array
- gallery.push( gallery_item );
- });
- // now loop through all items in the gallery and build up the markup
- for (var i = 0; i < gallery.length; i++) {
- // Set default active class, then check if array item active is true and update string for class
- var is_active = '';
- var aria_label = gallery[i].rawdesc ? 'Image: ' + gallery[i].rawdesc : 'Image ' + i + ' no description';
- if ( gallery[i].active ) {
- is_active = ' ' + self.private_options.active_class;
- }
- // if gallery item has source error, output message rather than undefined image
- var image_output = gallery[i].src_error ? gallery[i].url : '<img src="' + gallery[i].url + '" alt=" " style="width:100%">';
- // for each item build up the markup
- modaal_image_markup += '<div class="modaal-gallery-item gallery-item-' + i + is_active + '" aria-label="' + aria_label + '">' +
- image_output + gallery[i].desc +
- '</div>';
- }
- // Close off the markup for the gallery
- modaal_image_markup += '</div>';
- // Add next and previous buttons if outside
- if (self.options.outer_controls != true) {
- modaal_image_markup += self.scope.prev_btn + self.scope.next_btn;
- }
- } else {
- // This is only a single gallery item so let's grab the necessary values
- // define the source, check if content_source option exists, and use that or fall back to href.
- var this_img_src;
- var img_src_error = false;
- if ( self.$elem.attr('data-modaal-content-source') ) {
- this_img_src = self.$elem.attr('data-modaal-content-source');
- } else if ( self.$elem.attr('href') ) {
- this_img_src = self.$elem.attr('href');
- } else if ( self.$elem.attr('src') ) {
- this_img_src = self.$elem.attr('src');
- } else {
- this_img_src = 'trigger requires href or data-modaal-content-source attribute';
- img_src_error = true;
- }
- var this_img_alt_txt = '';
- var this_img_alt = '';
- var aria_label = '';
- if ( self.$elem.attr('data-modaal-desc') ) {
- aria_label = self.$elem.attr('data-modaal-desc');
- this_img_alt_txt = self.$elem.attr('data-modaal-desc');
- this_img_alt = '<div class="modaal-gallery-label"><span class="modaal-accessible-hide">Image - </span>' + this_img_alt_txt + '</div>';
- } else {
- aria_label = "Image with no description";
- }
- // if image item has source error, output message rather than undefined image
- var image_output = img_src_error ? this_img_src : '<img src="' + this_img_src + '" alt=" " style="width:100%">';
- // build up the html
- modaal_image_markup = '<div class="modaal-gallery-item is_active" aria-label="' + aria_label + '">' +
- image_output + this_img_alt +
- '</div>';
- }
- // Update content variable
- content = modaal_image_markup;
- // now push content into markup
- self.build_modal(content);
- // setup next & prev buttons
- if ( $('.modaal-gallery-item.is_active').is('.gallery-item-0') ) {
- $('.modaal-gallery-prev').hide();
- }
- if ( $('.modaal-gallery-item.is_active').is('.gallery-item-' + gallery_total) ) {
- $('.modaal-gallery-next').hide();
- }
- },
- // Gallery Change Image
- // ----------------------------------------------------------------
- gallery_update : function(direction) {
- var self = this;
- var this_gallery = $('#' + self.scope.id);
- var this_gallery_item = this_gallery.find('.modaal-gallery-item');
- var this_gallery_total = this_gallery_item.length - 1;
- // if single item, don't proceed
- if ( this_gallery_total == 0 ) {
- return false;
- }
- var prev_btn = this_gallery.find('.modaal-gallery-prev'),
- next_btn = this_gallery.find('.modaal-gallery-next');
- var duration = 250;
- var new_img_w = 0,
- new_img_h = 0;
- // CB: Before image change
- var current_item = this_gallery.find( '.modaal-gallery-item.' + self.private_options.active_class ),
- incoming_item = ( direction == 'next' ? current_item.next( '.modaal-gallery-item' ) : current_item.prev( '.modaal-gallery-item' ) );
- self.options.before_image_change.call(self, current_item, incoming_item);
- // stop change if at start of end
- if ( direction == 'prev' && this_gallery.find('.gallery-item-0').hasClass('is_active') ) {
- return false;
- } else if ( direction == 'next' && this_gallery.find('.gallery-item-' + this_gallery_total).hasClass('is_active') ) {
- return false;
- }
- // lock dimensions
- current_item.stop().animate({
- opacity: 0
- }, duration, function(){
- // Move to appropriate image
- incoming_item.addClass('is_next').css({
- 'position': 'absolute',
- 'display': 'block',
- 'opacity': 0
- });
- // Collect doc width
- var doc_width = $(document).width();
- var width_threshold = doc_width > 1140 ? 280 : 50;
- // start toggle to 'is_next'
- new_img_w = this_gallery.find('.modaal-gallery-item.is_next').width();
- new_img_h = this_gallery.find('.modaal-gallery-item.is_next').height();
- var new_natural_w = this_gallery.find('.modaal-gallery-item.is_next img').prop('naturalWidth');
- var new_natural_h = this_gallery.find('.modaal-gallery-item.is_next img').prop('naturalHeight');
- // if new image is wider than doc width
- if ( new_natural_w > (doc_width - width_threshold) ) {
- // set new width just below doc width
- new_img_w = doc_width - width_threshold;
- // Set temp widths so we can calulate the correct height;
- this_gallery.find('.modaal-gallery-item.is_next').css({ 'width': new_img_w });
- this_gallery.find('.modaal-gallery-item.is_next img').css({ 'width': new_img_w });
- // Set new height variable
- new_img_h = this_gallery.find('.modaal-gallery-item.is_next').find('img').height();
- } else {
- // new img is not wider than screen, so let's set the new dimensions
- new_img_w = new_natural_w;
- new_img_h = new_natural_h;
- }
- // resize gallery region
- this_gallery.find('.modaal-gallery-item-wrap').stop().animate({
- 'width': new_img_w,
- 'height': new_img_h
- }, duration, function() {
- // hide old active image
- current_item.removeClass(self.private_options.active_class + ' ' + self.options.gallery_active_class).removeAttr('style');
- current_item.find('img').removeAttr('style');
- // show new image
- incoming_item.addClass(self.private_options.active_class + ' ' + self.options.gallery_active_class).removeClass('is_next').css('position','');
- // animate in new image (now has the normal is_active class
- incoming_item.stop().animate({
- opacity: 1
- }, duration, function(){
- $(this).removeAttr('style').css({
- 'width': '100%'
- });
- $(this).find('img').css('width', '100%');
- // remove dimension lock
- this_gallery.find('.modaal-gallery-item-wrap').removeAttr('style');
- // CB: After image change
- self.options.after_image_change.call( self, incoming_item );
- });
- // Focus on the new gallery item
- this_gallery.find('.modaal-gallery-item').removeAttr('tabindex');
- this_gallery.find('.modaal-gallery-item.' + self.private_options.active_class + '').attr('tabindex', '0').focus();
- // hide/show next/prev
- if ( this_gallery.find('.modaal-gallery-item.' + self.private_options.active_class).is('.gallery-item-0') ) {
- prev_btn.stop().animate({
- opacity: 0
- }, 150, function(){
- $(this).hide();
- });
- } else {
- prev_btn.stop().css({
- 'display': 'block',
- 'opacity': prev_btn.css('opacity')
- }).animate({
- opacity: 1
- }, 150);
- }
- if ( this_gallery.find('.modaal-gallery-item.' + self.private_options.active_class).is('.gallery-item-' + this_gallery_total) ) {
- next_btn.stop().animate({
- opacity: 0
- }, 150, function(){
- $(this).hide();
- });
- } else {
- next_btn.stop().css({
- 'display': 'block',
- 'opacity': prev_btn.css('opacity')
- }).animate({
- opacity: 1
- }, 150);
- }
- });
- });
- },
- // Create Video Modal
- // ----------------------------------------------------------------
- create_video : function(url) {
- var self = this;
- var content;
- // video markup
- content = '<iframe src="' + url + '" class="modaal-video-frame" frameborder="0" allowfullscreen></iframe>';
- // now push content into markup
- self.build_modal('<div class="modaal-video-container">' + content + '</div>');
- },
- // Create iFrame Modal
- // ----------------------------------------------------------------
- create_iframe : function(url) {
- var self = this;
- var content;
- if ( self.options.width !== null || self.options.width !== undefined || self.options.height !== null || self.options.height !== undefined ) {
- // video markup
- content = '<iframe src="' + url + '" class="modaal-iframe-elem" frameborder="0" allowfullscreen></iframe>';
- } else {
- content = '<div class="modaal-content-container">Please specify a width and height for your iframe</div>';
- }
- // now push content into markup
- self.build_modal(content);
- },
- // Open Modaal
- // ----------------------------------------------------------------
- modaal_open : function() {
- var self = this;
- var modal_wrapper = $( '#' + self.scope.id );
- var animation_type = self.options.animation;
- if (animation_type === 'none' ){
- modal_wrapper.removeClass('modaal-start_none');
- self.options.after_open.call(self, modal_wrapper);
- }
- // Open with fade
- if (animation_type === 'fade') {
- modal_wrapper.removeClass('modaal-start_fade');
- }
- // Open with slide down
- if (animation_type === 'slide-down') {
- modal_wrapper.removeClass('modaal-start_slide_down');
- }
- var focusTarget = modal_wrapper;
- // Switch focusTarget tabindex (switch from other modal if exists)
- $('.modaal-wrapper *[tabindex=0]').removeAttr('tabindex');
- if ( self.options.type == 'image' ) {
- focusTarget = $('#' + self.scope.id).find('.modaal-gallery-item.' + self.private_options.active_class);
- } else if ( modal_wrapper.find('.modaal-iframe-elem').length ) {
- focusTarget = modal_wrapper.find('.modaal-iframe-elem');
- } else if ( modal_wrapper.find('.modaal-video-wrap').length ) {
- focusTarget = modal_wrapper.find('.modaal-video-wrap');
- } else {
- focusTarget = modal_wrapper.find('.modaal-focus');
- }
- // now set the focus
- focusTarget.attr('tabindex', '0').focus();
- // Run after_open
- if (animation_type !== 'none') {
- // CB: after_open
- setTimeout(function() {
- self.options.after_open.call(self, modal_wrapper)
- }, self.options.after_callback_delay);
- }
- },
- // Close Modal
- // ----------------------------------------------------------------
- modaal_close : function() {
- var self = this;
- var modal_wrapper = $( '#' + self.scope.id );
- // CB: before_close
- self.options.before_close.call(self, modal_wrapper);
- if (self.xhr !== null){
- self.xhr.abort();
- self.xhr = null;
- }
- // Now we close the modal
- if (self.options.animation === 'none' ){
- modal_wrapper.addClass('modaal-start_none');
- }
- // Close with fade
- if (self.options.animation === 'fade') {
- modal_wrapper.addClass('modaal-start_fade');
- }
- // Close with slide up (using initial slide down)
- if (self.options.animation === 'slide-down') {
- modal_wrapper.addClass('modaal-start_slide_down');
- }
- // CB: after_close and remove
- setTimeout(function() {
- // clone inline content back to origin place
- if (self.options.type == 'inline') {
- $('#' + self.scope.id + ' .modaal-content-container').contents().detach().appendTo( self.scope.source )
- }
- // remove markup from dom
- modal_wrapper.remove();
- // CB: after_close
- self.options.after_close.call(self);
- // scope is now closed
- self.scope.is_open = false;
- }, self.options.after_callback_delay);
- // Call overlay hide
- self.modaal_overlay('hide');
- // Roll back to last focus state before modal open. If was closed programmatically, this might not be set
- if (self.lastFocus != null) {
- self.lastFocus.focus();
- }
- },
- // Overlay control (accepts action for show or hide)
- // ----------------------------------------------------------------
- modaal_overlay : function(action) {
- var self = this;
- if (action == 'show') {
- // Modal is open so update scope
- self.scope.is_open = true;
- // set body to overflow hidden if background_scroll is false
- if (! self.options.background_scroll) {
- self.dom.addClass('modaal-noscroll');
- }
- // append modaal overlay
- if ($('#' + self.scope.id + '_overlay').length < 1) {
- self.dom.append('<div class="modaal-overlay" id="' + self.scope.id + '_overlay"></div>');
- }
- // now show
- $('#' + self.scope.id + '_overlay').css('background', self.options.background).stop().animate({
- opacity: self.options.overlay_opacity
- }, self.options.animation_speed, function(){
- // now open the modal
- self.modaal_open();
- });
- } else if (action == 'hide') {
- // now hide the overlay
- $('#' + self.scope.id + '_overlay').stop().animate({
- opacity: 0
- }, self.options.animation_speed, function(){
- // remove overlay from dom
- $(this).remove();
- // remove body overflow lock
- self.dom.removeClass('modaal-noscroll');
- });
- }
- },
- // Check if is touch
- // ----------------------------------------------------------------
- is_touch : function() {
- return 'ontouchstart' in window || navigator.maxTouchPoints;
- }
- };
- // Define default object to store
- var modaal_existing_selectors = [];
- // Declare the modaal jQuery method
- // ------------------------------------------------------------
- $.fn.modaal = function(options) {
- return this.each(function (i) {
- var existing_modaal = $(this).data('modaal');
- if ( existing_modaal ){
- // Checking for string value, used for methods
- if (typeof(options) == 'string'){
- switch (options) {
- case 'open':
- // create the modal
- existing_modaal.create_modaal(existing_modaal);
- break;
- case 'close':
- existing_modaal.modaal_close();
- break;
- }
- }
- } else {
- // Not a string, so let's setup the modal ready to use
- var modaal = Object.create(Modaal);
- modaal.init(options, this);
- $.data(this, "modaal", modaal);
- // push this select into existing selectors array which is referenced during modaal_dom_observer
- modaal_existing_selectors.push({
- 'element': $(this).attr('class'),
- 'options': options
- });
- }
- });
- };
- // Default options
- // ------------------------------------------------------------
- $.fn.modaal.options = {
- //General
- type: 'inline',
- content_source: null,
- animation: 'fade',
- animation_speed: 300,
- after_callback_delay: 350,
- is_locked: false,
- hide_close: false,
- background: '#000',
- overlay_opacity: '0.8',
- overlay_close: true,
- accessible_title: 'Dialog Window',
- start_open: false,
- fullscreen: false,
- custom_class: '',
- background_scroll: false,
- should_open: true,
- close_text: 'Close',
- close_aria_label: 'Close (Press escape to close)',
- width: null,
- height: null,
- //Events
- before_open: function(){},
- after_open: function(){},
- before_close: function(){},
- after_close: function(){},
- source: function( element, src ){
- return src;
- },
- //Confirm Modal
- confirm_button_text: 'Confirm', // text on confirm button
- confirm_cancel_button_text: 'Cancel',
- confirm_title: 'Confirm Title', // title for confirm modal
- confirm_content: '<p>This is the default confirm dialog content. Replace me through the options</p>', // html for confirm message
- confirm_callback: function() {},
- confirm_cancel_callback: function() {},
- //Gallery Modal
- gallery_active_class: 'gallery_active_item',
- outer_controls: false,
- before_image_change: function( current_item, incoming_item ) {},
- after_image_change: function( current_item ) {},
- //Ajax Modal
- loading_content: modaal_loading_spinner,
- loading_class: 'is_loading',
- ajax_error_class: 'modaal-error',
- ajax_success: function(){},
- //Instagram
- instagram_id: null
- };
- // Check and Set Inline Options
- // ------------------------------------------------------------
- function modaal_inline_options(self) {
- // new empty options
- var options = {};
- var inline_options = false;
- // option: type
- if ( self.attr('data-modaal-type') ) {
- inline_options = true;
- options.type = self.attr('data-modaal-type');
- }
- // option: type
- if ( self.attr('data-modaal-content-source') ) {
- inline_options = true;
- options.content_source = self.attr('data-modaal-content-source');
- }
- // option: animation
- if ( self.attr('data-modaal-animation') ) {
- inline_options = true;
- options.animation = self.attr('data-modaal-animation');
- }
- // option: animation_speed
- if ( self.attr('data-modaal-animation-speed') ) {
- inline_options = true;
- options.animation_speed = self.attr('data-modaal-animation-speed');
- }
- // option: after_callback_delay
- if ( self.attr('data-modaal-after-callback-delay') ) {
- inline_options = true;
- options.after_callback_delay = self.attr('data-modaal-after-callback-delay');
- }
- // option: is_locked
- if ( self.attr('data-modaal-is-locked') ) {
- inline_options = true;
- options.is_locked = (self.attr('data-modaal-is-locked') === 'true' ? true : false);
- }
- // option: hide_close
- if ( self.attr('data-modaal-hide-close') ) {
- inline_options = true;
- options.hide_close = (self.attr('data-modaal-hide-close') === 'true' ? true : false);
- }
- // option: background
- if ( self.attr('data-modaal-background') ) {
- inline_options = true;
- options.background = self.attr('data-modaal-background');
- }
- // option: overlay_opacity
- if ( self.attr('data-modaal-overlay-opacity') ) {
- inline_options = true;
- options.overlay_opacity = self.attr('data-modaal-overlay-opacity');
- }
- // option: overlay_close
- if ( self.attr('data-modaal-overlay-close') ) {
- inline_options = true;
- options.overlay_close = (self.attr('data-modaal-overlay-close') === 'false' ? false : true);
- }
- // option: accessible_title
- if ( self.attr('data-modaal-accessible-title') ) {
- inline_options = true;
- options.accessible_title = self.attr('data-modaal-accessible-title');
- }
- // option: start_open
- if ( self.attr('data-modaal-start-open') ) {
- inline_options = true;
- options.start_open = (self.attr('data-modaal-start-open') === 'true' ? true : false);
- }
- // option: fullscreen
- if ( self.attr('data-modaal-fullscreen') ) {
- inline_options = true;
- options.fullscreen = (self.attr('data-modaal-fullscreen') === 'true' ? true : false);
- }
- // option: custom_class
- if ( self.attr('data-modaal-custom-class') ) {
- inline_options = true;
- options.custom_class = self.attr('data-modaal-custom-class');
- }
- // option: close_text
- if ( self.attr('data-modaal-close-text') ) {
- inline_options = true;
- options.close_text = self.attr('data-modaal-close-text');
- }
- // option: close_aria_label
- if ( self.attr('data-modaal-close-aria-label') ) {
- inline_options = true;
- options.close_aria_label = self.attr('data-modaal-close-aria-label');
- }
- // option: background_scroll
- if ( self.attr('data-modaal-background-scroll') ) {
- inline_options = true;
- options.background_scroll = (self.attr('data-modaal-background-scroll') === 'true' ? true : false);
- }
- // option: width
- if ( self.attr('data-modaal-width') ) {
- inline_options = true;
- options.width = parseInt( self.attr('data-modaal-width') );
- }
- // option: height
- if ( self.attr('data-modaal-height') ) {
- inline_options = true;
- options.height = parseInt( self.attr('data-modaal-height') );
- }
- // option: confirm_button_text
- if ( self.attr('data-modaal-confirm-button-text') ) {
- inline_options = true;
- options.confirm_button_text = self.attr('data-modaal-confirm-button-text');
- }
- // option: confirm_cancel_button_text
- if ( self.attr('data-modaal-confirm-cancel-button-text') ) {
- inline_options = true;
- options.confirm_cancel_button_text = self.attr('data-modaal-confirm-cancel-button-text');
- }
- // option: confirm_title
- if ( self.attr('data-modaal-confirm-title') ) {
- inline_options = true;
- options.confirm_title = self.attr('data-modaal-confirm-title');
- }
- // option: confirm_content
- if ( self.attr('data-modaal-confirm-content') ) {
- inline_options = true;
- options.confirm_content = self.attr('data-modaal-confirm-content');
- }
- // option: gallery_active_class
- if ( self.attr('data-modaal-gallery-active-class') ) {
- inline_options = true;
- options.gallery_active_class = self.attr('data-modaal-gallery-active-class');
- }
- // option: loading_content
- if ( self.attr('data-modaal-loading-content') ) {
- inline_options = true;
- options.loading_content = self.attr('data-modaal-loading-content');
- }
- // option: loading_class
- if ( self.attr('data-modaal-loading-class') ) {
- inline_options = true;
- options.loading_class = self.attr('data-modaal-loading-class');
- }
- // option: ajax_error_class
- if ( self.attr('data-modaal-ajax-error-class') ) {
- inline_options = true;
- options.ajax_error_class = self.attr('data-modaal-ajax-error-class');
- }
- // option: start_open
- if ( self.attr('data-modaal-instagram-id') ) {
- inline_options = true;
- options.instagram_id = self.attr('data-modaal-instagram-id');
- }
- // now set it up for the trigger, but only if inline_options is true
- if ( inline_options ) {
- self.modaal(options);
- }
- };
- // On body load (or now, if already loaded), init any modaals defined inline
- // Ensure this is done after $.fn.modaal and default options are declared
- // ----------------------------------------------------------------
- $(function(){
- var single_modaal = $('.modaal');
- // Check for existing modaal elements
- if ( single_modaal.length ) {
- single_modaal.each(function() {
- var self = $(this);
- modaal_inline_options(self);
- });
- }
- // Obvserve DOM mutations for newly added triggers
- var modaal_dom_observer = new MutationObserver(function(mutations) {
- mutations.forEach(function(mutation) {
- if (mutation.addedNodes && mutation.addedNodes.length > 0) {
- // element added to DOM
- var findElement = [].some.call(mutation.addedNodes, function(el) {
- var elm = $(el);
- if ( elm.is('a') || elm.is('button') ) {
-
- if ( elm.hasClass('modaal') ) {
- // is inline Modaal, initialise options
- modaal_inline_options(elm);
- } else {
- // is not inline modaal. Check for existing selector
- modaal_existing_selectors.forEach(function(modaalSelector) {
- if ( modaalSelector.element == elm.attr('class') ) {
- $(elm).modaal( modaalSelector.options );
- return false;
- }
- });
- }
- }
- });
- }
- });
- });
- var observer_config = {
- subtree: true,
- attributes: true,
- childList: true,
- characterData: true
- };
- // pass in the target node, as well as the observer options
- setTimeout(function() {
- modaal_dom_observer.observe(document.body, observer_config);
- }, 500);
- });
- } ( jQuery, window, document ) );