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