PageSourceSearch

https://asapscreens.app/wp-content/plugins/whizz-plugins/lib/whizz…/js/jquery.justifiedGallery.js?ver=1.1

js asapscreens.app collected 2026-10-02 01:13:18 UTC 43,248 bytes, 1,150 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.