1/* 2 * blueimp Gallery JS 2.14.0 3 * https://github.com/blueimp/Gallery 4 * 5 * Copyright 2013, Sebastian Tschan 6 * https://blueimp.net 7 * 8 * Swipe implementation based on 9 * https://github.com/bradbirdsall/Swipe 10 * 11 * Licensed under the MIT license: 12 * http://www.opensource.org/licenses/MIT 13 */ 14 15/* global define, window, document, DocumentTouch */ 16 17(function (factory) { 18 'use strict'; 19 if (typeof define === 'function' && define.amd) { 20 // Register as an anonymous AMD module: 21 define(['./blueimp-helper'], factory); 22 } else { 23 // Browser globals: 24 window.blueimp = window.blueimp || {}; 25 window.blueimp.Gallery = factory( 26 window.blueimp.helper || window.jQuery 27 ); 28 } 29}(function ($) { 30 'use strict'; 31 32 function Gallery(list, options) { 33 if (document.body.style.maxHeight === undefined) { 34 // document.body.style.maxHeight is undefined on IE6 and lower 35 return null; 36 } 37 if (!this || this.options !== Gallery.prototype.options) { 38 // Called as function instead of as constructor, 39 // so we simply return a new instance: 40 return new Gallery(list, options); 41 } 42 if (!list || !list.length) { 43 this.console.log( 44 'blueimp Gallery: No or empty list provided as first argument.', 45 list 46 ); 47 return; 48 } 49 this.list = list; 50 this.num = list.length; 51 this.initOptions(options); 52 this.initialize(); 53 } 54 55 $.extend(Gallery.prototype, { 56 57 options: { 58 // The Id, element or querySelector of the gallery widget: 59 container: '#blueimp-gallery', 60 // The tag name, Id, element or querySelector of the slides container: 61 slidesContainer: 'div', 62 // The tag name, Id, element or querySelector of the title element: 63 titleElement: 'h3', 64 // The class to add when the gallery is visible: 65 displayClass: 'blueimp-gallery-display', 66 // The class to add when the gallery controls are visible: 67 controlsClass: 'blueimp-gallery-controls', 68 // The class to add when the gallery only displays one element: 69 singleClass: 'blueimp-gallery-single', 70 // The class to add when the left edge has been reached: 71 leftEdgeClass: 'blueimp-gallery-left', 72 // The class to add when the right edge has been reached: 73 rightEdgeClass: 'blueimp-gallery-right', 74 // The class to add when the automatic slideshow is active: 75 playingClass: 'blueimp-gallery-playing', 76 // The class for all slides: 77 slideClass: 'slide', 78 // The slide class for loading elements: 79 slideLoadingClass: 'slide-loading', 80 // The slide class for elements that failed to load: 81 slideErrorClass: 'slide-error', 82 // The class for the content element loaded into each slide: 83 slideContentClass: 'slide-content', 84 // The class for the "toggle" control: 85 toggleClass: 'toggle', 86 // The class for the "prev" control: 87 prevClass: 'prev', 88 // The class for the "next" control: 89 nextClass: 'next', 90 // The class for the "close" control: 91 closeClass: 'close', 92 // The class for the "play-pause" toggle control: 93 playPauseClass: 'play-pause', 94 // The list object property (or data attribute) with the object type: 95 typeProperty: 'type', 96 // The list object property (or data attribute) with the object title: 97 titleProperty: 'title', 98 // The list object property (or data attribute) with the object URL: 99 urlProperty: 'href', 100 // The gallery listens for transitionend events before triggering the 101 // opened and closed events, unless the following option is set to false: 102 displayTransition: true, 103 // Defines if the gallery slides are cleared from the gallery modal, 104 // or reused for the next gallery initialization: 105 clearSlides: true, 106 // Defines if images should be stretched to fill the available space, 107 // while maintaining their aspect ratio (will only be enabled for browsers 108 // supporting background-size="contain", which excludes IE < 9). 109 // Set to "cover", to make images cover all available space (requires 110 // support for background-size="cover", which excludes IE < 9): 111 stretchImages: false, 112 // Toggle the controls on pressing the Return key: 113 toggleControlsOnReturn: true, 114 // Toggle the automatic slideshow interval on pressing the Space key: 115 toggleSlideshowOnSpace: true, 116 // Navigate the gallery by pressing left and right on the keyboard: 117 enableKeyboardNavigation: true, 118 // Close the gallery on pressing the Esc key: 119 closeOnEscape: true, 120 // Close the gallery when clicking on an empty slide area: 121 closeOnSlideClick: true, 122 // Close the gallery by swiping up or down: 123 closeOnSwipeUpOrDown: true, 124 // Emulate touch events on mouse-pointer devices such as desktop browsers: 125 emulateTouchEvents: true, 126 // Stop touch events from bubbling up to ancestor elements of the Gallery: 127 stopTouchEventsPropagation: false, 128 // Hide the page scrollbars: 129 hidePageScrollbars: true, 130 // Stops any touches on the container from scrolling the page: 131 disableScroll: true, 132 // Carousel mode (shortcut for carousel specific options): 133 carousel: false, 134 // Allow continuous navigation, moving from last to first 135 // and from first to last slide: 136 continuous: true, 137 // Remove elements outside of the preload range from the DOM: 138 unloadElements: true, 139 // Start with the automatic slideshow: 140 startSlideshow: false, 141 // Delay in milliseconds between slides for the automatic slideshow: 142 slideshowInterval: 5000, 143 // The starting index as integer. 144 // Can also be an object of the given list, 145 // or an equal object with the same url property: 146 index: 0, 147 // The number of elements to load around the current index: 148 preloadRange: 2, 149 // The transition speed between slide changes in milliseconds: 150 transitionSpeed: 400, 151 // The transition speed for automatic slide changes, set to an integer 152 // greater 0 to override the default transition speed: 153 slideshowTransitionSpeed: undefined, 154 // The event object for which the default action will be canceled 155 // on Gallery initialization (e.g. the click event to open the Gallery): 156 event: undefined, 157 // Callback function executed when the Gallery is initialized. 158 // Is called with the gallery instance as "this" object: 159 onopen: undefined, 160 // Callback function executed when the Gallery has been initialized 161 // and the initialization transition has been completed. 162 // Is called with the gallery instance as "this" object: 163 onopened: undefined, 164 // Callback function executed on slide change. 165 // Is called with the gallery instance as "this" object and the 166 // current index and slide as arguments: 167 onslide: undefined, 168 // Callback function executed after the slide change transition. 169 // Is called with the gallery instance as "this" object and the 170 // current index and slide as arguments: 171 onslideend: undefined, 172 // Callback function executed on slide content load. 173 // Is called with the gallery instance as "this" object and the 174 // slide index and slide element as arguments: 175 onslidecomplete: undefined, 176 // Callback function executed when the Gallery is about to be closed. 177 // Is called with the gallery instance as "this" object: 178 onclose: undefined, 179 // Callback function executed when the Gallery has been closed 180 // and the closing transition has been completed. 181 // Is called with the gallery instance as "this" object: 182 onclosed: undefined 183 }, 184 185 carouselOptions: { 186 hidePageScrollbars: false, 187 toggleControlsOnReturn: false, 188 toggleSlideshowOnSpace: false, 189 enableKeyboardNavigation: false, 190 closeOnEscape: false, 191 closeOnSlideClick: false, 192 closeOnSwipeUpOrDown: false, 193 disableScroll: false, 194 startSlideshow: true 195 }, 196 197 console: window.console && typeof window.console.log === 'function' ? 198 window.console : 199 {log: function () {}}, 200 201 // Detect touch, transition, transform and background-size support: 202 support: (function (element) { 203 var support = { 204 touch: window.ontouchstart !== undefined || 205 (window.DocumentTouch && document instanceof DocumentTouch) 206 }, 207 transitions = { 208 webkitTransition: { 209 end: 'webkitTransitionEnd', 210 prefix: '-webkit-' 211 }, 212 MozTransition: { 213 end: 'transitionend', 214 prefix: '-moz-' 215 }, 216 OTransition: { 217 end: 'otransitionend', 218 prefix: '-o-' 219 }, 220 transition: { 221 end: 'transitionend', 222 prefix: '' 223 } 224 }, 225 elementTests = function () { 226 var transition = support.transition, 227 prop, 228 translateZ; 229 document.body.appendChild(element); 230 if (transition) { 231 prop = transition.name.slice(0, -9) + 'ransform'; 232 if (element.style[prop] !== undefined) { 233 element.style[prop] = 'translateZ(0)'; 234 translateZ = window.getComputedStyle(element) 235 .getPropertyValue(transition.prefix + 'transform'); 236 support.transform = { 237 prefix: transition.prefix, 238 name: prop, 239 translate: true, 240 translateZ: !!translateZ && translateZ !== 'none' 241 }; 242 } 243 } 244 if (element.style.backgroundSize !== undefined) { 245 support.backgroundSize = {}; 246 element.style.backgroundSize = 'contain'; 247 support.backgroundSize.contain = window 248 .getComputedStyle(element) 249 .getPropertyValue('background-size') === 'contain'; 250 element.style.backgroundSize = 'cover'; 251 support.backgroundSize.cover = window 252 .getComputedStyle(element) 253 .getPropertyValue('background-size') === 'cover'; 254 } 255 document.body.removeChild(element); 256 }; 257 (function (support, transitions) { 258 var prop; 259 for (prop in transitions) { 260 if (transitions.hasOwnProperty(prop) && 261 element.style[prop] !== undefined) { 262 support.transition = transitions[prop]; 263 support.transition.name = prop; 264 break; 265 } 266 } 267 }(support, transitions)); 268 if (document.body) { 269 elementTests(); 270 } else { 271 $(document).on('DOMContentLoaded', elementTests); 272 } 273 return support; 274 // Test element, has to be standard HTML and must not be hidden 275 // for the CSS3 tests using window.getComputedStyle to be applicable: 276 }(document.createElement('div'))), 277 278 requestAnimationFrame: window.requestAnimationFrame || 279 window.webkitRequestAnimationFrame || 280 window.mozRequestAnimationFrame, 281 282 initialize: function () { 283 this.initStartIndex(); 284 if (this.initWidget() === false) { 285 return false;
286 } 287 this.initEventListeners(); 288 // Load the slide at the given index: 289 this.onslide(this.index); 290 // Manually trigger the slideend event for the initial slide: 291 this.ontransitionend(); 292 // Start the automatic slideshow if applicable: 293 if (this.options.startSlideshow) { 294 this.play(); 295 } 296 }, 297 298 slide: function (to, speed) { 299 window.clearTimeout(this.timeout); 300 var index = this.index, 301 direction, 302 naturalDirection, 303 diff; 304 if (index === to || this.num === 1) { 305 return; 306 } 307 if (!speed) { 308 speed = this.options.transitionSpeed; 309 } 310 if (this.support.transition) { 311 if (!this.options.continuous) { 312 to = this.circle(to); 313 } 314 // 1: backward, -1: forward: 315 direction = Math.abs(index - to) / (index - to); 316 // Get the actual position of the slide: 317 if (this.options.continuous) { 318 naturalDirection = direction; 319 direction = -this.positions[this.circle(to)] / this.slideWidth; 320 // If going forward but to < index, use to = slides.length + to 321 // If going backward but to > index, use to = -slides.length + to 322 if (direction !== naturalDirection) { 323 to = -direction * this.num + to; 324 } 325 } 326 diff = Math.abs(index - to) - 1; 327 // Move all the slides between index and to in the right direction: 328 while (diff) { 329 diff -= 1; 330 this.move( 331 this.circle((to > index ? to : index) - diff - 1), 332 this.slideWidth * direction, 333 0 334 ); 335 } 336 to = this.circle(to); 337 this.move(index, this.slideWidth * direction, speed); 338 this.move(to, 0, speed); 339 if (this.options.continuous) { 340 this.move( 341 this.circle(to - direction), 342 -(this.slideWidth * direction), 343 0 344 ); 345 } 346 } else { 347 to = this.circle(to); 348 this.animate(index * -this.slideWidth, to * -this.slideWidth, speed); 349 } 350 this.onslide(to); 351 }, 352 353 getIndex: function () { 354 return this.index; 355 }, 356 357 getNumber: function () { 358 return this.num; 359 }, 360 361 prev: function () { 362 if (this.options.continuous || this.index) { 363 this.slide(this.index - 1); 364 } 365 }, 366 367 next: function () { 368 if (this.options.continuous || this.index < this.num - 1) { 369 this.slide(this.index + 1); 370 } 371 }, 372 373 play: function (time) { 374 var that = this; 375 window.clearTimeout(this.timeout); 376 this.interval = time || this.options.slideshowInterval; 377 if (this.elements[this.index] > 1) { 378 this.timeout = this.setTimeout( 379 (!this.requestAnimationFrame && this.slide) || function (to, speed) { 380 that.animationFrameId = that.requestAnimationFrame.call( 381 window, 382 function () { 383 that.slide(to, speed); 384 } 385 ); 386 }, 387 [this.index + 1, this.options.slideshowTransitionSpeed], 388 this.interval 389 ); 390 } 391 this.container.addClass(this.options.playingClass); 392 }, 393 394 pause: function () { 395 window.clearTimeout(this.timeout); 396 this.interval = null; 397 this.container.removeClass(this.options.playingClass); 398 }, 399 400 add: function (list) { 401 var i; 402 if (!list.concat) { 403 // Make a real array out of the list to add: 404 list = Array.prototype.slice.call(list); 405 } 406 if (!this.list.concat) { 407 // Make a real array out of the Gallery list: 408 this.list = Array.prototype.slice.call(this.list); 409 } 410 this.list = this.list.concat(list); 411 this.num = this.list.length; 412 if (this.num > 2 && this.options.continuous === null) { 413 this.options.continuous = true; 414 this.container.removeClass(this.options.leftEdgeClass); 415 } 416 this.container 417 .removeClass(this.options.rightEdgeClass) 418 .removeClass(this.options.singleClass); 419 for (i = this.num - list.length; i < this.num; i += 1) { 420 this.addSlide(i); 421 this.positionSlide(i); 422 } 423 this.positions.length = this.num; 424 this.initSlides(true); 425 }, 426 427 resetSlides: function () { 428 this.slidesContainer.empty(); 429 this.slides = []; 430 }, 431 432 handleClose: function () { 433 var options = this.options; 434 this.destroyEventListeners(); 435 // Cancel the slideshow: 436 this.pause(); 437 this.container[0].style.display = 'none'; 438 this.container 439 .removeClass(options.displayClass) 440 .removeClass(options.singleClass) 441 .removeClass(options.leftEdgeClass) 442 .removeClass(options.rightEdgeClass); 443 if (options.hidePageScrollbars) { 444 document.body.style.overflow = this.bodyOverflowStyle; 445 } 446 if (this.options.clearSlides) { 447 this.resetSlides(); 448 } 449 if (this.options.onclosed) { 450 this.options.onclosed.call(this); 451 } 452 }, 453 454 close: function () { 455 var that = this, 456 closeHandler = function (event) { 457 if (event.target === that.container[0]) { 458 that.container.off( 459 that.support.transition.end, 460 closeHandler 461 ); 462 that.handleClose(); 463 } 464 }; 465 if (this.options.onclose) { 466 this.options.onclose.call(this); 467 } 468 if (this.support.transition && this.options.displayTransition) { 469 this.container.on( 470 this.support.transition.end, 471 closeHandler 472 ); 473 this.container.removeClass(this.options.displayClass); 474 } else { 475 this.handleClose(); 476 } 477 }, 478 479 circle: function (index) { 480 // Always return a number inside of the slides index range: 481 return (this.num + (index % this.num)) % this.num; 482 }, 483 484 move: function (index, dist, speed) { 485 this.translateX(index, dist, speed); 486 this.positions[index] = dist; 487 }, 488 489 translate: function (index, x, y, speed) { 490 var style = this.slides[index].style, 491 transition = this.support.transition, 492 transform = this.support.transform; 493 style[transition.name + 'Duration'] = speed + 'ms'; 494 style[transform.name] = 'translate(' + x + 'px, ' + y + 'px)' + 495 (transform.translateZ ? ' translateZ(0)' : ''); 496 }, 497 498 translateX: function (index, x, speed) { 499 this.translate(index, x, 0, speed); 500 }, 501 502 translateY: function (index, y, speed) { 503 this.translate(index, 0, y, speed); 504 }, 505 506 animate: function (from, to, speed) { 507 if (!speed) { 508 this.slidesContainer[0].style.left = to + 'px'; 509 return; 510 } 511 var that = this, 512 start = new Date().getTime(), 513 timer = window.setInterval(function () { 514 var timeElap = new Date().getTime() - start; 515 if (timeElap > speed) { 516 that.slidesContainer[0].style.left = to + 'px'; 517 that.ontransitionend(); 518 window.clearInterval(timer); 519 return; 520 } 521 that.slidesContainer[0].style.left = (((to - from) * 522 (Math.floor((timeElap / speed) * 100) / 100)) + 523 from) + 'px'; 524 }, 4); 525 }, 526 527 preventDefault: function (event) { 528 if (event.preventDefault) { 529 event.preventDefault(); 530 } else { 531 event.returnValue = false;
532 } 533 }, 534 535 stopPropagation: function (event) { 536 if (event.stopPropagation) { 537 event.stopPropagation(); 538 } else { 539 event.cancelBubble = true; 540 } 541 }, 542 543 onresize: function () { 544 this.initSlides(true); 545 }, 546 547 onmousedown: function (event) { 548 // Trigger on clicks of the left mouse button only 549 // and exclude video elements: 550 if (event.which && event.which === 1 && 551 event.target.nodeName !== 'VIDEO') { 552 // Preventing the default mousedown action is required 553 // to make touch emulation work with Firefox: 554 event.preventDefault(); 555 (event.originalEvent || event).touches = [{ 556 pageX: event.pageX, 557 pageY: event.pageY 558 }]; 559 this.ontouchstart(event); 560 } 561 }, 562 563 onmousemove: function (event) { 564 if (this.touchStart) { 565 (event.originalEvent || event).touches = [{ 566 pageX: event.pageX, 567 pageY: event.pageY 568 }]; 569 this.ontouchmove(event); 570 } 571 }, 572 573 onmouseup: function (event) { 574 if (this.touchStart) { 575 this.ontouchend(event); 576 delete this.touchStart; 577 } 578 }, 579 580 onmouseout: function (event) { 581 if (this.touchStart) { 582 var target = event.target, 583 related = event.relatedTarget; 584 if (!related || (related !== target && 585 !$.contains(target, related))) { 586 this.onmouseup(event); 587 } 588 } 589 }, 590 591 ontouchstart: function (event) { 592 if (this.options.stopTouchEventsPropagation) { 593 this.stopPropagation(event); 594 } 595 // jQuery doesn't copy touch event properties by default, 596 // so we have to access the originalEvent object: 597 var touches = (event.originalEvent || event).touches[0]; 598 this.touchStart = { 599 // Remember the initial touch coordinates: 600 x: touches.pageX, 601 y: touches.pageY, 602 // Store the time to determine touch duration: 603 time: Date.now() 604 }; 605 // Helper variable to detect scroll movement: 606 this.isScrolling = undefined; 607 // Reset delta values: 608 this.touchDelta = {}; 609 }, 610 611 ontouchmove: function (event) { 612 if (this.options.stopTouchEventsPropagation) { 613 this.stopPropagation(event); 614 } 615 // jQuery doesn't copy touch event properties by default, 616 // so we have to access the originalEvent object: 617 var touches = (event.originalEvent || event).touches[0], 618 scale = (event.originalEvent || event).scale, 619 index = this.index, 620 touchDeltaX, 621 indices; 622 // Ensure this is a one touch swipe and not, e.g. a pinch: 623 if (touches.length > 1 || (scale && scale !== 1)) { 624 return; 625 } 626 if (this.options.disableScroll) { 627 event.preventDefault(); 628 } 629 // Measure change in x and y coordinates: 630 this.touchDelta = { 631 x: touches.pageX - this.touchStart.x, 632 y: touches.pageY - this.touchStart.y 633 }; 634 touchDeltaX = this.touchDelta.x; 635 // Detect if this is a vertical scroll movement (run only once per touch): 636 if (this.isScrolling === undefined) { 637 this.isScrolling = this.isScrolling || 638 Math.abs(touchDeltaX) < Math.abs(this.touchDelta.y); 639 } 640 if (!this.isScrolling) { 641 // Always prevent horizontal scroll: 642 event.preventDefault(); 643 // Stop the slideshow: 644 window.clearTimeout(this.timeout); 645 if (this.options.continuous) { 646 indices = [ 647 this.circle(index + 1), 648 index, 649 this.circle(index - 1) 650 ]; 651 } else { 652 // Increase resistance if first slide and sliding left 653 // or last slide and sliding right: 654 this.touchDelta.x = touchDeltaX = 655 touchDeltaX / 656 (((!index && touchDeltaX > 0) || 657 (index === this.num - 1 && touchDeltaX < 0)) ? 658 (Math.abs(touchDeltaX) / this.slideWidth + 1) : 1); 659 indices = [index]; 660 if (index) { 661 indices.push(index - 1); 662 } 663 if (index < this.num - 1) { 664 indices.unshift(index + 1); 665 } 666 } 667 while (indices.length) { 668 index = indices.pop(); 669 this.translateX(index, touchDeltaX + this.positions[index], 0
669); 670 } 671 } else if (this.options.closeOnSwipeUpOrDown) { 672 this.translateY(index, this.touchDelta.y + this.positions[index], 0); 673 } 674 }, 675 676 ontouchend: function (event) { 677 if (this.options.stopTouchEventsPropagation) { 678 this.stopPropagation(event); 679 } 680 var index = this.index, 681 speed = this.options.transitionSpeed, 682 slideWidth = this.slideWidth, 683 isShortDuration = Number(Date.now() - this.touchStart.time) < 250, 684 // Determine if slide attempt triggers next/prev slide: 685 isValidSlide = (isShortDuration && Math.abs(this.touchDelta.x) > 20) || 686 Math.abs(this.touchDelta.x) > slideWidth / 2, 687 // Determine if slide attempt is past start or end: 688 isPastBounds = (!index && this.touchDelta.x > 0) || 689 (index === this.num - 1 && this.touchDelta.x < 0), 690 isValidClose = !isValidSlide && this.options.closeOnSwipeUpOrDown && 691 ((isShortDuration && Math.abs(this.touchDelta.y) > 20) || 692 Math.abs(this.touchDelta.y) > this.slideHeight / 2), 693 direction, 694 indexForward, 695 indexBackward, 696 distanceForward, 697 distanceBackward; 698 if (this.options.continuous) { 699 isPastBounds = false; 700 } 701 // Determine direction of swipe (true: right, false: left): 702 direction = this.touchDelta.x < 0 ? -1 : 1; 703 if (!this.isScrolling) { 704 if (isValidSlide && !isPastBounds) { 705 indexForward = index + direction; 706 indexBackward = index - direction; 707 distanceForward = slideWidth * direction; 708 distanceBackward = -slideWidth * direction; 709 if (this.options.continuous) { 710 this.move(this.circle(indexForward), distanceForward, 0); 711 this.move(this.circle(index - 2 * direction), distanceBackward, 0); 712 } else if (indexForward >= 0 && 713 indexForward < this.num) { 714 this.move(indexForward, distanceForward, 0); 715 } 716 this.move(index, this.positions[index] + distanceForward, speed); 717 this.move( 718 this.circle(indexBackward), 719 this.positions[this.circle(indexBackward)] + distanceForward, 720 speed 721 ); 722 index = this.circle(indexBackward); 723 this.onslide(index); 724 } else { 725 // Move back into position 726 if (this.options.continuous) { 727 this.move(this.circle(index - 1), -slideWidth, speed); 728 this.move(index, 0, speed); 729 this.move(this.circle(index + 1), slideWidth, speed); 730 } else { 731 if (index) { 732 this.move(index - 1, -slideWidth, speed); 733 } 734 this.move(index, 0, speed); 735 if (index < this.num - 1) { 736 this.move(index + 1, slideWidth, speed); 737 } 738 } 739 } 740 } else { 741 if (isValidClose) { 742 this.close(); 743 } else { 744 // Move back into position 745 this.translateY(index, 0, speed); 746 } 747 } 748 }, 749 750 ontouchcancel: function (event) { 751 if (this.touchStart) { 752 this.ontouchend(event); 753 delete this.touchStart; 754 } 755 }, 756 757 ontransitionend: function (event) { 758 var slide = this.slides[this.index]; 759 if (!event || slide === event.target) { 760 if (this.interval) { 761 this.play(); 762 } 763 this.setTimeout( 764 this.options.onslideend, 765 [this.index, slide] 766 ); 767 } 768 }, 769 770 oncomplete: function (event) { 771 var target = event.target || event.srcElement, 772 parent = target && target.parentNode, 773 index; 774 if (!target || !parent) { 775 return; 776 } 777 index = this.getNodeIndex(parent); 778 $(parent).removeClass(this.options.slideLoadingClass); 779 if (event.type === 'error') { 780 $(parent).addClass(this.options.slideErrorClass); 781 this.elements[index] = 3; // Fail 782 } else { 783 this.elements[index] = 2; // Done 784 } 785 // Fix for IE7's lack of support for percentage max-height: 786 if (target.clientHeight > this.container[0].clientHeight) { 787 target.style.maxHeight = this.container[0].clientHeight; 788 } 789 if (this.interval && this.slides[this.index] === parent) { 790 this.play(); 791 } 792 this.setTimeout( 793 this.options.onslidecomplete, 794 [index, parent] 795 ); 796 }, 797 798 onload: function (event) { 799 this.oncomplete(event); 800 }, 801 802 onerror: function (event) { 803 this.oncomplete(event); 804 }, 805 806 onkeydown: function (event) { 807 switch (event.which || event.keyCode) { 808 case 13: // Return 809 if (this.options.toggleControlsOnReturn) { 810 this.preventDefault(event); 811 this.toggleControls(); 812 } 813 break; 814 case 27: // Esc 815 if (this.options.closeOnEscape) { 816 this.close(); 817 } 818 break; 819 case 32: // Space 820 if (this.options.toggleSlideshowOnSpace) { 821 this.preventDefault(event); 822 this.toggleSlideshow(); 823 } 824 break; 825 case 37: // Left 826 if (this.options.enableKeyboardNavigation) { 827 this.preventDefault(event); 828 this.prev(); 829 } 830 break; 831 case 39: // Right 832 if (this.options.enableKeyboardNavigation) { 833 this.preventDefault(event); 834 this.next(); 835 } 836 break; 837 } 838 }, 839 840 handleClick: function (event) { 841 var options = this.options, 842 target = event.target || event.srcElement, 843 parent = target.parentNode, 844 isTarget = function (className) { 845 return $(target).hasClass(className) || 846 $(parent).hasClass(className); 847 }; 848 if (isTarget(options.toggleClass)) { 849 // Click on "toggle" control 850 this.preventDefault(event); 851 this.toggleControls(); 852 } else if (isTarget(options.prevClass)) { 853 // Click on "prev" control 854 this.preventDefault(event); 855 this.prev(); 856 } else if (isTarget(options.nextClass)) { 857 // Click on "next" control 858 this.preventDefault(event); 859 this.next(); 860 } else if (isTarget(options.closeClass)) { 861 // Click on "close" control 862 this.preventDefault(event); 863 this.close(); 864 } else if (isTarget(options.playPauseClass)) { 865 // Click on "play-pause" control 866 this.preventDefault(event); 867 this.toggleSlideshow(); 868 } else if (parent === this.slidesContainer[0]) { 869 // Click on slide background 870 this.preventDefault(event); 871 if (options.closeOnSlideClick) { 872 this.close(); 873 } else { 874 this.toggleControls(); 875 } 876 } else if (parent.parentNode && 877 parent.parentNode === this.slidesContainer[0]) { 878 // Click on displayed element 879 this.preventDefault(event); 880 this.toggleControls(); 881 } 882 }, 883 884 onclick: function (event) { 885 if (this.options.emulateTouchEvents && 886 this.touchDelta && (Math.abs(this.touchDelta.x) > 20 || 887 Math.abs(this.touchDelta.y) >
887 20)) { 888 delete this.touchDelta; 889 return; 890 } 891 return this.handleClick(event); 892 }, 893 894 updateEdgeClasses: function (index) { 895 if (!index) { 896 this.container.addClass(this.options.leftEdgeClass); 897 } else { 898 this.container.removeClass(this.options.leftEdgeClass); 899 } 900 if (index === this.num - 1) { 901 this.container.addClass(this.options.rightEdgeClass); 902 } else { 903 this.container.removeClass(this.options.rightEdgeClass); 904 } 905 }, 906 907 handleSlide: function (index) { 908 if (!this.options.continuous) { 909 this.updateEdgeClasses(index); 910 } 911 this.loadElements(index); 912 if (this.options.unloadElements) { 913 this.unloadElements(index); 914 } 915 this.setTitle(index); 916 }, 917 918 onslide: function (index) { 919 this.index = index; 920 this.handleSlide(index); 921 this.setTimeout(this.options.onslide, [index, this.slides[index]]); 922 }, 923 924 setTitle: function (index) { 925 var text = this.slides[index].firstChild.title, 926 titleElement = this.titleElement; 927 if (titleElement.length) { 928 this.titleElement.empty(); 929 if (text) { 930 titleElement[0].appendChild(document.createTextNode(text)); 931 } 932 } 933 }, 934 935 setTimeout: function (func, args, wait) { 936 var that = this; 937 return func && window.setTimeout(function () { 938 func.apply(that, args || []); 939 }, wait || 0); 940 }, 941 942 imageFactory: function (obj, callback) { 943 var that = this, 944 img = this.imagePrototype.cloneNode(false), 945 url = obj, 946 backgroundSize = this.options.stretchImages, 947 called, 948 element, 949 callbackWrapper = function (event) { 950 if (!called) { 951 event = { 952 type: event.type, 953 target: element 954 }; 955 if (!element.parentNode) { 956 // Fix for IE7 firing the load event for 957 // cached images before the element could 958 // be added to the DOM: 959 return that.setTimeout(callbackWrapper, [event]); 960 } 961 called = true; 962 $(img).off('load error', callbackWrapper); 963 if (backgroundSize) { 964 if (event.type === 'load') { 965 element.style.background = 'url("' + url + 966 '") center no-repeat'; 967 element.style.backgroundSize = backgroundSize; 968 } 969 } 970 callback(event); 971 } 972 }, 973 title; 974 if (typeof url !== 'string') { 975 url = this.getItemProperty(obj, this.options.urlProperty); 976 title = this.getItemProperty(obj, this.options.titleProperty); 977 } 978 if (backgroundSize === true) { 979 backgroundSize = 'contain'; 980 } 981 backgroundSize = this.support.backgroundSize && 982 this.support.backgroundSize[backgroundSize] && backgroundSize; 983 if (backgroundSize) { 984 element = this.elementPrototype.cloneNode(false); 985 } else { 986 element = img; 987 img.draggable = false; 988 } 989 if (title) { 990 element.title = title; 991 } 992 $(img).on('load error', callbackWrapper); 993 img.src = url; 994 return element; 995 }, 996 997 createElement: function (obj, callback) { 998 var type = obj && this.getItemProperty(obj, this.options.typeProperty), 999 factory = (type && this[type.split('/')[0] + 'Factory']) || 1000 this.imageFactory, 1001 element = obj && factory.call(this, obj, callback); 1002 if (!element) { 1003 element = this.elementPrototype.cloneNode(false); 1004 this.setTimeout(callback, [{ 1005 type: 'error', 1006 target: element 1007 }]); 1008 } 1009 $(element).addClass(this.options.slideContentClass); 1010 return element; 1011 }, 1012 1013 loadElement: function (index) { 1014 if (!this.elements[index]) { 1015 if (this.slides[index].firstChild) { 1016 this.elements[index] = $(this.slides[index]) 1017 .hasClass(this.options.slideErrorClass) ? 3 : 2; 1018 } else { 1019 this.elements[index] = 1; // Loading 1020 $(this.slides[index]).addClass(this.options.slideLoadingClass); 1021 this.slides[index].appendChild(this.createElement( 1022 this.list[index], 1023 this.proxyListener 1024 )); 1025 } 1026 } 1027 }, 1028 1029 loadElements: function (index) { 1030 var limit = Math.min(this.num, this.options.preloadRange * 2 + 1), 1031 j = index, 1032 i; 1033 for (i = 0; i < limit; i += 1) { 1034 // First load the current slide element (0), 1035 // then the next one (+1), 1036 // then the previous one (-2), 1037 // then the next after next (+2), etc.: 1038 j += i * (i % 2 === 0 ? -1 : 1); 1039 // Connect the ends of the list to load slide elements for 1040 // continuous navigation: 1041 j = this.circle(j); 1042 this.loadElement(j); 1043 } 1044 }, 1045 1046 unloadElements: function (index) { 1047 var i, 1048 slide, 1049 diff; 1050 for (i in this.elements) { 1051 if (this.elements.hasOwnProperty(i)) { 1052 diff = Math.abs(index - i); 1053 if (diff > this.options.preloadRange && 1054 diff + this.options.preloadRange < this.num) { 1055 slide = this.slides[i]; 1056 slide.removeChild(slide.firstChild); 1057 delete this.elements[i]; 1058 } 1059 } 1060 } 1061 }, 1062 1063 addSlide: function (index) { 1064 var slide = this.slidePrototype.cloneNode(false); 1065 slide.setAttribute('data-index', index); 1066 this.slidesContainer[0].appendChild(slide); 1067 this.slides.push(slide); 1068 }, 1069 1070 positionSlide: function (index) { 1071 var slide = this.slides[index]; 1072 slide.style.width = this.slideWidth + 'px'; 1073 if (this.support.transition) { 1074 slide.style.left = (index * -this.slideWidth) + 'px'; 1075 this.move(index, this.index > index ? -this.slideWidth : 1076 (this.index < index ? this.slideWidth : 0), 0); 1077 } 1078 }, 1079 1080 initSlides: function (reload) { 1081 var clearSlides, 1082 i; 1083 if (!reload) { 1084 this.positions = []; 1085 this.positions.length = this.num; 1086 this.elements = {}; 1087 this.imagePrototype = document.createElement('img'); 1088 this.elementPrototype = document.createElement('div'); 1089 this.slidePrototype = document.createElement('div'); 1090 $(this.slidePrototype).addClass(this.options.slideClass); 1091 this.slides = this.slidesContainer[0].children; 1092 clearSlides = this.options.clearSlides || 1093 this.slides.length !== this.num; 1094 } 1095 this.slideWidth = this.container[0].offsetWidth; 1096 this.slideHeight = this.container[0].offsetHeight; 1097 this.slidesContainer[0].style.width = 1098 (this.num * this.slideWidth) + 'px'; 1099 if (clearSlides) { 1100 this.resetSlides(); 1101 } 1102 for (i = 0; i < this.num; i += 1) { 1103 if (clearSlides) { 1104 this.addSlide(i); 1105 } 1106 this.positionSlide(i); 1107 } 1108 // Reposition the slides before and after the given index: 1109 if (this.options.continuous && this.support.transition) { 1110 this.move(this.circle(this.index - 1), -this.slideWidth, 0); 1111 this.move(this.circle(this.index + 1), this.slideWidth, 0); 1112 } 1113 if (!this.support.transition) { 1114 this.slidesContainer[0].style.left = 1115 (this.index * -this.slideWidth) + 'px'; 1116 } 1117 }, 1118 1119 toggleControls: function () { 1120 var controlsClass = this.options.controlsClass; 1121 if (this.container.hasClass(controlsClass)) { 1122 this.container.removeClass(controlsClass); 1123 } else { 1124 this.container.addClass(controlsClass); 1125 } 1126 }, 1127 1128 toggleSlideshow: function () { 1129 if (!this.interval) { 1130 this.play(); 1131 } else { 1132 this.pause(); 1133 } 1134 }, 1135 1136 getNodeIndex: function (element) { 1137 return parseInt(element.getAttribute('data-index'), 10); 1138 }, 1139 1140 getNestedProperty: function (obj, property) { 1141 property.replace( 1142 // Matches native JavaScript notation in a String, 1143 // e.g. '["doubleQuoteProp"].dotProp[2]' 1144 /\[(?:'([^']+)'|"([^"]+)"|(\d+))\]|(?:(?:^|\.)([^\.\[]+))/g, 1145 function (str, singleQuoteProp, doubleQuoteProp, arrayIndex, dotProp) { 1146 var prop = dotProp || singleQuoteProp || doubleQuoteProp || 1147 (arrayIndex && parseInt(arrayIndex, 10)); 1148 if (str && obj) { 1149 obj = obj[prop]; 1150 } 1151 } 1152 ); 1153 return obj; 1154 }, 1155 1156 getDataProperty: function (obj, property) { 1157 if (obj.getAttribute) { 1158 var prop = obj.getAttribute('data-' + 1159 property.replace(/([A-Z])/g, '-$1').toLowerCase()); 1160 if (typeof prop === 'string') { 1161 if (/^(true|false|null|-?\d+(\.\d+)?|\{[\s\S]*\}|\[[\s\S]*\])$/ 1162 .test(prop)) { 1163 try { 1164 return $.parseJSON(prop); 1165 } catch (ignore) {} 1166 } 1167 return prop; 1168 } 1169 } 1170 }, 1171 1172 getItemProperty: function (obj, property) { 1173 var prop = obj[property]; 1174 if (prop === undefined) { 1175 prop = this.getDataProperty(obj, property); 1176 if (prop === undefined) { 1177 prop = this.getNestedProperty(obj, property); 1178 } 1179 } 1180 return prop; 1181 }, 1182 1183 initStartIndex: function () { 1184 var index = this.options.index, 1185 urlProperty = this.options.urlProperty, 1186 i; 1187 // Check if the index is given as a list object: 1188 if (index && typeof index !== 'number') { 1189 for (i = 0; i < this.num; i += 1) { 1190 if (this.list[i] === index || 1191 this.getItemProperty(this.list[i], urlProperty) === 1192 this.getItemProperty(index, urlProperty)) { 1193 index = i; 1194 break; 1195 } 1196 } 1197 } 1198 // Make sure the index is in the list range: 1199 this.index = this.circle(parseInt(index, 10) || 0); 1200 }, 1201 1202 initEventListeners: function () { 1203 var that = this, 1204 slidesContainer = this.slidesContainer, 1205 proxyListener = function (event) { 1206 var type = that.support.transition && 1207 that.support.transition.end === event.type ? 1208 'transitionend' : event.type; 1209 that['on' + type](event); 1210 }; 1211 $(window).on('resize', proxyListener); 1212 $(document.body).on('keydown', proxyListener); 1213 this.container.on('click', proxyListener); 1214 if (this.support.touch) { 1215 slidesContainer 1216 .on('touchstart touchmove touchend touchcancel', proxyListener); 1217 } else if (this.options.emulateTouchEvents && 1218 this.support.transition) { 1219 slidesContainer 1220 .on('mousedown mousemove mouseup mouseout', proxyListener); 1221 } 1222 if (this.support.transition) { 1223 slidesContainer.on( 1224 this.support.transition.end, 1225 proxyListener 1226 ); 1227 } 1228 this.proxyListener = proxyListener; 1229 }, 1230 1231 destroyEventListeners: function () { 1232 var slidesContainer = this.slidesContainer, 1233 proxyListener = this.proxyListener; 1234 $(window).off('resize', proxyListener); 1235 $(document.body).off('keydown', proxyListener); 1236 this.container.off('click', proxyListener); 1237 if (this.support.touch) { 1238 slidesContainer 1239 .off('touchstart touchmove touchend touchcancel', proxyListener); 1240 } else if (this.options.emulateTouchEvents && 1241 this.support.transition) { 1242 slidesContainer 1243 .off('mousedown mousemove mouseup mouseout', proxyListener); 1244 } 1245 if (this.support.transition) { 1246 slidesContainer.off( 1247 this.support.transition.end, 1248 proxyListener 1249 ); 1250 } 1251 }, 1252 1253 handleOpen: function () { 1254 if (this.options.onopened) { 1255 this.options.onopened.call(this); 1256 } 1257 }, 1258 1259 initWidget: function () { 1260 var that = this, 1261 openHandler = function (event) { 1262 if (event.target === that.container[0]) { 1263 that.container.off( 1264 that.support.transition.end, 1265 openHandler 1266 ); 1267 that.handleOpen(); 1268 } 1269 }; 1270 this.container = $(this.options.container); 1271 if (!this.container.length) { 1272 this.console.log( 1273 'blueimp Gallery: Widget container not found.', 1274 this.options.container 1275 ); 1276 return false;
1277 } 1278 this.slidesContainer = this.container.find( 1279 this.options.slidesContainer 1280 ).first(); 1281 if (!this.slidesContainer.length) { 1282 this.console.log( 1283 'blueimp Gallery: Slides container not found.', 1284 this.options.slidesContainer 1285 ); 1286 return false; 1287 } 1288 this.titleElement = this.container.find( 1289 this.options.titleElement 1290 ).first(); 1291 if (this.num === 1) { 1292 this.container.addClass(this.options.singleClass); 1293 } 1294 if (this.options.onopen) { 1295 this.options.onopen.call(this); 1296 } 1297 if (this.support.transition && this.options.displayTransition) { 1298 this.container.on( 1299 this.support.transition.end, 1300 openHandler 1301 ); 1302 } else { 1303 this.handleOpen(); 1304 } 1305 if (this.options.hidePageScrollbars) { 1306 // Hide the page scrollbars: 1307 this.bodyOverflowStyle = document.body.style.overflow; 1308 document.body.style.overflow = 'hidden'; 1309 } 1310 this.container[0].style.display = 'block'; 1311 this.initSlides(); 1312 this.container.addClass(this.options.displayClass); 1313 }, 1314 1315 initOptions: function (options) { 1316 // Create a copy of the prototype options: 1317 this.options = $.extend({}, this.options); 1318 // Check if carousel mode is enabled: 1319 if ((options && options.carousel) || 1320 (this.options.carousel && (!options || options.carousel !== false))) { 1321 $.extend(this.options, this.carouselOptions); 1322 } 1323 // Override any given options: 1324 $.extend(this.options, options); 1325 if (this.num < 3) { 1326 // 1 or 2 slides cannot be displayed continuous, 1327 // remember the original option by setting to null instead of false: 1328 this.options.continuous = this.options.continuous ? null : false; 1329 } 1330 if (!this.support.transition) { 1331 this.options.emulateTouchEvents = false; 1332 } 1333 if (this.options.event) { 1334 this.preventDefault(this.options.event); 1335 } 1336 } 1337 1338 }); 1339 1340 return Gallery; 1341}));
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.