/*! photobox v1.9 (c) 2013 Yair Even Or MIT-style license. */ (function($, doc, win){ "use strict"; var Photobox, photoboxes = [], photobox, options, images=[], imageLinks, activeImage = -1, activeURL, lastActive, activeType, prevImage, nextImage, thumbsStripe, docElm, APControl, transitionend = "transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd", isOldIE = !('placeholder' in doc.createElement('input')), noPointerEvents = (function(){ var el = $('

')[0]; el.style.cssText = 'pointer-events:auto'; return !el.style.pointerEvents})(), isMobile = 'ontouchend' in doc, // should be updated to something that detects the lack of a mouse thumbsContainerWidth, thumbsTotalWidth, activeThumb = $(), blankImg = "data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==", transformOrigin = getPrefixed('transformOrigin'), transition = getPrefixed('transition'), // Preload images preload = {}, preloadPrev = new Image(), preloadNext = new Image(), // DOM elements closeBtn, image, video, prevBtn, nextBtn, thumbsToggler, caption, captionText, pbLoader, autoplayBtn, thumbs, wrapper, defaults = { single: false, // if "true" - gallery will only show a single image, with no way to navigate beforeShow: null, // Callback before showing an image afterClose: null, // Callback after closing the gallery loop: true, // Allows to navigate between first and last images thumb: null, // A relative path from the link to the thumbnail (if it's not inside the link) thumbs: true, // Show gallery thumbnails below the presented photo counter: "(A/B)", // Counts which piece of content is being viewed, relative to the total count of items in the photobox set. ["false","String"] title: true, // show the original alt or title attribute of the image's thumbnail. (path to image, relative to the element which triggers photobox) autoplay: false, // should autoplay on first time or not time: 3000, // autoplay interval, in miliseconds (less than 1000 will hide the autoplay button) history: true, // should use history hashing if possible (HTML5 API) hideFlash: true, // Hides flash elements on the page when photobox is activated. NOTE: flash elements must have wmode parameter set to "opaque" or "transparent" if this is set to false zoomable: true, // disable/enable mousewheel image zooming keys: { close: '27, 88, 67', // keycodes to close photobox, default: esc (27), 'x' (88), 'c' (67) prev: '37, 80', // keycodes to navigate to the previous image, default: Left arrow (37), 'p' (80) next: '39, 78' // keycodes to navigate to the next image, default: Right arrow (39), 'n' (78) } }, // DOM structure overlay = $('

').append( thumbsToggler = $(''), pbLoader = $('
'), prevBtn = $('
').on('click', next_prev), nextBtn = $('
').on('click', next_prev), wrapper = $('
').append( // gives Perspective image = $(''), video = $('
') ), closeBtn = $('
').on('click', close)[0], autoplayBtn = $('
').append( $('
') ), caption = $('
').append( '', captionText = $('
').append('
'), thumbs = $('
').addClass('pbThumbs') ) ); /*--------------------------------------------------------------- Initialization (on DOM ready) */ function prepareDOM(){ noPointerEvents && overlay.hide(); autoplayBtn.off().on('click', APControl.toggle); // attach a delegated event on the thumbs container thumbs.off().on('click', 'a', thumbsStripe.click); // if useragent is IE < 10 (user deserves a slap on the face, but I gotta support them still...) isOldIE && overlay.addClass('msie'); isMobile && overlay.addClass('mobile'); // cancel prorogation up to the overlay container so it won't close overlay.off().on('click', 'img', function(e){ e.stopPropagation(); }); $(doc.body).append(overlay); // need this for later: docElm = doc.documentElement; } // @param [List of elements to work on, Custom settings, Callback after image is loaded] $.fn.photobox = function(target, settings, callback){ return this.each(function(){ var o, pb, PB_data = $(this).data('_photobox'); if( PB_data ){ // don't initiate the plugin more than once on the same element if( target === 'destroy') PB_data.destroy(); return this; } if( typeof target != 'string' ) target = 'a'; if( target === 'prepareDOM' ){ prepareDOM(); return this; } o = $.extend({}, defaults, settings || {}); pb = new Photobox(o, this, target); // Saves the insance on the gallery's target element $(this).data('_photobox', pb); // add a callback to the specific gallery pb.callback = callback; // save every created gallery pointer photoboxes.push( pb ); }); } Photobox = function(_options, object, target){ this.options = $.extend({}, _options); this.target = target; this.selector = $(object || doc); this.thumbsList = null; // filter the links which actually HAS an image as a child var filtered = this.imageLinksFilter( this.selector.find(target) ); this.imageLinks = filtered[0]; // Array of jQuery links this.images = filtered[1]; // 2D Array of image URL & title this.init(); }; Photobox.prototype = { init : function(){ var that = this; // only generates the thumbStripe once, and listen for any DOM changes on the selector element, if so, re-generate if( this.options.thumbs ){ // generate gallery thumbnails every time (because links might have changed) this.thumbsList = thumbsStripe.generate.apply(this); } this.selector.on('click.photobox', this.target, function(e){ e.preventDefault(); that.open(this); }); // if any node was added or removed from the Selector of the gallery this.observerTimeout = null; if( this.selector[0].nodeType == 1 ) // observe normal nodes that.observeDOM( that.selector[0], function(){ // use a timeout to prevent more than one DOM change event firing at once, and also to overcome the fact that IE's DOMNodeRemoved is fired BEFORE elements were actually removed clearTimeout(that.observerTimeout); that.observerTimeout = setTimeout( function(){ var filtered = that.imageLinksFilter( that.selector.find(that.target) ), activeIndex = 0; // Make sure that ONLY DOM changes in the photobox number of items will trigger a change if(that.imageLinks.length == filtered[0].length) return; that.imageLinks = filtered[0]; that.images = filtered[1]; // if photobox is opened if( photobox ){ // if gallery which was changed is the currently viewed one: if( that.selector == photobox.selector ){ images = that.images; imageLinks = that.imageLinks; // check if the currently VIEWED photo has been detached from a photobox set // if so, remove navigation arrows // TODO: fix the "images" to be an object and not an array. for( var i = images.length; i--; ){ if( images[i][0] == activeURL ) return; // if not exits any more } overlay.removeClass('hasArrows'); } } // if this gallery has thumbs if( that.options.thumbs ){ that.thumbsList = thumbsStripe.generate.apply(that); thumbs.html( that.thumbsList ); } if( that.images.length && activeURL && that.options.thumbs ){ activeIndex = that.thumbsList.find('a[href="'+activeURL+'"]').eq(0).parent().index(); if( activeIndex == -1 ) activeIndex = 0; updateIndexes(activeIndex); thumbsStripe.changeActive(activeIndex, 0); } }, 50); }); }, open : function(link){ var startImage = $.inArray(link, this.imageLinks); // if image link does not exist in the imageLinks array (probably means it's not a valid part of the gallery) if( startImage == -1 ) return false; // load the right gallery selector... options = this.options; images = this.images; imageLinks = this.imageLinks; photobox = this; this.setup(1); overlay.on(transitionend, function(){ overlay.off(transitionend).addClass('on'); // class 'on' is set when the initial fade-in of the overlay is done changeImage(startImage, true); }).addClass('show'); if( isOldIE ) overlay.trigger('MSTransitionEnd'); return false; }, imageLinksFilter : function(obj){ var that = this, images = [], caption = {}, captionlink; return [obj.filter(function(i){ // search for the thumb inside the link, if not found then see if there's a 'that.settings.thumb' pointer to the thumbnail var link = $(this), thumbImg; if( that.options.thumb ) thumbImg = link.find(that.options.thumb)[0]; // try a direct child lookup if( !that.options.thumb || !thumbImg ) thumbImg = link.find('img')[0]; // if no img child found in the link if( thumbImg ) captionlink = thumbImg.getAttribute('data-pb-captionlink'); caption.content = ( thumbImg.getAttribute('alt') || thumbImg.getAttribute('title') || ''); // if there is a caption link to be added: if( captionlink ){ captionlink = captionlink.split('['); // parse complex links: text[www.site.com] if( captionlink.length == 2 ){ caption.linkText = captionlink[0]; caption.linkHref = captionlink[1].slice(0,-1); } else{ caption.linkText = captionlink; caption.linkHref = captionlink; } caption.content += ' ' + caption.linkText + ''; } images.push( [link[0].href, caption.content, thumbImg.getAttribute('src')] ); return true; }), images]; }, //check if DOM nodes were added or removed, to re-build the imageLinks and thumbnails observeDOM : (function(){ var MutationObserver = win.MutationObserver || win.WebKitMutationObserver, eventListenerSupported = win.addEventListener; return function(obj, callback){ if( MutationObserver ){ // define a new observer var obs = new MutationObserver(function(mutations, observer){ if( mutations[0].addedNodes.length || mutations[0].removedNodes.length ) callback(); }); // have the observer observe foo for changes in children obs.observe( obj, { childList:true, subtree:true }); } else if( eventListenerSupported ){ obj.addEventListener('DOMNodeInserted', callback, false); obj.addEventListener('DOMNodeRemoved', callback, false); } } })(), // things that should happen every time the gallery opens or closes (some messed up code below..) setup : function (open){ var fn = open ? "on" : "off"; // a hack to change the image src to nothing, because you can't do that in CHROME image[0].src = blankImg; // thumbs stuff if( options.thumbs ){ if( !isMobile ){ thumbs[fn]('mouseenter.photobox', thumbsStripe.calc) [fn]('mousemove.photobox', thumbsStripe.move); } } if( open ){ image.css({'transition':'0s'}).removeAttr('style'); // reset any transition that might be on the element (yes it's ugly) overlay.show(); // Clean up if another gallery was viewed before, which had a thumbsList thumbs .html( this.thumbsList ) .trigger('mouseenter.photobox'); if( options.thumbs ){ overlay.addClass('thumbs'); } else{ thumbsToggler.prop('checked', false); overlay.removeClass('thumbs'); } // things to hide if there are less than 2 images if( this.images.length < 2 || options.single ) overlay.removeClass('thumbs hasArrows hasCounter hasAutoplay'); else{ overlay.addClass('hasArrows hasCounter') // check is the autoplay button should be visible (per gallery) and if so, should it autoplay or not. if( options.time > 1000 ){ overlay.addClass('hasAutoplay'); if( options.autoplay ) APControl.progress.start(); else APControl.pause(); } else overlay.removeClass('hasAutoplay'); } options.hideFlash && $('iframe, object, embed').css('visibility', 'hidden'); } else { $(win).off('resize.photobox'); } $(doc).off("keydown.photobox")[fn]({ "keydown.photobox": keyDown }); if( isMobile ){ overlay.removeClass('hasArrows'); // no need for Arrows on touch-enabled wrapper[fn]('swipe', onSwipe); } if( options.zoomable ){ overlay[fn]({"mousewheel.photobox": scrollZoom }); if( !isOldIE) thumbs[fn]({"mousewheel.photobox": thumbsResize }); } if( !options.single ){ overlay[fn]({"mousewheel.photobox": wheelNextPrev }); } }, destroy : function(){ options = this.options; this.selector .off('click.photobox', this.target) .removeData('_photobox'); close(); } } // on touch-devices only function onSwipe(e, Dx, Dy){ if( Dx == 1 ){ image.css({transform:'translateX(25%)', transition:'.2s', opacity:0}); setTimeout(function(){ changeImage(prevImage) }, 200); } else if( Dx == -1 ){ image.css({transform:'translateX(-25%)', transition:'.2s', opacity:0}); setTimeout(function(){ changeImage(nextImage) }, 200); } if( Dy == 1 ) thumbsToggler.prop('checked', true); else if( Dy == -1 ) thumbsToggler.prop('checked', false); } // manage the (bottom) thumbs strip thumbsStripe = (function(){ var containerWidth = 0, scrollWidth = 0, posFromLeft = 0, // Stripe position from the left of the screen stripePos = 0, // When relative mouse position inside the thumbs stripe animated = null, padding, // in percentage to the containerWidth el, $el, ratio, scrollPos, pos; return{ // returns a