/** * ContentONE Lightbox * * Opens a lightbox container and does one of the following: * - displays thumbnails and large images determined from href of provided links. * - displays href of provided links in an iframe. * * The height of the content in the lightbox is dynamically determined based on * the available screen real estate. * * Lightbox based off jquery-lightbox plugin by Leandro Vieira Pinho - http://leandrovieira.com. * * @version 1.2 * @date April 26, 2012 * @copyright (c) World Web Management Services (http://www.worldwebms.com/) */ (function($) { var no_refresh = false; /** * Create C1 jQuery extension. */ if( !$.fn.c1 ) { $.fn.c1 = function( name, options ) { if( this.c1[name] ) return this.c1[name].call( this, options ); return this; } } /** * Create C1 Lightbox jQuery extension. */ $.fn.c1.lightbox = function( options ) { var t = this; // If the options is a string, then execute a function if( typeof options == 'string' ) { switch( options ) { case 'close': no_refresh = false; var close = $('#c1-lightbox-close a'); close.click(); return close.length > 0; case 'close-noRefresh': no_refresh = true; var close = $('#c1-lightbox-close a'); close.click(); return close.length > 0; } return this; } // Setup the default options options = $.extend( { // Lightbox settings speed: 'normal', // Animation speed thumbs: true, // True to display thumbnail images thumbWidth: 90, // Width of lightbox thumbnail images thumbHeight: 60, // Height of lightbox thumbnail images captions: true, // True to display image captions html: false, // True to indicate captions are html based download: false, // True to display "Download Image" link in caption area imageMode: '', // Mode for images (use crop for full screen) track: '', // Enable tracking by default hide: 'embed, object, select, iframe', // Elements to hide when the lightbox is opened type: 'normal', // Type of gallery behaviour // Custom content settings api: false, // If supplied, then contains profile of api call close: true, // If supplied, then true shows close button, false hides close button inline: false, // If supplied, the selector for inline content iframe: false, // If true then the links will be opened in an iframe load: false, // If supplied, a function to generate the content with upon load unload: false, // If supplied, a callback function before the lightbox is closed map: false, // If supplied, the map information width: false, // If supplied, this is the maximum width of the lightbox height: false, // If supplied, this is the maximum height of the lightbox minWidth: false, // If supplied, this is the minimum width of the lightbox minHeight: false, // If supplied, this is the maximum height of the lightbox fixedWidth: false, // If supplied, then the width of the lightbox will match the width fixedHeight: false, // If supplied, then the height of the lightbox will match the height // Overlay settings overlayBgColor: '#000', // Background colour of overlay overlayOpacity: 0.75 // Overlay opacity }, options ); // If there is a map if( options.map ) { // Ensure the C1 maps api has been loaded if( $.fn.c1.maps ) { // Used to intercept close events var closeMap = true; var unloadMap = function() { GUnload(); }; // Setup the lightbox options.inline = true; // Set default zoom level if( options.map.zoom == null ) options.map.zoom = 13; // Add callback to generate the map options.onopen = function() { $(this).c1( 'maps', options.map ); } // Display error } else { alert('Lightbox: Please load the c1.maps API'); } // If there is inline content } else if( options.api || options.inline ) { // Set inline flag if( options.api && options.inline == false ) options.inline = true; // Add placeholder function so the lightbox behaves in custom content mode if( !options.onopen ) { options.onopen = function() { // Load content from an API call if( options.api ) { $.c1.api( { 'module': options.api.module, 'controller': options.api.controller, 'method': options.api.method, 'data': jQuery.isFunction( options.api.params ) ? options.api.params.call( options._source ) : options.api.params, 'success': function( data ) { if( options.api.result ) data.html = options.api.result( data ); if( data.html ) { $('#c1-lightbox-content').html( data.html ); $('#c1-lightbox')[ options.touch ? 'show' : 'fadeIn' ](); _resize_interface(); return; } $('#c1-lightbox-close a').click(); } }); // Display video. // Remove instance of video to ensure it doesn't open again. } else if( options.video ) { $('#' + options.video.id).c1('video', options.video); options.video = null; } }; } // Add the content only after the lightbox is fully visible options.onafterfadein = function() { // As we hide embedded content in the _start function, we want // to show the embedded content that is in the inline element if( !options.api ) { if( options.autoHeight ) $('#c1-lightbox-images').css('height', 'auto'); $(options.hide, options.inline).css( 'visibility', 'visible' ); $('#c1-lightbox-content').append( $(options.inline).children() ); $(options.inline).trigger( 'c1.lightbox.open' ); } }; // Add callback to revert the content if( options.onclose ) options._onclose = options.onclose; options.onclose = function() { if( options._onclose ) options._onclose.call( this ); if( !options.api ) $(options.inline).append( $('#c1-lightbox-content').children() ); }; // If a sprite based 360 gallery } else if( options.type == 'sprite' ) { options._sprite = { 'start': function( event ) { options._sprite.x = event.pageX; options._sprite.p = options.position; $('body') .bind( 'mousemove', options._sprite.update ) .bind( 'mouseup', options._sprite.finish ); }, 'update': function( event ) { var diff = options._sprite.x - event.pageX; var shift = Math.floor( diff / 10 ) % options.images.length; var pos = options._sprite.p + shift; if( pos < 0 ) pos += options.images.length; else if( pos >= options.images.length ) pos -= options.images.length; _show_image( pos ); }, 'finish': function() { $('body') .unbind( 'mousemove', options._sprite.update ) .unbind( 'mouseup', options._sprite.finish ); } }; options.thumbs = false; } // If in iframe or custom content mode, ensure there is a width and height if( options.iframe || options.onopen ) { if( !options.width ) options.width = 1000; else if( !options.resize ) options.fixedWidth = true; if( !options.height ) options.height = 750; options.thumbs = false; if( options.onopen ) options.captions = false; } // Declare variables var preloader, preloadNext, preloadPrev, loadingTimeout = null; // Get the image urls var captions = false; if( !options.iframe && !options.onopen && !options.inline ) { if( options.images == null ) { options.images = []; options.imageCaptions = []; for( var i = 0; i < this.length; i++ ) { options.images.push( $(this[i]).attr( 'href' ) ); options.imageCaptions.push( this[i].title ); if( this[i].title ) captions = true; } if( options.images.length <= 1 ) options.thumbs = false; } // Get the urls } else { options.urls = []; options.urlCaptions = []; for( var i = 0; i < this.length; i++ ) { options.urls.push( $(this[i]).attr( 'href' ) ); options.urlCaptions.push( this[i].title ); if( this[i].title ) captions = true; } } // Remove captions if there are none if( captions == false ) options.captions = false; // Remove padding if there are no thumbs or captions if( !options.inline && !options.thumbs && !options.captions && !options.images ) options.className = ( options.className || '' ) + ' nopadding'; // Add default tracking if( options.track === '' ) { if( options.map ) options.track = 'Map'; else if( options.images ) options.track = 'Gallery'; else options.track = 'General'; } /** * Sends out all queued events. */ var track_queue = []; var track_timeout = null; function _track_send() { clearTimeout( track_queue ); for( var i = 0; i < track_queue.length; i++ ) $(document).c1( 'analytics', track_queue[i] ); track_queue = []; } /** * Records an event. */ function _track_event( action, url, position, clear ) { clearTimeout( track_timeout ); if( options.track ) { // Remove all existing events if( clear ) track_queue = []; // Queue the event track_queue.push( { 'type': 'lightbox', 'action': action, 'track': options.track, 'target': t, 'position': position, 'url': url } ); // Send the event instantly if required if( action == 'Open' || action == 'Close' ) _track_send(); else track_timeout = setTimeout( _track_send, 500 ); } } /** * Closes the window when back button is pressed. */ function _hash_change() { if (window.location.hash == '') { $('#c1-lightbox-close a').click(); } } /** * Starts the lightbox effect * @param object objClicked The element that was clicked to open the lightbox */ function _start( objClicked ) { // Hide some elements to avoid conflict with overlay in IE. These elements appear above the overlay. $(options.hide).css( {'visibility': 'hidden'} ); // If a mobile device, remove most animations options.touch = window.C1Device && window.C1Device.touch; // Check if lightbox should not be opened. // There are issues on touch devices with scrolling the contents // of iframes, especially PDF documents. If found to be a // touch device and linking to any file then the lightbox // will be cancelled. if( options.iframe && options.touch ) { var extension = $(objClicked).attr( 'href' ).match( /\.[a-z]+$/ ); if( extension ) return true; } // Force full screen if touch capable device if( options.touch && ( options.images || options.map ) ) options.fullscreen = true; // Force dark display if images else if( options.images && options.className == null ) options.className = 'dark'; // Fullscreen mode if( options.fullscreen ) { options.className = ( options.className || '' ) + ' dark'; if( options.touch ) options.download = '[full size]'; options.count = true; } // Create the interface options.position = null; options._source = objClicked; _create_interface(); options._init = true; // Remove preload variables preload = null; // Display the selected image var position = 0; if( options.images ) { var href = $(objClicked).attr( 'href' ); for( var i = options.images.length - 1; i >= 0; i-- ) { if( options.images[i] == href ) { position = i; break; } } _show_image( position ); // Display the selected link in an iframe } else if( options.iframe ) { var href = $(objClicked).attr( 'href' ); for( var i = options.urls.length - 1; i >= 0; i-- ) { if( options.urls[i] == href ) { position = i; break; } } _show_iframe( position ); // Display the custom content } else if( options.onopen ) { options.onopen.call( $('#c1-lightbox-content').get( 0 ), options ); } // Track opens $(this).c1( 'analytics', { 'tracked': objClicked } ); _track_event( 'Open', '', position ); // Detect if hashchange is available if (options.close && window.addEventListener) { window.addEventListener('hashchange', _hash_change, false); window.location.hash = 'lightbox-open'; } return false; }; /** * Determines the thumb and image sizes based on the current screen resolution. */ function _detect_sizes( hide ) { // Reset the content size var pageSizes = ___getPageSize(); var pageScroll = ___getPageScroll(); // Determine content size and detect automatic height options.contentSize = ( options.images ? _get_url_dimensions( options.images[0] ) : { width: options.width, height: options.height } ); options.autoHeight = options.contentSize.height == 'auto'; if( options.autoHeight ) options.contentSize.height = 0; // Convert percentage based heights var perc_regex = /^(\d+)\%$/; var perc_width = false; if( result = perc_regex.exec( '' + options.contentSize.width ) ) { options.contentSize.width = Math.floor( pageSizes[2] * ( result[1] / 100 ) ); perc_width = true; } var perc_height = false; if( result = perc_regex.exec( '' + options.contentSize.height ) ) { options.contentSize.height = Math.floor( pageSizes[3] * ( result[1] / 100 ) ); perc_height = true; } // Determine size of padding surrounding lightbox for larger resolutions if( pageSizes[2] >= 500 ) { options._rounding = 50; options._padding = Math.round( pageSizes[3] / 320 ) * ( options._rounding / 2 ); // Determine size of padding surrounding lightbox for smaller resolutions } else { options._rounding = 10; options._padding = 10; } // Ensure padding is not too low if( options._padding < options._rounding ) options._padding = options._rounding; // If full screen if( options.fullscreen ) { // Fill the screen options.contentSize.width = pageSizes[2] - options._lightboxSize.width; options.contentSize.height = pageSizes[3] - options._lightboxSize.height; // If not full screen } else { // If the resulting lightbox is higher than the allowed size if( !options.autoHeight && ( options.contentSize.height + options._lightboxSize.height + ( options._padding * 2 ) ) >= pageSizes[3] ) { var newHeight = Math.ceil( Math.ceil( ( pageSizes[3] - ( options._padding * 2 ) - options._lightboxSize.height ) / options._rounding ) * options._rounding ); if( !perc_width && !options.fixedWidth ) options.contentSize.width = Math.ceil( options.contentSize.width * ( newHeight / options.contentSize.height ) ); if( !options.fixedHeight ) options.contentSize.height = newHeight; } // If the resulting lightbox is wider than the allowed size if( ( !options.fixedWidth || options.fixedWidth && options.width > ( pageSizes[2] - options._padding * 2 ) ) && ( options.contentSize.width + options._lightboxSize.width + ( options._padding * 2 ) ) >= pageSizes[2] ) { var newWidth = Math.ceil( Math.ceil( ( pageSizes[2] - ( options._padding * 2 ) - options._lightboxSize.width ) / options._rounding ) * options._rounding ); if( !perc_height && !options.fixedHeight && !options.autoHeight ) options.contentSize.height = Math.ceil( options.contentSize.height * ( newWidth / options.contentSize.width ) ); options.contentSize.width = newWidth; } // Ensure width is above the minimum if( !options.fixedWidth && options.minWidth && options.contentSize.width < options.minWidth ) options.contentSize.width = options.minWidth; // If height should be automatically determined then move the // content in to the container and snap shot the size if( options.autoHeight && typeof options.inline === 'string' ) { $('#c1-lightbox').css('width', options.contentSize.width + options._lightboxSize.width + 'px'); var move_content = $('#c1-lightbox-content').children().length == 0; if( move_content ) $('#c1-lightbox-content').append( $(options.inline).children() ); options.contentSize.height = $('#c1-lightbox-content').height(); if( move_content ) $(options.inline).append( $('#c1-lightbox-content').children() ); } // Ensure height is above minimum if( !options.fixedHeight && !options.autoHeight && options.minHeight && options.contentSize.height < options.minHeight ) options.contentSize.height = options.minHeight; } // Determine new height var width = options.contentSize.width + options._lightboxSize.width; var height = options.contentSize.height + options._lightboxSize.height; // Style content if( options.contentSize.height > 0 ) { $('#c1-lightbox-images').css( { // width: options.contentSize.width + 'px', - disabled as sometimes lightbox width is calculated differently height: options.contentSize.height + 'px' } ); } // Calculate offset and width of the lightbox and show it $('#c1-lightbox').css( { top: pageScroll[1] + ( pageSizes[3] / 2 ) - ( height / 2 ), left: pageScroll[0] + ( pageSizes[2] / 2 ) - ( width / 2 ), width: width + 'px', // height: height + 'px' - disabled as IE calculates lightbox height bigger than FF display: hide ? 'none' : 'block' } ); }; var prevPosition = null; /** * Resizes the interface. */ function _resize_interface() { // Determine screen size var pageSizes = ___getPageSize(); var pageScroll = ___getPageScroll(); var lightbox = $('#c1-lightbox'); // Proceed if values have changed. // The iPhone was triggering resize events even when nothing changed. var pos = pageSizes[2] + 'x' + pageSizes[3] + ' ' + pageScroll[0] + ', ' + pageScroll[1]; if( pos == prevPosition ) return; prevPosition = pos; // Reset lightbox if full screen or if lightbox is now outside the screen boundaries if( options.fullscreen || ( pageSizes[2] < lightbox.width() ) || ( pageSizes[3] < lightbox.height() ) ) { _detect_sizes(); if( options.images ) _show_image( options.position, true ); // Move the lightbox into the middle of the screen } else { lightbox.css( { top: pageScroll[1] + ( pageSizes[3] / 2 ) - ( lightbox.height() / 2 ), left: pageScroll[0] + ( pageSizes[2] / 2 ) - ( lightbox.width() / 2 ) } ); } // Change the dimensions of the overlay $('#c1-lightbox-overlay').css( { width: pageSizes[0], height: pageSizes[1] } ); }; /** * Builds the interface. * @param integer The */ function _create_interface() { // Add the HTML $('body').append( '
' + ' ' ); // Get page sizes var pageSizes = ___getPageSize(); // Style overlay and show it $('#c1-lightbox-overlay').css( { backgroundColor: options.overlayBgColor, opacity: options.overlayOpacity, width: pageSizes[0], height: pageSizes[1] } )[ options.touch ? 'show' : 'fadeIn' ](); // Hide close button if( options.close == false ) $('#c1-lightbox-close').hide(); // Force the width of the content to be zero // Sometimes IE was making the content be full screen width (inRent Redemptions). // @todo The cause is unknown so this is added as a workaround. if( !options.images ) $('#c1-lightbox-content').css( 'width', '0px' ); // Adjust the height of thumbnails if screen resolution is too small if( !options._init && pageSizes[2] < 500 ) { options.thumbWidth = Math.floor( options.thumbWidth * 0.5 ); options.thumbHeight = Math.floor( options.thumbHeight * 0.5 ); } // Get a handle on the lightbox var lightbox = $('#c1-lightbox'); if( options.images ) $('#c1-lightbox-thumbs ul li a').height( options.thumbHeight ).width( 1 ); options._lightboxSize = { width: lightbox.width() - ( options.images ? 1 : 0 ), height: lightbox.height() }; lightbox.addClass( 'init' ); // Reset the width of the content if( !options.images ) $('#c1-lightbox-content').css( 'width', 'auto' ); // Detect content size _detect_sizes( true ); if( !options.api ) { if( options.touch ) { $('#c1-lightbox').show(); if( options.onafterfadein ) options.onafterfadein.call( $('#c1-lightbox-content').get( 0 ), options ); } else { $('#c1-lightbox').fadeIn( function() { if( options.onafterfadein ) options.onafterfadein.call( $('#c1-lightbox-content').get( 0 ), options ); } ); } } // Add thumbnails if( options.thumbs ) _create_thumbs(); // Add show/hide thumbs event $('#c1-lightbox-thumbs-icon').click( function() { var thumbs = $('#c1-lightbox-thumbs'); $('#c1-lightbox-thumbs').toggleClass( 'hidden' ); } ); // Add next and previous thumbs events $('#c1-lightbox-thumbs-prev').click( function() { _scroll_thumbs( -1 ); return false; } ); $('#c1-lightbox-thumbs-next').click( function() { _scroll_thumbs( 1 ); return false; } ); // Add next and previous button event $('#c1-lightbox-image-prev').click( function() { if( options.images ) _show_image( options.position - 1 ); else if( options.iframe ) _show_iframe( options.position - 1 ); _track_event( 'Prev', '', options.position + 1 ); return false; } ); $('#c1-lightbox-image-next').click( function() { if( options.images ) _show_image( options.position + 1 ); else if( options.iframe ) _show_iframe( options.position + 1 ); _track_event( 'Next', '', options.position + 1 ); return false; } ); // Do not close the lightbox when download link clicked $('#c1-lightbox-download').click( function() { var href = this.href; if( options.download == 'original' ) href = _get_media_url( href ); if( options.touch ) window.location = href; else window.open( href ); return false; } ).show(); // Close lightbox on click $('#c1-lightbox-close a' + (options.close ? ', #c1-lightbox-overlay' : '')).click( function() { if( options.cancelClose ) { options.cancelClose = null; return false; } // Track close stats only if close was successful if( _finish() ) _track_event( 'Close' ); return false; } ); // Add touch gesture support to change images if( options.images && $.fn.c1.touch ) { // Add touch support to the lightbox container $('#c1-lightbox').c1( 'touch', { 'options': { 'prevent_default': true, // Stop scrolling of content 'swipe_velocity': 0.3 // Make swipes detected better }, // Handle swipes 'swipeleft swiperight swipeup swipedown': function( e ) { // Determine direction var dir = 0; if( e.type == 'swipeleft' || e.type == 'swipedown' ) dir = 1; else if( e.type == 'swiperight' || e.type == 'swipeup' ) dir = -1; // Show next image if( dir != 0 ) { if( options.images ) _show_image( options.position + dir ); else if( options.iframe ) _show_iframe( options.position + dir ); } }, // As preventing default events then need to convert tap events // to click events 'tap': function( e ) { var el = $(e.target); if( el.is( 'a' ) ) { el.click(); return false; } }, // Force double click events anything that isn't an anchor // as users might double click a link to perform an action. 'doubletap': function( e ) { if( !$(e.target).is( 'a' ) ) { $('#c1-lightbox-close a').click(); return false; } } } ); } // If inline content is shown, then stop clicking within the content to close the lightbox if( options.inline ) { $('#c1-lightbox-content').click( function( event ) { event.stopPropagation(); } ); } // If window was resized, calculate the new overlay dimensions $(window).resize( _resize_interface ).scroll( _resize_interface ); }; /** * Creates the thumbnails. */ function _create_thumbs() { // If there are no thumbnails, bomb out if( !options.images ) return; // Add thumbnail elements var list = $('#c1-lightbox-thumbs ul').empty(); for( var i = 0; i < options.images.length; i++ ) { var url = _get_thumb_url( options.images[i], options.thumbWidth, options.thumbHeight, 'crop' ); $( '