/** 
* jQuery Galleriffic plugin 
* 
* Copyright (c) 2008 Trent Foley (http://trentacular.com) 
* Licensed under the MIT License: 
* http://www.opensource.org/licenses/mit-license.php 
* 
* Much thanks to primary contributer Ponticlaro (http://www.ponticlaro.com) 
*/ 
;(function($) { 
// Globally keep track of all images by their unique hash. Each item is an image data object. 
var allImages = {}; 
var imageCounter = 0; 

// Galleriffic static class 
$.galleriffic = { 
version: '2.0.1', 

// Strips invalid characters and any leading # characters 
normalizeHash: function(hash) { 
return hash.replace(/^.*#/, '').replace(/\?.*$/, ''); 
}, 

getImage: function(hash) { 
if (!hash) 
return undefined; 

hash = $.galleriffic.normalizeHash(hash); 
return allImages[hash]; 
}, 

// Global function that looks up an image by its hash and displays the image. 
// Returns false when an image is not found for the specified hash. 
// @param {String} hash This is the unique hash value assigned to an image. 
gotoImage: function(hash) { 
var imageData = $.galleriffic.getImage(hash); 
if (!imageData) 
return false; 

var gallery = imageData.gallery; 
gallery.gotoImage(imageData); 

return true; 
}, 

// Removes an image from its respective gallery by its hash. 
// Returns false when an image is not found for the specified hash or the 
// specified owner gallery does match the located images gallery. 
// @param {String} hash This is the unique hash value assigned to an image. 
// @param {Object} ownerGallery (Optional) When supplied, the located images 
// gallery is verified to be the same as the specified owning gallery before 
// performing the remove operation. 
removeImageByHash: function(hash, ownerGallery) { 
var imageData = $.galleriffic.getImage(hash); 
if (!imageData) 
return false; 

var gallery = imageData.gallery; 
if (ownerGallery && ownerGallery != gallery) 
return false; 

return gallery.removeImageByIndex(imageData.index); 
} 
}; 

var defaults = { 
delay: 3000, 
numThumbs: 20, 
preloadAhead: 40, // Set to -1 to preload all images 
enableTopPager: false, 
enableBottomPager: true, 
maxPagesToShow: 7, 
imageContainerSel: '', 
captionContainerSel: '', 
controlsContainerSel: '', 
loadingContainerSel: '', 
renderSSControls: true, 
renderNavControls: true, 
playLinkText: 'Play', 
pauseLinkText: 'Pause', 
prevLinkText: 'Previous', 
nextLinkText: 'Next', 
nextPageLinkText: 'Next &rsaquo;', 
prevPageLinkText: '&lsaquo; Prev', 
enableHistory: false, 
enableKeyboardNavigation: true, 
autoStart: false, 
syncTransitions: false, 
defaultTransitionDuration: 1000, 
onSlideChange: undefined, // accepts a delegate like such: function(prevIndex, nextIndex) { ... } 
onTransitionOut: undefined, // accepts a delegate like such: function(slide, caption, isSync, callback) { ... } 
onTransitionIn: undefined, // accepts a delegate like such: function(slide, caption, isSync) { ... } 
onPageTransitionOut: undefined, // accepts a delegate like such: function(callback) { ... } 
onPageTransitionIn: undefined, // accepts a delegate like such: function() { ... } 
onImageAdded: undefined, // accepts a delegate like such: function(imageData, $li) { ... } 
onImageRemoved: undefined // accepts a delegate like such: function(imageData, $li) { ... } 
}; 

// Primary Galleriffic initialization function that should be called on the thumbnail container. 
$.fn.galleriffic = function(settings) { 
// Extend Gallery Object 
$.extend(this, { 
// Returns the version of the script 
version: $.galleriffic.version, 

// Current state of the slideshow 
isSlideshowRunning: false, 
slideshowTimeout: undefined, 

// This function is attached to the click event of generated hyperlinks within the gallery 
clickHandler: function(e, link) { 
this.pause(); 

if (!this.enableHistory) { 
// The href attribute holds the unique hash for an image 
var hash = $.galleriffic.normalizeHash($(link).attr('href')); 
$.galleriffic.gotoImage(hash); 
e.preventDefault(); 
} 
}, 

// Appends an image to the end of the set of images. Argument listItem can be either a jQuery DOM element or arbitrary html. 
// @param listItem Either a jQuery object or a string of html of the list item that is to be added to the gallery. 
appendImage: function(listItem) { 
this.addImage(listItem, false, false); 
return this; 
}, 

// Inserts an image into the set of images. Argument listItem can be either a jQuery DOM element or arbitrary html. 
// @param listItem Either a jQuery object or a string of html of the list item that is to be added to the gallery. 
// @param {Integer} position The index within the gallery where the item shouold be added. 
insertImage: function(listItem, position) { 
this.addImage(listItem, false, true, position); 
return this; 
}, 

// Adds an image to the gallery and optionally inserts/appends it to the DOM (thumbExists) 
// @param listItem Either a jQuery object or a string of html of the list item that is to be added to the gallery. 
// @param {Boolean} thumbExists Specifies whether the thumbnail already exists in the DOM or if it needs to be added. 
// @param {Boolean} insert Specifies whether the the image is appended to the end or inserted into the gallery. 
// @param {Integer} position The index within the gallery where the item shouold be added. 
addImage: function(listItem, thumbExists, insert, position) { 
var $li = ( typeof listItem === "string" ) ? $(listItem) : listItem; 
var $aThumb = $li.find('a.thumb'); 
var slideUrl = $aThumb.attr('href'); 
var title = $aThumb.attr('title'); 
var $caption = $li.find('.caption').remove(); 
var hash = $aThumb.attr('name'); 

// Increment the image counter 
imageCounter++; 

// Autogenerate a hash value if none is present or if it is a duplicate 
if (!hash || allImages[''+hash]) { 
hash = imageCounter; 
} 

// Set position to end when not specified 
if (!insert) 
position = this.data.length; 

var imageData = { 
title:title, 
slideUrl:slideUrl, 
caption:$caption, 
hash:hash, 
gallery:this, 
index:position 
}; 

// Add the imageData to this gallery's array of images 
if (insert) { 
this.data.splice(position, 0, imageData); 

// Reset index value on all imageData objects 
this.updateIndices(position); 
} 
else { 
this.data.push(imageData); 
} 

var gallery = this; 

// Add the element to the DOM 
if (!thumbExists) { 
// Update thumbs passing in addition post transition out handler 
this.updateThumbs(function() { 
var $thumbsUl = gallery.find('ul.thumbs'); 
if (insert) 
$thumbsUl.children(':eq('+position+')').before($li); 
else 
$thumbsUl.append($li); 

if (gallery.onImageAdded) 
gallery.onImageAdded(imageData, $li); 
}); 
} 

// Register the image globally 
allImages[''+hash] = imageData; 

// Setup attributes and click handler 
$aThumb.attr('rel', 'history') 
.attr('href', '#'+hash) 
.removeAttr('name') 
.click(function(e) { 
gallery.clickHandler(e, this); 
}); 

return this; 
}, 

// Removes an image from the gallery based on its index. 
// Returns false when the index is out of range. 
removeImageByIndex: function(index) { 
if (index < 0 || index >= this.data.length) 
return false; 

var imageData = this.data[index]; 
if (!imageData) 
return false; 

this.removeImage(imageData); 

return true; 
}, 

// Convenience method that simply calls the global removeImageByHash method. 
removeImageByHash: function(hash) { 
return $.galleriffic.removeImageByHash(hash, this); 
}, 

// Removes an image from the gallery. 
removeImage: function(imageData) { 
var index = imageData.index; 

// Remove the image from the gallery data array 
this.data.splice(index, 1); 

// Remove the global registration 
delete allImages[''+imageData.hash]; 

// Remove the image's list item from the DOM 
this.updateThumbs(function() { 
var $li = gallery.find('ul.thumbs') 
.children(':eq('+index+')') 
.remove(); 

if (gallery.onImageRemoved) 
gallery.onImageRemoved(imageData, $li); 
}); 

// Update each image objects index value 
this.updateIndices(index); 

return this; 
}, 

// Updates the index values of the each of the images in the gallery after the specified index 
updateIndices: function(startIndex) { 
for (i = startIndex; i < this.data.length; i++) { 
this.data[i].index = i; 
} 

return this; 
}, 

// Scraped the thumbnail container for thumbs and adds each to the gallery 
initializeThumbs: function() { 
this.data = []; 
var gallery = this; 

this.find('ul.thumbs > li').each(function(i) { 
gallery.addImage($(this), true, false); 
}); 

return this; 
}, 

isPreloadComplete: false, 

// Initalizes the image preloader 
preloadInit: function() { 
if (this.preloadAhead == 0) return this; 

this.preloadStartIndex = this.currentImage.index; 
var nextIndex = this.getNextIndex(this.preloadStartIndex); 
return this.preloadRecursive(this.preloadStartIndex, nextIndex); 
}, 

// Changes the location in the gallery the preloader should work 
// @param {Integer} index The index of the image where the preloader should restart at. 
preloadRelocate: function(index) { 
// By changing this startIndex, the current preload script will restart 
this.preloadStartIndex = index; 
return this; 
}, 

// Recursive function that performs the image preloading 
// @param {Integer} startIndex The index of the first image the current preloader started on. 
// @param {Integer} currentIndex The index of the current image to preload. 
preloadRecursive: function(startIndex, currentIndex) { 
// Check if startIndex has been relocated 
if (startIndex != this.preloadStartIndex) { 
var nextIndex = this.getNextIndex(this.preloadStartIndex); 
return this.preloadRecursive(this.preloadStartIndex, nextIndex); 
} 

var gallery = this; 

// Now check for preloadAhead count 
var preloadCount = currentIndex - startIndex; 
if (preloadCount < 0) 
preloadCount = this.data.length-1-startIndex+currentIndex; 
if (this.preloadAhead >= 0 && preloadCount > this.preloadAhead) { 
// Do this in order to keep checking for relocated start index 
setTimeout(function() { gallery.preloadRecursive(startIndex, currentIndex); }, 500); 
return this; 
} 

var imageData = this.data[currentIndex]; 
if (!imageData) 
return this; 

// If already loaded, continue 
if (imageData.image) 
return this.preloadNext(startIndex, currentIndex); 

// Preload the image 
var image = new Image(); 

image.onload = function() { 
imageData.image = this; 
gallery.preloadNext(startIndex, currentIndex); 
}; 

image.alt = imageData.title; 
image.src = imageData.slideUrl; 

return this; 
}, 

// Called by preloadRecursive in order to preload the next image after the previous has loaded. 
// @param {Integer} startIndex The index of the first image the current preloader started on. 
// @param {Integer} currentIndex The index of the current image to preload. 
preloadNext: function(startIndex, currentIndex) { 
var nextIndex = this.getNextIndex(currentIndex); 
if (nextIndex == startIndex) { 
this.isPreloadComplete = true; 
} else { 
// Use setTimeout to free up thread 
var gallery = this; 
setTimeout(function() { gallery.preloadRecursive(startIndex, nextIndex); }, 100); 
} 

return this; 
}, 

// Safe way to get the next image index relative to the current image. 
// If the current image is the last, returns 0 
getNextIndex: function(index) { 
var nextIndex = index+1; 
if (nextIndex >= this.data.length) 
nextIndex = 0; 
return nextIndex; 
}, 

// Safe way to get the previous image index relative to the current image. 
// If the current image is the first, return the index of the last image in the gallery. 
getPrevIndex: function(index) { 
var prevIndex = index-1; 
if (prevIndex < 0) 
prevIndex = this.data.length-1; 
return prevIndex; 
}, 

// Pauses the slideshow 
pause: function() { 
this.isSlideshowRunning = false; 
if (this.slideshowTimeout) { 
clearTimeout(this.slideshowTimeout); 
this.slideshowTimeout = undefined; 
} 

if (this.$controlsContainer) { 
this.$controlsContainer 
.find('div.ss-controls a').removeClass().addClass('play') 
.attr('title', this.playLinkText) 
.attr('href', '#play') 
.html(this.playLinkText); 
} 

return this; 
}, 

// Plays the slideshow 
play: function() { 
this.isSlideshowRunning = true; 

if (this.$controlsContainer) { 
this.$controlsContainer 
.find('div.ss-controls a').removeClass().addClass('pause') 
.attr('title', this.pauseLinkText) 
.attr('href', '#pause') 
.html(this.pauseLinkText); 
} 

if (!this.slideshowTimeout) { 
var gallery = this; 
this.slideshowTimeout = setTimeout(function() { gallery.ssAdvance(); }, this.delay); 
} 

return this; 
}, 

// Toggles the state of the slideshow (playing/paused) 
toggleSlideshow: function() { 
if (this.isSlideshowRunning) 
this.pause(); 
else 
this.play(); 

return this; 
}, 

// Advances the slideshow to the next image and delegates navigation to the 
// history plugin when history is enabled 
// enableHistory is true 
ssAdvance: function() { 
if (this.isSlideshowRunning) 
this.next(true); 

return this; 
}, 

// Advances the gallery to the next image. 
// @param {Boolean} dontPause Specifies whether to pause the slideshow. 
// @param {Boolean} bypassHistory Specifies whether to delegate navigation to the history plugin when history is enabled. 
next: function(dontPause, bypassHistory) { 
this.gotoIndex(this.getNextIndex(this.currentImage.index), dontPause, bypassHistory); 
return this; 
}, 

// Navigates to the previous image in the gallery. 
// @param {Boolean} dontPause Specifies whether to pause the slideshow. 
// @param {Boolean} bypassHistory Specifies whether to delegate navigation to the history plugin when history is enabled. 
previous: function(dontPause, bypassHistory) { 
this.gotoIndex(this.getPrevIndex(this.currentImage.index), dontPause, bypassHistory); 
return this; 
}, 

// Navigates to the next page in the gallery. 
// @param {Boolean} dontPause Specifies whether to pause the slideshow. 
// @param {Boolean} bypassHistory Specifies whether to delegate navigation to the history plugin when history is enabled. 
nextPage: function(dontPause, bypassHistory) { 
var page = this.getCurrentPage(); 
var lastPage = this.getNumPages() - 1; 
if (page < lastPage) { 
var startIndex = page * this.numThumbs; 
var nextPage = startIndex + this.numThumbs; 
this.gotoIndex(nextPage, dontPause, bypassHistory); 
} 

return this; 
}, 

// Navigates to the previous page in the gallery. 
// @param {Boolean} dontPause Specifies whether to pause the slideshow. 
// @param {Boolean} bypassHistory Specifies whether to delegate navigation to the history plugin when history is enabled. 
previousPage: function(dontPause, bypassHistory) { 
var page = this.getCurrentPage(); 
if (page > 0) { 
var startIndex = page * this.numThumbs; 
var prevPage = startIndex - this.numThumbs; 
this.gotoIndex(prevPage, dontPause, bypassHistory); 
} 

return this; 
}, 

// Navigates to the image at the specified index in the gallery 
// @param {Integer} index The index of the image in the gallery to display. 
// @param {Boolean} dontPause Specifies whether to pause the slideshow. 
// @param {Boolean} bypassHistory Specifies whether to delegate navigation to the history plugin when history is enabled. 
gotoIndex: function(index, dontPause, bypassHistory) { 
if (!dontPause) 
this.pause(); 

if (index < 0) index = 0; 
else if (index >= this.data.length) index = this.data.length-1; 

var imageData = this.data[index]; 

if (!bypassHistory && this.enableHistory) 
$.historyLoad(String(imageData.hash)); // At the moment, historyLoad only accepts string arguments 
else 
this.gotoImage(imageData); 

return this; 
}, 

// This function is garaunteed to be called anytime a gallery slide changes. 
// @param {Object} imageData An object holding the image metadata of the image to navigate to. 
gotoImage: function(imageData) { 
var index = imageData.index; 

if (this.onSlideChange) 
this.onSlideChange(this.currentImage.index, index); 

this.currentImage = imageData; 
this.preloadRelocate(index); 

this.refresh(); 

return this; 
}, 

// Returns the default transition duration value. The value is halved when not 
// performing a synchronized transition. 
// @param {Boolean} isSync Specifies whether the transitions are synchronized. 
getDefaultTransitionDuration: function(isSync) { 
if (isSync) 
return this.defaultTransitionDuration; 
return this.defaultTransitionDuration / 2; 
}, 

// Rebuilds the slideshow image and controls and performs transitions 
refresh: function() { 
var imageData = this.currentImage; 
if (!imageData) 
return this; 

var index = imageData.index; 

// Update Controls 
if (this.$controlsContainer) { 
this.$controlsContainer 
.find('div.nav-controls a.prev').attr('href', '#'+this.data[this.getPrevIndex(index)].hash).end() 
.find('div.nav-controls a.next').attr('href', '#'+this.data[this.getNextIndex(index)].hash); 
} 

var previousSlide = this.$imageContainer.find('span.current').addClass('previous').removeClass('current'); 
var previousCaption = 0; 

if (this.$captionContainer) { 
previousCaption = this.$captionContainer.find('span.current').addClass('previous').removeClass('current'); 
} 

// Perform transitions simultaneously if syncTransitions is true and the next image is already preloaded 
var isSync = this.syncTransitions && imageData.image; 

// Flag we are transitioning 
var isTransitioning = true; 
var gallery = this; 

var transitionOutCallback = function() { 
// Flag that the transition has completed 
isTransitioning = false; 

// Remove the old slide 
previousSlide.remove(); 

// Remove old caption 
if (previousCaption) 
previousCaption.remove(); 

if (!isSync) { 
if (imageData.image && imageData.hash == gallery.data[gallery.currentImage.index].hash) { 
gallery.buildImage(imageData, isSync); 
} else { 
// Show loading container 
if (gallery.$loadingContainer) { 
gallery.$loadingContainer.show(); 
} 
} 
} 
}; 

if (previousSlide.length == 0) { 
// For the first slide, the previous slide will be empty, so we will call the callback immediately 
transitionOutCallback(); 
} else { 
if (this.onTransitionOut) { 
this.onTransitionOut(previousSlide, previousCaption, isSync, transitionOutCallback); 
} else { 
previousSlide.fadeTo(this.getDefaultTransitionDuration(isSync), 0.0, transitionOutCallback); 
if (previousCaption) 
previousCaption.fadeTo(this.getDefaultTransitionDuration(isSync), 0.0); 
} 
} 

// Go ahead and begin transitioning in of next image 
if (isSync) 
this.buildImage(imageData, isSync); 

if (!imageData.image) { 
var image = new Image(); 

// Wire up mainImage onload event 
image.onload = function() { 
imageData.image = this; 

// Only build image if the out transition has completed and we are still on the same image hash 
if (!isTransitioning && imageData.hash == gallery.data[gallery.currentImage.index].hash) { 
gallery.buildImage(imageData, isSync); 
} 
}; 

// set alt and src 
image.alt = imageData.title; 
image.src = imageData.slideUrl; 
} 

// This causes the preloader (if still running) to relocate out from the currentIndex 
this.relocatePreload = true; 

return this.syncThumbs(); 
}, 

// Called by the refresh method after the previous image has been transitioned out or at the same time 
// as the out transition when performing a synchronous transition. 
// @param {Object} imageData An object holding the image metadata of the image to build. 
// @param {Boolean} isSync Specifies whether the transitions are synchronized. 
buildImage: function(imageData, isSync) { 
var gallery = this; 
var nextIndex = this.getNextIndex(imageData.index); 

// Construct new hidden span for the image 
var newSlide = this.$imageContainer 
.append('<span class="image-wrapper current"><a class="advance-link" rel="history" href="#'+this.data[nextIndex].hash+'" title="'+imageData.title+'">&nbsp;</a></span>') 
.find('span.current').css('opacity', '0'); 

newSlide.find('a') 
.append(imageData.image) 
.click(function(e) { 
gallery.clickHandler(e, this); 
}); 

var newCaption = 0; 
if (this.$captionContainer) { 
// Construct new hidden caption for the image 
newCaption = this.$captionContainer 
.append('<span class="image-caption current"></span>') 
.find('span.current').css('opacity', '0') 
.append(imageData.caption); 
} 

// Hide the loading conatiner 
if (this.$loadingContainer) { 
this.$loadingContainer.hide(); 
} 

// Transition in the new image 
if (this.onTransitionIn) { 
this.onTransitionIn(newSlide, newCaption, isSync); 
} else { 
newSlide.fadeTo(this.getDefaultTransitionDuration(isSync), 1.0); 
if (newCaption) 
newCaption.fadeTo(this.getDefaultTransitionDuration(isSync), 1.0); 
} 

if (this.isSlideshowRunning) { 
if (this.slideshowTimeout) 
clearTimeout(this.slideshowTimeout); 

this.slideshowTimeout = setTimeout(function() { gallery.ssAdvance(); }, this.delay); 
} 

return this; 
}, 

// Returns the current page index that should be shown for the currentImage 
getCurrentPage: function() { 
return Math.floor(this.currentImage.index / this.numThumbs); 
}, 

// Applies the selected class to the current image's corresponding thumbnail. 
// Also checks if the current page has changed and updates the displayed page of thumbnails if necessary. 
syncThumbs: function() { 
var page = this.getCurrentPage(); 
if (page != this.displayedPage) 
this.updateThumbs(); 

// Remove existing selected class and add selected class to new thumb 
var $thumbs = this.find('ul.thumbs').children(); 
$thumbs.filter('.selected').removeClass('selected'); 
$thumbs.eq(this.currentImage.index).addClass('selected'); 

return this; 
}, 

// Performs transitions on the thumbnails container and updates the set of 
// thumbnails that are to be displayed and the navigation controls. 
// @param {Delegate} postTransitionOutHandler An optional delegate that is called after 
// the thumbnails container has transitioned out and before the thumbnails are rebuilt. 
updateThumbs: function(postTransitionOutHandler) { 
var gallery = this; 
var transitionOutCallback = function() { 
// Call the Post-transition Out Handler 
if (postTransitionOutHandler) 
postTransitionOutHandler(); 

gallery.rebuildThumbs(); 

// Transition In the thumbsContainer 
if (gallery.onPageTransitionIn) 
gallery.onPageTransitionIn(); 
else 
gallery.show(); 
}; 

// Transition Out the thumbsContainer 
if (this.onPageTransitionOut) { 
this.onPageTransitionOut(transitionOutCallback); 
} else { 
this.hide(); 
transitionOutCallback(); 
} 

return this; 
}, 

// Updates the set of thumbnails that are to be displayed and the navigation controls. 
rebuildThumbs: function() { 
var needsPagination = this.data.length > this.numThumbs; 

// Rebuild top pager 
if (this.enableTopPager) { 
var $topPager = this.find('div.top'); 
if ($topPager.length == 0) 
$topPager = this.prepend('<div class="top pagination"></div>').find('div.top'); 
else 
$topPager.empty(); 

if (needsPagination) 
this.buildPager($topPager); 
} 

// Rebuild bottom pager 
if (this.enableBottomPager) { 
var $bottomPager = this.find('div.bottom'); 
if ($bottomPager.length == 0) 
$bottomPager = this.append('<div class="bottom pagination"></div>').find('div.bottom'); 
else 
$bottomPager.empty(); 

if (needsPagination) 
this.buildPager($bottomPager); 
} 

var page = this.getCurrentPage(); 
var startIndex = page*this.numThumbs; 
var stopIndex = startIndex+this.numThumbs-1; 
if (stopIndex >= this.data.length) 
stopIndex = this.data.length-1; 

// Show/Hide thumbs 
var $thumbsUl = this.find('ul.thumbs'); 
$thumbsUl.find('li').each(function(i) { 
var $li = $(this); 
if (i >= startIndex && i <= stopIndex) { 
$li.show(); 
} else { 
$li.hide(); 
} 
}); 

this.displayedPage = page; 

// Remove the noscript class from the thumbs container ul 
$thumbsUl.removeClass('noscript'); 

return this; 
}, 

// Returns the total number of pages required to display all the thumbnails. 
getNumPages: function() { 
return Math.ceil(this.data.length/this.numThumbs); 
}, 

// Rebuilds the pager control in the specified matched element. 
// @param {jQuery} pager A jQuery element set matching the particular pager to be rebuilt. 
buildPager: function(pager) { 
var gallery = this; 
var numPages = this.getNumPages(); 
var page = this.getCurrentPage(); 
var startIndex = page * this.numThumbs; 
var pagesRemaining = this.maxPagesToShow - 1; 

var pageNum = page - Math.floor((this.maxPagesToShow - 1) / 2) + 1; 
if (pageNum > 0) { 
var remainingPageCount = numPages - pageNum; 
if (remainingPageCount < pagesRemaining) { 
pageNum = pageNum - (pagesRemaining - remainingPageCount); 
} 
} 

if (pageNum < 0) { 
pageNum = 0; 
} 

// Prev Page Link 
if (page > 0) { 
var prevPage = startIndex - this.numThumbs; 
pager.append('<a rel="history" href="#'+this.data[prevPage].hash+'" title="'+this.prevPageLinkText+'">'+this.prevPageLinkText+'</a>'); 
} 

// Create First Page link if needed 
if (pageNum > 0) { 
this.buildPageLink(pager, 0, numPages); 
if (pageNum > 1) 
pager.append('<span class="ellipsis">&hellip;</span>'); 

pagesRemaining--; 
} 

// Page Index Links 
while (pagesRemaining > 0) { 
this.buildPageLink(pager, pageNum, numPages); 
pagesRemaining--; 
pageNum++; 
} 

// Create Last Page link if needed 
if (pageNum < numPages) { 
var lastPageNum = numPages - 1; 
if (pageNum < lastPageNum) 
pager.append('<span class="ellipsis">&hellip;</span>'); 

this.buildPageLink(pager, lastPageNum, numPages); 
} 

// Next Page Link 
var nextPage = startIndex + this.numThumbs; 
if (nextPage < this.data.length) { 
pager.append('<a rel="history" href="#'+this.data[nextPage].hash+'" title="'+this.nextPageLinkText+'">'+this.nextPageLinkText+'</a>'); 
} 

pager.find('a').click(function(e) { 
gallery.clickHandler(e, this); 
}); 

return this; 
}, 

// Builds a single page link within a pager. This function is called by buildPager 
// @param {jQuery} pager A jQuery element set matching the particular pager to be rebuilt. 
// @param {Integer} pageNum The page number of the page link to build. 
// @param {Integer} numPages The total number of pages required to display all thumbnails. 
buildPageLink: function(pager, pageNum, numPages) { 
var pageLabel = pageNum + 1; 
var currentPage = this.getCurrentPage(); 
if (pageNum == currentPage) 
pager.append('<span class="current">'+pageLabel+'</span>'); 
else if (pageNum < numPages) { 
var imageIndex = pageNum*this.numThumbs; 
pager.append('<a rel="history" href="#'+this.data[imageIndex].hash+'" title="'+pageLabel+'">'+pageLabel+'</a>'); 
} 

return this; 
} 
}); 

// Now initialize the gallery 
$.extend(this, defaults, settings); 

// Verify the history plugin is available 
if (this.enableHistory && !$.historyInit) 
this.enableHistory = false; 

// Select containers 
if (this.imageContainerSel) this.$imageContainer = $(this.imageContainerSel); 
if (this.captionContainerSel) this.$captionContainer = $(this.captionContainerSel); 
if (this.loadingContainerSel) this.$loadingContainer = $(this.loadingContainerSel); 

// Initialize the thumbails 
this.initializeThumbs(); 

if (this.maxPagesToShow < 3) 
this.maxPagesToShow = 3; 

this.displayedPage = -1; 
this.currentImage = this.data[0]; 
var gallery = this; 

// Hide the loadingContainer 
if (this.$loadingContainer) 
this.$loadingContainer.hide(); 

// Setup controls 
if (this.controlsContainerSel) { 
this.$controlsContainer = $(this.controlsContainerSel).empty(); 

if (this.renderSSControls) { 
if (this.autoStart) { 
this.$controlsContainer 
.append('<div class="ss-controls"><a href="#pause" class="pause" title="'+this.pauseLinkText+'">'+this.pauseLinkText+'</a></div>'); 
} else { 
this.$controlsContainer 
.append('<div class="ss-controls"><a href="#play" class="play" title="'+this.playLinkText+'">'+this.playLinkText+'</a></div>'); 
} 

this.$controlsContainer.find('div.ss-controls a') 
.click(function(e) { 
gallery.toggleSlideshow(); 
e.preventDefault(); 
return false; 
}); 
} 

if (this.renderNavControls) { 
this.$controlsContainer 
.append('<div class="nav-controls"><a class="prev" rel="history" title="'+this.prevLinkText+'">'+this.prevLinkText+'</a>&nbsp;&nbsp;&nbsp;<a class="next" rel="history" title="'+this.nextLinkText+'">'+this.nextLinkText+'</a></div>') 
.find('div.nav-controls a') 
.click(function(e) { 
gallery.clickHandler(e, this); 
}); 
} 
} 

var initFirstImage = !this.enableHistory || !location.hash; 
if (this.enableHistory && location.hash) { 
var hash = $.galleriffic.normalizeHash(location.hash); 
var imageData = allImages[hash]; 
if (!imageData) 
initFirstImage = true; 
} 

// Setup gallery to show the first image 
if (initFirstImage) 
this.gotoIndex(0, false, true); 

// Setup Keyboard Navigation 
if (this.enableKeyboardNavigation) { 
$(document).keydown(function(e) { 
var key = e.charCode ? e.charCode : e.keyCode ? e.keyCode : 0; 
switch(key) { 
case 32: // space 
gallery.next(); 
e.preventDefault(); 
break; 
case 33: // Page Up 
gallery.previousPage(); 
e.preventDefault(); 
break; 
case 34: // Page Down 
gallery.nextPage(); 
e.preventDefault(); 
break; 
case 35: // End 
gallery.gotoIndex(gallery.data.length-1); 
e.preventDefault(); 
break; 
case 36: // Home 
gallery.gotoIndex(0); 
e.preventDefault(); 
break; 
case 37: // left arrow 
gallery.previous(); 
e.preventDefault(); 
break; 
case 39: // right arrow 
gallery.next(); 
e.preventDefault(); 
break; 
} 
}); 
} 

// Auto start the slideshow 
if (this.autoStart) 
this.play(); 

// Kickoff Image Preloader after 1 second 
setTimeout(function() { gallery.preloadInit(); }, 1000); 

return this; 
}; 
})(jQuery); 


