PageSourceSearch

https://ritagiang.com/picJS/js/jquery.galleriffic.js

js ritagiang.com collected 2026-10-02 12:55:36 UTC 32,119 bytes, 979 lines download raw bytes

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+'">&nbsp;</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">&hellip;</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">&hellip;</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);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.