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.