aboutsummaryrefslogtreecommitdiffstats
path: root/library
diff options
context:
space:
mode:
authorredmatrix <redmatrix@redmatrix.me>2015-07-01 18:51:47 -0700
committerredmatrix <redmatrix@redmatrix.me>2015-07-01 18:51:47 -0700
commit54301a9ff518224309664cd8c2f23cba0d30cb2d (patch)
treedca8fa5cbee573d2fc2629f4e6f00eefb0606de1 /library
parent3f38e52caf5ba8d9d3b92ae8c9557276eab62596 (diff)
parente068bdc9d7ce0a0c9068727aa1111c849a8f3984 (diff)
downloadvolse-hubzilla-54301a9ff518224309664cd8c2f23cba0d30cb2d.tar.gz
volse-hubzilla-54301a9ff518224309664cd8c2f23cba0d30cb2d.tar.bz2
volse-hubzilla-54301a9ff518224309664cd8c2f23cba0d30cb2d.zip
Merge https://github.com/redmatrix/redmatrix into pending_merge
Diffstat (limited to 'library')
-rw-r--r--library/justifiedGallery/jquery.justifiedGallery.js1540
-rw-r--r--library/justifiedGallery/jquery.justifiedGallery.min.js7
-rw-r--r--library/justifiedGallery/justifiedGallery.css5
-rw-r--r--library/justifiedGallery/justifiedGallery.min.css7
4 files changed, 987 insertions, 572 deletions
diff --git a/library/justifiedGallery/jquery.justifiedGallery.js b/library/justifiedGallery/jquery.justifiedGallery.js
index 213a7f286..7c63149a3 100644
--- a/library/justifiedGallery/jquery.justifiedGallery.js
+++ b/library/justifiedGallery/jquery.justifiedGallery.js
@@ -1,699 +1,1097 @@
/*!
- * Justified Gallery - v3.5.4
+ * Justified Gallery - v3.6.0
* http://miromannino.github.io/Justified-Gallery/
* Copyright (c) 2015 Miro Mannino
* Licensed under the MIT license.
*/
(function($) {
- /* Events
- jg.complete : called when all the gallery has been created
- jg.resize : called when the gallery has been resized
- */
-
- $.fn.justifiedGallery = function (arg) {
-
- // Default options
- var defaults = {
- sizeRangeSuffixes : {
- 'lt100': '', // e.g. Flickr uses '_t'
- 'lt240': '', // e.g. Flickr uses '_m'
- 'lt320': '', // e.g. Flickr uses '_n'
- 'lt500': '', // e.g. Flickr uses ''
- 'lt640': '', // e.g. Flickr uses '_z'
- 'lt1024': '', // e.g. Flickr uses '_b'
- },
- rowHeight : 120,
- maxRowHeight : 0, // negative value = no limits, 0 = 1.5 * rowHeight
- margins : 1,
- border: -1, // negative value = same as margins, 0 = disabled
-
- lastRow : 'nojustify', // or can be 'justify' or 'hide'
- justifyThreshold: 0.75, /* if row width / available space > 0.75 it will be always justified
- (i.e. lastRow setting is not considered) */
- fixedHeight : false,
- waitThumbnailsLoad : true,
- captions : true,
- cssAnimation: false,
- imagesAnimationDuration : 500, // ignored with css animations
- captionSettings : { // ignored with css animations
- animationDuration : 500,
- visibleOpacity : 0.7,
- nonVisibleOpacity : 0.0
- },
- rel : null, // rewrite the rel of each analyzed links
- target : null, // rewrite the target of all links
- extension : /\.[^.\\/]+$/,
- refreshTime : 100,
- randomize : false
+ /**
+ * Justified Gallery controller constructor
+ *
+ * @param $gallery the gallery to build
+ * @param settings the settings (the defaults are in $.fn.justifiedGallery.defaults)
+ * @constructor
+ */
+ var JustifiedGallery = function ($gallery, settings) {
+
+ this.settings = settings;
+ this.checkSettings();
+
+ this.imgAnalyzerTimeout = null;
+ this.entries = null;
+ this.buildingRow = {
+ entriesBuff : [],
+ width : 0,
+ aspectRatio : 0
+ };
+ this.lastAnalyzedIndex = -1;
+ this.yield = {
+ every : 2, // do a flush every n flushes (must be greater than 1)
+ flushed : 0 // flushed rows without a yield
};
+ this.border = settings.border >= 0 ? settings.border : settings.margins;
+ this.maxRowHeight = this.retrieveMaxRowHeight();
+ this.suffixRanges = this.retrieveSuffixRanges();
+ this.offY = this.border;
+ this.spinner = {
+ phase : 0,
+ timeSlot : 150,
+ $el : $('<div class="spinner"><span></span><span></span><span></span></div>'),
+ intervalId : null
+ };
+ this.checkWidthIntervalId = null;
+ this.galleryWidth = $gallery.width();
+ this.$gallery = $gallery;
- function getSuffix(width, height, context) {
- var longestSide;
- longestSide = (width > height) ? width : height;
- if (longestSide <= 100) {
- return context.settings.sizeRangeSuffixes.lt100;
- } else if (longestSide <= 240) {
- return context.settings.sizeRangeSuffixes.lt240;
- } else if (longestSide <= 320) {
- return context.settings.sizeRangeSuffixes.lt320;
- } else if (longestSide <= 500) {
- return context.settings.sizeRangeSuffixes.lt500;
- } else if (longestSide <= 640) {
- return context.settings.sizeRangeSuffixes.lt640;
- } else {
- return context.settings.sizeRangeSuffixes.lt1024;
+ };
+
+ /** @returns {String} the best suffix given the width and the height */
+ JustifiedGallery.prototype.getSuffix = function (width, height) {
+ var longestSide, i;
+ longestSide = (width > height) ? width : height;
+ for (i = 0; i < this.suffixRanges.length; i++) {
+ if (longestSide <= this.suffixRanges[i]) {
+ return this.settings.sizeRangeSuffixes[this.suffixRanges[i]];
}
}
+ return this.settings.sizeRangeSuffixes[this.suffixRanges[i - 1]];
+ };
+
+ /**
+ * Remove the suffix from the string
+ *
+ * @returns {string} a new string without the suffix
+ */
+ JustifiedGallery.prototype.removeSuffix = function (str, suffix) {
+ return str.substring(0, str.length - suffix.length);
+ };
- function endsWith(str, suffix) {
- return str.indexOf(suffix, str.length - suffix.length) !== -1;
+ /**
+ * @returns {boolean} a boolean to say if the suffix is contained in the str or not
+ */
+ JustifiedGallery.prototype.endsWith = function (str, suffix) {
+ return str.indexOf(suffix, str.length - suffix.length) !== -1;
+ };
+
+ /**
+ * Get the used suffix of a particular url
+ *
+ * @param str
+ * @returns {String} return the used suffix
+ */
+ JustifiedGallery.prototype.getUsedSuffix = function (str) {
+ for (var si in this.settings.sizeRangeSuffixes) {
+ if (this.settings.sizeRangeSuffixes.hasOwnProperty(si)) {
+ if (this.settings.sizeRangeSuffixes[si].length === 0) continue;
+ if (this.endsWith(str, this.settings.sizeRangeSuffixes[si])) return this.settings.sizeRangeSuffixes[si];
+ }
}
+ return '';
+ };
+
+ /**
+ * Given an image src, with the width and the height, returns the new image src with the
+ * best suffix to show the best quality thumbnail.
+ *
+ * @returns {String} the suffix to use
+ */
+ JustifiedGallery.prototype.newSrc = function (imageSrc, imgWidth, imgHeight) {
+ var matchRes = imageSrc.match(this.settings.extension);
+ var ext = (matchRes != null) ? matchRes[0] : '';
+ var newImageSrc = imageSrc.replace(this.settings.extension, '');
+ newImageSrc = this.removeSuffix(newImageSrc, this.getUsedSuffix(newImageSrc));
+ newImageSrc += this.getSuffix(imgWidth, imgHeight) + ext;
+ return newImageSrc;
+ };
- function removeSuffix(str, suffix) {
- return str.substring(0, str.length - suffix.length);
+ /**
+ * Shows the images that is in the given entry
+ *
+ * @param $entry the entry
+ * @param callback the callback that is called when the show animation is finished
+ */
+ JustifiedGallery.prototype.showImg = function ($entry, callback) {
+ if (this.settings.cssAnimation) {
+ $entry.addClass('entry-visible');
+ if (callback) callback();
+ } else {
+ $entry.stop().fadeTo(this.settings.imagesAnimationDuration, 1.0, callback);
}
+ };
+
+ /**
+ * Extract the image src form the image, looking from the 'safe-src', and if it can't be found, from the
+ * 'src' attribute. It saves in the image data the 'jg.originalSrc' field, with the extracted src.
+ *
+ * @param $image the image to analyze
+ * @returns {String} the extracted src
+ */
+ JustifiedGallery.prototype.extractImgSrcFromImage = function ($image) {
+ var imageSrc = (typeof $image.data('safe-src') !== 'undefined') ? $image.data('safe-src') : $image.attr('src');
+ $image.data('jg.originalSrc', imageSrc);
+ return imageSrc;
+ };
+
+ /** @returns {jQuery} the image in the given entry */
+ JustifiedGallery.prototype.imgFromEntry = function ($entry) {
+ var $img = $entry.find('> img');
+ if ($img.length === 0) $img = $entry.find('> a > img');
+ return $img.length === 0 ? null : $img;
+ };
+
+ /** @returns {jQuery} the caption in the given entry */
+ JustifiedGallery.prototype.captionFromEntry = function ($entry) {
+ var $caption = $entry.find('> .caption');
+ return $caption.length === 0 ? null : $caption;
+ };
- function getUsedSuffix(str, context) {
- var voidSuffix = false;
- for (var si in context.settings.sizeRangeSuffixes) {
- if (context.settings.sizeRangeSuffixes[si].length === 0) {
- voidSuffix = true;
- continue;
+ /**
+ * Display the entry
+ *
+ * @param {jQuery} $entry the entry to display
+ * @param {int} x the x position where the entry must be positioned
+ * @param y the y position where the entry must be positioned
+ * @param imgWidth the image width
+ * @param imgHeight the image height
+ * @param rowHeight the row height of the row that owns the entry
+ */
+ JustifiedGallery.prototype.displayEntry = function ($entry, x, y, imgWidth, imgHeight, rowHeight) {
+ $entry.width(imgWidth);
+ $entry.height(rowHeight);
+ $entry.css('top', y);
+ $entry.css('left', x);
+
+ var $image = this.imgFromEntry($entry);
+ if ($image !== null) {
+ $image.css('width', imgWidth);
+ $image.css('height', imgHeight);
+ $image.css('margin-left', - imgWidth / 2);
+ $image.css('margin-top', - imgHeight / 2);
+
+ // Image reloading for an high quality of thumbnails
+ var imageSrc = $image.attr('src');
+ var newImageSrc = this.newSrc(imageSrc, imgWidth, imgHeight);
+
+ $image.one('error', function () {
+ $image.attr('src', $image.data('jg.originalSrc')); //revert to the original thumbnail, we got it.
+ });
+
+ var loadNewImage = function () {
+ if (imageSrc !== newImageSrc) { //load the new image after the fadeIn
+ $image.attr('src', newImageSrc);
}
- if (endsWith(str, context.settings.sizeRangeSuffixes[si])) {
- return context.settings.sizeRangeSuffixes[si];
+ };
+
+ if ($entry.data('jg.loaded') === 'skipped') {
+ this.onImageEvent(imageSrc, $.proxy(function() {
+ this.showImg($entry, loadNewImage);
+ $entry.data('jg.loaded', true);
+ }, this));
+ } else {
+ this.showImg($entry, loadNewImage);
+ }
+
+ } else {
+ this.showImg($entry);
+ }
+
+ this.displayEntryCaption($entry);
+ };
+
+ /**
+ * Display the entry caption. If the caption element doesn't exists, it creates the caption using the 'alt'
+ * or the 'title' attributes.
+ *
+ * @param {jQuery} $entry the entry to process
+ */
+ JustifiedGallery.prototype.displayEntryCaption = function ($entry) {
+ var $image = this.imgFromEntry($entry);
+ if ($image !== null && this.settings.captions) {
+ var $imgCaption = this.captionFromEntry($entry);
+
+ // Create it if it doesn't exists
+ if ($imgCaption == null) {
+ var caption = $image.attr('alt');
+ if (typeof caption === 'undefined') caption = $entry.attr('title');
+ if (typeof caption !== 'undefined') { // Create only we found something
+ $imgCaption = $('<div class="caption">' + caption + '</div>');
+ $entry.append($imgCaption);
+ $entry.data('jg.createdCaption', true);
}
}
- if (voidSuffix) return "";
- else throw 'unknown suffix for ' + str;
+ // Create events (we check again the $imgCaption because it can be still inexistent)
+ if ($imgCaption !== null) {
+ if (!this.settings.cssAnimation) $imgCaption.stop().fadeTo(0, this.settings.captionSettings.nonVisibleOpacity);
+ this.addCaptionEventsHandlers($entry);
+ }
+ } else {
+ this.removeCaptionEventsHandlers($entry);
}
+ };
- /* Given an image src, with the width and the height, returns the new image src with the
- best suffix to show the best quality thumbnail. */
- function newSrc(imageSrc, imgWidth, imgHeight, context) {
- var matchRes = imageSrc.match(context.settings.extension);
- var ext = (matchRes != null) ? matchRes[0] : '';
- var newImageSrc = imageSrc.replace(context.settings.extension, '');
- newImageSrc = removeSuffix(newImageSrc, getUsedSuffix(newImageSrc, context));
- newImageSrc += getSuffix(imgWidth, imgHeight, context) + ext;
- return newImageSrc;
+ /**
+ * The callback for the event 'mouseenter'. It assumes that the event currentTarget is an entry.
+ * It shows the caption using jQuery (or using CSS if it is configured so)
+ *
+ * @param {Event} eventObject the event object
+ */
+ JustifiedGallery.prototype.onEntryMouseEnterForCaption = function (eventObject) {
+ var $caption = this.captionFromEntry($(eventObject.currentTarget));
+ if (this.settings.cssAnimation) {
+ $caption.addClass('caption-visible').removeClass('caption-hidden');
+ } else {
+ $caption.stop().fadeTo(this.settings.captionSettings.animationDuration,
+ this.settings.captionSettings.visibleOpacity);
}
+ };
- function onEntryMouseEnterForCaption (ev) {
- var $caption = $(ev.currentTarget).find('.caption');
- if (ev.data.settings.cssAnimation) {
- $caption.addClass('caption-visible').removeClass('caption-hidden');
- } else {
- $caption.stop().fadeTo(ev.data.settings.captionSettings.animationDuration,
- ev.data.settings.captionSettings.visibleOpacity);
- }
+ /**
+ * The callback for the event 'mouseleave'. It assumes that the event currentTarget is an entry.
+ * It hides the caption using jQuery (or using CSS if it is configured so)
+ *
+ * @param {Event} eventObject the event object
+ */
+ JustifiedGallery.prototype.onEntryMouseLeaveForCaption = function (eventObject) {
+ var $caption = this.captionFromEntry($(eventObject.currentTarget));
+ if (this.settings.cssAnimation) {
+ $caption.removeClass('caption-visible').removeClass('caption-hidden');
+ } else {
+ $caption.stop().fadeTo(this.settings.captionSettings.animationDuration,
+ this.settings.captionSettings.nonVisibleOpacity);
}
+ };
- function onEntryMouseLeaveForCaption (ev) {
- var $caption = $(ev.currentTarget).find('.caption');
- if (ev.data.settings.cssAnimation) {
- $caption.removeClass('caption-visible').removeClass('caption-hidden');
- } else {
- $caption.stop().fadeTo(ev.data.settings.captionSettings.animationDuration,
- ev.data.settings.captionSettings.nonVisibleOpacity);
- }
+ /**
+ * Add the handlers of the entry for the caption
+ *
+ * @param $entry the entry to modify
+ */
+ JustifiedGallery.prototype.addCaptionEventsHandlers = function ($entry) {
+ var captionMouseEvents = $entry.data('jg.captionMouseEvents');
+ if (typeof captionMouseEvents === 'undefined') {
+ captionMouseEvents = {
+ mouseenter: $.proxy(this.onEntryMouseEnterForCaption, this),
+ mouseleave: $.proxy(this.onEntryMouseLeaveForCaption, this)
+ };
+ $entry.on('mouseenter', undefined, undefined, captionMouseEvents.mouseenter);
+ $entry.on('mouseleave', undefined, undefined, captionMouseEvents.mouseleave);
+ $entry.data('jg.captionMouseEvents', captionMouseEvents);
}
+ };
- function showImg($entry, callback, context) {
- if (context.settings.cssAnimation) {
- $entry.addClass('entry-visible');
- callback();
- } else {
- $entry.stop().fadeTo(context.settings.imagesAnimationDuration, 1.0, callback);
+ /**
+ * Remove the handlers of the entry for the caption
+ *
+ * @param $entry the entry to modify
+ */
+ JustifiedGallery.prototype.removeCaptionEventsHandlers = function ($entry) {
+ var captionMouseEvents = $entry.data('jg.captionMouseEvents');
+ if (typeof captionMouseEvents !== 'undefined') {
+ $entry.off('mouseenter', undefined, captionMouseEvents.mouseenter);
+ $entry.off('mouseleave', undefined, captionMouseEvents.mouseleave);
+ $entry.removeData('jg.captionMouseEvents');
+ }
+ };
+
+ /**
+ * Justify the building row, preparing it to
+ *
+ * @param isLastRow
+ * @returns {*}
+ */
+ JustifiedGallery.prototype.prepareBuildingRow = function (isLastRow) {
+ var i, $entry, imgAspectRatio, newImgW, newImgH, justify = true;
+ var minHeight = 0;
+ var availableWidth = this.galleryWidth - 2 * this.border - (
+ (this.buildingRow.entriesBuff.length - 1) * this.settings.margins);
+ var rowHeight = availableWidth / this.buildingRow.aspectRatio;
+ var justifiable = this.buildingRow.width / availableWidth > this.settings.justifyThreshold;
+
+ //Skip the last row if we can't justify it and the lastRow == 'hide'
+ if (isLastRow && this.settings.lastRow === 'hide' && !justifiable) {
+ for (i = 0; i < this.buildingRow.entriesBuff.length; i++) {
+ $entry = this.buildingRow.entriesBuff[i];
+ if (this.settings.cssAnimation)
+ $entry.removeClass('entry-visible');
+ else
+ $entry.stop().fadeTo(0, 0);
}
+ return -1;
}
- function hideImgImmediately($entry, context) {
- if (context.settings.cssAnimation) {
- $entry.removeClass('entry-visible');
+ // With lastRow = nojustify, justify if is justificable (the images will not become too big)
+ if (isLastRow && !justifiable && this.settings.lastRow === 'nojustify') justify = false;
+
+ for (i = 0; i < this.buildingRow.entriesBuff.length; i++) {
+ $entry = this.buildingRow.entriesBuff[i];
+ imgAspectRatio = $entry.data('jg.width') / $entry.data('jg.height');
+
+ if (justify) {
+ newImgW = (i === this.buildingRow.entriesBuff.length - 1) ? availableWidth : rowHeight * imgAspectRatio;
+ newImgH = rowHeight;
+
+ /* With fixedHeight the newImgH must be greater than rowHeight.
+ In some cases here this is not satisfied (due to the justification).
+ But we comment it, because is better to have a shorter but justified row instead
+ to have a cropped image at the end. */
+ /*if (this.settings.fixedHeight && newImgH < this.settings.rowHeight) {
+ newImgW = this.settings.rowHeight * imgAspectRatio;
+ newImgH = this.settings.rowHeight;
+ }*/
+
} else {
- $entry.stop().fadeTo(0, 0);
+ newImgW = this.settings.rowHeight * imgAspectRatio;
+ newImgH = this.settings.rowHeight;
}
+
+ availableWidth -= Math.round(newImgW);
+ $entry.data('jg.jwidth', Math.round(newImgW));
+ $entry.data('jg.jheight', Math.ceil(newImgH));
+ if (i === 0 || minHeight > newImgH) minHeight = newImgH;
}
- function imgFromEntry($entry) {
- var $img = $entry.find('> img');
- if ($img.length === 0) $img = $entry.find('> a > img');
- return $img;
+ if (this.settings.fixedHeight && minHeight > this.settings.rowHeight)
+ minHeight = this.settings.rowHeight;
+
+ return {minHeight: minHeight, justify: justify};
+ };
+
+ /**
+ * Clear the building row data to be used for a new row
+ */
+ JustifiedGallery.prototype.clearBuildingRow = function () {
+ this.buildingRow.entriesBuff = [];
+ this.buildingRow.aspectRatio = 0;
+ this.buildingRow.width = 0;
+ };
+
+ /**
+ * Flush a row: justify it, modify the gallery height accordingly to the row height
+ *
+ * @param isLastRow
+ */
+ JustifiedGallery.prototype.flushRow = function (isLastRow) {
+ var settings = this.settings;
+ var $entry, minHeight, buildingRowRes, offX = this.border;
+
+ buildingRowRes = this.prepareBuildingRow(isLastRow);
+ minHeight = buildingRowRes.minHeight;
+ if (isLastRow && settings.lastRow === 'hide' && minHeight === -1) {
+ this.clearBuildingRow();
+ return;
}
- function displayEntry($entry, x, y, imgWidth, imgHeight, rowHeight, context) {
- var $image = imgFromEntry($entry);
- $image.css('width', imgWidth);
- $image.css('height', imgHeight);
- //if ($entry.get(0) === $image.parent().get(0)) { // this creates an error in link_around_img test
- $image.css('margin-left', - imgWidth / 2);
- $image.css('margin-top', - imgHeight / 2);
- //}
- $entry.width(imgWidth);
- $entry.height(rowHeight);
- $entry.css('top', y);
- $entry.css('left', x);
+ if (this.maxRowHeight.percentage) {
+ if (this.maxRowHeight.value * settings.rowHeight < minHeight) minHeight = this.maxRowHeight.value * settings.rowHeight;
+ } else {
+ if (this.maxRowHeight.value > 0 && this.maxRowHeight.value < minHeight) minHeight = this.maxRowHeight.value;
+ }
- //DEBUG// console.log('displayEntry (w: ' + $image.width() + ' h: ' + $image.height());
+ for (var i = 0; i < this.buildingRow.entriesBuff.length; i++) {
+ $entry = this.buildingRow.entriesBuff[i];
+ this.displayEntry($entry, offX, this.offY, $entry.data('jg.jwidth'), $entry.data('jg.jheight'), minHeight);
+ offX += $entry.data('jg.jwidth') + settings.margins;
+ }
- // Image reloading for an high quality of thumbnails
- var imageSrc = $image.attr('src');
- var newImageSrc = newSrc(imageSrc, imgWidth, imgHeight, context);
+ //Gallery Height
+ this.$gallery.height(this.offY + minHeight + this.border + (this.isSpinnerActive() ? this.getSpinnerHeight() : 0));
- $image.one('error', function () {
- //DEBUG// console.log('revert the original image');
- $image.attr('src', $image.data('jg.originalSrc')); //revert to the original thumbnail, we got it.
- });
+ if (!isLastRow || (minHeight <= this.settings.rowHeight && buildingRowRes.justify)) {
+ //Ready for a new row
+ this.offY += minHeight + this.settings.margins;
+ this.clearBuildingRow();
+ this.$gallery.trigger('jg.rowflush');
+ }
+ };
- function loadNewImage() {
- if (imageSrc !== newImageSrc) { //load the new image after the fadeIn
- $image.attr('src', newImageSrc);
- }
+ /**
+ * Checks the width of the gallery container, to know if a new justification is needed
+ */
+ JustifiedGallery.prototype.checkWidth = function () {
+ this.checkWidthIntervalId = setInterval($.proxy(function () {
+ var galleryWidth = parseInt(this.$gallery.width(), 10);
+ if (this.galleryWidth !== galleryWidth) {
+ this.galleryWidth = galleryWidth;
+ this.rewind();
+
+ // Restart to analyze
+ this.startImgAnalyzer(true);
}
+ }, this), this.settings.refreshTime);
+ };
+
+ /**
+ * @returns {boolean} a boolean saying if the spinner is active or not
+ */
+ JustifiedGallery.prototype.isSpinnerActive = function () {
+ return this.spinner.intervalId != null;
+ };
+
+ /**
+ * @returns {int} the spinner height
+ */
+ JustifiedGallery.prototype.getSpinnerHeight = function () {
+ return this.spinner.$el.innerHeight();
+ };
+
+ /**
+ * Stops the spinner animation and modify the gallery height to exclude the spinner
+ */
+ JustifiedGallery.prototype.stopLoadingSpinnerAnimation = function () {
+ clearInterval(this.spinner.intervalId);
+ this.spinner.intervalId = null;
+ this.$gallery.height(this.$gallery.height() - this.getSpinnerHeight());
+ this.spinner.$el.detach();
+ };
- if ($image.data('jg.loaded') === 'skipped') {
- onImageEvent(imageSrc, function() {
- showImg($entry, loadNewImage, context);
- $image.data('jg.loaded', true);
- });
+ /**
+ * Starts the spinner animation
+ */
+ JustifiedGallery.prototype.startLoadingSpinnerAnimation = function () {
+ var spinnerContext = this.spinner;
+ var $spinnerPoints = spinnerContext.$el.find('span');
+ clearInterval(spinnerContext.intervalId);
+ this.$gallery.append(spinnerContext.$el);
+ this.$gallery.height(this.offY + this.getSpinnerHeight());
+ spinnerContext.intervalId = setInterval(function () {
+ if (spinnerContext.phase < $spinnerPoints.length) {
+ $spinnerPoints.eq(spinnerContext.phase).fadeTo(spinnerContext.timeSlot, 1);
} else {
- showImg($entry, loadNewImage, context);
+ $spinnerPoints.eq(spinnerContext.phase - $spinnerPoints.length).fadeTo(spinnerContext.timeSlot, 0);
}
+ spinnerContext.phase = (spinnerContext.phase + 1) % ($spinnerPoints.length * 2);
+ }, spinnerContext.timeSlot);
+ };
- // Captions ------------------------------
- var captionMouseEvents = $entry.data('jg.captionMouseEvents');
- if (context.settings.captions === true) {
- var $imgCaption = $entry.find('.caption');
- if ($imgCaption.length === 0) { // Create it if it doesn't exists
- var caption = $image.attr('alt');
- if (typeof caption === 'undefined') caption = $entry.attr('title');
- if (typeof caption !== 'undefined') { // Create only we found something
- $imgCaption = $('<div class="caption">' + caption + '</div>');
- $entry.append($imgCaption);
- }
- }
-
- // Create events (we check again the $imgCaption because it can be still inexistent)
- if ($imgCaption.length !== 0) {
- if (!context.settings.cssAnimation) {
- $imgCaption.stop().fadeTo(context.settings.imagesAnimationDuration,
- context.settings.captionSettings.nonVisibleOpacity);
- }
- if (typeof captionMouseEvents === 'undefined') {
- captionMouseEvents = {
- mouseenter: onEntryMouseEnterForCaption,
- mouseleave: onEntryMouseLeaveForCaption
- };
- $entry.on('mouseenter', undefined, context, captionMouseEvents.mouseenter);
- $entry.on('mouseleave', undefined, context, captionMouseEvents.mouseleave);
- $entry.data('jg.captionMouseEvents', captionMouseEvents);
- }
- }
+ /**
+ * Rewind the image analysis to start from the first entry.
+ */
+ JustifiedGallery.prototype.rewind = function () {
+ this.lastAnalyzedIndex = -1;
+ this.offY = this.border;
+ this.clearBuildingRow();
+ };
+
+ /**
+ * Hide the image of the buildingRow to prevent strange effects when the row will be
+ * re-justified again
+ */
+ JustifiedGallery.prototype.hideBuildingRowImages = function () {
+ for (var i = 0; i < this.buildingRow.entriesBuff.length; i++) {
+ if (this.settings.cssAnimation) {
+ this.buildingRow.entriesBuff[i].removeClass('entry-visible');
} else {
- if (typeof captionMouseEvents !== 'undefined') {
- $entry.off('mouseenter', undefined, context, captionMouseEvents.mouseenter);
- $entry.off('mouseleave', undefined, context, captionMouseEvents.mouseleave);
- $entry.removeData('jg.captionMouseEvents');
- }
+ this.buildingRow.entriesBuff[i].stop().fadeTo(0, 0);
}
+ }
+ };
+ /**
+ * Update the entries searching it from the justified gallery HTML element
+ *
+ * @param norewind if norewind only the new entries will be changed (i.e. randomized, sorted or filtered)
+ * @returns {boolean} true if some entries has been founded
+ */
+ JustifiedGallery.prototype.updateEntries = function (norewind) {
+ this.entries = this.$gallery.find(this.settings.selector).toArray();
+ if (this.entries.length === 0) return false;
+
+ // Filter
+ if (this.settings.filter) {
+ this.modifyEntries(this.filterArray, norewind);
+ } else {
+ this.modifyEntries(this.resetFilters, norewind);
}
- function prepareBuildingRow(context, isLastRow) {
- var settings = context.settings;
- var i, $entry, $image, imgAspectRatio, newImgW, newImgH, justify = true;
- var minHeight = 0;
- var availableWidth = context.galleryWidth - 2 * context.border - (
- (context.buildingRow.entriesBuff.length - 1) * settings.margins);
- var rowHeight = availableWidth / context.buildingRow.aspectRatio;
- var justificable = context.buildingRow.width / availableWidth > settings.justifyThreshold;
-
- //Skip the last row if we can't justify it and the lastRow == 'hide'
- if (isLastRow && settings.lastRow === 'hide' && !justificable) {
- for (i = 0; i < context.buildingRow.entriesBuff.length; i++) {
- $entry = context.buildingRow.entriesBuff[i];
- if (settings.cssAnimation)
- $entry.removeClass('entry-visible');
- else
- $entry.stop().fadeTo(0, 0);
- }
- return -1;
- }
+ // Sort or randomize
+ if ($.isFunction(this.settings.sort)) {
+ this.modifyEntries(this.sortArray, norewind);
+ } else if (this.settings.randomize) {
+ this.modifyEntries(this.shuffleArray, norewind);
+ }
- // With lastRow = nojustify, justify if is justificable (the images will not become too big)
- if (isLastRow && !justificable && settings.lastRow === 'nojustify') justify = false;
+ return true;
+ };
+
+ /**
+ * Apply the entries order to the DOM, iterating the entries and appending the images
+ *
+ * @param entries the entries that has been modified and that must be re-ordered in the DOM
+ */
+ JustifiedGallery.prototype.insertToGallery = function (entries) {
+ var that = this;
+ $.each(entries, function () {
+ $(this).appendTo(that.$gallery);
+ });
+ };
- for (i = 0; i < context.buildingRow.entriesBuff.length; i++) {
- $image = imgFromEntry(context.buildingRow.entriesBuff[i]);
- imgAspectRatio = $image.data('jg.imgw') / $image.data('jg.imgh');
+ /**
+ * Shuffle the array using the Fisher-Yates shuffle algorithm
+ *
+ * @param a the array to shuffle
+ * @return the shuffled array
+ */
+ JustifiedGallery.prototype.shuffleArray = function (a) {
+ var i, j, temp;
+ for (i = a.length - 1; i > 0; i--) {
+ j = Math.floor(Math.random() * (i + 1));
+ temp = a[i];
+ a[i] = a[j];
+ a[j] = temp;
+ }
+ this.insertToGallery(a);
+ return a;
+ };
- if (justify) {
- newImgW = (i === context.buildingRow.entriesBuff.length - 1) ? availableWidth
- : rowHeight * imgAspectRatio;
- newImgH = rowHeight;
+ /**
+ * Sort the array using settings.comparator as comparator
+ *
+ * @param a the array to sort (it is sorted)
+ * @return the sorted array
+ */
+ JustifiedGallery.prototype.sortArray = function (a) {
+ a.sort(this.settings.sort);
+ this.insertToGallery(a);
+ return a;
+ };
- /* With fixedHeight the newImgH must be greater than rowHeight.
- In some cases here this is not satisfied (due to the justification).
- But we comment it, because is better to have a shorter but justified row instead
- to have a cropped image at the end. */
- /*if (settings.fixedHeight && newImgH < settings.rowHeight) {
- newImgW = settings.rowHeight * imgAspectRatio;
- newImgH = settings.rowHeight;
- }*/
+ /**
+ * Reset the filters removing the 'jg-filtered' class from all the entries
+ *
+ * @param a the array to reset
+ */
+ JustifiedGallery.prototype.resetFilters = function (a) {
+ for (var i = 0; i < a.length; i++) $(a[i]).removeClass('jg-filtered');
+ return a;
+ };
+ /**
+ * Filter the entries considering theirs classes (if a string has been passed) or using a function for filtering.
+ *
+ * @param a the array to filter
+ * @return the filtered array
+ */
+ JustifiedGallery.prototype.filterArray = function (a) {
+ var settings = this.settings;
+ if ($.type(settings.filter) === 'string') {
+ // Filter only keeping the entries passed in the string
+ return a.filter(function (el) {
+ var $el = $(el);
+ if ($el.is(settings.filter)) {
+ $el.removeClass('jg-filtered');
+ return true;
} else {
- newImgW = settings.rowHeight * imgAspectRatio;
- newImgH = settings.rowHeight;
+ $el.addClass('jg-filtered');
+ return false;
}
+ });
+ } else if ($.isFunction(settings.filter)) {
+ // Filter using the passed function
+ return a.filter(settings.filter);
+ }
+ };
- availableWidth -= Math.round(newImgW);
- $image.data('jg.jimgw', Math.round(newImgW));
- $image.data('jg.jimgh', Math.ceil(newImgH));
- if (i === 0 || minHeight > newImgH) minHeight = newImgH;
- }
+ /**
+ * Modify the entries. With norewind only the new inserted images will be modified (the ones after lastAnalyzedIndex)
+ *
+ * @param functionToApply the function to call to modify the entries (e.g. sorting, randomization, filtering)
+ * @param norewind specify if the norewind has been called or not
+ */
+ JustifiedGallery.prototype.modifyEntries = function (functionToApply, norewind) {
+ var lastEntries = norewind ?
+ this.entries.splice(this.lastAnalyzedIndex + 1, this.entries.length - this.lastAnalyzedIndex - 1)
+ : this.entries;
+ lastEntries = functionToApply.call(this, lastEntries);
+ this.entries = norewind ? this.entries.concat(lastEntries) : lastEntries;
+ };
- if (settings.fixedHeight && minHeight > settings.rowHeight)
- minHeight = settings.rowHeight;
+ /**
+ * Destroy the Justified Gallery instance.
+ *
+ * It clears all the css properties added in the style attributes. We doesn't backup the original
+ * values for those css attributes, because it costs (performance) and because in general one
+ * shouldn't use the style attribute for an uniform set of images (where we suppose the use of
+ * classes). Creating a backup is also difficult because JG could be called multiple times and
+ * with different style attributes.
+ */
+ JustifiedGallery.prototype.destroy = function () {
+ clearInterval(this.checkWidthIntervalId);
+
+ $.each(this.entries, $.proxy(function(_, entry) {
+ var $entry = $(entry);
+
+ // Reset entry style
+ $entry.css('width', '');
+ $entry.css('height', '');
+ $entry.css('top', '');
+ $entry.css('left', '');
+ $entry.data('jg.loaded', undefined);
+ $entry.removeClass('jg-entry');
+
+ // Reset image style
+ var $img = this.imgFromEntry($entry);
+ $img.css('width', '');
+ $img.css('height', '');
+ $img.css('margin-left', '');
+ $img.css('margin-top', '');
+ $img.attr('src', $img.data('jg.originalSrc'));
+ $img.data('jg.originalSrc', undefined);
+
+ // Remove caption
+ this.removeCaptionEventsHandlers($entry);
+ var $caption = this.captionFromEntry($entry);
+ if ($entry.data('jg.createdCaption')) {
+ // remove also the caption element (if created by jg)
+ $entry.data('jg.createdCaption', undefined);
+ if ($caption != null) $caption.remove();
+ } else {
+ if ($caption != null) $caption.fadeTo(0, 1);
+ }
- return {minHeight: minHeight, justify: justify};
- }
+ }, this));
- function rewind(context) {
- context.lastAnalyzedIndex = -1;
- context.buildingRow.entriesBuff = [];
- context.buildingRow.aspectRatio = 0;
- context.buildingRow.width = 0;
- context.offY = context.border;
- }
+ this.$gallery.css('height', '');
+ this.$gallery.removeClass('justified-gallery');
+ this.$gallery.data('jg.controller', undefined);
+ };
- function flushRow(context, isLastRow) {
- var settings = context.settings;
- var $entry, $image, minHeight, buildingRowRes, offX = context.border;
+ /**
+ * Analyze the images and builds the rows. It returns if it found an image that is not loaded.
+ *
+ * @param isForResize if the image analyzer is called for resizing or not, to call a different callback at the end
+ */
+ JustifiedGallery.prototype.analyzeImages = function (isForResize) {
+ for (var i = this.lastAnalyzedIndex + 1; i < this.entries.length; i++) {
+ var $entry = $(this.entries[i]);
+ if ($entry.data('jg.loaded') === true || $entry.data('jg.loaded') === 'skipped') {
+ var availableWidth = this.galleryWidth - 2 * this.border - (
+ (this.buildingRow.entriesBuff.length - 1) * this.settings.margins);
+ var imgAspectRatio = $entry.data('jg.width') / $entry.data('jg.height');
+ if (availableWidth / (this.buildingRow.aspectRatio + imgAspectRatio) < this.settings.rowHeight) {
+ this.flushRow(false);
+ if(++this.yield.flushed >= this.yield.every) {
+ this.startImgAnalyzer(isForResize);
+ return;
+ }
+ }
- //DEBUG// console.log('flush (isLastRow: ' + isLastRow + ')');
+ this.buildingRow.entriesBuff.push($entry);
+ this.buildingRow.aspectRatio += imgAspectRatio;
+ this.buildingRow.width += imgAspectRatio * this.settings.rowHeight;
+ this.lastAnalyzedIndex = i;
- buildingRowRes = prepareBuildingRow(context, isLastRow);
- minHeight = buildingRowRes.minHeight;
- if (isLastRow && settings.lastRow === 'hide' && minHeight === -1) {
- context.buildingRow.entriesBuff = [];
- context.buildingRow.aspectRatio = 0;
- context.buildingRow.width = 0;
+ } else if ($entry.data('jg.loaded') !== 'error') {
return;
}
+ }
- if (settings.maxRowHeight > 0 && settings.maxRowHeight < minHeight)
- minHeight = settings.maxRowHeight;
- else if (settings.maxRowHeight === 0 && (1.5 * settings.rowHeight) < minHeight)
- minHeight = 1.5 * settings.rowHeight;
-
- for (var i = 0; i < context.buildingRow.entriesBuff.length; i++) {
- $entry = context.buildingRow.entriesBuff[i];
- $image = imgFromEntry($entry);
- displayEntry($entry, offX, context.offY, $image.data('jg.jimgw'),
- $image.data('jg.jimgh'), minHeight, context);
- offX += $image.data('jg.jimgw') + settings.margins;
- }
+ // Last row flush (the row is not full)
+ if (this.buildingRow.entriesBuff.length > 0) this.flushRow(true);
- //Gallery Height
- context.$gallery.height(context.offY + minHeight + context.border +
- (context.spinner.active ? context.spinner.$el.innerHeight() : 0)
- );
+ if (this.isSpinnerActive()) {
+ this.stopLoadingSpinnerAnimation();
+ }
- if (!isLastRow || (minHeight <= context.settings.rowHeight && buildingRowRes.justify)) {
- //Ready for a new row
- context.offY += minHeight + context.settings.margins;
+ /* Stop, if there is, the timeout to start the analyzeImages.
+ This is because an image can be set loaded, and the timeout can be set,
+ but this image can be analyzed yet.
+ */
+ this.stopImgAnalyzerStarter();
- //DEBUG// console.log('minHeight: ' + minHeight + ' offY: ' + context.offY);
+ //On complete callback
+ this.$gallery.trigger(isForResize ? 'jg.resize' : 'jg.complete');
+ };
- context.buildingRow.entriesBuff = []; //clear the array creating a new one
- context.buildingRow.aspectRatio = 0;
- context.buildingRow.width = 0;
- context.$gallery.trigger('jg.rowflush');
- }
+ /**
+ * Stops any ImgAnalyzer starter (that has an assigned timeout)
+ */
+ JustifiedGallery.prototype.stopImgAnalyzerStarter = function () {
+ this.yield.flushed = 0;
+ if (this.imgAnalyzerTimeout !== null) clearTimeout(this.imgAnalyzerTimeout);
+ };
+
+ /**
+ * Starts the image analyzer. It is not immediately called to let the browser to update the view
+ *
+ * @param isForResize specifies if the image analyzer must be called for resizing or not
+ */
+ JustifiedGallery.prototype.startImgAnalyzer = function (isForResize) {
+ var that = this;
+ this.stopImgAnalyzerStarter();
+ this.imgAnalyzerTimeout = setTimeout(function () {
+ that.analyzeImages(isForResize);
+ }, 0.001); // we can't start it immediately due to a IE different behaviour
+ };
+
+ /**
+ * Checks if the image is loaded or not using another image object. We cannot use the 'complete' image property,
+ * because some browsers, with a 404 set complete = true.
+ *
+ * @param imageSrc the image src to load
+ * @param onLoad callback that is called when the image has been loaded
+ * @param onError callback that is called in case of an error
+ */
+ JustifiedGallery.prototype.onImageEvent = function (imageSrc, onLoad, onError) {
+ if (!onLoad && !onError) return;
+
+ var memImage = new Image();
+ var $memImage = $(memImage);
+ if (onLoad) {
+ $memImage.one('load', function () {
+ $memImage.off('load error');
+ onLoad(memImage);
+ });
}
+ if (onError) {
+ $memImage.one('error', function() {
+ $memImage.off('load error');
+ onError(memImage);
+ });
+ }
+ memImage.src = imageSrc;
+ };
- function checkWidth(context) {
- context.checkWidthIntervalId = setInterval(function () {
- var galleryWidth = parseInt(context.$gallery.width(), 10);
- if (context.galleryWidth !== galleryWidth) {
- //DEBUG// console.log("resize. old: " + context.galleryWidth + " new: " + galleryWidth);
-
- context.galleryWidth = galleryWidth;
- rewind(context);
+ /**
+ * Init of Justified Gallery controlled
+ * It analyzes all the entries starting theirs loading and calling the image analyzer (that works with loaded images)
+ */
+ JustifiedGallery.prototype.init = function () {
+ var imagesToLoad = false, skippedImages = false, that = this;
+ $.each(this.entries, function (index, entry) {
+ var $entry = $(entry);
+ var $image = that.imgFromEntry($entry);
- // Restart to analyze
- startImgAnalyzer(context, true);
- }
- }, context.settings.refreshTime);
- }
-
- function startLoadingSpinnerAnimation(spinnerContext) {
- clearInterval(spinnerContext.intervalId);
- spinnerContext.intervalId = setInterval(function () {
- if (spinnerContext.phase < spinnerContext.$points.length)
- spinnerContext.$points.eq(spinnerContext.phase).fadeTo(spinnerContext.timeslot, 1);
- else
- spinnerContext.$points.eq(spinnerContext.phase - spinnerContext.$points.length)
- .fadeTo(spinnerContext.timeslot, 0);
- spinnerContext.phase = (spinnerContext.phase + 1) % (spinnerContext.$points.length * 2);
- }, spinnerContext.timeslot);
- }
-
- function stopLoadingSpinnerAnimation(spinnerContext) {
- clearInterval(spinnerContext.intervalId);
- spinnerContext.intervalId = null;
- }
-
- function stopImgAnalyzerStarter(context) {
- context.yield.flushed = 0;
- if (context.imgAnalyzerTimeout !== null) clearTimeout(context.imgAnalyzerTimeout);
- }
-
- function startImgAnalyzer(context, isForResize) {
- stopImgAnalyzerStarter(context);
- context.imgAnalyzerTimeout = setTimeout(function () {
- analyzeImages(context, isForResize);
- }, 0.001);
- analyzeImages(context, isForResize);
- }
-
- function analyzeImages(context, isForResize) {
-
- /* //DEBUG//
- var rnd = parseInt(Math.random() * 10000, 10);
- console.log('analyzeImages ' + rnd + ' start');
- console.log('images status: ');
- for (var i = 0; i < context.entries.length; i++) {
- var $entry = $(context.entries[i]);
- var $image = imgFromEntry($entry);
- console.log(i + ' (alt: ' + $image.attr('alt') + 'loaded: ' + $image.data('jg.loaded') + ')');
- }*/
-
- /* The first row */
- var settings = context.settings;
- var isLastRow;
-
- for (var i = context.lastAnalyzedIndex + 1; i < context.entries.length; i++) {
- var $entry = $(context.entries[i]);
- var $image = imgFromEntry($entry);
-
- if ($image.data('jg.loaded') === true || $image.data('jg.loaded') === 'skipped') {
- isLastRow = i >= context.entries.length - 1;
-
- var availableWidth = context.galleryWidth - 2 * context.border - (
- (context.buildingRow.entriesBuff.length - 1) * settings.margins);
- var imgAspectRatio = $image.data('jg.imgw') / $image.data('jg.imgh');
- if (availableWidth / (context.buildingRow.aspectRatio + imgAspectRatio) < settings.rowHeight) {
- flushRow(context, isLastRow);
- if(++context.yield.flushed >= context.yield.every) {
- //DEBUG// console.log("yield");
- startImgAnalyzer(context, isForResize);
- return;
- }
- }
+ $entry.addClass('jg-entry');
- context.buildingRow.entriesBuff.push($entry);
- context.buildingRow.aspectRatio += imgAspectRatio;
- context.buildingRow.width += imgAspectRatio * settings.rowHeight;
- context.lastAnalyzedIndex = i;
+ if ($entry.data('jg.loaded') !== true && $entry.data('jg.loaded') !== 'skipped') {
- } else if ($image.data('jg.loaded') !== 'error') {
- return;
- }
- }
+ // Link Rel global overwrite
+ if (that.settings.rel !== null) $entry.attr('rel', that.settings.rel);
- // Last row flush (the row is not full)
- if (context.buildingRow.entriesBuff.length > 0) flushRow(context, true);
+ // Link Target global overwrite
+ if (that.settings.target !== null) $entry.attr('target', that.settings.target);
- if (context.spinner.active) {
- context.spinner.active = false;
- context.$gallery.height(context.$gallery.height() - context.spinner.$el.innerHeight());
- context.spinner.$el.detach();
- stopLoadingSpinnerAnimation(context.spinner);
- }
+ if ($image !== null) {
- /* Stop, if there is, the timeout to start the analyzeImages.
- This is because an image can be set loaded, and the timeout can be set,
- but this image can be analyzed yet.
- */
- stopImgAnalyzerStarter(context);
+ // Image src
+ var imageSrc = that.extractImgSrcFromImage($image);
+ $image.attr('src', imageSrc);
- //On complete callback
- if (!isForResize)
- context.$gallery.trigger('jg.complete');
- else
- context.$gallery.trigger('jg.resize');
+ /* If we have the height and the width, we don't wait that the image is loaded, but we start directly
+ * with the justification */
+ if (that.settings.waitThumbnailsLoad === false) {
+ var width = parseInt($image.attr('width'), 10);
+ var height = parseInt($image.attr('height'), 10);
+ if (!isNaN(width) && !isNaN(height)) {
+ $entry.data('jg.width', width);
+ $entry.data('jg.height', height);
+ $entry.data('jg.loaded', 'skipped');
+ skippedImages = true;
+ that.startImgAnalyzer(false);
+ return true; // continue
+ }
+ }
- //DEBUG// console.log('analyzeImages ' + rnd + ' end');
- }
+ $entry.data('jg.loaded', false);
+ imagesToLoad = true;
- function checkSettings (context) {
- var settings = context.settings;
+ // Spinner start
+ if (!that.isSpinnerActive()) {
+ that.startLoadingSpinnerAnimation();
+ }
- function checkSuffixesRange(range) {
- if (typeof settings.sizeRangeSuffixes[range] !== 'string')
- throw 'sizeRangeSuffixes.' + range + ' must be a string';
- }
+ that.onImageEvent(imageSrc, function (loadImg) { // image loaded
+ $entry.data('jg.width', loadImg.width);
+ $entry.data('jg.height', loadImg.height);
+ $entry.data('jg.loaded', true);
+ that.startImgAnalyzer(false);
+ }, function () { // image load error
+ $entry.data('jg.loaded', 'error');
+ that.startImgAnalyzer(false);
+ });
- function checkOrConvertNumber(parent, settingName) {
- if (typeof parent[settingName] === 'string') {
- parent[settingName] = parseFloat(parent[settingName], 10);
- if (isNaN(parent[settingName])) throw 'invalid number for ' + settingName;
- } else if (typeof parent[settingName] === 'number') {
- if (isNaN(parent[settingName])) throw 'invalid number for ' + settingName;
} else {
- throw settingName + ' must be a number';
+ $entry.data('jg.loaded', true);
+ $entry.data('jg.width', $entry.width() | $entry.css('width') | 1);
+ $entry.data('jg.height', $entry.height() | $entry.css('height') | 1);
}
- }
- if (typeof settings.sizeRangeSuffixes !== 'object')
- throw 'sizeRangeSuffixes must be defined and must be an object';
+ }
- checkSuffixesRange('lt100');
- checkSuffixesRange('lt240');
- checkSuffixesRange('lt320');
- checkSuffixesRange('lt500');
- checkSuffixesRange('lt640');
- checkSuffixesRange('lt1024');
+ });
- checkOrConvertNumber(settings, 'rowHeight');
- checkOrConvertNumber(settings, 'maxRowHeight');
+ if (!imagesToLoad && !skippedImages) this.startImgAnalyzer(false);
+ this.checkWidth();
+ };
- if (settings.maxRowHeight > 0 &&
- settings.maxRowHeight < settings.rowHeight) {
- settings.maxRowHeight = settings.rowHeight;
- }
-
- checkOrConvertNumber(settings, 'margins');
- checkOrConvertNumber(settings, 'border');
-
- if (settings.lastRow !== 'nojustify' &&
- settings.lastRow !== 'justify' &&
- settings.lastRow !== 'hide') {
- throw 'lastRow must be "nojustify", "justify" or "hide"';
- }
+ /**
+ * Checks that it is a valid number. If a string is passed it is converted to a number
+ *
+ * @param settingContainer the object that contains the setting (to allow the conversion)
+ * @param settingName the setting name
+ */
+ JustifiedGallery.prototype.checkOrConvertNumber = function (settingContainer, settingName) {
+ if ($.type(settingContainer[settingName]) === 'string') {
+ settingContainer[settingName] = parseFloat(settingContainer[settingName]);
+ }
- checkOrConvertNumber(settings, 'justifyThreshold');
- if (settings.justifyThreshold < 0 || settings.justifyThreshold > 1)
- throw 'justifyThreshold must be in the interval [0,1]';
- if (typeof settings.cssAnimation !== 'boolean') {
- throw 'cssAnimation must be a boolean';
- }
-
- checkOrConvertNumber(settings.captionSettings, 'animationDuration');
- checkOrConvertNumber(settings, 'imagesAnimationDuration');
+ if ($.type(settingContainer[settingName]) === 'number') {
+ if (isNaN(settingContainer[settingName])) throw 'invalid number for ' + settingName;
+ } else {
+ throw settingName + ' must be a number';
+ }
+ };
- checkOrConvertNumber(settings.captionSettings, 'visibleOpacity');
- if (settings.captionSettings.visibleOpacity < 0 || settings.captionSettings.visibleOpacity > 1)
- throw 'captionSettings.visibleOpacity must be in the interval [0, 1]';
+ /**
+ * Checks the sizeRangeSuffixes and, if necessary, converts
+ * its keys from string (e.g. old settings with 'lt100') to int.
+ */
+ JustifiedGallery.prototype.checkSizeRangesSuffixes = function () {
+ if ($.type(this.settings.sizeRangeSuffixes) !== 'object') {
+ throw 'sizeRangeSuffixes must be defined and must be an object';
+ }
- checkOrConvertNumber(settings.captionSettings, 'nonVisibleOpacity');
- if (settings.captionSettings.visibleOpacity < 0 || settings.captionSettings.visibleOpacity > 1)
- throw 'captionSettings.nonVisibleOpacity must be in the interval [0, 1]';
+ var suffixRanges = [];
+ for (var rangeIdx in this.settings.sizeRangeSuffixes) {
+ if (this.settings.sizeRangeSuffixes.hasOwnProperty(rangeIdx)) suffixRanges.push(rangeIdx);
+ }
- if (typeof settings.fixedHeight !== 'boolean') {
- throw 'fixedHeight must be a boolean';
+ var newSizeRngSuffixes = {0: ''};
+ for (var i = 0; i < suffixRanges.length; i++) {
+ if ($.type(suffixRanges[i]) === 'string') {
+ try {
+ var numIdx = parseInt(suffixRanges[i].replace(/^[a-z]+/, ''), 10);
+ newSizeRngSuffixes[numIdx] = this.settings.sizeRangeSuffixes[suffixRanges[i]];
+ } catch (e) {
+ throw 'sizeRangeSuffixes keys must contains correct numbers (' + e + ')';
+ }
+ } else {
+ newSizeRngSuffixes[suffixRanges[i]] = this.settings.sizeRangeSuffixes[suffixRanges[i]];
}
+ }
- if (typeof settings.captions !== 'boolean') {
- throw 'captions must be a boolean';
- }
+ this.settings.sizeRangeSuffixes = newSizeRngSuffixes;
+ };
- checkOrConvertNumber(settings, 'refreshTime');
+ /**
+ * check and convert the maxRowHeight setting
+ */
+ JustifiedGallery.prototype.retrieveMaxRowHeight = function () {
+ var newMaxRowHeight = { };
- if (typeof settings.randomize !== 'boolean') {
- throw 'randomize must be a boolean';
+ if ($.type(this.settings.maxRowHeight) === 'string') {
+ if (this.settings.maxRowHeight.match(/^[0-9]+%$/)) {
+ newMaxRowHeight.value = parseFloat(this.settings.maxRowHeight.match(/^([0-9])+%$/)[1]) / 100;
+ newMaxRowHeight.percentage = false;
+ } else {
+ newMaxRowHeight.value = parseFloat(this.settings.maxRowHeight);
+ newMaxRowHeight.percentage = true;
}
-
+ } else if ($.type(this.settings.maxRowHeight) === 'number') {
+ newMaxRowHeight.value = this.settings.maxRowHeight;
+ newMaxRowHeight.percentage = false;
+ } else {
+ throw 'maxRowHeight must be a number or a percentage';
}
- function onImageEvent(imageSrc, onLoad, onError) {
- if (!onLoad && !onError) {
- return;
- }
- /* Check if the image is loaded or not using another image object.
- We cannot use the 'complete' image property, because some browsers,
- with a 404 set complete = true */
- var memImage = new Image();
- var $memImage = $(memImage);
- if (onLoad) {
- $memImage.one('load', function () {
- $memImage.off('load error');
- onLoad(memImage);
- });
- }
- if (onError) {
- $memImage.one('error', function() {
- $memImage.off('load error');
- onError(memImage);
- });
+ // check if the converted value is not a number
+ if (isNaN(newMaxRowHeight.value)) throw 'invalid number for maxRowHeight';
+
+ // check values
+ if (newMaxRowHeight.percentage) {
+ if (newMaxRowHeight.value < 100) newMaxRowHeight.value = 100;
+ } else {
+ if (newMaxRowHeight.value > 0 && newMaxRowHeight.value < this.settings.rowHeight) {
+ newMaxRowHeight.value = this.settings.rowHeight;
}
- memImage.src = imageSrc;
}
- return this.each(function (index, gallery) {
+ return newMaxRowHeight;
- var $gallery = $(gallery);
- $gallery.addClass('justified-gallery');
+ };
- var context = $gallery.data('jg.context');
- if (typeof context === 'undefined') {
+ /**
+ * Checks the settings
+ */
+ JustifiedGallery.prototype.checkSettings = function () {
+ this.checkSizeRangesSuffixes();
- if (typeof arg !== 'undefined' && arg !== null && typeof arg !== 'object')
- throw 'The argument must be an object';
+ this.checkOrConvertNumber(this.settings, 'rowHeight');
+ this.checkOrConvertNumber(this.settings, 'margins');
+ this.checkOrConvertNumber(this.settings, 'border');
- // Spinner init
- var $spinner = $('<div class="spinner"><span></span><span></span><span></span></div>');
- var extendedSettings = $.extend({}, defaults, arg);
-
- var border = extendedSettings.border >= 0 ? extendedSettings.border : extendedSettings.margins;
-
- //Context init
- context = {
- settings : extendedSettings,
- imgAnalyzerTimeout : null,
- entries : null,
- buildingRow : {
- entriesBuff : [],
- width : 0,
- aspectRatio : 0
- },
- lastAnalyzedIndex : -1,
- yield : {
- every : 2, /* do a flush every context.yield.every flushes (
- * must be greater than 1, else the analyzeImages will loop */
- flushed : 0 //flushed rows without a yield
- },
- border : border,
- offY : border,
- spinner : {
- active : false,
- phase : 0,
- timeslot : 150,
- $el : $spinner,
- $points : $spinner.find('span'),
- intervalId : null
- },
- checkWidthIntervalId : null,
- galleryWidth : $gallery.width(),
- $gallery : $gallery
- };
-
- $gallery.data('jg.context', context);
+ if (this.settings.lastRow !== 'nojustify' &&
+ this.settings.lastRow !== 'justify' &&
+ this.settings.lastRow !== 'hide') {
+ throw 'lastRow must be "nojustify", "justify" or "hide"';
+ }
- } else if (arg === 'norewind') {
- /* Hide the image of the buildingRow to prevent strange effects when the row will be
- re-justified again */
- for (var i = 0; i < context.buildingRow.entriesBuff.length; i++) {
- hideImgImmediately(context.buildingRow.entriesBuff[i], context);
- }
- // In this case we don't rewind, and analyze all the images
- } else {
- context.settings = $.extend({}, context.settings, arg);
- context.border = context.settings.border >= 0 ? context.settings.border : context.settings.margins;
- rewind(context);
- }
-
- checkSettings(context);
-
- context.entries = $gallery.find('> a, > div:not(.spinner, #page-end)').toArray();
- if (context.entries.length === 0) return;
-
- // Randomize
- if (context.settings.randomize) {
- context.entries.sort(function () { return Math.random() * 2 - 1; });
- $.each(context.entries, function () {
- $(this).appendTo($gallery);
- });
- }
+ this.checkOrConvertNumber(this.settings, 'justifyThreshold');
+ if (this.settings.justifyThreshold < 0 || this.settings.justifyThreshold > 1) {
+ throw 'justifyThreshold must be in the interval [0,1]';
+ }
+ if ($.type(this.settings.cssAnimation) !== 'boolean') {
+ throw 'cssAnimation must be a boolean';
+ }
- var imagesToLoad = false;
- var skippedImages = false;
- $.each(context.entries, function (index, entry) {
- var $entry = $(entry);
- var $image = imgFromEntry($entry);
+ if ($.type(this.settings.captions) !== 'boolean') throw 'captions must be a boolean';
+ this.checkOrConvertNumber(this.settings.captionSettings, 'animationDuration');
- $entry.addClass('jg-entry');
+ this.checkOrConvertNumber(this.settings.captionSettings, 'visibleOpacity');
+ if (this.settings.captionSettings.visibleOpacity < 0 ||
+ this.settings.captionSettings.visibleOpacity > 1) {
+ throw 'captionSettings.visibleOpacity must be in the interval [0, 1]';
+ }
- if ($image.data('jg.loaded') !== true && $image.data('jg.loaded') !== 'skipped') {
+ this.checkOrConvertNumber(this.settings.captionSettings, 'nonVisibleOpacity');
+ if (this.settings.captionSettings.nonVisibleOpacity < 0 ||
+ this.settings.captionSettings.nonVisibleOpacity > 1) {
+ throw 'captionSettings.nonVisibleOpacity must be in the interval [0, 1]';
+ }
- // Link Rel global overwrite
- if (context.settings.rel !== null) $entry.attr('rel', context.settings.rel);
+ if ($.type(this.settings.fixedHeight) !== 'boolean') throw 'fixedHeight must be a boolean';
+ this.checkOrConvertNumber(this.settings, 'imagesAnimationDuration');
+ this.checkOrConvertNumber(this.settings, 'refreshTime');
+ if ($.type(this.settings.randomize) !== 'boolean') throw 'randomize must be a boolean';
+ if ($.type(this.settings.selector) !== 'string') throw 'selector must be a string';
- // Link Target global overwrite
- if (context.settings.target !== null) $entry.attr('target', context.settings.target);
+ if (this.settings.sort !== false && !$.isFunction(this.settings.sort)) {
+ throw 'sort must be false or a comparison function';
+ }
- // Image src
- var imageSrc = (typeof $image.data('safe-src') !== 'undefined') ?
- $image.data('safe-src') : $image.attr('src');
- $image.data('jg.originalSrc', imageSrc);
- $image.attr('src', imageSrc);
+ if (this.settings.filter !== false && !$.isFunction(this.settings.sort) &&
+ $.type(this.settings.filter) !== 'string') {
+ throw 'filter must be false, a string or a filter function';
+ }
+ };
- var width = parseInt($image.attr('width'), 10);
- var height = parseInt($image.attr('height'), 10);
- if(context.settings.waitThumbnailsLoad !== true && !isNaN(width) && !isNaN(height)) {
- $image.data('jg.imgw', width);
- $image.data('jg.imgh', height);
- $image.data('jg.loaded', 'skipped');
- skippedImages = true;
- startImgAnalyzer(context, false);
- return true;
- }
+ /**
+ * It brings all the indexes from the sizeRangeSuffixes and it orders them. They are then sorted and returned.
+ * @returns {Array} sorted suffix ranges
+ */
+ JustifiedGallery.prototype.retrieveSuffixRanges = function () {
+ var suffixRanges = [];
+ for (var rangeIdx in this.settings.sizeRangeSuffixes) {
+ if (this.settings.sizeRangeSuffixes.hasOwnProperty(rangeIdx)) suffixRanges.push(parseInt(rangeIdx, 10));
+ }
+ suffixRanges.sort(function (a, b) { return a > b ? 1 : a < b ? -1 : 0; });
+ return suffixRanges;
+ };
- $image.data('jg.loaded', false);
- imagesToLoad = true;
+ /**
+ * Update the existing settings only changing some of them
+ *
+ * @param newSettings the new settings (or a subgroup of them)
+ */
+ JustifiedGallery.prototype.updateSettings = function (newSettings) {
+ // In this case Justified Gallery has been called again changing only some options
+ this.settings = $.extend({}, this.settings, newSettings);
+ this.checkSettings();
+
+ // As reported in the settings: negative value = same as margins, 0 = disabled
+ this.border = this.settings.border >= 0 ? this.settings.border : this.settings.margins;
+
+ this.maxRowHeight = this.retrieveMaxRowHeight();
+ this.suffixRanges = this.retrieveSuffixRanges();
+ };
- // Spinner start
- if (context.spinner.active === false) {
- context.spinner.active = true;
- $gallery.append(context.spinner.$el);
- $gallery.height(context.offY + context.spinner.$el.innerHeight());
- startLoadingSpinnerAnimation(context.spinner);
- }
+ /**
+ * Justified Gallery plugin for jQuery
+ *
+ * Events
+ * - jg.complete : called when all the gallery has been created
+ * - jg.resize : called when the gallery has been resized
+ * - jg.rowflush : when a new row appears
+ *
+ * @param arg the action (or the settings) passed when the plugin is called
+ * @returns {*} the object itself
+ */
+ $.fn.justifiedGallery = function (arg) {
+ return this.each(function (index, gallery) {
- onImageEvent(imageSrc, function imgLoaded (loadImg) {
- //DEBUG// console.log('img load (alt: ' + $image.attr('alt') + ')');
- $image.data('jg.imgw', loadImg.width);
- $image.data('jg.imgh', loadImg.height);
- $image.data('jg.loaded', true);
- startImgAnalyzer(context, false);
- }, function imgLoadError () {
- //DEBUG// console.log('img error (alt: ' + $image.attr('alt') + ')');
- $image.data('jg.loaded', 'error');
- startImgAnalyzer(context, false);
- });
+ var $gallery = $(gallery);
+ $gallery.addClass('justified-gallery');
+ var controller = $gallery.data('jg.controller');
+ if (typeof controller === 'undefined') {
+ // Create controller and assign it to the object data
+ if (typeof arg !== 'undefined' && arg !== null && $.type(arg) !== 'object') {
+ throw 'The argument must be an object';
}
+ controller = new JustifiedGallery($gallery, $.extend({}, $.fn.justifiedGallery.defaults, arg));
+ $gallery.data('jg.controller', controller);
+ } else if (arg === 'norewind') {
+ // In this case we don't rewind: we analyze only the latest images (e.g. to complete the last unfinished row
+ controller.hideBuildingRowImages();
+ } else if (arg === 'destroy') {
+ controller.destroy();
+ return;
+ } else {
+ // In this case Justified Gallery has been called again changing only some options
+ controller.updateSettings(arg);
+ controller.rewind();
+ }
- });
+ // Update the entries list
+ if (!controller.updateEntries(arg === 'norewind')) return;
+
+ // Init justified gallery
+ controller.init();
- if (!imagesToLoad && !skippedImages) startImgAnalyzer(context, false);
- checkWidth(context);
});
+ };
+ // Default options
+ $.fn.justifiedGallery.defaults = {
+ sizeRangeSuffixes: { }, /* e.g. Flickr configuration
+ {
+ 100: '_t', // used when longest is less than 100px
+ 240: '_m', // used when longest is between 101px and 240px
+ 320: '_n', // ...
+ 500: '',
+ 640: '_z',
+ 1024: '_b' // used as else case because it is the last
+ }
+ */
+ rowHeight: 120,
+ maxRowHeight: '200%', // negative value = no limits, number to express the value in pixels,
+ // '[0-9]+%' to express in percentage (e.g. 200% means that the row height
+ // can't exceed 2 * rowHeight)
+ margins: 1,
+ border: -1, // negative value = same as margins, 0 = disabled, any other value to set the border
+
+ lastRow: 'nojustify', // or can be 'justify' or 'hide'
+ justifyThreshold: 0.75, /* if row width / available space > 0.75 it will be always justified
+ * (i.e. lastRow setting is not considered) */
+ fixedHeight: false,
+ waitThumbnailsLoad: true,
+ captions: true,
+ cssAnimation: false,
+ imagesAnimationDuration: 500, // ignored with css animations
+ captionSettings: { // ignored with css animations
+ animationDuration: 500,
+ visibleOpacity: 0.7,
+ nonVisibleOpacity: 0.0
+ },
+ rel: null, // rewrite the rel of each analyzed links
+ target: null, // rewrite the target of all links
+ extension: /\.[^.\\/]+$/, // regexp to capture the extension of an image
+ refreshTime: 100, // time interval (in ms) to check if the page changes its width
+ randomize: false,
+ sort: false, /*
+ - false: to do not sort
+ - function: to sort them using the function as comparator (see Array.prototype.sort())
+ */
+ filter: false, /*
+ - false: for a disabled filter
+ - a string: an entry is kept if entry.is(filter string) returns true
+ see jQuery's .is() function for further information
+ - a function: invoked with arguments (entry, index, array). Return true to keep the entry, false otherwise.
+ see Array.prototype.filter for further information.
+ */
+ selector: '> a, > div:not(.spinner)' // The selector that is used to know what are the entries of the gallery
};
-
+
}(jQuery));
diff --git a/library/justifiedGallery/jquery.justifiedGallery.min.js b/library/justifiedGallery/jquery.justifiedGallery.min.js
new file mode 100644
index 000000000..74f333208
--- /dev/null
+++ b/library/justifiedGallery/jquery.justifiedGallery.min.js
@@ -0,0 +1,7 @@
+/*!
+ * Justified Gallery - v3.6.0
+ * http://miromannino.github.io/Justified-Gallery/
+ * Copyright (c) 2015 Miro Mannino
+ * Licensed under the MIT license.
+ */
+!function(a){var b=function(b,c){this.settings=c,this.checkSettings(),this.imgAnalyzerTimeout=null,this.entries=null,this.buildingRow={entriesBuff:[],width:0,aspectRatio:0},this.lastAnalyzedIndex=-1,this.yield={every:2,flushed:0},this.border=c.border>=0?c.border:c.margins,this.maxRowHeight=this.retrieveMaxRowHeight(),this.suffixRanges=this.retrieveSuffixRanges(),this.offY=this.border,this.spinner={phase:0,timeSlot:150,$el:a('<div class="spinner"><span></span><span></span><span></span></div>'),intervalId:null},this.checkWidthIntervalId=null,this.galleryWidth=b.width(),this.$gallery=b};b.prototype.getSuffix=function(a,b){var c,d;for(c=a>b?a:b,d=0;d<this.suffixRanges.length;d++)if(c<=this.suffixRanges[d])return this.settings.sizeRangeSuffixes[this.suffixRanges[d]];return this.settings.sizeRangeSuffixes[this.suffixRanges[d-1]]},b.prototype.removeSuffix=function(a,b){return a.substring(0,a.length-b.length)},b.prototype.endsWith=function(a,b){return-1!==a.indexOf(b,a.length-b.length)},b.prototype.getUsedSuffix=function(a){for(var b in this.settings.sizeRangeSuffixes)if(this.settings.sizeRangeSuffixes.hasOwnProperty(b)){if(0===this.settings.sizeRangeSuffixes[b].length)continue;if(this.endsWith(a,this.settings.sizeRangeSuffixes[b]))return this.settings.sizeRangeSuffixes[b]}return""},b.prototype.newSrc=function(a,b,c){var d=a.match(this.settings.extension),e=null!=d?d[0]:"",f=a.replace(this.settings.extension,"");return f=this.removeSuffix(f,this.getUsedSuffix(f)),f+=this.getSuffix(b,c)+e},b.prototype.showImg=function(a,b){this.settings.cssAnimation?(a.addClass("entry-visible"),b&&b()):a.stop().fadeTo(this.settings.imagesAnimationDuration,1,b)},b.prototype.extractImgSrcFromImage=function(a){var b="undefined"!=typeof a.data("safe-src")?a.data("safe-src"):a.attr("src");return a.data("jg.originalSrc",b),b},b.prototype.imgFromEntry=function(a){var b=a.find("> img");return 0===b.length&&(b=a.find("> a > img")),0===b.length?null:b},b.prototype.captionFromEntry=function(a){var b=a.find("> .caption");return 0===b.length?null:b},b.prototype.displayEntry=function(b,c,d,e,f,g){b.width(e),b.height(g),b.css("top",d),b.css("left",c);var h=this.imgFromEntry(b);if(null!==h){h.css("width",e),h.css("height",f),h.css("margin-left",-e/2),h.css("margin-top",-f/2);var i=h.attr("src"),j=this.newSrc(i,e,f);h.one("error",function(){h.attr("src",h.data("jg.originalSrc"))});var k=function(){i!==j&&h.attr("src",j)};"skipped"===b.data("jg.loaded")?this.onImageEvent(i,a.proxy(function(){this.showImg(b,k),b.data("jg.loaded",!0)},this)):this.showImg(b,k)}else this.showImg(b);this.displayEntryCaption(b)},b.prototype.displayEntryCaption=function(b){var c=this.imgFromEntry(b);if(null!==c&&this.settings.captions){var d=this.captionFromEntry(b);if(null==d){var e=c.attr("alt");"undefined"==typeof e&&(e=b.attr("title")),"undefined"!=typeof e&&(d=a('<div class="caption">'+e+"</div>"),b.append(d),b.data("jg.createdCaption",!0))}null!==d&&(this.settings.cssAnimation||d.stop().fadeTo(0,this.settings.captionSettings.nonVisibleOpacity),this.addCaptionEventsHandlers(b))}else this.removeCaptionEventsHandlers(b)},b.prototype.onEntryMouseEnterForCaption=function(b){var c=this.captionFromEntry(a(b.currentTarget));this.settings.cssAnimation?c.addClass("caption-visible").removeClass("caption-hidden"):c.stop().fadeTo(this.settings.captionSettings.animationDuration,this.settings.captionSettings.visibleOpacity)},b.prototype.onEntryMouseLeaveForCaption=function(b){var c=this.captionFromEntry(a(b.currentTarget));this.settings.cssAnimation?c.removeClass("caption-visible").removeClass("caption-hidden"):c.stop().fadeTo(this.settings.captionSettings.animationDuration,this.settings.captionSettings.nonVisibleOpacity)},b.prototype.addCaptionEventsHandlers=function(b){var c=b.data("jg.captionMouseEvents");"undefined"==typeof c&&(c={mouseenter:a.proxy(this.onEntryMouseEnterForCaption,this),mouseleave:a.proxy(this.onEntryMouseLeaveForCaption,this)},b.on("mouseenter",void 0,void 0,c.mouseenter),b.on("mouseleave",void 0,void 0,c.mouseleave),b.data("jg.captionMouseEvents",c))},b.prototype.removeCaptionEventsHandlers=function(a){var b=a.data("jg.captionMouseEvents");"undefined"!=typeof b&&(a.off("mouseenter",void 0,b.mouseenter),a.off("mouseleave",void 0,b.mouseleave),a.removeData("jg.captionMouseEvents"))},b.prototype.prepareBuildingRow=function(a){var b,c,d,e,f,g=!0,h=0,i=this.galleryWidth-2*this.border-(this.buildingRow.entriesBuff.length-1)*this.settings.margins,j=i/this.buildingRow.aspectRatio,k=this.buildingRow.width/i>this.settings.justifyThreshold;if(a&&"hide"===this.settings.lastRow&&!k){for(b=0;b<this.buildingRow.entriesBuff.length;b++)c=this.buildingRow.entriesBuff[b],this.settings.cssAnimation?c.removeClass("entry-visible"):c.stop().fadeTo(0,0);return-1}for(a&&!k&&"nojustify"===this.settings.lastRow&&(g=!1),b=0;b<this.buildingRow.entriesBuff.length;b++)c=this.buildingRow.entriesBuff[b],d=c.data("jg.width")/c.data("jg.height"),g?(e=b===this.buildingRow.entriesBuff.length-1?i:j*d,f=j):(e=this.settings.rowHeight*d,f=this.settings.rowHeight),i-=Math.round(e),c.data("jg.jwidth",Math.round(e)),c.data("jg.jheight",Math.ceil(f)),(0===b||h>f)&&(h=f);return this.settings.fixedHeight&&h>this.settings.rowHeight&&(h=this.settings.rowHeight),{minHeight:h,justify:g}},b.prototype.clearBuildingRow=function(){this.buildingRow.entriesBuff=[],this.buildingRow.aspectRatio=0,this.buildingRow.width=0},b.prototype.flushRow=function(a){var b,c,d,e=this.settings,f=this.border;if(d=this.prepareBuildingRow(a),c=d.minHeight,a&&"hide"===e.lastRow&&-1===c)return void this.clearBuildingRow();this.maxRowHeight.percentage?this.maxRowHeight.value*e.rowHeight<c&&(c=this.maxRowHeight.value*e.rowHeight):this.maxRowHeight.value>0&&this.maxRowHeight.value<c&&(c=this.maxRowHeight.value);for(var g=0;g<this.buildingRow.entriesBuff.length;g++)b=this.buildingRow.entriesBuff[g],this.displayEntry(b,f,this.offY,b.data("jg.jwidth"),b.data("jg.jheight"),c),f+=b.data("jg.jwidth")+e.margins;this.$gallery.height(this.offY+c+this.border+(this.isSpinnerActive()?this.getSpinnerHeight():0)),(!a||c<=this.settings.rowHeight&&d.justify)&&(this.offY+=c+this.settings.margins,this.clearBuildingRow(),this.$gallery.trigger("jg.rowflush"))},b.prototype.checkWidth=function(){this.checkWidthIntervalId=setInterval(a.proxy(function(){var a=parseInt(this.$gallery.width(),10);this.galleryWidth!==a&&(this.galleryWidth=a,this.rewind(),this.startImgAnalyzer(!0))},this),this.settings.refreshTime)},b.prototype.isSpinnerActive=function(){return null!=this.spinner.intervalId},b.prototype.getSpinnerHeight=function(){return this.spinner.$el.innerHeight()},b.prototype.stopLoadingSpinnerAnimation=function(){clearInterval(this.spinner.intervalId),this.spinner.intervalId=null,this.$gallery.height(this.$gallery.height()-this.getSpinnerHeight()),this.spinner.$el.detach()},b.prototype.startLoadingSpinnerAnimation=function(){var a=this.spinner,b=a.$el.find("span");clearInterval(a.intervalId),this.$gallery.append(a.$el),this.$gallery.height(this.offY+this.getSpinnerHeight()),a.intervalId=setInterval(function(){a.phase<b.length?b.eq(a.phase).fadeTo(a.timeSlot,1):b.eq(a.phase-b.length).fadeTo(a.timeSlot,0),a.phase=(a.phase+1)%(2*b.length)},a.timeSlot)},b.prototype.rewind=function(){this.lastAnalyzedIndex=-1,this.offY=this.border,this.clearBuildingRow()},b.prototype.hideBuildingRowImages=function(){for(var a=0;a<this.buildingRow.entriesBuff.length;a++)this.settings.cssAnimation?this.buildingRow.entriesBuff[a].removeClass("entry-visible"):this.buildingRow.entriesBuff[a].stop().fadeTo(0,0)},b.prototype.updateEntries=function(b){return this.entries=this.$gallery.find(this.settings.selector).toArray(),0===this.entries.length?!1:(this.settings.filter?this.modifyEntries(this.filterArray,b):this.modifyEntries(this.resetFilters,b),a.isFunction(this.settings.sort)?this.modifyEntries(this.sortArray,b):this.settings.randomize&&this.modifyEntries(this.shuffleArray,b),!0)},b.prototype.insertToGallery=function(b){var c=this;a.each(b,function(){a(this).appendTo(c.$gallery)})},b.prototype.shuffleArray=function(a){var b,c,d;for(b=a.length-1;b>0;b--)c=Math.floor(Math.random()*(b+1)),d=a[b],a[b]=a[c],a[c]=d;return this.insertToGallery(a),a},b.prototype.sortArray=function(a){return a.sort(this.settings.sort),this.insertToGallery(a),a},b.prototype.resetFilters=function(b){for(var c=0;c<b.length;c++)a(b[c]).removeClass("jg-filtered");return b},b.prototype.filterArray=function(b){var c=this.settings;return"string"===a.type(c.filter)?b.filter(function(b){var d=a(b);return d.is(c.filter)?(d.removeClass("jg-filtered"),!0):(d.addClass("jg-filtered"),!1)}):a.isFunction(c.filter)?b.filter(c.filter):void 0},b.prototype.modifyEntries=function(a,b){var c=b?this.entries.splice(this.lastAnalyzedIndex+1,this.entries.length-this.lastAnalyzedIndex-1):this.entries;c=a.call(this,c),this.entries=b?this.entries.concat(c):c},b.prototype.destroy=function(){clearInterval(this.checkWidthIntervalId),a.each(this.entries,a.proxy(function(b,c){var d=a(c);d.css("width",""),d.css("height",""),d.css("top",""),d.css("left",""),d.data("jg.loaded",void 0),d.removeClass("jg-entry");var e=this.imgFromEntry(d);e.css("width",""),e.css("height",""),e.css("margin-left",""),e.css("margin-top",""),e.attr("src",e.data("jg.originalSrc")),e.data("jg.originalSrc",void 0),this.removeCaptionEventsHandlers(d);var f=this.captionFromEntry(d);d.data("jg.createdCaption")?(d.data("jg.createdCaption",void 0),null!=f&&f.remove()):null!=f&&f.fadeTo(0,1)},this)),this.$gallery.css("height",""),this.$gallery.removeClass("justified-gallery"),this.$gallery.data("jg.controller",void 0)},b.prototype.analyzeImages=function(b){for(var c=this.lastAnalyzedIndex+1;c<this.entries.length;c++){var d=a(this.entries[c]);if(d.data("jg.loaded")===!0||"skipped"===d.data("jg.loaded")){var e=this.galleryWidth-2*this.border-(this.buildingRow.entriesBuff.length-1)*this.settings.margins,f=d.data("jg.width")/d.data("jg.height");if(e/(this.buildingRow.aspectRatio+f)<this.settings.rowHeight&&(this.flushRow(!1),++this.yield.flushed>=this.yield.every))return void this.startImgAnalyzer(b);this.buildingRow.entriesBuff.push(d),this.buildingRow.aspectRatio+=f,this.buildingRow.width+=f*this.settings.rowHeight,this.lastAnalyzedIndex=c}else if("error"!==d.data("jg.loaded"))return}this.buildingRow.entriesBuff.length>0&&this.flushRow(!0),this.isSpinnerActive()&&this.stopLoadingSpinnerAnimation(),this.stopImgAnalyzerStarter(),this.$gallery.trigger(b?"jg.resize":"jg.complete")},b.prototype.stopImgAnalyzerStarter=function(){this.yield.flushed=0,null!==this.imgAnalyzerTimeout&&clearTimeout(this.imgAnalyzerTimeout)},b.prototype.startImgAnalyzer=function(a){var b=this;this.stopImgAnalyzerStarter(),this.imgAnalyzerTimeout=setTimeout(function(){b.analyzeImages(a)},.001)},b.prototype.onImageEvent=function(b,c,d){if(c||d){var e=new Image,f=a(e);c&&f.one("load",function(){f.off("load error"),c(e)}),d&&f.one("error",function(){f.off("load error"),d(e)}),e.src=b}},b.prototype.init=function(){var b=!1,c=!1,d=this;a.each(this.entries,function(e,f){var g=a(f),h=d.imgFromEntry(g);if(g.addClass("jg-entry"),g.data("jg.loaded")!==!0&&"skipped"!==g.data("jg.loaded"))if(null!==d.settings.rel&&g.attr("rel",d.settings.rel),null!==d.settings.target&&g.attr("target",d.settings.target),null!==h){var i=d.extractImgSrcFromImage(h);if(h.attr("src",i),d.settings.waitThumbnailsLoad===!1){var j=parseInt(h.attr("width"),10),k=parseInt(h.attr("height"),10);if(!isNaN(j)&&!isNaN(k))return g.data("jg.width",j),g.data("jg.height",k),g.data("jg.loaded","skipped"),c=!0,d.startImgAnalyzer(!1),!0}g.data("jg.loaded",!1),b=!0,d.isSpinnerActive()||d.startLoadingSpinnerAnimation(),d.onImageEvent(i,function(a){g.data("jg.width",a.width),g.data("jg.height",a.height),g.data("jg.loaded",!0),d.startImgAnalyzer(!1)},function(){g.data("jg.loaded","error"),d.startImgAnalyzer(!1)})}else g.data("jg.loaded",!0),g.data("jg.width",g.width()|g.css("width")|1),g.data("jg.height",g.height()|g.css("height")|1)}),b||c||this.startImgAnalyzer(!1),this.checkWidth()},b.prototype.checkOrConvertNumber=function(b,c){if("string"===a.type(b[c])&&(b[c]=parseFloat(b[c])),"number"!==a.type(b[c]))throw c+" must be a number";if(isNaN(b[c]))throw"invalid number for "+c},b.prototype.checkSizeRangesSuffixes=function(){if("object"!==a.type(this.settings.sizeRangeSuffixes))throw"sizeRangeSuffixes must be defined and must be an object";var b=[];for(var c in this.settings.sizeRangeSuffixes)this.settings.sizeRangeSuffixes.hasOwnProperty(c)&&b.push(c);for(var d={0:""},e=0;e<b.length;e++)if("string"===a.type(b[e]))try{var f=parseInt(b[e].replace(/^[a-z]+/,""),10);d[f]=this.settings.sizeRangeSuffixes[b[e]]}catch(g){throw"sizeRangeSuffixes keys must contains correct numbers ("+g+")"}else d[b[e]]=this.settings.sizeRangeSuffixes[b[e]];this.settings.sizeRangeSuffixes=d},b.prototype.retrieveMaxRowHeight=function(){var b={};if("string"===a.type(this.settings.maxRowHeight))this.settings.maxRowHeight.match(/^[0-9]+%$/)?(b.value=parseFloat(this.settings.maxRowHeight.match(/^([0-9])+%$/)[1])/100,b.percentage=!1):(b.value=parseFloat(this.settings.maxRowHeight),b.percentage=!0);else{if("number"!==a.type(this.settings.maxRowHeight))throw"maxRowHeight must be a number or a percentage";b.value=this.settings.maxRowHeight,b.percentage=!1}if(isNaN(b.value))throw"invalid number for maxRowHeight";return b.percentage?b.value<100&&(b.value=100):b.value>0&&b.value<this.settings.rowHeight&&(b.value=this.settings.rowHeight),b},b.prototype.checkSettings=function(){if(this.checkSizeRangesSuffixes(),this.checkOrConvertNumber(this.settings,"rowHeight"),this.checkOrConvertNumber(this.settings,"margins"),this.checkOrConvertNumber(this.settings,"border"),"nojustify"!==this.settings.lastRow&&"justify"!==this.settings.lastRow&&"hide"!==this.settings.lastRow)throw'lastRow must be "nojustify", "justify" or "hide"';if(this.checkOrConvertNumber(this.settings,"justifyThreshold"),this.settings.justifyThreshold<0||this.settings.justifyThreshold>1)throw"justifyThreshold must be in the interval [0,1]";if("boolean"!==a.type(this.settings.cssAnimation))throw"cssAnimation must be a boolean";if("boolean"!==a.type(this.settings.captions))throw"captions must be a boolean";if(this.checkOrConvertNumber(this.settings.captionSettings,"animationDuration"),this.checkOrConvertNumber(this.settings.captionSettings,"visibleOpacity"),this.settings.captionSettings.visibleOpacity<0||this.settings.captionSettings.visibleOpacity>1)throw"captionSettings.visibleOpacity must be in the interval [0, 1]";if(this.checkOrConvertNumber(this.settings.captionSettings,"nonVisibleOpacity"),this.settings.captionSettings.nonVisibleOpacity<0||this.settings.captionSettings.nonVisibleOpacity>1)throw"captionSettings.nonVisibleOpacity must be in the interval [0, 1]";if("boolean"!==a.type(this.settings.fixedHeight))throw"fixedHeight must be a boolean";if(this.checkOrConvertNumber(this.settings,"imagesAnimationDuration"),this.checkOrConvertNumber(this.settings,"refreshTime"),"boolean"!==a.type(this.settings.randomize))throw"randomize must be a boolean";if("string"!==a.type(this.settings.selector))throw"selector must be a string";if(this.settings.sort!==!1&&!a.isFunction(this.settings.sort))throw"sort must be false or a comparison function";if(this.settings.filter!==!1&&!a.isFunction(this.settings.sort)&&"string"!==a.type(this.settings.filter))throw"filter must be false, a string or a filter function"},b.prototype.retrieveSuffixRanges=function(){var a=[];for(var b in this.settings.sizeRangeSuffixes)this.settings.sizeRangeSuffixes.hasOwnProperty(b)&&a.push(parseInt(b,10));return a.sort(function(a,b){return a>b?1:b>a?-1:0}),a},b.prototype.updateSettings=function(b){this.settings=a.extend({},this.settings,b),this.checkSettings(),this.border=this.settings.border>=0?this.settings.border:this.settings.margins,this.maxRowHeight=this.retrieveMaxRowHeight(),this.suffixRanges=this.retrieveSuffixRanges()},a.fn.justifiedGallery=function(c){return this.each(function(d,e){var f=a(e);f.addClass("justified-gallery");var g=f.data("jg.controller");if("undefined"==typeof g){if("undefined"!=typeof c&&null!==c&&"object"!==a.type(c))throw"The argument must be an object";g=new b(f,a.extend({},a.fn.justifiedGallery.defaults,c)),f.data("jg.controller",g)}else if("norewind"===c)g.hideBuildingRowImages();else{if("destroy"===c)return void g.destroy();g.updateSettings(c),g.rewind()}g.updateEntries("norewind"===c)&&g.init()})},a.fn.justifiedGallery.defaults={sizeRangeSuffixes:{},rowHeight:120,maxRowHeight:"200%",margins:1,border:-1,lastRow:"nojustify",justifyThreshold:.75,fixedHeight:!1,waitThumbnailsLoad:!0,captions:!0,cssAnimation:!1,imagesAnimationDuration:500,captionSettings:{animationDuration:500,visibleOpacity:.7,nonVisibleOpacity:0},rel:null,target:null,extension:/\.[^.\\/]+$/,refreshTime:100,randomize:!1,sort:!1,filter:!1,selector:"> a, > div:not(.spinner)"}}(jQuery); \ No newline at end of file
diff --git a/library/justifiedGallery/justifiedGallery.css b/library/justifiedGallery/justifiedGallery.css
index 3a0d55ad5..0d45475ce 100644
--- a/library/justifiedGallery/justifiedGallery.css
+++ b/library/justifiedGallery/justifiedGallery.css
@@ -1,5 +1,5 @@
/*!
- * Justified Gallery - v3.5.4
+ * Justified Gallery - v3.6.0
* http://miromannino.github.io/Justified-Gallery/
* Copyright (c) 2015 Miro Mannino
* Licensed under the MIT license.
@@ -126,6 +126,9 @@
-moz-animation: justified-gallery-show-entry-animation 500ms 0 ease;
-ms-animation: justified-gallery-show-entry-animation 500ms 0 ease;
}
+.justified-gallery > .jg-filtered {
+ display: none;
+}
.justified-gallery > .spinner {
position: absolute;
bottom: 0;
diff --git a/library/justifiedGallery/justifiedGallery.min.css b/library/justifiedGallery/justifiedGallery.min.css
new file mode 100644
index 000000000..d7b1c6726
--- /dev/null
+++ b/library/justifiedGallery/justifiedGallery.min.css
@@ -0,0 +1,7 @@
+/*!
+ * Justified Gallery - v3.6.0
+ * http://miromannino.github.io/Justified-Gallery/
+ * Copyright (c) 2015 Miro Mannino
+ * Licensed under the MIT license.
+ */
+@-webkit-keyframes justified-gallery-show-caption-animation{from{opacity:0}to{opacity:.7}}@-moz-keyframes justified-gallery-show-caption-animation{from{opacity:0}to{opacity:.7}}@-o-keyframes justified-gallery-show-caption-animation{from{opacity:0}to{opacity:.7}}@keyframes justified-gallery-show-caption-animation{from{opacity:0}to{opacity:.7}}@-webkit-keyframes justified-gallery-show-entry-animation{from{opacity:0}to{opacity:1}}@-moz-keyframes justified-gallery-show-entry-animation{from{opacity:0}to{opacity:1}}@-o-keyframes justified-gallery-show-entry-animation{from{opacity:0}to{opacity:1}}@keyframes justified-gallery-show-entry-animation{from{opacity:0}to{opacity:1}}.justified-gallery{width:100%;position:relative;overflow:hidden}.justified-gallery>a,.justified-gallery>div{position:absolute;display:inline-block;overflow:hidden;opacity:0;filter:alpha(opacity=0)}.justified-gallery>a>img,.justified-gallery>div>img,.justified-gallery>a>a>img,.justified-gallery>div>a>img{position:absolute;top:50%;left:50%;margin:0;padding:0;border:0}.justified-gallery>a>.caption,.justified-gallery>div>.caption{display:none;position:absolute;bottom:0;padding:5px;background-color:#000;left:0;right:0;margin:0;color:#fff;font-size:12px;font-weight:300;font-family:sans-serif}.justified-gallery>a>.caption.caption-visible,.justified-gallery>div>.caption.caption-visible{display:initial;opacity:.7;filter:"alpha(opacity=70)";-webkit-animation:justified-gallery-show-caption-animation 500ms 0 ease;-moz-animation:justified-gallery-show-caption-animation 500ms 0 ease;-ms-animation:justified-gallery-show-caption-animation 500ms 0 ease}.justified-gallery>.entry-visible{opacity:1;filter:alpha(opacity=100);-webkit-animation:justified-gallery-show-entry-animation 500ms 0 ease;-moz-animation:justified-gallery-show-entry-animation 500ms 0 ease;-ms-animation:justified-gallery-show-entry-animation 500ms 0 ease}.justified-gallery>.jg-filtered{display:none}.justified-gallery>.spinner{position:absolute;bottom:0;margin-left:-24px;padding:10px 0;left:50%;opacity:initial;filter:initial;overflow:initial}.justified-gallery>.spinner>span{display:inline-block;opacity:0;filter:alpha(opacity=0);width:8px;height:8px;margin:0 4px;background-color:#000;border-top-left-radius:6px;border-top-right-radius:6px;border-bottom-right-radius:6px;border-bottom-left-radius:6px} \ No newline at end of file