| 0 | 1 /** | 
|  | 2  * jQuery Galleriffic plugin | 
|  | 3  * | 
|  | 4  * Copyright (c) 2008 Trent Foley (http://trentacular.com) | 
|  | 5  * Licensed under the MIT License: | 
|  | 6  *   http://www.opensource.org/licenses/mit-license.php | 
|  | 7  * | 
|  | 8  * Much thanks to primary contributer Ponticlaro (http://www.ponticlaro.com) | 
|  | 9  */ | 
|  | 10 ;(function($) { | 
|  | 11 	// Globally keep track of all images by their unique hash.  Each item is an image data object. | 
|  | 12 	var allImages = {}; | 
|  | 13 	var imageCounter = 0; | 
|  | 14 | 
|  | 15 	// Galleriffic static class | 
|  | 16 	$.galleriffic = { | 
|  | 17 		version: '2.0.1', | 
|  | 18 | 
|  | 19 		// Strips invalid characters and any leading # characters | 
|  | 20 		normalizeHash: function(hash) { | 
|  | 21 			return hash.replace(/^.*#/, '').replace(/\?.*$/, ''); | 
|  | 22 		}, | 
|  | 23 | 
|  | 24 		getImage: function(hash) { | 
|  | 25 			if (!hash) | 
|  | 26 				return undefined; | 
|  | 27 | 
|  | 28 			hash = $.galleriffic.normalizeHash(hash); | 
|  | 29 			return allImages[hash]; | 
|  | 30 		}, | 
|  | 31 | 
|  | 32 		// Global function that looks up an image by its hash and displays the image. | 
|  | 33 		// Returns false when an image is not found for the specified hash. | 
|  | 34 		// @param {String} hash This is the unique hash value assigned to an image. | 
|  | 35 		gotoImage: function(hash) { | 
|  | 36 			var imageData = $.galleriffic.getImage(hash); | 
|  | 37 			if (!imageData) | 
|  | 38 				return false; | 
|  | 39 | 
|  | 40 			var gallery = imageData.gallery; | 
|  | 41 			gallery.gotoImage(imageData); | 
|  | 42 | 
|  | 43 			return true; | 
|  | 44 		}, | 
|  | 45 | 
|  | 46 		// Removes an image from its respective gallery by its hash. | 
|  | 47 		// Returns false when an image is not found for the specified hash or the | 
|  | 48 		// specified owner gallery does match the located images gallery. | 
|  | 49 		// @param {String} hash This is the unique hash value assigned to an image. | 
|  | 50 		// @param {Object} ownerGallery (Optional) When supplied, the located images | 
|  | 51 		// gallery is verified to be the same as the specified owning gallery before | 
|  | 52 		// performing the remove operation. | 
|  | 53 		removeImageByHash: function(hash, ownerGallery) { | 
|  | 54 			var imageData = $.galleriffic.getImage(hash); | 
|  | 55 			if (!imageData) | 
|  | 56 				return false; | 
|  | 57 | 
|  | 58 			var gallery = imageData.gallery; | 
|  | 59 			if (ownerGallery && ownerGallery != gallery) | 
|  | 60 				return false; | 
|  | 61 | 
|  | 62 			return gallery.removeImageByIndex(imageData.index); | 
|  | 63 		} | 
|  | 64 	}; | 
|  | 65 | 
|  | 66 	var defaults = { | 
|  | 67 		delay:                     3000, | 
|  | 68 		numThumbs:                 20, | 
|  | 69 		preloadAhead:              40, // Set to -1 to preload all images | 
|  | 70 		enableTopPager:            false, | 
|  | 71 		enableBottomPager:         true, | 
|  | 72 		maxPagesToShow:            7, | 
|  | 73 		imageContainerSel:         '', | 
|  | 74 		captionContainerSel:       '', | 
|  | 75 		controlsContainerSel:      '', | 
|  | 76 		loadingContainerSel:       '', | 
|  | 77 		renderSSControls:          true, | 
|  | 78 		renderNavControls:         true, | 
|  | 79 		playLinkText:              'Play', | 
|  | 80 		pauseLinkText:             'Pause', | 
|  | 81 		prevLinkText:              'Previous', | 
|  | 82 		nextLinkText:              'Next', | 
|  | 83 		nextPageLinkText:          'Next ›', | 
|  | 84 		prevPageLinkText:          '‹ Prev', | 
|  | 85 		enableHistory:             false, | 
|  | 86 		enableKeyboardNavigation:  true, | 
|  | 87 		autoStart:                 false, | 
|  | 88 		syncTransitions:           false, | 
|  | 89 		defaultTransitionDuration: 1000, | 
|  | 90 		onSlideChange:             undefined, // accepts a delegate like such: function(prevIndex, nextIndex) { ... } | 
|  | 91 		onTransitionOut:           undefined, // accepts a delegate like such: function(slide, caption, isSync, callback) { ... } | 
|  | 92 		onTransitionIn:            undefined, // accepts a delegate like such: function(slide, caption, isSync) { ... } | 
|  | 93 		onPageTransitionOut:       undefined, // accepts a delegate like such: function(callback) { ... } | 
|  | 94 		onPageTransitionIn:        undefined, // accepts a delegate like such: function() { ... } | 
|  | 95 		onImageAdded:              undefined, // accepts a delegate like such: function(imageData, $li) { ... } | 
|  | 96 		onImageRemoved:            undefined  // accepts a delegate like such: function(imageData, $li) { ... } | 
|  | 97 	}; | 
|  | 98 | 
|  | 99 	// Primary Galleriffic initialization function that should be called on the thumbnail container. | 
|  | 100 	$.fn.galleriffic = function(settings) { | 
|  | 101 		//  Extend Gallery Object | 
|  | 102 		$.extend(this, { | 
|  | 103 			// Returns the version of the script | 
|  | 104 			version: $.galleriffic.version, | 
|  | 105 | 
|  | 106 			// Current state of the slideshow | 
|  | 107 			isSlideshowRunning: false, | 
|  | 108 			slideshowTimeout: undefined, | 
|  | 109 | 
|  | 110 			// This function is attached to the click event of generated hyperlinks within the gallery | 
|  | 111 			clickHandler: function(e, link) { | 
|  | 112 				this.pause(); | 
|  | 113 | 
|  | 114 				if (!this.enableHistory) { | 
|  | 115 					// The href attribute holds the unique hash for an image | 
|  | 116 					var hash = $.galleriffic.normalizeHash($(link).attr('href')); | 
|  | 117 					$.galleriffic.gotoImage(hash); | 
|  | 118 					e.preventDefault(); | 
|  | 119 				} | 
|  | 120 			}, | 
|  | 121 | 
|  | 122 			// Appends an image to the end of the set of images.  Argument listItem can be either a jQuery DOM element or arbitrary html. | 
|  | 123 			// @param listItem Either a jQuery object or a string of html of the list item that is to be added to the gallery. | 
|  | 124 			appendImage: function(listItem) { | 
|  | 125 				this.addImage(listItem, false, false); | 
|  | 126 				return this; | 
|  | 127 			}, | 
|  | 128 | 
|  | 129 			// Inserts an image into the set of images.  Argument listItem can be either a jQuery DOM element or arbitrary html. | 
|  | 130 			// @param listItem Either a jQuery object or a string of html of the list item that is to be added to the gallery. | 
|  | 131 			// @param {Integer} position The index within the gallery where the item shouold be added. | 
|  | 132 			insertImage: function(listItem, position) { | 
|  | 133 				this.addImage(listItem, false, true, position); | 
|  | 134 				return this; | 
|  | 135 			}, | 
|  | 136 | 
|  | 137 			// Adds an image to the gallery and optionally inserts/appends it to the DOM (thumbExists) | 
|  | 138 			// @param listItem Either a jQuery object or a string of html of the list item that is to be added to the gallery. | 
|  | 139 			// @param {Boolean} thumbExists Specifies whether the thumbnail already exists in the DOM or if it needs to be added. | 
|  | 140 			// @param {Boolean} insert Specifies whether the the image is appended to the end or inserted into the gallery. | 
|  | 141 			// @param {Integer} position The index within the gallery where the item shouold be added. | 
|  | 142 			addImage: function(listItem, thumbExists, insert, position) { | 
|  | 143 				var $li = ( typeof listItem === "string" ) ? $(listItem) : listItem; | 
|  | 144 				var $aThumb = $li.find('a.thumb'); | 
|  | 145 				var slideUrl = $aThumb.attr('href'); | 
|  | 146 				var title = $aThumb.attr('title'); | 
|  | 147 				var $caption = $li.find('.caption').remove(); | 
|  | 148 				var hash = $aThumb.attr('name'); | 
|  | 149 | 
|  | 150 				// Increment the image counter | 
|  | 151 				imageCounter++; | 
|  | 152 | 
|  | 153 				// Autogenerate a hash value if none is present or if it is a duplicate | 
|  | 154 				if (!hash || allImages[''+hash]) { | 
|  | 155 					hash = imageCounter; | 
|  | 156 				} | 
|  | 157 | 
|  | 158 				// Set position to end when not specified | 
|  | 159 				if (!insert) | 
|  | 160 					position = this.data.length; | 
|  | 161 | 
|  | 162 				var imageData = { | 
|  | 163 					title:title, | 
|  | 164 					slideUrl:slideUrl, | 
|  | 165 					caption:$caption, | 
|  | 166 					hash:hash, | 
|  | 167 					gallery:this, | 
|  | 168 					index:position | 
|  | 169 				}; | 
|  | 170 | 
|  | 171 				// Add the imageData to this gallery's array of images | 
|  | 172 				if (insert) { | 
|  | 173 					this.data.splice(position, 0, imageData); | 
|  | 174 | 
|  | 175 					// Reset index value on all imageData objects | 
|  | 176 					this.updateIndices(position); | 
|  | 177 				} | 
|  | 178 				else { | 
|  | 179 					this.data.push(imageData); | 
|  | 180 				} | 
|  | 181 | 
|  | 182 				var gallery = this; | 
|  | 183 | 
|  | 184 				// Add the element to the DOM | 
|  | 185 				if (!thumbExists) { | 
|  | 186 					// Update thumbs passing in addition post transition out handler | 
|  | 187 					this.updateThumbs(function() { | 
|  | 188 						var $thumbsUl = gallery.find('ul.thumbs'); | 
|  | 189 						if (insert) | 
|  | 190 							$thumbsUl.children(':eq('+position+')').before($li); | 
|  | 191 						else | 
|  | 192 							$thumbsUl.append($li); | 
|  | 193 | 
|  | 194 						if (gallery.onImageAdded) | 
|  | 195 							gallery.onImageAdded(imageData, $li); | 
|  | 196 					}); | 
|  | 197 				} | 
|  | 198 | 
|  | 199 				// Register the image globally | 
|  | 200 				allImages[''+hash] = imageData; | 
|  | 201 | 
|  | 202 				// Setup attributes and click handler | 
|  | 203 				$aThumb.attr('rel', 'history') | 
|  | 204 					.attr('href', '#'+hash) | 
|  | 205 					.removeAttr('name') | 
|  | 206 					.click(function(e) { | 
|  | 207 						gallery.clickHandler(e, this); | 
|  | 208 					}); | 
|  | 209 | 
|  | 210 				return this; | 
|  | 211 			}, | 
|  | 212 | 
|  | 213 			// Removes an image from the gallery based on its index. | 
|  | 214 			// Returns false when the index is out of range. | 
|  | 215 			removeImageByIndex: function(index) { | 
|  | 216 				if (index < 0 || index >= this.data.length) | 
|  | 217 					return false; | 
|  | 218 | 
|  | 219 				var imageData = this.data[index]; | 
|  | 220 				if (!imageData) | 
|  | 221 					return false; | 
|  | 222 | 
|  | 223 				this.removeImage(imageData); | 
|  | 224 | 
|  | 225 				return true; | 
|  | 226 			}, | 
|  | 227 | 
|  | 228 			// Convenience method that simply calls the global removeImageByHash method. | 
|  | 229 			removeImageByHash: function(hash) { | 
|  | 230 				return $.galleriffic.removeImageByHash(hash, this); | 
|  | 231 			}, | 
|  | 232 | 
|  | 233 			// Removes an image from the gallery. | 
|  | 234 			removeImage: function(imageData) { | 
|  | 235 				var index = imageData.index; | 
|  | 236 | 
|  | 237 				// Remove the image from the gallery data array | 
|  | 238 				this.data.splice(index, 1); | 
|  | 239 | 
|  | 240 				// Remove the global registration | 
|  | 241 				delete allImages[''+imageData.hash]; | 
|  | 242 | 
|  | 243 				// Remove the image's list item from the DOM | 
|  | 244 				this.updateThumbs(function() { | 
|  | 245 					var $li = gallery.find('ul.thumbs') | 
|  | 246 						.children(':eq('+index+')') | 
|  | 247 						.remove(); | 
|  | 248 | 
|  | 249 					if (gallery.onImageRemoved) | 
|  | 250 						gallery.onImageRemoved(imageData, $li); | 
|  | 251 				}); | 
|  | 252 | 
|  | 253 				// Update each image objects index value | 
|  | 254 				this.updateIndices(index); | 
|  | 255 | 
|  | 256 				return this; | 
|  | 257 			}, | 
|  | 258 | 
|  | 259 			// Updates the index values of the each of the images in the gallery after the specified index | 
|  | 260 			updateIndices: function(startIndex) { | 
|  | 261 				for (i = startIndex; i < this.data.length; i++) { | 
|  | 262 					this.data[i].index = i; | 
|  | 263 				} | 
|  | 264 | 
|  | 265 				return this; | 
|  | 266 			}, | 
|  | 267 | 
|  | 268 			// Scraped the thumbnail container for thumbs and adds each to the gallery | 
|  | 269 			initializeThumbs: function() { | 
|  | 270 				this.data = []; | 
|  | 271 				var gallery = this; | 
|  | 272 | 
|  | 273 				this.find('ul.thumbs > li').each(function(i) { | 
|  | 274 					gallery.addImage($(this), true, false); | 
|  | 275 				}); | 
|  | 276 | 
|  | 277 				return this; | 
|  | 278 			}, | 
|  | 279 | 
|  | 280 			isPreloadComplete: false, | 
|  | 281 | 
|  | 282 			// Initalizes the image preloader | 
|  | 283 			preloadInit: function() { | 
|  | 284 				if (this.preloadAhead == 0) return this; | 
|  | 285 | 
|  | 286 				this.preloadStartIndex = this.currentImage.index; | 
|  | 287 				var nextIndex = this.getNextIndex(this.preloadStartIndex); | 
|  | 288 				return this.preloadRecursive(this.preloadStartIndex, nextIndex); | 
|  | 289 			}, | 
|  | 290 | 
|  | 291 			// Changes the location in the gallery the preloader should work | 
|  | 292 			// @param {Integer} index The index of the image where the preloader should restart at. | 
|  | 293 			preloadRelocate: function(index) { | 
|  | 294 				// By changing this startIndex, the current preload script will restart | 
|  | 295 				this.preloadStartIndex = index; | 
|  | 296 				return this; | 
|  | 297 			}, | 
|  | 298 | 
|  | 299 			// Recursive function that performs the image preloading | 
|  | 300 			// @param {Integer} startIndex The index of the first image the current preloader started on. | 
|  | 301 			// @param {Integer} currentIndex The index of the current image to preload. | 
|  | 302 			preloadRecursive: function(startIndex, currentIndex) { | 
|  | 303 				// Check if startIndex has been relocated | 
|  | 304 				if (startIndex != this.preloadStartIndex) { | 
|  | 305 					var nextIndex = this.getNextIndex(this.preloadStartIndex); | 
|  | 306 					return this.preloadRecursive(this.preloadStartIndex, nextIndex); | 
|  | 307 				} | 
|  | 308 | 
|  | 309 				var gallery = this; | 
|  | 310 | 
|  | 311 				// Now check for preloadAhead count | 
|  | 312 				var preloadCount = currentIndex - startIndex; | 
|  | 313 				if (preloadCount < 0) | 
|  | 314 					preloadCount = this.data.length-1-startIndex+currentIndex; | 
|  | 315 				if (this.preloadAhead >= 0 && preloadCount > this.preloadAhead) { | 
|  | 316 					// Do this in order to keep checking for relocated start index | 
|  | 317 					setTimeout(function() { gallery.preloadRecursive(startIndex, currentIndex); }, 500); | 
|  | 318 					return this; | 
|  | 319 				} | 
|  | 320 | 
|  | 321 				var imageData = this.data[currentIndex]; | 
|  | 322 				if (!imageData) | 
|  | 323 					return this; | 
|  | 324 | 
|  | 325 				// If already loaded, continue | 
|  | 326 				if (imageData.image) | 
|  | 327 					return this.preloadNext(startIndex, currentIndex); | 
|  | 328 | 
|  | 329 				// Preload the image | 
|  | 330 				var image = new Image(); | 
|  | 331 | 
|  | 332 				image.onload = function() { | 
|  | 333 					imageData.image = this; | 
|  | 334 					gallery.preloadNext(startIndex, currentIndex); | 
|  | 335 				}; | 
|  | 336 | 
|  | 337 				image.alt = imageData.title; | 
|  | 338 				image.src = imageData.slideUrl; | 
|  | 339 | 
|  | 340 				return this; | 
|  | 341 			}, | 
|  | 342 | 
|  | 343 			// Called by preloadRecursive in order to preload the next image after the previous has loaded. | 
|  | 344 			// @param {Integer} startIndex The index of the first image the current preloader started on. | 
|  | 345 			// @param {Integer} currentIndex The index of the current image to preload. | 
|  | 346 			preloadNext: function(startIndex, currentIndex) { | 
|  | 347 				var nextIndex = this.getNextIndex(currentIndex); | 
|  | 348 				if (nextIndex == startIndex) { | 
|  | 349 					this.isPreloadComplete = true; | 
|  | 350 				} else { | 
|  | 351 					// Use setTimeout to free up thread | 
|  | 352 					var gallery = this; | 
|  | 353 					setTimeout(function() { gallery.preloadRecursive(startIndex, nextIndex); }, 100); | 
|  | 354 				} | 
|  | 355 | 
|  | 356 				return this; | 
|  | 357 			}, | 
|  | 358 | 
|  | 359 			// Safe way to get the next image index relative to the current image. | 
|  | 360 			// If the current image is the last, returns 0 | 
|  | 361 			getNextIndex: function(index) { | 
|  | 362 				var nextIndex = index+1; | 
|  | 363 				if (nextIndex >= this.data.length) | 
|  | 364 					nextIndex = 0; | 
|  | 365 				return nextIndex; | 
|  | 366 			}, | 
|  | 367 | 
|  | 368 			// Safe way to get the previous image index relative to the current image. | 
|  | 369 			// If the current image is the first, return the index of the last image in the gallery. | 
|  | 370 			getPrevIndex: function(index) { | 
|  | 371 				var prevIndex = index-1; | 
|  | 372 				if (prevIndex < 0) | 
|  | 373 					prevIndex = this.data.length-1; | 
|  | 374 				return prevIndex; | 
|  | 375 			}, | 
|  | 376 | 
|  | 377 			// Pauses the slideshow | 
|  | 378 			pause: function() { | 
|  | 379 				this.isSlideshowRunning = false; | 
|  | 380 				if (this.slideshowTimeout) { | 
|  | 381 					clearTimeout(this.slideshowTimeout); | 
|  | 382 					this.slideshowTimeout = undefined; | 
|  | 383 				} | 
|  | 384 | 
|  | 385 				if (this.$controlsContainer) { | 
|  | 386 					this.$controlsContainer | 
|  | 387 						.find('div.ss-controls a').removeClass().addClass('play') | 
|  | 388 						.attr('title', this.playLinkText) | 
|  | 389 						.attr('href', '#play') | 
|  | 390 						.html(this.playLinkText); | 
|  | 391 				} | 
|  | 392 | 
|  | 393 				return this; | 
|  | 394 			}, | 
|  | 395 | 
|  | 396 			// Plays the slideshow | 
|  | 397 			play: function() { | 
|  | 398 				this.isSlideshowRunning = true; | 
|  | 399 | 
|  | 400 				if (this.$controlsContainer) { | 
|  | 401 					this.$controlsContainer | 
|  | 402 						.find('div.ss-controls a').removeClass().addClass('pause') | 
|  | 403 						.attr('title', this.pauseLinkText) | 
|  | 404 						.attr('href', '#pause') | 
|  | 405 						.html(this.pauseLinkText); | 
|  | 406 				} | 
|  | 407 | 
|  | 408 				if (!this.slideshowTimeout) { | 
|  | 409 					var gallery = this; | 
|  | 410 					this.slideshowTimeout = setTimeout(function() { gallery.ssAdvance(); }, this.delay); | 
|  | 411 				} | 
|  | 412 | 
|  | 413 				return this; | 
|  | 414 			}, | 
|  | 415 | 
|  | 416 			// Toggles the state of the slideshow (playing/paused) | 
|  | 417 			toggleSlideshow: function() { | 
|  | 418 				if (this.isSlideshowRunning) | 
|  | 419 					this.pause(); | 
|  | 420 				else | 
|  | 421 					this.play(); | 
|  | 422 | 
|  | 423 				return this; | 
|  | 424 			}, | 
|  | 425 | 
|  | 426 			// Advances the slideshow to the next image and delegates navigation to the | 
|  | 427 			// history plugin when history is enabled | 
|  | 428 			// enableHistory is true | 
|  | 429 			ssAdvance: function() { | 
|  | 430 				if (this.isSlideshowRunning) | 
|  | 431 					this.next(true); | 
|  | 432 | 
|  | 433 				return this; | 
|  | 434 			}, | 
|  | 435 | 
|  | 436 			// Advances the gallery to the next image. | 
|  | 437 			// @param {Boolean} dontPause Specifies whether to pause the slideshow. | 
|  | 438 			// @param {Boolean} bypassHistory Specifies whether to delegate navigation to the history plugin when history is enabled. | 
|  | 439 			next: function(dontPause, bypassHistory) { | 
|  | 440 				this.gotoIndex(this.getNextIndex(this.currentImage.index), dontPause, bypassHistory); | 
|  | 441 				return this; | 
|  | 442 			}, | 
|  | 443 | 
|  | 444 			// Navigates to the previous image in the gallery. | 
|  | 445 			// @param {Boolean} dontPause Specifies whether to pause the slideshow. | 
|  | 446 			// @param {Boolean} bypassHistory Specifies whether to delegate navigation to the history plugin when history is enabled. | 
|  | 447 			previous: function(dontPause, bypassHistory) { | 
|  | 448 				this.gotoIndex(this.getPrevIndex(this.currentImage.index), dontPause, bypassHistory); | 
|  | 449 				return this; | 
|  | 450 			}, | 
|  | 451 | 
|  | 452 			// Navigates to the next page in the gallery. | 
|  | 453 			// @param {Boolean} dontPause Specifies whether to pause the slideshow. | 
|  | 454 			// @param {Boolean} bypassHistory Specifies whether to delegate navigation to the history plugin when history is enabled. | 
|  | 455 			nextPage: function(dontPause, bypassHistory) { | 
|  | 456 				var page = this.getCurrentPage(); | 
|  | 457 				var lastPage = this.getNumPages() - 1; | 
|  | 458 				if (page < lastPage) { | 
|  | 459 					var startIndex = page * this.numThumbs; | 
|  | 460 					var nextPage = startIndex + this.numThumbs; | 
|  | 461 					this.gotoIndex(nextPage, dontPause, bypassHistory); | 
|  | 462 				} | 
|  | 463 | 
|  | 464 				return this; | 
|  | 465 			}, | 
|  | 466 | 
|  | 467 			// Navigates to the previous page in the gallery. | 
|  | 468 			// @param {Boolean} dontPause Specifies whether to pause the slideshow. | 
|  | 469 			// @param {Boolean} bypassHistory Specifies whether to delegate navigation to the history plugin when history is enabled. | 
|  | 470 			previousPage: function(dontPause, bypassHistory) { | 
|  | 471 				var page = this.getCurrentPage(); | 
|  | 472 				if (page > 0) { | 
|  | 473 					var startIndex = page * this.numThumbs; | 
|  | 474 					var prevPage = startIndex - this.numThumbs; | 
|  | 475 					this.gotoIndex(prevPage, dontPause, bypassHistory); | 
|  | 476 				} | 
|  | 477 | 
|  | 478 				return this; | 
|  | 479 			}, | 
|  | 480 | 
|  | 481 			// Navigates to the image at the specified index in the gallery | 
|  | 482 			// @param {Integer} index The index of the image in the gallery to display. | 
|  | 483 			// @param {Boolean} dontPause Specifies whether to pause the slideshow. | 
|  | 484 			// @param {Boolean} bypassHistory Specifies whether to delegate navigation to the history plugin when history is enabled. | 
|  | 485 			gotoIndex: function(index, dontPause, bypassHistory) { | 
|  | 486 				if (!dontPause) | 
|  | 487 					this.pause(); | 
|  | 488 | 
|  | 489 				if (index < 0) index = 0; | 
|  | 490 				else if (index >= this.data.length) index = this.data.length-1; | 
|  | 491 | 
|  | 492 				var imageData = this.data[index]; | 
|  | 493 | 
|  | 494 				if (!bypassHistory && this.enableHistory) | 
|  | 495 					$.historyLoad(String(imageData.hash));  // At the moment, historyLoad only accepts string arguments | 
|  | 496 				else | 
|  | 497 					this.gotoImage(imageData); | 
|  | 498 | 
|  | 499 				return this; | 
|  | 500 			}, | 
|  | 501 | 
|  | 502 			// This function is garaunteed to be called anytime a gallery slide changes. | 
|  | 503 			// @param {Object} imageData An object holding the image metadata of the image to navigate to. | 
|  | 504 			gotoImage: function(imageData) { | 
|  | 505 				var index = imageData.index; | 
|  | 506 | 
|  | 507 				if (this.onSlideChange) | 
|  | 508 					this.onSlideChange(this.currentImage.index, index); | 
|  | 509 | 
|  | 510 				this.currentImage = imageData; | 
|  | 511 				this.preloadRelocate(index); | 
|  | 512 | 
|  | 513 				this.refresh(); | 
|  | 514 | 
|  | 515 				return this; | 
|  | 516 			}, | 
|  | 517 | 
|  | 518 			// Returns the default transition duration value.  The value is halved when not | 
|  | 519 			// performing a synchronized transition. | 
|  | 520 			// @param {Boolean} isSync Specifies whether the transitions are synchronized. | 
|  | 521 			getDefaultTransitionDuration: function(isSync) { | 
|  | 522 				if (isSync) | 
|  | 523 					return this.defaultTransitionDuration; | 
|  | 524 				return this.defaultTransitionDuration / 2; | 
|  | 525 			}, | 
|  | 526 | 
|  | 527 			// Rebuilds the slideshow image and controls and performs transitions | 
|  | 528 			refresh: function() { | 
|  | 529 				var imageData = this.currentImage; | 
|  | 530 				if (!imageData) | 
|  | 531 					return this; | 
|  | 532 | 
|  | 533 				var index = imageData.index; | 
|  | 534 | 
|  | 535 				// Update Controls | 
|  | 536 				if (this.$controlsContainer) { | 
|  | 537 					this.$controlsContainer | 
|  | 538 						.find('div.nav-controls a.prev').attr('href', '#'+this.data[this.getPrevIndex(index)].hash).end() | 
|  | 539 						.find('div.nav-controls a.next').attr('href', '#'+this.data[this.getNextIndex(index)].hash); | 
|  | 540 				} | 
|  | 541 | 
|  | 542 				var previousSlide = this.$imageContainer.find('span.current').addClass('previous').removeClass('current'); | 
|  | 543 				var previousCaption = 0; | 
|  | 544 | 
|  | 545 				if (this.$captionContainer) { | 
|  | 546 					previousCaption = this.$captionContainer.find('span.current').addClass('previous').removeClass('current'); | 
|  | 547 				} | 
|  | 548 | 
|  | 549 				// Perform transitions simultaneously if syncTransitions is true and the next image is already preloaded | 
|  | 550 				var isSync = this.syncTransitions && imageData.image; | 
|  | 551 | 
|  | 552 				// Flag we are transitioning | 
|  | 553 				var isTransitioning = true; | 
|  | 554 				var gallery = this; | 
|  | 555 | 
|  | 556 				var transitionOutCallback = function() { | 
|  | 557 					// Flag that the transition has completed | 
|  | 558 					isTransitioning = false; | 
|  | 559 | 
|  | 560 					// Remove the old slide | 
|  | 561 					previousSlide.remove(); | 
|  | 562 | 
|  | 563 					// Remove old caption | 
|  | 564 					if (previousCaption) | 
|  | 565 						previousCaption.remove(); | 
|  | 566 | 
|  | 567 					if (!isSync) { | 
|  | 568 						if (imageData.image && imageData.hash == gallery.data[gallery.currentImage.index].hash) { | 
|  | 569 							gallery.buildImage(imageData, isSync); | 
|  | 570 						} else { | 
|  | 571 							// Show loading container | 
|  | 572 							if (gallery.$loadingContainer) { | 
|  | 573 								gallery.$loadingContainer.show(); | 
|  | 574 							} | 
|  | 575 						} | 
|  | 576 					} | 
|  | 577 				}; | 
|  | 578 | 
|  | 579 				if (previousSlide.length == 0) { | 
|  | 580 					// For the first slide, the previous slide will be empty, so we will call the callback immediately | 
|  | 581 					transitionOutCallback(); | 
|  | 582 				} else { | 
|  | 583 					if (this.onTransitionOut) { | 
|  | 584 						this.onTransitionOut(previousSlide, previousCaption, isSync, transitionOutCallback); | 
|  | 585 					} else { | 
|  | 586 						previousSlide.fadeTo(this.getDefaultTransitionDuration(isSync), 0.0, transitionOutCallback); | 
|  | 587 						if (previousCaption) | 
|  | 588 							previousCaption.fadeTo(this.getDefaultTransitionDuration(isSync), 0.0); | 
|  | 589 					} | 
|  | 590 				} | 
|  | 591 | 
|  | 592 				// Go ahead and begin transitioning in of next image | 
|  | 593 				if (isSync) | 
|  | 594 					this.buildImage(imageData, isSync); | 
|  | 595 | 
|  | 596 				if (!imageData.image) { | 
|  | 597 					var image = new Image(); | 
|  | 598 | 
|  | 599 					// Wire up mainImage onload event | 
|  | 600 					image.onload = function() { | 
|  | 601 						imageData.image = this; | 
|  | 602 | 
|  | 603 						// Only build image if the out transition has completed and we are still on the same image hash | 
|  | 604 						if (!isTransitioning && imageData.hash == gallery.data[gallery.currentImage.index].hash) { | 
|  | 605 							gallery.buildImage(imageData, isSync); | 
|  | 606 						} | 
|  | 607 					}; | 
|  | 608 | 
|  | 609 					// set alt and src | 
|  | 610 					image.alt = imageData.title; | 
|  | 611 					image.src = imageData.slideUrl; | 
|  | 612 				} | 
|  | 613 | 
|  | 614 				// This causes the preloader (if still running) to relocate out from the currentIndex | 
|  | 615 				this.relocatePreload = true; | 
|  | 616 | 
|  | 617 				return this.syncThumbs(); | 
|  | 618 			}, | 
|  | 619 | 
|  | 620 			// Called by the refresh method after the previous image has been transitioned out or at the same time | 
|  | 621 			// as the out transition when performing a synchronous transition. | 
|  | 622 			// @param {Object} imageData An object holding the image metadata of the image to build. | 
|  | 623 			// @param {Boolean} isSync Specifies whether the transitions are synchronized. | 
|  | 624 			buildImage: function(imageData, isSync) { | 
|  | 625 				var gallery = this; | 
|  | 626 				var nextIndex = this.getNextIndex(imageData.index); | 
|  | 627 | 
|  | 628 				// Construct new hidden span for the image | 
|  | 629 				var newSlide = this.$imageContainer | 
|  | 630 					.append('<span class="image-wrapper current"><a class="advance-link" rel="history" href="#'+this.data[nextIndex].hash+'" title="'+imageData.title+'"> </a></span>') | 
|  | 631 					.find('span.current').css('opacity', '0'); | 
|  | 632 | 
|  | 633 				newSlide.find('a') | 
|  | 634 					.append(imageData.image) | 
|  | 635 					.click(function(e) { | 
|  | 636 						gallery.clickHandler(e, this); | 
|  | 637 					}); | 
|  | 638 | 
|  | 639 				var newCaption = 0; | 
|  | 640 				if (this.$captionContainer) { | 
|  | 641 					// Construct new hidden caption for the image | 
|  | 642 					newCaption = this.$captionContainer | 
|  | 643 						.append('<span class="image-caption current"></span>') | 
|  | 644 						.find('span.current').css('opacity', '0') | 
|  | 645 						.append(imageData.caption); | 
|  | 646 				} | 
|  | 647 | 
|  | 648 				// Hide the loading conatiner | 
|  | 649 				if (this.$loadingContainer) { | 
|  | 650 					this.$loadingContainer.hide(); | 
|  | 651 				} | 
|  | 652 | 
|  | 653 				// Transition in the new image | 
|  | 654 				if (this.onTransitionIn) { | 
|  | 655 					this.onTransitionIn(newSlide, newCaption, isSync); | 
|  | 656 				} else { | 
|  | 657 					newSlide.fadeTo(this.getDefaultTransitionDuration(isSync), 1.0); | 
|  | 658 					if (newCaption) | 
|  | 659 						newCaption.fadeTo(this.getDefaultTransitionDuration(isSync), 1.0); | 
|  | 660 				} | 
|  | 661 | 
|  | 662 				if (this.isSlideshowRunning) { | 
|  | 663 					if (this.slideshowTimeout) | 
|  | 664 						clearTimeout(this.slideshowTimeout); | 
|  | 665 | 
|  | 666 					this.slideshowTimeout = setTimeout(function() { gallery.ssAdvance(); }, this.delay); | 
|  | 667 				} | 
|  | 668 | 
|  | 669 				return this; | 
|  | 670 			}, | 
|  | 671 | 
|  | 672 			// Returns the current page index that should be shown for the currentImage | 
|  | 673 			getCurrentPage: function() { | 
|  | 674 				return Math.floor(this.currentImage.index / this.numThumbs); | 
|  | 675 			}, | 
|  | 676 | 
|  | 677 			// Applies the selected class to the current image's corresponding thumbnail. | 
|  | 678 			// Also checks if the current page has changed and updates the displayed page of thumbnails if necessary. | 
|  | 679 			syncThumbs: function() { | 
|  | 680 				var page = this.getCurrentPage(); | 
|  | 681 				if (page != this.displayedPage) | 
|  | 682 					this.updateThumbs(); | 
|  | 683 | 
|  | 684 				// Remove existing selected class and add selected class to new thumb | 
|  | 685 				var $thumbs = this.find('ul.thumbs').children(); | 
|  | 686 				$thumbs.filter('.selected').removeClass('selected'); | 
|  | 687 				$thumbs.eq(this.currentImage.index).addClass('selected'); | 
|  | 688 | 
|  | 689 				return this; | 
|  | 690 			}, | 
|  | 691 | 
|  | 692 			// Performs transitions on the thumbnails container and updates the set of | 
|  | 693 			// thumbnails that are to be displayed and the navigation controls. | 
|  | 694 			// @param {Delegate} postTransitionOutHandler An optional delegate that is called after | 
|  | 695 			// the thumbnails container has transitioned out and before the thumbnails are rebuilt. | 
|  | 696 			updateThumbs: function(postTransitionOutHandler) { | 
|  | 697 				var gallery = this; | 
|  | 698 				var transitionOutCallback = function() { | 
|  | 699 					// Call the Post-transition Out Handler | 
|  | 700 					if (postTransitionOutHandler) | 
|  | 701 						postTransitionOutHandler(); | 
|  | 702 | 
|  | 703 					gallery.rebuildThumbs(); | 
|  | 704 | 
|  | 705 					// Transition In the thumbsContainer | 
|  | 706 					if (gallery.onPageTransitionIn) | 
|  | 707 						gallery.onPageTransitionIn(); | 
|  | 708 					else | 
|  | 709 						gallery.show(); | 
|  | 710 				}; | 
|  | 711 | 
|  | 712 				// Transition Out the thumbsContainer | 
|  | 713 				if (this.onPageTransitionOut) { | 
|  | 714 					this.onPageTransitionOut(transitionOutCallback); | 
|  | 715 				} else { | 
|  | 716 					this.hide(); | 
|  | 717 					transitionOutCallback(); | 
|  | 718 				} | 
|  | 719 | 
|  | 720 				return this; | 
|  | 721 			}, | 
|  | 722 | 
|  | 723 			// Updates the set of thumbnails that are to be displayed and the navigation controls. | 
|  | 724 			rebuildThumbs: function() { | 
|  | 725 				var needsPagination = this.data.length > this.numThumbs; | 
|  | 726 | 
|  | 727 				// Rebuild top pager | 
|  | 728 				if (this.enableTopPager) { | 
|  | 729 					var $topPager = this.find('div.top'); | 
|  | 730 					if ($topPager.length == 0) | 
|  | 731 						$topPager = this.prepend('<div class="top pagination"></div>').find('div.top'); | 
|  | 732 					else | 
|  | 733 						$topPager.empty(); | 
|  | 734 | 
|  | 735 					if (needsPagination) | 
|  | 736 						this.buildPager($topPager); | 
|  | 737 				} | 
|  | 738 | 
|  | 739 				// Rebuild bottom pager | 
|  | 740 				if (this.enableBottomPager) { | 
|  | 741 					var $bottomPager = this.find('div.bottom'); | 
|  | 742 					if ($bottomPager.length == 0) | 
|  | 743 						$bottomPager = this.append('<div class="bottom pagination"></div>').find('div.bottom'); | 
|  | 744 					else | 
|  | 745 						$bottomPager.empty(); | 
|  | 746 | 
|  | 747 					if (needsPagination) | 
|  | 748 						this.buildPager($bottomPager); | 
|  | 749 				} | 
|  | 750 | 
|  | 751 				var page = this.getCurrentPage(); | 
|  | 752 				var startIndex = page*this.numThumbs; | 
|  | 753 				var stopIndex = startIndex+this.numThumbs-1; | 
|  | 754 				if (stopIndex >= this.data.length) | 
|  | 755 					stopIndex = this.data.length-1; | 
|  | 756 | 
|  | 757 				// Show/Hide thumbs | 
|  | 758 				var $thumbsUl = this.find('ul.thumbs'); | 
|  | 759 				$thumbsUl.find('li').each(function(i) { | 
|  | 760 					var $li = $(this); | 
|  | 761 					if (i >= startIndex && i <= stopIndex) { | 
|  | 762 						$li.show(); | 
|  | 763 					} else { | 
|  | 764 						$li.hide(); | 
|  | 765 					} | 
|  | 766 				}); | 
|  | 767 | 
|  | 768 				this.displayedPage = page; | 
|  | 769 | 
|  | 770 				// Remove the noscript class from the thumbs container ul | 
|  | 771 				$thumbsUl.removeClass('noscript'); | 
|  | 772 | 
|  | 773 				return this; | 
|  | 774 			}, | 
|  | 775 | 
|  | 776 			// Returns the total number of pages required to display all the thumbnails. | 
|  | 777 			getNumPages: function() { | 
|  | 778 				return Math.ceil(this.data.length/this.numThumbs); | 
|  | 779 			}, | 
|  | 780 | 
|  | 781 			// Rebuilds the pager control in the specified matched element. | 
|  | 782 			// @param {jQuery} pager A jQuery element set matching the particular pager to be rebuilt. | 
|  | 783 			buildPager: function(pager) { | 
|  | 784 				var gallery = this; | 
|  | 785 				var numPages = this.getNumPages(); | 
|  | 786 				var page = this.getCurrentPage(); | 
|  | 787 				var startIndex = page * this.numThumbs; | 
|  | 788 				var pagesRemaining = this.maxPagesToShow - 1; | 
|  | 789 | 
|  | 790 				var pageNum = page - Math.floor((this.maxPagesToShow - 1) / 2) + 1; | 
|  | 791 				if (pageNum > 0) { | 
|  | 792 					var remainingPageCount = numPages - pageNum; | 
|  | 793 					if (remainingPageCount < pagesRemaining) { | 
|  | 794 						pageNum = pageNum - (pagesRemaining - remainingPageCount); | 
|  | 795 					} | 
|  | 796 				} | 
|  | 797 | 
|  | 798 				if (pageNum < 0) { | 
|  | 799 					pageNum = 0; | 
|  | 800 				} | 
|  | 801 | 
|  | 802 				// Prev Page Link | 
|  | 803 				if (page > 0) { | 
|  | 804 					var prevPage = startIndex - this.numThumbs; | 
|  | 805 					pager.append('<a rel="history" href="#'+this.data[prevPage].hash+'" title="'+this.prevPageLinkText+'">'+this.prevPageLinkText+'</a>'); | 
|  | 806 				} | 
|  | 807 | 
|  | 808 				// Create First Page link if needed | 
|  | 809 				if (pageNum > 0) { | 
|  | 810 					this.buildPageLink(pager, 0, numPages); | 
|  | 811 					if (pageNum > 1) | 
|  | 812 						pager.append('<span class="ellipsis">…</span>'); | 
|  | 813 | 
|  | 814 					pagesRemaining--; | 
|  | 815 				} | 
|  | 816 | 
|  | 817 				// Page Index Links | 
|  | 818 				while (pagesRemaining > 0) { | 
|  | 819 					this.buildPageLink(pager, pageNum, numPages); | 
|  | 820 					pagesRemaining--; | 
|  | 821 					pageNum++; | 
|  | 822 				} | 
|  | 823 | 
|  | 824 				// Create Last Page link if needed | 
|  | 825 				if (pageNum < numPages) { | 
|  | 826 					var lastPageNum = numPages - 1; | 
|  | 827 					if (pageNum < lastPageNum) | 
|  | 828 						pager.append('<span class="ellipsis">…</span>'); | 
|  | 829 | 
|  | 830 					this.buildPageLink(pager, lastPageNum, numPages); | 
|  | 831 				} | 
|  | 832 | 
|  | 833 				// Next Page Link | 
|  | 834 				var nextPage = startIndex + this.numThumbs; | 
|  | 835 				if (nextPage < this.data.length) { | 
|  | 836 					pager.append('<a rel="history" href="#'+this.data[nextPage].hash+'" title="'+this.nextPageLinkText+'">'+this.nextPageLinkText+'</a>'); | 
|  | 837 				} | 
|  | 838 | 
|  | 839 				pager.find('a').click(function(e) { | 
|  | 840 					gallery.clickHandler(e, this); | 
|  | 841 				}); | 
|  | 842 | 
|  | 843 				return this; | 
|  | 844 			}, | 
|  | 845 | 
|  | 846 			// Builds a single page link within a pager.  This function is called by buildPager | 
|  | 847 			// @param {jQuery} pager A jQuery element set matching the particular pager to be rebuilt. | 
|  | 848 			// @param {Integer} pageNum The page number of the page link to build. | 
|  | 849 			// @param {Integer} numPages The total number of pages required to display all thumbnails. | 
|  | 850 			buildPageLink: function(pager, pageNum, numPages) { | 
|  | 851 				var pageLabel = pageNum + 1; | 
|  | 852 				var currentPage = this.getCurrentPage(); | 
|  | 853 				if (pageNum == currentPage) | 
|  | 854 					pager.append('<span class="current">'+pageLabel+'</span>'); | 
|  | 855 				else if (pageNum < numPages) { | 
|  | 856 					var imageIndex = pageNum*this.numThumbs; | 
|  | 857 					pager.append('<a rel="history" href="#'+this.data[imageIndex].hash+'" title="'+pageLabel+'">'+pageLabel+'</a>'); | 
|  | 858 				} | 
|  | 859 | 
|  | 860 				return this; | 
|  | 861 			} | 
|  | 862 		}); | 
|  | 863 | 
|  | 864 		// Now initialize the gallery | 
|  | 865 		$.extend(this, defaults, settings); | 
|  | 866 | 
|  | 867 		// Verify the history plugin is available | 
|  | 868 		if (this.enableHistory && !$.historyInit) | 
|  | 869 			this.enableHistory = false; | 
|  | 870 | 
|  | 871 		// Select containers | 
|  | 872 		if (this.imageContainerSel) this.$imageContainer = $(this.imageContainerSel); | 
|  | 873 		if (this.captionContainerSel) this.$captionContainer = $(this.captionContainerSel); | 
|  | 874 		if (this.loadingContainerSel) this.$loadingContainer = $(this.loadingContainerSel); | 
|  | 875 | 
|  | 876 		// Initialize the thumbails | 
|  | 877 		this.initializeThumbs(); | 
|  | 878 | 
|  | 879 		if (this.maxPagesToShow < 3) | 
|  | 880 			this.maxPagesToShow = 3; | 
|  | 881 | 
|  | 882 		this.displayedPage = -1; | 
|  | 883 		this.currentImage = this.data[0]; | 
|  | 884 		var gallery = this; | 
|  | 885 | 
|  | 886 		// Hide the loadingContainer | 
|  | 887 		if (this.$loadingContainer) | 
|  | 888 			this.$loadingContainer.hide(); | 
|  | 889 | 
|  | 890 		// Setup controls | 
|  | 891 		if (this.controlsContainerSel) { | 
|  | 892 			this.$controlsContainer = $(this.controlsContainerSel).empty(); | 
|  | 893 | 
|  | 894 			if (this.renderSSControls) { | 
|  | 895 				if (this.autoStart) { | 
|  | 896 					this.$controlsContainer | 
|  | 897 						.append('<div class="ss-controls"><a href="#pause" class="pause" title="'+this.pauseLinkText+'">'+this.pauseLinkText+'</a></div>'); | 
|  | 898 				} else { | 
|  | 899 					this.$controlsContainer | 
|  | 900 						.append('<div class="ss-controls"><a href="#play" class="play" title="'+this.playLinkText+'">'+this.playLinkText+'</a></div>'); | 
|  | 901 				} | 
|  | 902 | 
|  | 903 				this.$controlsContainer.find('div.ss-controls a') | 
|  | 904 					.click(function(e) { | 
|  | 905 						gallery.toggleSlideshow(); | 
|  | 906 						e.preventDefault(); | 
|  | 907 						return false; | 
|  | 908 					}); | 
|  | 909 			} | 
|  | 910 | 
|  | 911 			if (this.renderNavControls) { | 
|  | 912 				this.$controlsContainer | 
|  | 913 					.append('<div class="nav-controls"><a class="prev" rel="history" title="'+this.prevLinkText+'">'+this.prevLinkText+'</a><a class="next" rel="history" title="'+this.nextLinkText+'">'+this.nextLinkText+'</a></div>') | 
|  | 914 					.find('div.nav-controls a') | 
|  | 915 					.click(function(e) { | 
|  | 916 						gallery.clickHandler(e, this); | 
|  | 917 					}); | 
|  | 918 			} | 
|  | 919 		} | 
|  | 920 | 
|  | 921 		var initFirstImage = !this.enableHistory || !location.hash; | 
|  | 922 		if (this.enableHistory && location.hash) { | 
|  | 923 			var hash = $.galleriffic.normalizeHash(location.hash); | 
|  | 924 			var imageData = allImages[hash]; | 
|  | 925 			if (!imageData) | 
|  | 926 				initFirstImage = true; | 
|  | 927 		} | 
|  | 928 | 
|  | 929 		// Setup gallery to show the first image | 
|  | 930 		if (initFirstImage) | 
|  | 931 			this.gotoIndex(0, false, true); | 
|  | 932 | 
|  | 933 		// Setup Keyboard Navigation | 
|  | 934 		if (this.enableKeyboardNavigation) { | 
|  | 935 			$(document).keydown(function(e) { | 
|  | 936 				var key = e.charCode ? e.charCode : e.keyCode ? e.keyCode : 0; | 
|  | 937 				switch(key) { | 
|  | 938 					case 32: // space | 
|  | 939 						gallery.next(); | 
|  | 940 						e.preventDefault(); | 
|  | 941 						break; | 
|  | 942 					case 33: // Page Up | 
|  | 943 						gallery.previousPage(); | 
|  | 944 						e.preventDefault(); | 
|  | 945 						break; | 
|  | 946 					case 34: // Page Down | 
|  | 947 						gallery.nextPage(); | 
|  | 948 						e.preventDefault(); | 
|  | 949 						break; | 
|  | 950 					case 35: // End | 
|  | 951 						gallery.gotoIndex(gallery.data.length-1); | 
|  | 952 						e.preventDefault(); | 
|  | 953 						break; | 
|  | 954 					case 36: // Home | 
|  | 955 						gallery.gotoIndex(0); | 
|  | 956 						e.preventDefault(); | 
|  | 957 						break; | 
|  | 958 					case 37: // left arrow | 
|  | 959 						gallery.previous(); | 
|  | 960 						e.preventDefault(); | 
|  | 961 						break; | 
|  | 962 					case 39: // right arrow | 
|  | 963 						gallery.next(); | 
|  | 964 						e.preventDefault(); | 
|  | 965 						break; | 
|  | 966 				} | 
|  | 967 			}); | 
|  | 968 		} | 
|  | 969 | 
|  | 970 		// Auto start the slideshow | 
|  | 971 		if (this.autoStart) | 
|  | 972 			this.play(); | 
|  | 973 | 
|  | 974 		// Kickoff Image Preloader after 1 second | 
|  | 975 		setTimeout(function() { gallery.preloadInit(); }, 1000); | 
|  | 976 | 
|  | 977 		return this; | 
|  | 978 	}; | 
|  | 979 })(jQuery); |