You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

912 lines
34 KiB

  1. /**
  2. * @projectDescription JsFlickrGallery - Simple JavaScript Flickr gallery,
  3. * http://petejank.github.io/js-flickr-gallery/
  4. *
  5. * @version 1.24
  6. * @author Peter Jankowski http://likeadev.com
  7. * @license MIT license.
  8. */
  9. ;(function ( $, window, document, undefined ) {
  10. 'use strict';
  11. // "Constants"
  12. var FORMAT = 'json',
  13. SEARCH_API_METHOD = 'flickr.photos.search',
  14. SETS_API_METHOD = 'flickr.photosets.getPhotos',
  15. API_KEY = '62525ee8c8d131d708d33d61f29434b6',
  16. // Tag attributes
  17. DATA_TAGS_ATTR = 'data-tags',
  18. DATA_USER_ID_ATTR = 'data-user-id',
  19. DATA_SET_ID_ATTR = 'data-set-id',
  20. DATA_PER_PAGE_ATTR = 'data-per-page',
  21. DATA_GALLERY_ID_ATTR = 'data-gallery-id',
  22. DATA_TOGGLE_ATTR = 'jsfg',
  23. // Minor stuff
  24. RESPONSIVE_WIDTH = 767,
  25. FLICKR_REQUEST_TIMEOUT = 10000,
  26. // Generated modal window stuff
  27. GEN_HEADER_CONTAINER_CLASS = 'modal-header',
  28. GEN_TITLE_TAG = 'h3',
  29. GEN_BODY_CONTAINER_CLASS = 'modal-body',
  30. GEN_IMAGE_CONTAINER_CLASS = 'modal-image',
  31. GEN_FOOTER_CONTAINER_CLASS = 'modal-footer'
  32. ;
  33. // Plugin name declaration
  34. var pluginName = 'jsFlickrGallery',
  35. defaults = {
  36. 'fetchImages' : true,
  37. 'animation' : 'fade',
  38. 'animationSpeed' : 250,
  39. 'preload' : { // false to disable
  40. 'range' : 2
  41. },
  42. 'structure' : {
  43. 'ulClass' : '.thumbnails',
  44. 'liClass' : '.span1',
  45. 'aClass' : '.thumbnail'
  46. },
  47. 'modal' : { // false to disable
  48. 'generate' : true,
  49. 'id' : 'jsfg-modal',
  50. 'title' : '.' + GEN_HEADER_CONTAINER_CLASS + ' ' + GEN_TITLE_TAG,
  51. 'imageContainerClass' : '.' + GEN_IMAGE_CONTAINER_CLASS,
  52. 'onContainerNext' : true,
  53. 'imageFadeTime' : 250,
  54. 'prevClass' : '.btn.modal-prev',
  55. 'nextClass' : '.btn.modal-next',
  56. 'prevText' : 'Previous image',
  57. 'nextText' : 'Next image',
  58. 'offsetWidth' : 100,
  59. 'offsetHeight' : 200
  60. },
  61. 'pagination' : { // false to disable
  62. 'generate' : true,
  63. 'containerClass' : '.pagination',
  64. 'prevClass' : '.btn.pagination-prev',
  65. 'nextClass' : '.btn.pagination-next',
  66. 'prevText' : 'Previous page',
  67. 'nextText' : 'Next page'
  68. },
  69. 'loader' : { // false to disable
  70. 'animation' : true,
  71. 'loaderClass' : '.jsfg-loader',
  72. 'text' : 'Loading',
  73. 'interval' : 200,
  74. 'mark' : '.',
  75. 'markClass': '.animation-marks',
  76. 'maxMarks' : 3
  77. },
  78. 'url' : {
  79. 'per_page' : 30,
  80. 'tag_mode' : 'all'
  81. },
  82. 'error' : {
  83. 'text' : 'No photos found',
  84. 'tagClass' : 'error'
  85. },
  86. 'imageSizes' : {
  87. 'small' : 's', // small (up to 75 x 75)
  88. 'medium_100' : 't', // medium (up to 100 x 75)
  89. 'medium' : 'q', // medium (up to 150 x 150)
  90. 'medium_640' : 'z', // medium (up to 620 x 640)
  91. 'large' : 'b', // large (up to 1024 in any of two dimensions)
  92. 'original' : 'o' // original image size
  93. },
  94. 'apiUrl' : 'https://api.flickr.com/services/rest/?jsoncallback=?',
  95. 'setDefaultSize' : function() {
  96. this.thumbnailSize = this.imageSizes.medium;
  97. this.imageSize = this.imageSizes.large;
  98. }
  99. };
  100. /**
  101. * Plugin constructor
  102. *
  103. * @param Object element
  104. * @param Object options
  105. * @return Plugin
  106. * @constructor
  107. */
  108. function Plugin( element, options ) {
  109. this.element = element;
  110. // Select this DOM element with jQuery - for future use
  111. this.$element = $( element );
  112. // Merge passed options with defaults
  113. this.options = $.extend( true, {}, defaults, options );
  114. // Set contexts for pagination and modal
  115. this.paginationContext = this.options.pagination && this.options.pagination.generate ? this.element : document;
  116. if ( !this.options.thumbnailSize && !this.options.imageSize ) {
  117. this.options.setDefaultSize();
  118. }
  119. // Assign gallery instance id
  120. this.galleryId = this.element.id || Math.random().toString( 36 );
  121. // Starting page value
  122. this.page = 1;
  123. this.init();
  124. }
  125. // Define Plugin init method
  126. Plugin.prototype = {
  127. /**
  128. * Creates gallery structure for the node
  129. *
  130. * @return void
  131. * @method
  132. * @memberOf Plugin
  133. */
  134. init : function() {
  135. if ( this.options.fetchImages ) {
  136. // Add gallery loader if available
  137. if ( this.options.loader ) {
  138. this.loaderInterval = this._createLoader(this.element);
  139. }
  140. this.createGallery(); // async, rest of the init code will be shot before this
  141. } else {
  142. // Assign anchors selector to local instance
  143. this.anchors = this._getAnchors();
  144. }
  145. if ( this.options.pagination && this.options.fetchImages ) {
  146. if ( this.options.pagination.generate ) {
  147. this._createPagination();
  148. }
  149. this._bindPaginationEvents();
  150. }
  151. if ( this.options.modal ) {
  152. if ( this.options.modal.generate ) {
  153. this._createModal();
  154. }
  155. this._bindModalEvents();
  156. }
  157. },
  158. /**
  159. * Get JSON image data using JSONP from flickr and create an gallery instance.
  160. * Does NOT clear the container content but appends to it
  161. *
  162. * @param Integer page Starting pagination page
  163. * @return Plugin
  164. * @method
  165. * @memberOf Plugin
  166. */
  167. createGallery : function( page ) {
  168. // Assign constants to url options
  169. this.options.url.format = FORMAT;
  170. this.options.url.api_key = API_KEY;
  171. this.options.url.photoset_id = this.$element.attr( DATA_SET_ID_ATTR ) || this.options.url.photoset_id;
  172. if ( this.options.url.photoset_id ) {
  173. // Fetch data for certain photo set
  174. this.options.url.method = SETS_API_METHOD;
  175. delete this.options.url.tag_mode;
  176. } else {
  177. // Fetch photos by tags/user_id criteria
  178. this.options.url.method = SEARCH_API_METHOD;
  179. delete this.options.url.photoset_id;
  180. // Tags are mandatory when fetching photos from Flickr
  181. this.options.url.tags = this.$element.attr( DATA_TAGS_ATTR ) || this.options.url.tags;
  182. // Check if only certain user's photos should be fetched
  183. this.options.url.user_id = this.$element.attr( DATA_USER_ID_ATTR ) || this.options.url.user_id;
  184. if ( !this.options.url.user_id ) {
  185. delete this.options.url.user_id;
  186. }
  187. }
  188. // Set displayed page
  189. this.options.url.page = this.page = page || this.page;
  190. // How many photos should be fetched?
  191. this.options.url.per_page = this.$element.attr( DATA_PER_PAGE_ATTR ) || this.options.url.per_page;
  192. // Get images using ajax and display them on success
  193. this._getPhotos();
  194. return this;
  195. },
  196. /**
  197. * Hide gallery items and remove them
  198. *
  199. * @param Integer page
  200. * @return Plugin
  201. * @method
  202. * @memberOf Plugin
  203. */
  204. clearGallery : function( page ) {
  205. var $galleryEl = $( this.options.structure.ulClass, this.element ),
  206. self = this
  207. ;
  208. switch( this.options.animation )
  209. {
  210. case 'fade':
  211. $galleryEl.fadeOut( this.options.animationSpeed, _replaceWithLoader );
  212. break;
  213. case 'show':
  214. $galleryEl.hide( this.options.animationSpeed, _replaceWithLoader );
  215. break;
  216. case false:
  217. $galleryEl.hide( 0 , _replaceWithLoader );
  218. }
  219. /**
  220. * Replace gallery content with loader
  221. *
  222. * @return void
  223. * @internal
  224. * @memberOf Plugin
  225. */
  226. function _replaceWithLoader() {
  227. if ( self.options.loader ) {
  228. self.loaderInterval = self._createLoader( self.element );
  229. }
  230. // Init creation of new gallery if page is present
  231. if ( page ) {
  232. self.createGallery(page);
  233. }
  234. $galleryEl.remove();
  235. }
  236. return this;
  237. },
  238. /**
  239. * Check if current page is the last page of the gallery
  240. *
  241. * @return boolean
  242. * @method
  243. * @memberOf Plugin
  244. */
  245. isLastPage : function() {
  246. return ( !this.anchors || this.anchors.length < this.options.url.per_page ) ? true : false;
  247. },
  248. /**
  249. * Display next page of the gallery
  250. *
  251. * @return Plugin | boolean False when current page is last one
  252. * @method
  253. * @memberOf Plugin
  254. */
  255. nextPage : function() {
  256. if ( !this.isLastPage() ) {
  257. return this.clearGallery( this.page + 1 );
  258. } else {
  259. return false;
  260. }
  261. },
  262. /**
  263. * Display previous page of the gallery
  264. *
  265. * @return Plugin | boolean False when page < 1
  266. * @method
  267. * @memberOf Plugin
  268. */
  269. prevPage : function() {
  270. if ( this.page > 1 ) {
  271. return this.clearGallery( this.page - 1 );
  272. } else {
  273. return false;
  274. }
  275. },
  276. /**
  277. * Display previous gallery image in modal window
  278. *
  279. * @return Plugin
  280. * @method
  281. * @memberOf Plugin
  282. */
  283. prevImage : function() {
  284. this.index -= 1;
  285. if (this.index < 0) {
  286. this.index = this.anchors.length - 1;
  287. }
  288. return this._loadImage( false );
  289. },
  290. /**
  291. * Diplay next gallery image in modal window
  292. *
  293. * @return Plugin
  294. * @method
  295. * @memberOf Plugin
  296. */
  297. nextImage : function() {
  298. this.index += 1;
  299. if ( this.index > this.anchors.length - 1 ) {
  300. this.index = 0;
  301. }
  302. return this._loadImage( false );
  303. },
  304. /**
  305. * Fetch photos from Flickr
  306. *
  307. * @return Plugin
  308. * @private
  309. * @memberOf Plugin
  310. */
  311. _getPhotos : function( ) {
  312. var self = this;
  313. $.ajax({
  314. type: 'GET',
  315. url: self.options.apiUrl,
  316. data: self.options.url,
  317. dataType: 'jsonp',
  318. timeout: FLICKR_REQUEST_TIMEOUT
  319. }).done(function( data ) {
  320. // Once data is returned, create gallery instance
  321. self._renderGalleryContent( data.photos || data.photoset );
  322. }).always(function( data, textStatus ) {
  323. // Try again
  324. if (textStatus === 'timeout') {
  325. self._getPhotos();
  326. }
  327. });
  328. },
  329. /**
  330. * Create and render gallery instance. Not for public consumption
  331. *
  332. * @param Object photos
  333. * @return Plugin
  334. * @private
  335. * @method
  336. * @memberOf Plugin
  337. */
  338. _renderGalleryContent : function( photos ) {
  339. var self = this,
  340. $images,
  341. $ul,
  342. listItems = '',
  343. loadedImg = 0,
  344. link,
  345. title,
  346. error,
  347. liClassNoDots = this._replaceDots( self.options.structure.liClass ),
  348. aClassNoDots = this._replaceDots( self.options.structure.aClass )
  349. ;
  350. // Check if there's more than one gallery item returned
  351. if ( photos.photo.length > 0 ) {
  352. // Gallery is hidden by default for image loading purposes
  353. $ul = $( '<ul ' + 'class="' + this._replaceDots( self.options.structure.ulClass ) +
  354. '" style="display: none">' );
  355. for ( var i = 0; i < photos.photo.length; i++ ) {
  356. link = 'https://farm' + photos.photo[ i ].farm +
  357. '.static.flickr.com/' + photos.photo[ i ].server + '/' + photos.photo[ i ].id + '_' +
  358. photos.photo[i].secret + '_';
  359. title = this._htmlEscape( photos.photo[ i ].title );
  360. listItems +=
  361. '<li ' + 'class="' + liClassNoDots + '">' +
  362. '<a rel="colorbox" href="' + link + self.options.imageSize + '.jpg" title="' + title +
  363. '" class="' + aClassNoDots + ' colorbox cboxElement" target="_blank">' + '<img alt="' + title + '" src="' + link + self.options.thumbnailSize + '.jpg"/>' + '</a>' + '</li>';
  364. }
  365. // Append thumbnails
  366. self.element.insertBefore( $ul.append( listItems )[0], self.element.firstChild);
  367. $images = $ul.find( 'img' );
  368. // Error handling
  369. $images.on( 'error', function() {
  370. var $this = $( this ),
  371. src = $this.attr( 'src' );
  372. $this.attr( 'src', null ).attr( 'src', src );
  373. });
  374. // Attach load listener for thumbnails
  375. $images.on('load', function() {
  376. loadedImg++;
  377. if ( loadedImg === photos.photo.length ) {
  378. // All images loaded, remove loader and display gallery content
  379. self._removeLoader( self.element );
  380. // Check for entry animation switch
  381. switch( self.options.animation )
  382. {
  383. case 'fade':
  384. $ul.fadeIn( self.options.animationSpeed );
  385. break;
  386. case 'show':
  387. $ul.show( self.options.animationSpeed );
  388. break;
  389. case false:
  390. $ul.show();
  391. }
  392. // Remove event listener
  393. $images.off( 'load' ).off( 'error' );
  394. // Assign anchors selector to local instance
  395. self.anchors = self._getAnchors();
  396. // Toggle pagination
  397. self._togglePagination();
  398. }
  399. });
  400. } else {
  401. error = document.createElement('span');
  402. error.className = self.options.error.tagClass;
  403. error.innerHTML = self.options.error.text;
  404. // Display error message..
  405. self.element.insertBefore( error, self.element.firstChild );
  406. // ..and remove loader
  407. self._removeLoader( self.element )._togglePagination();
  408. }
  409. return self;
  410. },
  411. /**
  412. * Generate pagination buttons (when pagination -> generated is true).
  413. * Not for public consumption
  414. *
  415. * @return Plugin
  416. * @private
  417. * @method
  418. * @memberOf Plugin
  419. */
  420. _createPagination : function() {
  421. var pagination = '',
  422. prev = $( this.options.pagination.prevClass, this.paginationContext )[0],
  423. next = $( this.options.pagination.nextClass, this.paginationContext )[0]
  424. ;
  425. if ( !prev && !next && this.options.pagination.generate ) {
  426. pagination += '<div class="' + this._replaceDots( this.options.pagination.containerClass ) + '">' +
  427. '<button ' + 'class="' +
  428. this._replaceDots( this.options.pagination.prevClass ) + '" ' +
  429. 'title="' + this.options.pagination.prevText + '" ' +
  430. 'disabled="disabled"><img src="images/arrow_left.svg"></button>' +
  431. '<button ' + 'class="' +
  432. this._replaceDots( this.options.pagination.nextClass ) + '" ' +
  433. 'title="' + this.options.pagination.nextText + '" ' +
  434. 'disabled="disabled"><img src="images/arrow_right.svg"></button>' +
  435. '</div>';
  436. this.element.appendChild( $( pagination )[0] );
  437. }
  438. return this;
  439. },
  440. /**
  441. * Bind modal pagination control events. Not for public consumption
  442. *
  443. * @return Plugin
  444. * @private
  445. * @memberOf Plugin
  446. */
  447. _bindPaginationEvents : function() {
  448. var self = this,
  449. $prev = $( this.options.pagination.prevClass, this.paginationContext ),
  450. $next = $( this.options.pagination.nextClass, this.paginationContext )
  451. ;
  452. // Previous page action
  453. $prev.click(function() {
  454. if ( !$prev.is( ':disabled' ) ) {
  455. $next.attr( 'disabled', 'disabled' );
  456. $prev.attr( 'disabled', 'disabled' );
  457. self.prevPage();
  458. }
  459. });
  460. // Next page action
  461. $next.click(function() {
  462. if ( !$next.is( ':disabled' ) ) {
  463. $prev.attr( 'disabled' , 'disabled' );
  464. $next.attr( 'disabled', 'disabled' );
  465. self.nextPage();
  466. }
  467. });
  468. },
  469. /**
  470. * Toggles pagination buttons based on current page number. Not for public consumption
  471. *
  472. * @return Plugin
  473. * @private
  474. * @method
  475. * @memberOf Plugin
  476. */
  477. _togglePagination : function() {
  478. var $prev = $( this.options.pagination.prevClass, this.paginationContext ),
  479. $next = $( this.options.pagination.nextClass, this.paginationContext );
  480. if ( this.page !== 1 ) {
  481. $prev.removeAttr( 'disabled' );
  482. } else {
  483. $prev.attr( 'disabled', 'disabled' );
  484. }
  485. if ( !this.isLastPage() ) {
  486. $next.removeAttr( 'disabled' );
  487. } else {
  488. $next.attr( 'disabled', 'disabled' );
  489. }
  490. return this;
  491. },
  492. /**
  493. * Bind modal event listeners and generate modal markup if required. Not for public consumption
  494. *
  495. * @return Plugin
  496. * @private
  497. * @method
  498. * @memberOf Plugin
  499. */
  500. _createModal : function() {
  501. // Check if modal structure is already available
  502. var header,
  503. body,
  504. footer,
  505. modal
  506. ;
  507. if ( !document.getElementById( this.options.modal.id ) ) {
  508. header = '<div class="' + GEN_HEADER_CONTAINER_CLASS + '">' +
  509. '<button type="button" class="close" data-dismiss="modal" aria-hidden="true">' +
  510. '&times;</button>' +
  511. '<' + GEN_TITLE_TAG + '></' + GEN_TITLE_TAG + '>' +
  512. '</div>';
  513. body = '<div class="' + GEN_BODY_CONTAINER_CLASS + '">' +
  514. '<div class="' + GEN_IMAGE_CONTAINER_CLASS + '"></div>' +
  515. '</div>';
  516. footer = '<div class="' + GEN_FOOTER_CONTAINER_CLASS + '">' +
  517. '<button title="' + this.options.modal.prevText +
  518. '" class="' + this._replaceDots( this.options.modal.prevClass ) +
  519. '">&laquo;</button>' +
  520. '<button title="' + this.options.modal.nextText +
  521. '" class="' + this._replaceDots( this.options.modal.nextClass ) +
  522. '">&raquo;</button>' +
  523. '</div>';
  524. // Append modal to body
  525. modal = document.createElement( 'div' );
  526. modal.id = this.options.modal.id;
  527. modal.className = 'modal jsfg-modal hide fade';
  528. modal.innerHTML = header + body + footer;
  529. document.body.appendChild( modal );
  530. }
  531. return this;
  532. },
  533. /**
  534. * Bind modal events to thumbnails and modal paging buttons
  535. *
  536. * @return Plugin
  537. * @private
  538. * @method
  539. * @memberOf Plugin
  540. */
  541. _bindModalEvents : function() {
  542. var self = this,
  543. next = this.options.modal.onContainerNext ? this.options.modal.nextClass + ', ' +
  544. this.options.modal.imageContainerClass : this.options.modal.nextClass,
  545. $modal = $( '#' + self.options.modal.id ),
  546. context = '#' + this.options.modal.id
  547. ;
  548. // Bind on thumbnail click event
  549. // this.$element.on( 'click', this.options.structure.aClass, function( event ) {
  550. // var i;
  551. // event.preventDefault();
  552. // // Assign gallery id to modal window
  553. // $modal.attr( DATA_GALLERY_ID_ATTR, self.galleryId );
  554. // // Also assign index to plugin instance
  555. // for ( i = 0; i < self.anchors.length; i++ ) {
  556. // if (self.anchors[ i ] === this) {
  557. // self.index = i;
  558. // }
  559. // }
  560. // self._loadImage( true );
  561. // });
  562. // Next image in modal
  563. $( next, context ).click(function( event ) {
  564. event.preventDefault();
  565. // Check if this click listener should be triggered
  566. if ( $modal.attr( DATA_GALLERY_ID_ATTR ) === self.galleryId ) {
  567. self.nextImage();
  568. }
  569. });
  570. // Previous image in modal
  571. $( this.options.modal.prevClass, context ).click(function( event ) {
  572. event.preventDefault();
  573. // Check if this click listener should be triggered
  574. if ( $modal.attr( DATA_GALLERY_ID_ATTR ) === self.galleryId ) {
  575. self.prevImage();
  576. }
  577. });
  578. return this;
  579. },
  580. /**
  581. * Load image in modal based on current index value stored in Plugin instance. Not for public consumption
  582. *
  583. * @param boolean showModal Should modal be displayed?
  584. * @return Plugin
  585. * @private
  586. * @method
  587. * @memberOf Plugin
  588. */
  589. _loadImage : function( showModal ) {
  590. var self = this,
  591. $modal = $( '#' + this.options.modal.id ),
  592. $modalTitle = $( this.options.modal.title, $modal ),
  593. imageIndex = self.index,
  594. $imageAnchor = $( this.anchors[ this.index ] ),
  595. $image = $( '<img/>' ),
  596. $imageContainer = $( this.options.modal.imageContainerClass, $modal ),
  597. $window = $( window )
  598. ;
  599. // Hide image container content
  600. $imageContainer.children().hide();
  601. // Set modal window title
  602. $modalTitle.text( $imageAnchor.attr( 'title' ) );
  603. // Show modal window if requested
  604. if ( showModal ) {
  605. $modal.modal( 'show' );
  606. }
  607. // Error handling
  608. $image.on( 'error', function() {
  609. $image.prop( 'src', null ).prop( 'src', $imageAnchor.attr( 'href' ) );
  610. });
  611. // Wait for image to load
  612. $image.on( 'load', function() {
  613. // Check if image is already loading
  614. if ( self.index === imageIndex ) {
  615. // Clear all image container children BESIDE added image
  616. $imageContainer.children().remove();
  617. // Disable loader
  618. self._removeLoader( $imageContainer[ 0 ] );
  619. // Resize image to fit box
  620. $image = self._resizeToFit( $image, $window );
  621. $modalTitle.width( $image.prop( 'width' ) );
  622. // Resize image container to it's content
  623. $imageContainer.height( $image.prop( 'height' ) ).width( $image.prop( 'width' ) );
  624. // Append image to image container
  625. $image.appendTo( $imageContainer );
  626. // If not responsive - center on both axes
  627. if ( $window.width() > RESPONSIVE_WIDTH ) {
  628. $modal.css( 'top', '' );
  629. ( $.support.transition ? $modal.animate : $modal.css ).call( $modal.stop(), {
  630. 'margin-left' : -$modal.outerWidth() / 2,
  631. 'margin-top' : -$modal.outerHeight() / 2
  632. });
  633. } else {
  634. // ..center on y axis
  635. $modal.css({
  636. 'top': ( $window.height() - $modal.outerHeight() ) / 2,
  637. 'margin-left' : '',
  638. 'margin-top' : ''
  639. });
  640. }
  641. // Fade image in and center modal
  642. $image.fadeIn( self.options.modal.imageFadeTime );
  643. // Cache near images
  644. if ( self.options.preload ) {
  645. self._preloadImages();
  646. }
  647. }
  648. $image.off( 'load' ).off( 'error' );
  649. });
  650. $image.prop( 'src', $imageAnchor.attr( 'href' ) ).attr( 'alt', $imageAnchor.attr( 'title' ) );
  651. if ( !$image[ 0 ].complete ) {
  652. // Display loader
  653. this.loaderInterval = this._createLoader( $imageContainer[ 0 ] );
  654. }
  655. return this;
  656. },
  657. /**
  658. * Image preload mechanism. Not for public consumption
  659. *
  660. * @return Plugin
  661. * @private
  662. * @method
  663. * @memberOf Plugin
  664. */
  665. _preloadImages : function() {
  666. // + 1 cause we need to skip current index
  667. var maxIndex = this.index + this.options.preload.range + 1,
  668. minIndex = this.index - this.options.preload.range,
  669. anchor,
  670. i,
  671. tempI
  672. ;
  673. // Cap values
  674. maxIndex = maxIndex > this.anchors.length ? maxIndex - this.anchors.length : maxIndex;
  675. minIndex = minIndex > maxIndex ? minIndex - this.anchors.length : minIndex ;
  676. for ( i = minIndex; i < maxIndex; i++ ) {
  677. tempI = i < 0 ? this.anchors.length + i : i;
  678. anchor = this.anchors[ tempI ];
  679. if ( anchor && tempI !== this.index ) {
  680. $( document.createElement( 'img' ) ).attr( 'src', anchor.href || $( anchor ).attr( 'href' ) );
  681. }
  682. }
  683. return this;
  684. },
  685. /**
  686. * Resize image to fit screen. Not for public consumption
  687. *
  688. * @param Object $image
  689. * @param Object $element
  690. * @return Object
  691. * @private
  692. * @method
  693. * @memberOf Plugin
  694. */
  695. _resizeToFit : function( $image, $element ) {
  696. var scale = 1,
  697. maxWidth,
  698. maxHeight,
  699. imgWidth = $image.prop( 'width' ),
  700. imgHeight = $image.prop( 'height' )
  701. ;
  702. // Scale image to fit page
  703. maxWidth = $element.width() - this.options.modal.offsetWidth;
  704. maxHeight = $element.height() - this.options.modal.offsetHeight;
  705. if ( imgWidth > maxWidth || imgHeight > maxHeight ) {
  706. scale = Math.min( maxWidth / imgWidth, maxHeight / imgHeight );
  707. }
  708. $image.prop( 'width', imgWidth * scale ).prop( 'height', imgHeight * scale );
  709. return $image;
  710. },
  711. /**
  712. * Display loading message and create animation interval for marks if required. Not for public
  713. * consumption
  714. *
  715. * @param Object $element
  716. * @return Object | boolean interval or true when animation disabled
  717. * @private
  718. * @method
  719. * @memberOf Plugin
  720. */
  721. _createLoader : function( element ) {
  722. var loaderMarks = document.createElement('span'),
  723. loaderContainer = document.createElement('p'),
  724. options = this.options
  725. ;
  726. loaderContainer.appendChild( document.createTextNode( options.loader.text ) );
  727. loaderContainer.className = this._replaceDots( options.loader.loaderClass );
  728. loaderMarks.className = this._replaceDots( options.loader.markClass );
  729. // Add loader node to gallery container
  730. element.insertBefore( loaderContainer.appendChild( loaderMarks ).parentNode, element.firstChild );
  731. if (options.loader.animation) {
  732. return setInterval(function() {
  733. if ( loaderMarks.innerHTML.length <= options.loader.maxMarks ) {
  734. loaderMarks.innerHTML += options.loader.mark;
  735. } else {
  736. loaderMarks.innerHTML = '';
  737. }
  738. }, options.loader.interval );
  739. } else {
  740. return true;
  741. }
  742. },
  743. /**
  744. * Returns all links to images in gallery as an array
  745. *
  746. * @return Array
  747. * @private
  748. * @method
  749. * @memberOf Plugin
  750. */
  751. _getAnchors : function() {
  752. return $( this.options.structure.aClass, this.element ).get();
  753. },
  754. /**
  755. * Remove loader instance. Not for public consumption
  756. *
  757. * @param Object $element
  758. * @return Plugin
  759. * @private
  760. * @method
  761. * @memberOf Plugin
  762. */
  763. _removeLoader : function( element ) {
  764. var loader = $( this.options.loader.loaderClass, element )[ 0 ];
  765. if ( this.loaderInterval && loader ) {
  766. element.removeChild( loader );
  767. clearInterval( this.loaderInterval );
  768. }
  769. return this;
  770. },
  771. /**
  772. * Escape special html characters. Not for public consumption
  773. *
  774. * @return string str
  775. * @private
  776. * @method
  777. * @memberOf Plugin
  778. */
  779. _htmlEscape : function( str ) {
  780. return str
  781. .replace( /&/g, '&amp;' )
  782. .replace( /"/g, '&quot;' )
  783. .replace( /'/g, '&#39;' )
  784. .replace( /</g, '&lt;' )
  785. .replace( />/g, '&gt;' );
  786. },
  787. /**
  788. * Replaces dots with whitespaces
  789. *
  790. * @param string str
  791. * @private
  792. * @method
  793. * @return string
  794. */
  795. _replaceDots : function( str ) {
  796. return str.replace(/\./g, ' ');
  797. }
  798. };
  799. // Attach plugin to jQuery function pool
  800. $.fn[ pluginName ] = function ( options ) {
  801. return this.each( function () {
  802. if ( !$.data( this, "plugin_" + pluginName ) ) {
  803. $.data( this, "plugin_" + pluginName, new Plugin( this, options ) );
  804. }
  805. });
  806. };
  807. // Automatically attach jsFlickrGallery
  808. $(function () {
  809. $( '[data-toggle="' + DATA_TOGGLE_ATTR + '"]' ).jsFlickrGallery();
  810. });
  811. })( jQuery, window, document );