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); |
---|