/*
* jQuery Popeye 2.0.4 - http://dev.herr-schuessler.de/jquery/popeye/
*
* converts a HTML image list in image gallery with inline enlargement
*
* Copyright (C) 2008 - 2010 Christoph Schuessler (schreib@herr-schuessler.de)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
*/
(function ($) {
////////////////////////////////////////////////////////////////////////////
//
// $.fn.popeye
// popeye definition
//
////////////////////////////////////////////////////////////////////////////
$.fn.popeye = function (options) {
// build main options before element iteration
//----------------------------------------------------------------------
var opts = $.extend({}, $.fn.popeye.defaults, options);
////////////////////////////////////////////////////////////////////////////////
//
// firebug console output
// @param text String the debug message
// @param type String the message type [info | warn] (optional)
//
////////////////////////////////////////////////////////////////////////////////
function debug(text,type) {
if (window.console && window.console.log && opts.debug) {
if(type == 'info' && window.console.info) {
window.console.info(text);
}
else if(type == 'warn' && window.console.warn) {
window.console.warn(text);
}
else {
window.console.log(text);
}
}
}
// let's go!
//----------------------------------------------------------------------
return this.each(function(){
// first thing to do: make ppy html visible
$(this).addClass('ppy-active');
// cache object
var $self = $(this),
// images
img = $self.find('.ppy-imglist > li > a > img'),
a = $self.find('.ppy-imglist > li > a'),
tot = img.length,
// single image mode
singleImageMode = (tot == 1) ? true : false,
// start in compact mode
enlarged = false,
// counter vars
cur = 0, // array index of currently displayed image
// extra classes
eclass = 'ppy-expanded', //class to be applied to enlarged popeye-box
lclass = 'ppy-loading', //class to be applied to stage while loading image
sclass = 'ppy-single-image', //class to be applied to popeye-box if there's only one image to display
// html nodes
ppyPlaceholder = $('
'),
ppyStageWrap = $(''),
ppyCaptionWrap = $(''),
ppyOuter = $self.find('.ppy-outer'),
ppyStage = $self.find('.ppy-stage'),
ppyNav = $self.find('.ppy-nav'),
ppyPrev = $self.find('.ppy-prev'),
ppyNext = $self.find('.ppy-next'),
ppySwitchEnlarge = $self.find('.ppy-switch-enlarge'),
ppySwitchCompact = $self.find('.ppy-switch-compact').addClass('ppy-hidden'),
ppyCaption = $self.find('.ppy-caption'),
ppyText = $self.find('.ppy-text'),
ppyCounter = $self.find('.ppy-counter'),
ppyCurrent = $self.find('.ppy-current'),
ppyTotal = $self.find('.ppy-total'),
// css objects
cssSelf = {
position: 'absolute',
width: 'auto',
height: 'auto',
margin: 0,
top: 0,
left: (opts.direction == 'right') ? 0 : 'auto',
right: (opts.direction == 'left') ? 0 : 'auto'
},
cssStage = {
height: ppyStage.height(),
width: ppyStage.width()
},
cssCaption = {
height: ppyCaption.height()
},
cssPlaceholder = {
height: (opts.caption == 'hover' || false) ? ppyOuter.outerHeight() : $self.outerHeight(),
width: (opts.caption == 'hover' || false) ? ppyOuter.outerWidth() : $self.outerWidth(),
float: $self.css('float'),
marginTop: $self.css('margin-top'),
marginRight: $self.css('margin-right'),
marginBottom: $self.css('margin-bottom'),
marginLeft: $self.css('margin-left')
};
// make caption array from caption element or alt tag
var cap = [];
for(var i=0; i 0 ? extcap.html() : img[i].alt;
}
// check for html errors
if( !ppyStage.length || !ppyNav.length || !ppyOuter.length ) {
debug('$.fn.popeye: Incorrect HTML structure','warn');
}
// check for images
else if( tot === 0 ) {
debug('$.fn.popeye: No images found','warn');
}
// no errors, setup done!
//------------------------------------------------------------------
else {
singleImageMode ? debug('$.fn.popeye -> SingleImageMode started') : debug('$.fn.popeye -> ' + tot + ' thumbnails found.');
init();
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.showThumb
// show thumbnail
// @param i Int the index of the thumbnail to show (optional)
// @param transition Bool show transition between images (optional)
//
////////////////////////////////////////////////////////////////////
function showThumb(i, transition) {
// optional parameters
transition = transition || false;
i = i || cur;
// set selected thumb as background image of stage
var cssStageImage = {
backgroundImage: 'url(' + img[i].src + ')'
};
// bogus animation css for IE
var cssTemp = {
height: '+=0'
};
// if we are in enlarged mode, return to thumb mode
if(enlarged) {
hideCaption();
// fade image out and compact stage with transition
ppyStage.fadeTo((opts.duration/2),0).animate( cssStage, {
queue: false,
duration: opts.duration,
easing: opts.easing,
complete: function() {
enlarged = false;
debug('$.fn.showThumb: Entering COMPACT MODE','info');
// remove extra styling and reset z-index
$self.removeClass(eclass);
$self.css('z-index','');
// switch buttons
ppySwitchEnlarge.removeClass('ppy-hidden');
ppySwitchCompact.addClass('ppy-hidden');
// recursive function call
showThumb();
// fade the stage back in
$(this).fadeTo((opts.duration/2),1);
}
});
}
else {
// if we navigate from one image to the next, fade out the stage
if(transition) {
// fade out image so that background shines through
// background can contain loading gfx
ppyStageWrap.addClass(lclass);
ppyStage.fadeTo((opts.duration/2), 0);
// once thumb has loadded...
var thumbPreloader = new Image();
thumbPreloader.onload = function() {
debug('$.fn.popeye.showThumb: Thumbnail ' + i + ' loaded', 'info');
// remove loading indicator
ppyStageWrap.removeClass(lclass);
// add all upcoming animations to the queue so that
// they won't start when the preolader has loaded but when the fadeOut has finished
ppyStage.animate(cssTemp,1,'linear',function(){
// set the new image
ppyStage.css(cssStageImage);
// fade the stage back in
$(this).fadeTo((opts.duration/2),1);
// update counter and caption
if(opts.caption == 'hover') {
showCaption(cap[i]);
}
else if(opts.caption == 'permanent') {
updateCaption(cap[i]);
}
updateCounter();
});
// fix IE animated gif bug
thumbPreloader.onload = function(){};
};
// preload thumb
thumbPreloader.src = img[i].src;
}
// or just drag the image to the stage
else {
ppyStage.css(cssStageImage);
updateCounter();
showCaption(cap[i],true);
}
// preload big image for instant availability
var preloader = new Image();
preloader.onload = function() {
debug('$.fn.popeye.showThumb: Image ' + i + ' loaded','info');
preloader.onload = function(){};
};
preloader.src = a[i].href;
}
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.showImage
// show large image
// @param i Int the index of the image to show (optional)
//
////////////////////////////////////////////////////////////////////
function showImage(i) {
// optional parameter i
i = i || cur;
// fade out image so that background shines through
// background can contain loading gfx
ppyStageWrap.addClass(lclass);
ppyStage.fadeTo((opts.duration/2), 0);
// if there are multiple popeyes opened at the same time,
// make sure the current one gets a higher z-index
var allPpy = $('.' + eclass);
allPpy.css('z-index',opts.zindex-1);
$self.css('z-index',opts.zindex);
// once image has loadded...
var preloader = new Image();
preloader.onload = function() {
// remove loading class
ppyStageWrap.removeClass(lclass);
// set css
var cssStageTo = {
width: preloader.width,
height: preloader.height
};
var cssStageIm = {
backgroundImage: 'url(' + a[i].href + ')',
backgroundPosition: 'left top'
};
hideCaption();
// show transitional animation
ppyStage.animate( cssStageTo, {
queue: false,
duration: opts.duration,
easing: opts.easing,
complete: function(){
if(opts.navigation == 'hover') {
showNav();
}
enlarged = true;
debug('$.fn.popeye.showImage: Entering ENLARGED MODE','info');
// add extra class, expanded box can be styled accordingly
$self.addClass(eclass);
// switch buttons
ppySwitchCompact.removeClass('ppy-hidden');
ppySwitchEnlarge.addClass('ppy-hidden');
updateCounter();
// set new bg image and fade it in
$(this).css(cssStageIm).fadeTo((opts.duration/2),1);
// show caption
showCaption(cap[i]);
preloadNeighbours();
}
});
};
// preload image
preloader.src = a[i].href;
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.updateCounter
// update image counter
// @param i Int the index of the image (optional)
//
////////////////////////////////////////////////////////////////////
function updateCounter(i) {
// optional parameter
i = i || cur;
ppyTotal.text(tot); // total images
ppyCurrent.text(i + 1); // current image number
debug('$.fn.popeye.updateCounter: Displaying image ' + (i + 1) + ' of ' + tot);
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.preloadNeighbours
// preload next and previos image
// @param i Int the index of the current image (optional)
//
////////////////////////////////////////////////////////////////////
function preloadNeighbours(i) {
// optional parameter
i = i || cur;
var preloaderNext = new Image();
var preloaderPrev = new Image();
var neighbour = i;
// next image
if( neighbour < ( tot - 1) ) {
neighbour++;
} else {
neighbour = 0;
}
preloaderNext.src = a[i].href[neighbour];
// previous image
neighbour = i;
if( neighbour <= 0 ) {
neighbour = tot - 1;
} else {
neighbour--;
}
preloaderPrev.src = a[i].href[neighbour];
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.showNav
//
////////////////////////////////////////////////////////////////////
function showNav() {
ppyNav.stop().fadeTo(150,opts.opacity);
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.hideNav
//
////////////////////////////////////////////////////////////////////
function hideNav() {
ppyNav.stop().fadeTo(150,0);
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.updateCaption
// @param caption String the caption string
//
////////////////////////////////////////////////////////////////////
function updateCaption(caption) {
if(opts.caption) {
// update text box
ppyText.html(caption);
}
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.showCaption
// @param caption String the caption string
// @param force Boolean force caption display even if caption string is empty
//
////////////////////////////////////////////////////////////////////
function showCaption(caption,force) {
// if caption string is not empty...
if(caption && opts.caption) {
updateCaption(caption);
debug('$.fn.popeye.showCaption -> ppyCaptionWrap.outerHeight(true): ' + ppyCaptionWrap.outerHeight(true));
// make caption box visible
var cssTempCaption = {
visibility: 'visible'
};
ppyCaption.css(cssTempCaption);
if(opts.caption === 'permanent' && !enlarged) {
// return to original caption height
ppyCaption.css(cssCaption);
}
else {
// or animate it to its childs height
ppyCaption.animate({'height': ppyCaptionWrap.outerHeight(true)}, {
queue: false,
duration: 90,
easing: opts.easing
});
}
}
// if there's no caption to show...
else if(!caption && !force) {
hideCaption();
}
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.hideCaption
//
////////////////////////////////////////////////////////////////////
function hideCaption() {
// css to hide caption but allow its inner text box to expand to content height
var cssTempCaption = {
visibility: 'hidden',
overflow: 'hidden'
};
// slide up caption box and hide it when done
ppyCaption.animate( {'height': '0px'}, {
queue: false,
duration: 90,
easing: opts.easing,
complete: function() {
ppyCaption.css(cssTempCaption);
}
});
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.previous
// show previous image
//
////////////////////////////////////////////////////////////////////
function previous() {
if( cur <= 0 ) {
cur = tot - 1;
} else {
cur--;
}
if(enlarged) {
showImage(cur);
}
else {
showThumb(cur, true);
}
return cur;
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.next
// show next image
//
////////////////////////////////////////////////////////////////////
function next() {
if( cur < ( tot - 1) ) {
cur++;
} else {
cur = 0;
}
if(enlarged) {
showImage(cur);
}
else {
showThumb(cur, true);
}
return cur;
}
////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.init
// setup of popeye DOM and events
//
////////////////////////////////////////////////////////////////////
function init() {
// popeye dom setup
//--------------------------------------------------------------
// add css
ppyPlaceholder.css(cssPlaceholder);
$self.css(cssSelf);
// wrap popeye in placeholder
$self.wrap(ppyPlaceholder);
// wrap stage in container for extra styling (e.g. loading gfx)
ppyStageWrap = ppyStage.wrap(ppyStageWrap).parent();
// wrap caption contents in wrapper (can't use wrap() here...)
ppyCaptionWrap = ppyCaption.wrapInner(ppyCaptionWrap).children().eq(0);
// display first image
showThumb();
// add event handlers
//--------------------------------------------------------------
// hover behaviour for navigation
if(opts.navigation == 'hover') {
hideNav();
$self.hover(
function(){
showNav();
},
function(){
hideNav();
}
);
ppyNav.hover(
function(){
showNav();
},
function(){
hideNav();
}
);
}
if(!singleImageMode) {
// previous image button
ppyPrev.click(previous);
// next image button
ppyNext.click(next);
}
else {
$self.addClass(sclass);
ppyPrev.remove();
ppyNext.remove();
ppyCounter.remove();
}
// hover behaviour for caption
if(opts.caption == 'hover') {
hideCaption();
$self.hover(
function(){
showCaption(cap[cur]);
},
function(){
hideCaption(true);
}
);
}
// enlarge image button
ppySwitchEnlarge.click(function(){
showImage();
return false;
});
// compact image button
ppySwitchCompact.click(function(){
showThumb(cur);
return false;
});
}
});
};
////////////////////////////////////////////////////////////////////////////
//
// $.fn.popeye.defaults
// set default options
//
////////////////////////////////////////////////////////////////////////////
$.fn.popeye.defaults = {
navigation: 'hover', //visibility of navigation - can be 'permanent' or 'hover'
caption: 'hover', //visibility of caption, based on image title - can be false, 'permanent' or 'hover'
zindex: 10000, //z-index of the expanded popeye-box. enter a z-index that works well with your site and doesn't overlay your site's navigational elements like dropdowns
direction: 'right', //direction that popeye-box opens, can be 'left' or 'right'
duration: 240, //duration of transitional effect when enlarging or closing the box
opacity: 0.8, //opacity of navigational overlay (only applicable if 'navigation' is set to 'hover'
easing: 'swing', //easing type, can be 'swing', 'linear' or any of jQuery Easing Plugin types (Plugin required)
debug: false //turn on console output (slows down IE8!)
};
// end of closure, bind to jQuery Object
})(jQuery);
////////////////////////////////////////////////////////////////////////////////
//
// avoid content flicker for non-js user agents
// (in order to use this, the js-files have to be included in the head of the
// html file!)
//
////////////////////////////////////////////////////////////////////////////////
jQuery('head').append('');