jquery.galleriffic.js 31 KB

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