justified-gallery.js 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246
  1. /*!
  2. Justified Gallery
  3. Version: 3.8.1
  4. Plugin URL: http://miromannino.github.io/Justified-Gallery/
  5. License: Copyright (c) 2020 Miro Mannino | Released under the MIT License
  6. !*/
  7. (function (factory) {
  8. if (typeof define === 'function' && define.amd) {
  9. // AMD. Register as an anonymous module.
  10. define(['jquery'], factory);
  11. } else if (typeof module === 'object' && module.exports) {
  12. // Node/CommonJS
  13. module.exports = function (root, jQuery) {
  14. if (jQuery === undefined) {
  15. // require('jQuery') returns a factory that requires window to
  16. // build a jQuery instance, we normalize how we use modules
  17. // that require this pattern but the window provided is a noop
  18. // if it's defined (how jquery works)
  19. if (typeof window !== 'undefined') {
  20. jQuery = require('jquery');
  21. }
  22. else {
  23. jQuery = require('jquery')(root);
  24. }
  25. }
  26. factory(jQuery);
  27. return jQuery;
  28. };
  29. } else {
  30. // Browser globals
  31. factory(jQuery);
  32. }
  33. }(function ($) {
  34. /**
  35. * Justified Gallery controller constructor
  36. *
  37. * @param $gallery the gallery to build
  38. * @param settings the settings (the defaults are in JustifiedGallery.defaults)
  39. * @constructor
  40. */
  41. var JustifiedGallery = function ($gallery, settings) {
  42. this.settings = settings;
  43. this.checkSettings();
  44. this.imgAnalyzerTimeout = null;
  45. this.entries = null;
  46. this.buildingRow = {
  47. entriesBuff: [],
  48. width: 0,
  49. height: 0,
  50. aspectRatio: 0
  51. };
  52. this.lastFetchedEntry = null;
  53. this.lastAnalyzedIndex = -1;
  54. this.yield = {
  55. every: 2, // do a flush every n flushes (must be greater than 1)
  56. flushed: 0 // flushed rows without a yield
  57. };
  58. this.border = settings.border >= 0 ? settings.border : settings.margins;
  59. this.maxRowHeight = this.retrieveMaxRowHeight();
  60. this.suffixRanges = this.retrieveSuffixRanges();
  61. this.offY = this.border;
  62. this.rows = 0;
  63. this.spinner = {
  64. phase: 0,
  65. timeSlot: 150,
  66. $el: $('<div class="jg-spinner"><span></span><span></span><span></span></div>'),
  67. intervalId: null
  68. };
  69. this.scrollBarOn = false;
  70. this.checkWidthIntervalId = null;
  71. this.galleryWidth = $gallery.width();
  72. this.$gallery = $gallery;
  73. };
  74. /** @returns {String} the best suffix given the width and the height */
  75. JustifiedGallery.prototype.getSuffix = function (width, height) {
  76. var longestSide, i;
  77. longestSide = (width > height) ? width : height;
  78. for (i = 0; i < this.suffixRanges.length; i++) {
  79. if (longestSide <= this.suffixRanges[i]) {
  80. return this.settings.sizeRangeSuffixes[this.suffixRanges[i]];
  81. }
  82. }
  83. return this.settings.sizeRangeSuffixes[this.suffixRanges[i - 1]];
  84. };
  85. /**
  86. * Remove the suffix from the string
  87. *
  88. * @returns {string} a new string without the suffix
  89. */
  90. JustifiedGallery.prototype.removeSuffix = function (str, suffix) {
  91. return str.substring(0, str.length - suffix.length);
  92. };
  93. /**
  94. * @returns {boolean} a boolean to say if the suffix is contained in the str or not
  95. */
  96. JustifiedGallery.prototype.endsWith = function (str, suffix) {
  97. return str.indexOf(suffix, str.length - suffix.length) !== -1;
  98. };
  99. /**
  100. * Get the used suffix of a particular url
  101. *
  102. * @param str
  103. * @returns {String} return the used suffix
  104. */
  105. JustifiedGallery.prototype.getUsedSuffix = function (str) {
  106. for (var si in this.settings.sizeRangeSuffixes) {
  107. if (this.settings.sizeRangeSuffixes.hasOwnProperty(si)) {
  108. if (this.settings.sizeRangeSuffixes[si].length === 0) continue;
  109. if (this.endsWith(str, this.settings.sizeRangeSuffixes[si])) return this.settings.sizeRangeSuffixes[si];
  110. }
  111. }
  112. return '';
  113. };
  114. /**
  115. * Given an image src, with the width and the height, returns the new image src with the
  116. * best suffix to show the best quality thumbnail.
  117. *
  118. * @returns {String} the suffix to use
  119. */
  120. JustifiedGallery.prototype.newSrc = function (imageSrc, imgWidth, imgHeight, image) {
  121. var newImageSrc;
  122. if (this.settings.thumbnailPath) {
  123. newImageSrc = this.settings.thumbnailPath(imageSrc, imgWidth, imgHeight, image);
  124. } else {
  125. var matchRes = imageSrc.match(this.settings.extension);
  126. var ext = (matchRes !== null) ? matchRes[0] : '';
  127. newImageSrc = imageSrc.replace(this.settings.extension, '');
  128. newImageSrc = this.removeSuffix(newImageSrc, this.getUsedSuffix(newImageSrc));
  129. newImageSrc += this.getSuffix(imgWidth, imgHeight) + ext;
  130. }
  131. return newImageSrc;
  132. };
  133. /**
  134. * Shows the images that is in the given entry
  135. *
  136. * @param $entry the entry
  137. * @param callback the callback that is called when the show animation is finished
  138. */
  139. JustifiedGallery.prototype.showImg = function ($entry, callback) {
  140. if (this.settings.cssAnimation) {
  141. $entry.addClass('jg-entry-visible');
  142. if (callback) callback();
  143. } else {
  144. $entry.stop().fadeTo(this.settings.imagesAnimationDuration, 1.0, callback);
  145. $entry.find(this.settings.imgSelector).stop().fadeTo(this.settings.imagesAnimationDuration, 1.0, callback);
  146. }
  147. };
  148. /**
  149. * Extract the image src form the image, looking from the 'safe-src', and if it can't be found, from the
  150. * 'src' attribute. It saves in the image data the 'jg.originalSrc' field, with the extracted src.
  151. *
  152. * @param $image the image to analyze
  153. * @returns {String} the extracted src
  154. */
  155. JustifiedGallery.prototype.extractImgSrcFromImage = function ($image) {
  156. var imageSrc = $image.data('safe-src');
  157. var imageSrcLoc = 'data-safe-src';
  158. if (typeof imageSrc === 'undefined') {
  159. imageSrc = $image.attr('src');
  160. imageSrcLoc = 'src';
  161. }
  162. $image.data('jg.originalSrc', imageSrc); // this is saved for the destroy method
  163. $image.data('jg.src', imageSrc); // this will change overtime
  164. $image.data('jg.originalSrcLoc', imageSrcLoc); // this is saved for the destroy method
  165. return imageSrc;
  166. };
  167. /** @returns {jQuery} the image in the given entry */
  168. JustifiedGallery.prototype.imgFromEntry = function ($entry) {
  169. var $img = $entry.find(this.settings.imgSelector);
  170. return $img.length === 0 ? null : $img;
  171. };
  172. /** @returns {jQuery} the caption in the given entry */
  173. JustifiedGallery.prototype.captionFromEntry = function ($entry) {
  174. var $caption = $entry.find('> .jg-caption');
  175. return $caption.length === 0 ? null : $caption;
  176. };
  177. /**
  178. * Display the entry
  179. *
  180. * @param {jQuery} $entry the entry to display
  181. * @param {int} x the x position where the entry must be positioned
  182. * @param y the y position where the entry must be positioned
  183. * @param imgWidth the image width
  184. * @param imgHeight the image height
  185. * @param rowHeight the row height of the row that owns the entry
  186. */
  187. JustifiedGallery.prototype.displayEntry = function ($entry, x, y, imgWidth, imgHeight, rowHeight) {
  188. $entry.width(imgWidth);
  189. $entry.height(rowHeight);
  190. $entry.css('top', y);
  191. $entry.css('left', x);
  192. var $image = this.imgFromEntry($entry);
  193. if ($image !== null) {
  194. $image.css('width', imgWidth);
  195. $image.css('height', imgHeight);
  196. $image.css('margin-left', - imgWidth / 2);
  197. $image.css('margin-top', - imgHeight / 2);
  198. // Image reloading for an high quality of thumbnails
  199. var imageSrc = $image.data('jg.src');
  200. if (imageSrc) {
  201. imageSrc = this.newSrc(imageSrc, imgWidth, imgHeight, $image[0]);
  202. $image.one('error', function () {
  203. this.resetImgSrc($image); //revert to the original thumbnail
  204. });
  205. var loadNewImage = function () {
  206. // if (imageSrc !== newImageSrc) {
  207. $image.attr('src', imageSrc);
  208. // }
  209. };
  210. if ($entry.data('jg.loaded') === 'skipped' && imageSrc) {
  211. this.onImageEvent(imageSrc, (function() {
  212. this.showImg($entry, loadNewImage); //load the new image after the fadeIn
  213. $entry.data('jg.loaded', true);
  214. }).bind(this));
  215. } else {
  216. this.showImg($entry, loadNewImage); //load the new image after the fadeIn
  217. }
  218. }
  219. } else {
  220. this.showImg($entry);
  221. }
  222. this.displayEntryCaption($entry);
  223. };
  224. /**
  225. * Display the entry caption. If the caption element doesn't exists, it creates the caption using the 'alt'
  226. * or the 'title' attributes.
  227. *
  228. * @param {jQuery} $entry the entry to process
  229. */
  230. JustifiedGallery.prototype.displayEntryCaption = function ($entry) {
  231. var $image = this.imgFromEntry($entry);
  232. if ($image !== null && this.settings.captions) {
  233. var $imgCaption = this.captionFromEntry($entry);
  234. // Create it if it doesn't exists
  235. if ($imgCaption === null) {
  236. var caption = $image.attr('alt');
  237. if (!this.isValidCaption(caption)) caption = $entry.attr('title');
  238. if (this.isValidCaption(caption)) { // Create only we found something
  239. $imgCaption = $('<div class="jg-caption">' + caption + '</div>');
  240. $entry.append($imgCaption);
  241. $entry.data('jg.createdCaption', true);
  242. }
  243. }
  244. // Create events (we check again the $imgCaption because it can be still inexistent)
  245. if ($imgCaption !== null) {
  246. if (!this.settings.cssAnimation) $imgCaption.stop().fadeTo(0, this.settings.captionSettings.nonVisibleOpacity);
  247. this.addCaptionEventsHandlers($entry);
  248. }
  249. } else {
  250. this.removeCaptionEventsHandlers($entry);
  251. }
  252. };
  253. /**
  254. * Validates the caption
  255. *
  256. * @param caption The caption that should be validated
  257. * @return {boolean} Validation result
  258. */
  259. JustifiedGallery.prototype.isValidCaption = function (caption) {
  260. return (typeof caption !== 'undefined' && caption.length > 0);
  261. };
  262. /**
  263. * The callback for the event 'mouseenter'. It assumes that the event currentTarget is an entry.
  264. * It shows the caption using jQuery (or using CSS if it is configured so)
  265. *
  266. * @param {Event} eventObject the event object
  267. */
  268. JustifiedGallery.prototype.onEntryMouseEnterForCaption = function (eventObject) {
  269. var $caption = this.captionFromEntry($(eventObject.currentTarget));
  270. if (this.settings.cssAnimation) {
  271. $caption.addClass('jg-caption-visible').removeClass('jg-caption-hidden');
  272. } else {
  273. $caption.stop().fadeTo(this.settings.captionSettings.animationDuration,
  274. this.settings.captionSettings.visibleOpacity);
  275. }
  276. };
  277. /**
  278. * The callback for the event 'mouseleave'. It assumes that the event currentTarget is an entry.
  279. * It hides the caption using jQuery (or using CSS if it is configured so)
  280. *
  281. * @param {Event} eventObject the event object
  282. */
  283. JustifiedGallery.prototype.onEntryMouseLeaveForCaption = function (eventObject) {
  284. var $caption = this.captionFromEntry($(eventObject.currentTarget));
  285. if (this.settings.cssAnimation) {
  286. $caption.removeClass('jg-caption-visible').removeClass('jg-caption-hidden');
  287. } else {
  288. $caption.stop().fadeTo(this.settings.captionSettings.animationDuration,
  289. this.settings.captionSettings.nonVisibleOpacity);
  290. }
  291. };
  292. /**
  293. * Add the handlers of the entry for the caption
  294. *
  295. * @param $entry the entry to modify
  296. */
  297. JustifiedGallery.prototype.addCaptionEventsHandlers = function ($entry) {
  298. var captionMouseEvents = $entry.data('jg.captionMouseEvents');
  299. if (typeof captionMouseEvents === 'undefined') {
  300. captionMouseEvents = {
  301. mouseenter: $.proxy(this.onEntryMouseEnterForCaption, this),
  302. mouseleave: $.proxy(this.onEntryMouseLeaveForCaption, this)
  303. };
  304. $entry.on('mouseenter', undefined, undefined, captionMouseEvents.mouseenter);
  305. $entry.on('mouseleave', undefined, undefined, captionMouseEvents.mouseleave);
  306. $entry.data('jg.captionMouseEvents', captionMouseEvents);
  307. }
  308. };
  309. /**
  310. * Remove the handlers of the entry for the caption
  311. *
  312. * @param $entry the entry to modify
  313. */
  314. JustifiedGallery.prototype.removeCaptionEventsHandlers = function ($entry) {
  315. var captionMouseEvents = $entry.data('jg.captionMouseEvents');
  316. if (typeof captionMouseEvents !== 'undefined') {
  317. $entry.off('mouseenter', undefined, captionMouseEvents.mouseenter);
  318. $entry.off('mouseleave', undefined, captionMouseEvents.mouseleave);
  319. $entry.removeData('jg.captionMouseEvents');
  320. }
  321. };
  322. /**
  323. * Clear the building row data to be used for a new row
  324. */
  325. JustifiedGallery.prototype.clearBuildingRow = function () {
  326. this.buildingRow.entriesBuff = [];
  327. this.buildingRow.aspectRatio = 0;
  328. this.buildingRow.width = 0;
  329. };
  330. /**
  331. * Justify the building row, preparing it to
  332. *
  333. * @param isLastRow
  334. * @param hiddenRow undefined or false for normal behavior. hiddenRow = true to hide the row.
  335. * @returns a boolean to know if the row has been justified or not
  336. */
  337. JustifiedGallery.prototype.prepareBuildingRow = function (isLastRow, hiddenRow) {
  338. var i, $entry, imgAspectRatio, newImgW, newImgH, justify = true;
  339. var minHeight = 0;
  340. var availableWidth = this.galleryWidth - 2 * this.border - (
  341. (this.buildingRow.entriesBuff.length - 1) * this.settings.margins);
  342. var rowHeight = availableWidth / this.buildingRow.aspectRatio;
  343. var defaultRowHeight = this.settings.rowHeight;
  344. var justifiable = this.buildingRow.width / availableWidth > this.settings.justifyThreshold;
  345. //Skip the last row if we can't justify it and the lastRow == 'hide'
  346. if (hiddenRow || (isLastRow && this.settings.lastRow === 'hide' && !justifiable)) {
  347. for (i = 0; i < this.buildingRow.entriesBuff.length; i++) {
  348. $entry = this.buildingRow.entriesBuff[i];
  349. if (this.settings.cssAnimation)
  350. $entry.removeClass('jg-entry-visible');
  351. else {
  352. $entry.stop().fadeTo(0, 0.1);
  353. $entry.find('> img, > a > img').fadeTo(0, 0);
  354. }
  355. }
  356. return -1;
  357. }
  358. // With lastRow = nojustify, justify if is justificable (the images will not become too big)
  359. if (isLastRow && !justifiable && this.settings.lastRow !== 'justify' && this.settings.lastRow !== 'hide') {
  360. justify = false;
  361. if (this.rows > 0) {
  362. defaultRowHeight = (this.offY - this.border - this.settings.margins * this.rows) / this.rows;
  363. justify = defaultRowHeight * this.buildingRow.aspectRatio / availableWidth > this.settings.justifyThreshold;
  364. }
  365. }
  366. for (i = 0; i < this.buildingRow.entriesBuff.length; i++) {
  367. $entry = this.buildingRow.entriesBuff[i];
  368. imgAspectRatio = $entry.data('jg.width') / $entry.data('jg.height');
  369. if (justify) {
  370. newImgW = (i === this.buildingRow.entriesBuff.length - 1) ? availableWidth : rowHeight * imgAspectRatio;
  371. newImgH = rowHeight;
  372. } else {
  373. newImgW = defaultRowHeight * imgAspectRatio;
  374. newImgH = defaultRowHeight;
  375. }
  376. availableWidth -= Math.round(newImgW);
  377. $entry.data('jg.jwidth', Math.round(newImgW));
  378. $entry.data('jg.jheight', Math.ceil(newImgH));
  379. if (i === 0 || minHeight > newImgH) minHeight = newImgH;
  380. }
  381. this.buildingRow.height = minHeight;
  382. return justify;
  383. };
  384. /**
  385. * Flush a row: justify it, modify the gallery height accordingly to the row height
  386. *
  387. * @param isLastRow
  388. * @param hiddenRow undefined or false for normal behavior. hiddenRow = true to hide the row.
  389. */
  390. JustifiedGallery.prototype.flushRow = function (isLastRow, hiddenRow) {
  391. var settings = this.settings;
  392. var $entry, buildingRowRes, offX = this.border, i;
  393. buildingRowRes = this.prepareBuildingRow(isLastRow, hiddenRow);
  394. if (hiddenRow || (isLastRow && settings.lastRow === 'hide' && buildingRowRes === -1)) {
  395. this.clearBuildingRow();
  396. return;
  397. }
  398. if (this.maxRowHeight) {
  399. if (this.maxRowHeight < this.buildingRow.height) this.buildingRow.height = this.maxRowHeight;
  400. }
  401. //Align last (unjustified) row
  402. if (isLastRow && (settings.lastRow === 'center' || settings.lastRow === 'right')) {
  403. var availableWidth = this.galleryWidth - 2 * this.border - (this.buildingRow.entriesBuff.length - 1) * settings.margins;
  404. for (i = 0; i < this.buildingRow.entriesBuff.length; i++) {
  405. $entry = this.buildingRow.entriesBuff[i];
  406. availableWidth -= $entry.data('jg.jwidth');
  407. }
  408. if (settings.lastRow === 'center')
  409. offX += Math.round(availableWidth / 2);
  410. else if (settings.lastRow === 'right')
  411. offX += availableWidth;
  412. }
  413. var lastEntryIdx = this.buildingRow.entriesBuff.length - 1;
  414. for (i = 0; i <= lastEntryIdx; i++) {
  415. $entry = this.buildingRow.entriesBuff[this.settings.rtl ? lastEntryIdx - i : i];
  416. this.displayEntry($entry, offX, this.offY, $entry.data('jg.jwidth'), $entry.data('jg.jheight'), this.buildingRow.height);
  417. offX += $entry.data('jg.jwidth') + settings.margins;
  418. }
  419. //Gallery Height
  420. this.galleryHeightToSet = this.offY + this.buildingRow.height + this.border;
  421. this.setGalleryTempHeight(this.galleryHeightToSet + this.getSpinnerHeight());
  422. if (!isLastRow || (this.buildingRow.height <= settings.rowHeight && buildingRowRes)) {
  423. //Ready for a new row
  424. this.offY += this.buildingRow.height + settings.margins;
  425. this.rows += 1;
  426. this.clearBuildingRow();
  427. this.settings.triggerEvent.call(this, 'jg.rowflush');
  428. }
  429. };
  430. // Scroll position not restoring: https://github.com/miromannino/Justified-Gallery/issues/221
  431. var galleryPrevStaticHeight = 0;
  432. JustifiedGallery.prototype.rememberGalleryHeight = function () {
  433. galleryPrevStaticHeight = this.$gallery.height();
  434. this.$gallery.height(galleryPrevStaticHeight);
  435. };
  436. // grow only
  437. JustifiedGallery.prototype.setGalleryTempHeight = function (height) {
  438. galleryPrevStaticHeight = Math.max(height, galleryPrevStaticHeight);
  439. this.$gallery.height(galleryPrevStaticHeight);
  440. };
  441. JustifiedGallery.prototype.setGalleryFinalHeight = function (height) {
  442. galleryPrevStaticHeight = height;
  443. this.$gallery.height(height);
  444. };
  445. /**
  446. * Checks the width of the gallery container, to know if a new justification is needed
  447. */
  448. JustifiedGallery.prototype.checkWidth = function () {
  449. this.checkWidthIntervalId = setInterval($.proxy(function () {
  450. // if the gallery is not currently visible, abort.
  451. if (!this.$gallery.is(":visible")) return;
  452. var galleryWidth = parseFloat(this.$gallery.width());
  453. if (Math.abs(galleryWidth - this.galleryWidth) > this.settings.refreshSensitivity) {
  454. this.galleryWidth = galleryWidth;
  455. this.rewind();
  456. this.rememberGalleryHeight();
  457. // Restart to analyze
  458. this.startImgAnalyzer(true);
  459. }
  460. }, this), this.settings.refreshTime);
  461. };
  462. /**
  463. * @returns {boolean} a boolean saying if the spinner is active or not
  464. */
  465. JustifiedGallery.prototype.isSpinnerActive = function () {
  466. return this.spinner.intervalId !== null;
  467. };
  468. /**
  469. * @returns {int} the spinner height
  470. */
  471. JustifiedGallery.prototype.getSpinnerHeight = function () {
  472. return this.spinner.$el.innerHeight();
  473. };
  474. /**
  475. * Stops the spinner animation and modify the gallery height to exclude the spinner
  476. */
  477. JustifiedGallery.prototype.stopLoadingSpinnerAnimation = function () {
  478. clearInterval(this.spinner.intervalId);
  479. this.spinner.intervalId = null;
  480. this.setGalleryTempHeight(this.$gallery.height() - this.getSpinnerHeight());
  481. this.spinner.$el.detach();
  482. };
  483. /**
  484. * Starts the spinner animation
  485. */
  486. JustifiedGallery.prototype.startLoadingSpinnerAnimation = function () {
  487. var spinnerContext = this.spinner;
  488. var $spinnerPoints = spinnerContext.$el.find('span');
  489. clearInterval(spinnerContext.intervalId);
  490. this.$gallery.append(spinnerContext.$el);
  491. this.setGalleryTempHeight(this.offY + this.buildingRow.height + this.getSpinnerHeight());
  492. spinnerContext.intervalId = setInterval(function () {
  493. if (spinnerContext.phase < $spinnerPoints.length) {
  494. $spinnerPoints.eq(spinnerContext.phase).fadeTo(spinnerContext.timeSlot, 1);
  495. } else {
  496. $spinnerPoints.eq(spinnerContext.phase - $spinnerPoints.length).fadeTo(spinnerContext.timeSlot, 0);
  497. }
  498. spinnerContext.phase = (spinnerContext.phase + 1) % ($spinnerPoints.length * 2);
  499. }, spinnerContext.timeSlot);
  500. };
  501. /**
  502. * Rewind the image analysis to start from the first entry.
  503. */
  504. JustifiedGallery.prototype.rewind = function () {
  505. this.lastFetchedEntry = null;
  506. this.lastAnalyzedIndex = -1;
  507. this.offY = this.border;
  508. this.rows = 0;
  509. this.clearBuildingRow();
  510. };
  511. /**
  512. * @returns {String} `settings.selector` rejecting spinner element
  513. */
  514. JustifiedGallery.prototype.getSelectorWithoutSpinner = function () {
  515. return this.settings.selector + ', div:not(.jg-spinner)';
  516. };
  517. /**
  518. * @returns {Array} all entries matched by `settings.selector`
  519. */
  520. JustifiedGallery.prototype.getAllEntries = function () {
  521. var selector = this.getSelectorWithoutSpinner();
  522. return this.$gallery.children(selector).toArray();
  523. };
  524. /**
  525. * Update the entries searching it from the justified gallery HTML element
  526. *
  527. * @param norewind if norewind only the new entries will be changed (i.e. randomized, sorted or filtered)
  528. * @returns {boolean} true if some entries has been founded
  529. */
  530. JustifiedGallery.prototype.updateEntries = function (norewind) {
  531. var newEntries;
  532. if (norewind && this.lastFetchedEntry != null) {
  533. var selector = this.getSelectorWithoutSpinner();
  534. newEntries = $(this.lastFetchedEntry).nextAll(selector).toArray();
  535. } else {
  536. this.entries = [];
  537. newEntries = this.getAllEntries();
  538. }
  539. if (newEntries.length > 0) {
  540. // Sort or randomize
  541. if ($.isFunction(this.settings.sort)) {
  542. newEntries = this.sortArray(newEntries);
  543. } else if (this.settings.randomize) {
  544. newEntries = this.shuffleArray(newEntries);
  545. }
  546. this.lastFetchedEntry = newEntries[newEntries.length - 1];
  547. // Filter
  548. if (this.settings.filter) {
  549. newEntries = this.filterArray(newEntries);
  550. } else {
  551. this.resetFilters(newEntries);
  552. }
  553. }
  554. this.entries = this.entries.concat(newEntries);
  555. return true;
  556. };
  557. /**
  558. * Apply the entries order to the DOM, iterating the entries and appending the images
  559. *
  560. * @param entries the entries that has been modified and that must be re-ordered in the DOM
  561. */
  562. JustifiedGallery.prototype.insertToGallery = function (entries) {
  563. var that = this;
  564. $.each(entries, function () {
  565. $(this).appendTo(that.$gallery);
  566. });
  567. };
  568. /**
  569. * Shuffle the array using the Fisher-Yates shuffle algorithm
  570. *
  571. * @param a the array to shuffle
  572. * @return the shuffled array
  573. */
  574. JustifiedGallery.prototype.shuffleArray = function (a) {
  575. var i, j, temp;
  576. for (i = a.length - 1; i > 0; i--) {
  577. j = Math.floor(Math.random() * (i + 1));
  578. temp = a[i];
  579. a[i] = a[j];
  580. a[j] = temp;
  581. }
  582. this.insertToGallery(a);
  583. return a;
  584. };
  585. /**
  586. * Sort the array using settings.comparator as comparator
  587. *
  588. * @param a the array to sort (it is sorted)
  589. * @return the sorted array
  590. */
  591. JustifiedGallery.prototype.sortArray = function (a) {
  592. a.sort(this.settings.sort);
  593. this.insertToGallery(a);
  594. return a;
  595. };
  596. /**
  597. * Reset the filters removing the 'jg-filtered' class from all the entries
  598. *
  599. * @param a the array to reset
  600. */
  601. JustifiedGallery.prototype.resetFilters = function (a) {
  602. for (var i = 0; i < a.length; i++) $(a[i]).removeClass('jg-filtered');
  603. };
  604. /**
  605. * Filter the entries considering theirs classes (if a string has been passed) or using a function for filtering.
  606. *
  607. * @param a the array to filter
  608. * @return the filtered array
  609. */
  610. JustifiedGallery.prototype.filterArray = function (a) {
  611. var settings = this.settings;
  612. if ($.type(settings.filter) === 'string') {
  613. // Filter only keeping the entries passed in the string
  614. return a.filter(function (el) {
  615. var $el = $(el);
  616. if ($el.is(settings.filter)) {
  617. $el.removeClass('jg-filtered');
  618. return true;
  619. } else {
  620. $el.addClass('jg-filtered').removeClass('jg-visible');
  621. return false;
  622. }
  623. });
  624. } else if ($.isFunction(settings.filter)) {
  625. // Filter using the passed function
  626. var filteredArr = a.filter(settings.filter);
  627. for (var i = 0; i < a.length; i++) {
  628. if (filteredArr.indexOf(a[i]) === -1) {
  629. $(a[i]).addClass('jg-filtered').removeClass('jg-visible');
  630. } else {
  631. $(a[i]).removeClass('jg-filtered');
  632. }
  633. }
  634. return filteredArr;
  635. }
  636. };
  637. /**
  638. * Revert the image src to the default value.
  639. */
  640. JustifiedGallery.prototype.resetImgSrc = function ($img) {
  641. if ($img.data('jg.originalSrcLoc') === 'src') {
  642. $img.attr('src', $img.data('jg.originalSrc'));
  643. } else {
  644. $img.attr('src', '');
  645. }
  646. };
  647. /**
  648. * Destroy the Justified Gallery instance.
  649. *
  650. * It clears all the css properties added in the style attributes. We doesn't backup the original
  651. * values for those css attributes, because it costs (performance) and because in general one
  652. * shouldn't use the style attribute for an uniform set of images (where we suppose the use of
  653. * classes). Creating a backup is also difficult because JG could be called multiple times and
  654. * with different style attributes.
  655. */
  656. JustifiedGallery.prototype.destroy = function () {
  657. clearInterval(this.checkWidthIntervalId);
  658. this.stopImgAnalyzerStarter();
  659. // Get fresh entries list since filtered entries are absent in `this.entries`
  660. $.each(this.getAllEntries(), $.proxy(function (_, entry) {
  661. var $entry = $(entry);
  662. // Reset entry style
  663. $entry.css('width', '');
  664. $entry.css('height', '');
  665. $entry.css('top', '');
  666. $entry.css('left', '');
  667. $entry.data('jg.loaded', undefined);
  668. $entry.removeClass('jg-entry jg-filtered jg-entry-visible');
  669. // Reset image style
  670. var $img = this.imgFromEntry($entry);
  671. if ($img) {
  672. $img.css('width', '');
  673. $img.css('height', '');
  674. $img.css('margin-left', '');
  675. $img.css('margin-top', '');
  676. this.resetImgSrc($img);
  677. $img.data('jg.originalSrc', undefined);
  678. $img.data('jg.originalSrcLoc', undefined);
  679. $img.data('jg.src', undefined);
  680. }
  681. // Remove caption
  682. this.removeCaptionEventsHandlers($entry);
  683. var $caption = this.captionFromEntry($entry);
  684. if ($entry.data('jg.createdCaption')) {
  685. // remove also the caption element (if created by jg)
  686. $entry.data('jg.createdCaption', undefined);
  687. if ($caption !== null) $caption.remove();
  688. } else {
  689. if ($caption !== null) $caption.fadeTo(0, 1);
  690. }
  691. }, this));
  692. this.$gallery.css('height', '');
  693. this.$gallery.removeClass('justified-gallery');
  694. this.$gallery.data('jg.controller', undefined);
  695. this.settings.triggerEvent.call(this, 'jg.destroy');
  696. };
  697. /**
  698. * Analyze the images and builds the rows. It returns if it found an image that is not loaded.
  699. *
  700. * @param isForResize if the image analyzer is called for resizing or not, to call a different callback at the end
  701. */
  702. JustifiedGallery.prototype.analyzeImages = function (isForResize) {
  703. for (var i = this.lastAnalyzedIndex + 1; i < this.entries.length; i++) {
  704. var $entry = $(this.entries[i]);
  705. if ($entry.data('jg.loaded') === true || $entry.data('jg.loaded') === 'skipped') {
  706. var availableWidth = this.galleryWidth - 2 * this.border - (
  707. (this.buildingRow.entriesBuff.length - 1) * this.settings.margins);
  708. var imgAspectRatio = $entry.data('jg.width') / $entry.data('jg.height');
  709. this.buildingRow.entriesBuff.push($entry);
  710. this.buildingRow.aspectRatio += imgAspectRatio;
  711. this.buildingRow.width += imgAspectRatio * this.settings.rowHeight;
  712. this.lastAnalyzedIndex = i;
  713. if (availableWidth / (this.buildingRow.aspectRatio + imgAspectRatio) < this.settings.rowHeight) {
  714. this.flushRow(false, this.settings.maxRowsCount > 0 && this.rows === this.settings.maxRowsCount);
  715. if (++this.yield.flushed >= this.yield.every) {
  716. this.startImgAnalyzer(isForResize);
  717. return;
  718. }
  719. }
  720. } else if ($entry.data('jg.loaded') !== 'error') {
  721. return;
  722. }
  723. }
  724. // Last row flush (the row is not full)
  725. if (this.buildingRow.entriesBuff.length > 0) {
  726. this.flushRow(true, this.settings.maxRowsCount > 0 && this.rows === this.settings.maxRowsCount);
  727. }
  728. if (this.isSpinnerActive()) {
  729. this.stopLoadingSpinnerAnimation();
  730. }
  731. /* Stop, if there is, the timeout to start the analyzeImages.
  732. This is because an image can be set loaded, and the timeout can be set,
  733. but this image can be analyzed yet.
  734. */
  735. this.stopImgAnalyzerStarter();
  736. this.setGalleryFinalHeight(this.galleryHeightToSet);
  737. //On complete callback
  738. this.settings.triggerEvent.call(this, isForResize ? 'jg.resize' : 'jg.complete');
  739. };
  740. /**
  741. * Stops any ImgAnalyzer starter (that has an assigned timeout)
  742. */
  743. JustifiedGallery.prototype.stopImgAnalyzerStarter = function () {
  744. this.yield.flushed = 0;
  745. if (this.imgAnalyzerTimeout !== null) {
  746. clearTimeout(this.imgAnalyzerTimeout);
  747. this.imgAnalyzerTimeout = null;
  748. }
  749. };
  750. /**
  751. * Starts the image analyzer. It is not immediately called to let the browser to update the view
  752. *
  753. * @param isForResize specifies if the image analyzer must be called for resizing or not
  754. */
  755. JustifiedGallery.prototype.startImgAnalyzer = function (isForResize) {
  756. var that = this;
  757. this.stopImgAnalyzerStarter();
  758. this.imgAnalyzerTimeout = setTimeout(function () {
  759. that.analyzeImages(isForResize);
  760. }, 0.001); // we can't start it immediately due to a IE different behaviour
  761. };
  762. /**
  763. * Checks if the image is loaded or not using another image object. We cannot use the 'complete' image property,
  764. * because some browsers, with a 404 set complete = true.
  765. *
  766. * @param imageSrc the image src to load
  767. * @param onLoad callback that is called when the image has been loaded
  768. * @param onError callback that is called in case of an error
  769. */
  770. JustifiedGallery.prototype.onImageEvent = function (imageSrc, onLoad, onError) {
  771. if (!onLoad && !onError) return;
  772. var memImage = new Image();
  773. var $memImage = $(memImage);
  774. if (onLoad) {
  775. $memImage.one('load', function () {
  776. $memImage.off('load error');
  777. onLoad(memImage);
  778. });
  779. }
  780. if (onError) {
  781. $memImage.one('error', function () {
  782. $memImage.off('load error');
  783. onError(memImage);
  784. });
  785. }
  786. memImage.src = imageSrc;
  787. };
  788. /**
  789. * Init of Justified Gallery controlled
  790. * It analyzes all the entries starting theirs loading and calling the image analyzer (that works with loaded images)
  791. */
  792. JustifiedGallery.prototype.init = function () {
  793. var imagesToLoad = false, skippedImages = false, that = this;
  794. $.each(this.entries, function (index, entry) {
  795. var $entry = $(entry);
  796. var $image = that.imgFromEntry($entry);
  797. $entry.addClass('jg-entry');
  798. if ($entry.data('jg.loaded') !== true && $entry.data('jg.loaded') !== 'skipped') {
  799. // Link Rel global overwrite
  800. if (that.settings.rel !== null) $entry.attr('rel', that.settings.rel);
  801. // Link Target global overwrite
  802. if (that.settings.target !== null) $entry.attr('target', that.settings.target);
  803. if ($image !== null) {
  804. // Image src
  805. var imageSrc = that.extractImgSrcFromImage($image);
  806. /* If we have the height and the width, we don't wait that the image is loaded,
  807. but we start directly with the justification */
  808. if (that.settings.waitThumbnailsLoad === false || !imageSrc) {
  809. var width = parseFloat($image.attr('width'));
  810. var height = parseFloat($image.attr('height'));
  811. if ($image.prop('tagName') === 'svg') {
  812. width = parseFloat($image[0].getBBox().width);
  813. height = parseFloat($image[0].getBBox().height);
  814. }
  815. if (!isNaN(width) && !isNaN(height)) {
  816. $entry.data('jg.width', width);
  817. $entry.data('jg.height', height);
  818. $entry.data('jg.loaded', 'skipped');
  819. skippedImages = true;
  820. that.startImgAnalyzer(false);
  821. return true; // continue
  822. }
  823. }
  824. $entry.data('jg.loaded', false);
  825. imagesToLoad = true;
  826. // Spinner start
  827. if (!that.isSpinnerActive()) that.startLoadingSpinnerAnimation();
  828. that.onImageEvent(imageSrc, function (loadImg) { // image loaded
  829. $entry.data('jg.width', loadImg.width);
  830. $entry.data('jg.height', loadImg.height);
  831. $entry.data('jg.loaded', true);
  832. that.startImgAnalyzer(false);
  833. }, function () { // image load error
  834. $entry.data('jg.loaded', 'error');
  835. that.startImgAnalyzer(false);
  836. });
  837. } else {
  838. $entry.data('jg.loaded', true);
  839. $entry.data('jg.width', $entry.width() | parseFloat($entry.css('width')) | 1);
  840. $entry.data('jg.height', $entry.height() | parseFloat($entry.css('height')) | 1);
  841. }
  842. }
  843. });
  844. if (!imagesToLoad && !skippedImages) this.startImgAnalyzer(false);
  845. this.checkWidth();
  846. };
  847. /**
  848. * Checks that it is a valid number. If a string is passed it is converted to a number
  849. *
  850. * @param settingContainer the object that contains the setting (to allow the conversion)
  851. * @param settingName the setting name
  852. */
  853. JustifiedGallery.prototype.checkOrConvertNumber = function (settingContainer, settingName) {
  854. if ($.type(settingContainer[settingName]) === 'string') {
  855. settingContainer[settingName] = parseFloat(settingContainer[settingName]);
  856. }
  857. if ($.type(settingContainer[settingName]) === 'number') {
  858. if (isNaN(settingContainer[settingName])) throw 'invalid number for ' + settingName;
  859. } else {
  860. throw settingName + ' must be a number';
  861. }
  862. };
  863. /**
  864. * Checks the sizeRangeSuffixes and, if necessary, converts
  865. * its keys from string (e.g. old settings with 'lt100') to int.
  866. */
  867. JustifiedGallery.prototype.checkSizeRangesSuffixes = function () {
  868. if ($.type(this.settings.sizeRangeSuffixes) !== 'object') {
  869. throw 'sizeRangeSuffixes must be defined and must be an object';
  870. }
  871. var suffixRanges = [];
  872. for (var rangeIdx in this.settings.sizeRangeSuffixes) {
  873. if (this.settings.sizeRangeSuffixes.hasOwnProperty(rangeIdx)) suffixRanges.push(rangeIdx);
  874. }
  875. var newSizeRngSuffixes = { 0: '' };
  876. for (var i = 0; i < suffixRanges.length; i++) {
  877. if ($.type(suffixRanges[i]) === 'string') {
  878. try {
  879. var numIdx = parseInt(suffixRanges[i].replace(/^[a-z]+/, ''), 10);
  880. newSizeRngSuffixes[numIdx] = this.settings.sizeRangeSuffixes[suffixRanges[i]];
  881. } catch (e) {
  882. throw 'sizeRangeSuffixes keys must contains correct numbers (' + e + ')';
  883. }
  884. } else {
  885. newSizeRngSuffixes[suffixRanges[i]] = this.settings.sizeRangeSuffixes[suffixRanges[i]];
  886. }
  887. }
  888. this.settings.sizeRangeSuffixes = newSizeRngSuffixes;
  889. };
  890. /**
  891. * check and convert the maxRowHeight setting
  892. * requires rowHeight to be already set
  893. * TODO: should be always called when only rowHeight is changed
  894. * @return number or null
  895. */
  896. JustifiedGallery.prototype.retrieveMaxRowHeight = function () {
  897. var newMaxRowHeight = null;
  898. var rowHeight = this.settings.rowHeight;
  899. if ($.type(this.settings.maxRowHeight) === 'string') {
  900. if (this.settings.maxRowHeight.match(/^[0-9]+%$/)) {
  901. newMaxRowHeight = rowHeight * parseFloat(this.settings.maxRowHeight.match(/^([0-9]+)%$/)[1]) / 100;
  902. } else {
  903. newMaxRowHeight = parseFloat(this.settings.maxRowHeight);
  904. }
  905. } else if ($.type(this.settings.maxRowHeight) === 'number') {
  906. newMaxRowHeight = this.settings.maxRowHeight;
  907. } else if (this.settings.maxRowHeight === false || this.settings.maxRowHeight == null) {
  908. return null;
  909. } else {
  910. throw 'maxRowHeight must be a number or a percentage';
  911. }
  912. // check if the converted value is not a number
  913. if (isNaN(newMaxRowHeight)) throw 'invalid number for maxRowHeight';
  914. // check values, maxRowHeight must be >= rowHeight
  915. if (newMaxRowHeight < rowHeight) newMaxRowHeight = rowHeight;
  916. return newMaxRowHeight;
  917. };
  918. /**
  919. * Checks the settings
  920. */
  921. JustifiedGallery.prototype.checkSettings = function () {
  922. this.checkSizeRangesSuffixes();
  923. this.checkOrConvertNumber(this.settings, 'rowHeight');
  924. this.checkOrConvertNumber(this.settings, 'margins');
  925. this.checkOrConvertNumber(this.settings, 'border');
  926. this.checkOrConvertNumber(this.settings, 'maxRowsCount');
  927. var lastRowModes = [
  928. 'justify',
  929. 'nojustify',
  930. 'left',
  931. 'center',
  932. 'right',
  933. 'hide'
  934. ];
  935. if (lastRowModes.indexOf(this.settings.lastRow) === -1) {
  936. throw 'lastRow must be one of: ' + lastRowModes.join(', ');
  937. }
  938. this.checkOrConvertNumber(this.settings, 'justifyThreshold');
  939. if (this.settings.justifyThreshold < 0 || this.settings.justifyThreshold > 1) {
  940. throw 'justifyThreshold must be in the interval [0,1]';
  941. }
  942. if ($.type(this.settings.cssAnimation) !== 'boolean') {
  943. throw 'cssAnimation must be a boolean';
  944. }
  945. if ($.type(this.settings.captions) !== 'boolean') throw 'captions must be a boolean';
  946. this.checkOrConvertNumber(this.settings.captionSettings, 'animationDuration');
  947. this.checkOrConvertNumber(this.settings.captionSettings, 'visibleOpacity');
  948. if (this.settings.captionSettings.visibleOpacity < 0 ||
  949. this.settings.captionSettings.visibleOpacity > 1) {
  950. throw 'captionSettings.visibleOpacity must be in the interval [0, 1]';
  951. }
  952. this.checkOrConvertNumber(this.settings.captionSettings, 'nonVisibleOpacity');
  953. if (this.settings.captionSettings.nonVisibleOpacity < 0 ||
  954. this.settings.captionSettings.nonVisibleOpacity > 1) {
  955. throw 'captionSettings.nonVisibleOpacity must be in the interval [0, 1]';
  956. }
  957. this.checkOrConvertNumber(this.settings, 'imagesAnimationDuration');
  958. this.checkOrConvertNumber(this.settings, 'refreshTime');
  959. this.checkOrConvertNumber(this.settings, 'refreshSensitivity');
  960. if ($.type(this.settings.randomize) !== 'boolean') throw 'randomize must be a boolean';
  961. if ($.type(this.settings.selector) !== 'string') throw 'selector must be a string';
  962. if (this.settings.sort !== false && !$.isFunction(this.settings.sort)) {
  963. throw 'sort must be false or a comparison function';
  964. }
  965. if (this.settings.filter !== false && !$.isFunction(this.settings.filter) &&
  966. $.type(this.settings.filter) !== 'string') {
  967. throw 'filter must be false, a string or a filter function';
  968. }
  969. };
  970. /**
  971. * It brings all the indexes from the sizeRangeSuffixes and it orders them. They are then sorted and returned.
  972. * @returns {Array} sorted suffix ranges
  973. */
  974. JustifiedGallery.prototype.retrieveSuffixRanges = function () {
  975. var suffixRanges = [];
  976. for (var rangeIdx in this.settings.sizeRangeSuffixes) {
  977. if (this.settings.sizeRangeSuffixes.hasOwnProperty(rangeIdx)) suffixRanges.push(parseInt(rangeIdx, 10));
  978. }
  979. suffixRanges.sort(function (a, b) { return a > b ? 1 : a < b ? -1 : 0; });
  980. return suffixRanges;
  981. };
  982. /**
  983. * Update the existing settings only changing some of them
  984. *
  985. * @param newSettings the new settings (or a subgroup of them)
  986. */
  987. JustifiedGallery.prototype.updateSettings = function (newSettings) {
  988. // In this case Justified Gallery has been called again changing only some options
  989. this.settings = $.extend({}, this.settings, newSettings);
  990. this.checkSettings();
  991. // As reported in the settings: negative value = same as margins, 0 = disabled
  992. this.border = this.settings.border >= 0 ? this.settings.border : this.settings.margins;
  993. this.maxRowHeight = this.retrieveMaxRowHeight();
  994. this.suffixRanges = this.retrieveSuffixRanges();
  995. };
  996. JustifiedGallery.prototype.defaults = {
  997. sizeRangeSuffixes: {}, /* e.g. Flickr configuration
  998. {
  999. 100: '_t', // used when longest is less than 100px
  1000. 240: '_m', // used when longest is between 101px and 240px
  1001. 320: '_n', // ...
  1002. 500: '',
  1003. 640: '_z',
  1004. 1024: '_b' // used as else case because it is the last
  1005. }
  1006. */
  1007. thumbnailPath: undefined, /* If defined, sizeRangeSuffixes is not used, and this function is used to determine the
  1008. path relative to a specific thumbnail size. The function should accept respectively three arguments:
  1009. current path, width and height */
  1010. rowHeight: 120, // required? required to be > 0?
  1011. maxRowHeight: false, // false or negative value to deactivate. Positive number to express the value in pixels,
  1012. // A string '[0-9]+%' to express in percentage (e.g. 300% means that the row height
  1013. // can't exceed 3 * rowHeight)
  1014. maxRowsCount: 0, // maximum number of rows to be displayed (0 = disabled)
  1015. margins: 1,
  1016. border: -1, // negative value = same as margins, 0 = disabled, any other value to set the border
  1017. lastRow: 'nojustify', // … which is the same as 'left', or can be 'justify', 'center', 'right' or 'hide'
  1018. justifyThreshold: 0.90, /* if row width / available space > 0.90 it will be always justified
  1019. * (i.e. lastRow setting is not considered) */
  1020. waitThumbnailsLoad: true,
  1021. captions: true,
  1022. cssAnimation: true,
  1023. imagesAnimationDuration: 500, // ignored with css animations
  1024. captionSettings: { // ignored with css animations
  1025. animationDuration: 500,
  1026. visibleOpacity: 0.7,
  1027. nonVisibleOpacity: 0.0
  1028. },
  1029. rel: null, // rewrite the rel of each analyzed links
  1030. target: null, // rewrite the target of all links
  1031. extension: /\.[^.\\/]+$/, // regexp to capture the extension of an image
  1032. refreshTime: 200, // time interval (in ms) to check if the page changes its width
  1033. refreshSensitivity: 0, // change in width allowed (in px) without re-building the gallery
  1034. randomize: false,
  1035. rtl: false, // right-to-left mode
  1036. sort: false, /*
  1037. - false: to do not sort
  1038. - function: to sort them using the function as comparator (see Array.prototype.sort())
  1039. */
  1040. filter: false, /*
  1041. - false, null or undefined: for a disabled filter
  1042. - a string: an entry is kept if entry.is(filter string) returns true
  1043. see jQuery's .is() function for further information
  1044. - a function: invoked with arguments (entry, index, array). Return true to keep the entry, false otherwise.
  1045. It follows the specifications of the Array.prototype.filter() function of JavaScript.
  1046. */
  1047. selector: 'a', // The selector that is used to know what are the entries of the gallery
  1048. imgSelector: '> img, > a > img, > svg, > a > svg', // The selector that is used to know what are the images of each entry
  1049. triggerEvent: function (event) { // This is called to trigger events, the default behavior is to call $.trigger
  1050. this.$gallery.trigger(event); // Consider that 'this' is this set to the JustifiedGallery object, so it can
  1051. } // access to fields such as $gallery, useful to trigger events with jQuery.
  1052. };
  1053. /**
  1054. * Justified Gallery plugin for jQuery
  1055. *
  1056. * Events
  1057. * - jg.complete : called when all the gallery has been created
  1058. * - jg.resize : called when the gallery has been resized
  1059. * - jg.rowflush : when a new row appears
  1060. *
  1061. * @param arg the action (or the settings) passed when the plugin is called
  1062. * @returns {*} the object itself
  1063. */
  1064. $.fn.justifiedGallery = function (arg) {
  1065. return this.each(function (index, gallery) {
  1066. var $gallery = $(gallery);
  1067. $gallery.addClass('justified-gallery');
  1068. var controller = $gallery.data('jg.controller');
  1069. if (typeof controller === 'undefined') {
  1070. // Create controller and assign it to the object data
  1071. if (typeof arg !== 'undefined' && arg !== null && $.type(arg) !== 'object') {
  1072. if (arg === 'destroy') return; // Just a call to an unexisting object
  1073. throw 'The argument must be an object';
  1074. }
  1075. controller = new JustifiedGallery($gallery, $.extend({}, JustifiedGallery.prototype.defaults, arg));
  1076. $gallery.data('jg.controller', controller);
  1077. } else if (arg === 'norewind') {
  1078. // In this case we don't rewind: we analyze only the latest images (e.g. to complete the last unfinished row
  1079. // ... left to be more readable
  1080. } else if (arg === 'destroy') {
  1081. controller.destroy();
  1082. return;
  1083. } else {
  1084. // In this case Justified Gallery has been called again changing only some options
  1085. controller.updateSettings(arg);
  1086. controller.rewind();
  1087. }
  1088. // Update the entries list
  1089. if (!controller.updateEntries(arg === 'norewind')) return;
  1090. // Init justified gallery
  1091. controller.init();
  1092. });
  1093. };
  1094. }));