egroupware_official/api/js/jquery/blueimp/js/blueimp-gallery.js

1381 lines
54 KiB
JavaScript

/*
* blueimp Gallery JS 2.14.1
* https://github.com/blueimp/Gallery
*
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
* Swipe implementation based on
* https://github.com/bradbirdsall/Swipe
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/* global define, window, document, DocumentTouch */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define(['./blueimp-helper'], factory);
} else {
// Browser globals:
window.blueimp = window.blueimp || {};
window.blueimp.Gallery = factory(
window.blueimp.helper || window.jQuery
);
}
}(function ($) {
'use strict';
function Gallery(list, options) {
if (document.body.style.maxHeight === undefined) {
// document.body.style.maxHeight is undefined on IE6 and lower
return null;
}
if (!this || this.options !== Gallery.prototype.options) {
// Called as function instead of as constructor,
// so we simply return a new instance:
return new Gallery(list, options);
}
if (!list || !list.length) {
this.console.log(
'blueimp Gallery: No or empty list provided as first argument.',
list
);
return;
}
this.list = list;
this.num = list.length;
this.initOptions(options);
this.initialize();
}
$.extend(Gallery.prototype, {
options: {
// The Id, element or querySelector of the gallery widget:
container: '#blueimp-gallery',
// The tag name, Id, element or querySelector of the slides container:
slidesContainer: 'div',
// The tag name, Id, element or querySelector of the title element:
titleElement: 'h3',
// The class to add when the gallery is visible:
displayClass: 'blueimp-gallery-display',
// The class to add when the gallery controls are visible:
controlsClass: 'blueimp-gallery-controls',
// The class to add when the gallery only displays one element:
singleClass: 'blueimp-gallery-single',
// The class to add when the left edge has been reached:
leftEdgeClass: 'blueimp-gallery-left',
// The class to add when the right edge has been reached:
rightEdgeClass: 'blueimp-gallery-right',
// The class to add when the automatic slideshow is active:
playingClass: 'blueimp-gallery-playing',
// The class for all slides:
slideClass: 'slide',
// The slide class for loading elements:
slideLoadingClass: 'slide-loading',
// The slide class for elements that failed to load:
slideErrorClass: 'slide-error',
// The class for the content element loaded into each slide:
slideContentClass: 'slide-content',
// The class for the "toggle" control:
toggleClass: 'toggle',
// The class for the "prev" control:
prevClass: 'prev',
// The class for the "next" control:
nextClass: 'next',
// The class for the "close" control:
closeClass: 'close',
// The class for the "play-pause" toggle control:
playPauseClass: 'play-pause',
// The class fullscreen button control
fullscreenClass:'fullscreen',
// The class download button control
downloadClass:'download',
// The list object property (or data attribute) with the object type:
typeProperty: 'type',
// The list object property (or data attribute) with the object title:
titleProperty: 'title',
// The list object property (or data attribute) with the object URL:
urlProperty: 'href',
// The gallery listens for transitionend events before triggering the
// opened and closed events, unless the following option is set to false:
displayTransition: true,
// Defines if the gallery slides are cleared from the gallery modal,
// or reused for the next gallery initialization:
clearSlides: true,
// Defines if images should be stretched to fill the available space,
// while maintaining their aspect ratio (will only be enabled for browsers
// supporting background-size="contain", which excludes IE < 9).
// Set to "cover", to make images cover all available space (requires
// support for background-size="cover", which excludes IE < 9):
stretchImages: false,
// Toggle the controls on pressing the Return key:
toggleControlsOnReturn: true,
// Toggle the automatic slideshow interval on pressing the Space key:
toggleSlideshowOnSpace: true,
// Toggle fullscreen mode when slideshow is running
toggleFullscreenOnSlideShow: false,
// Navigate the gallery by pressing left and right on the keyboard:
enableKeyboardNavigation: true,
// Close the gallery on pressing the Esc key:
closeOnEscape: true,
//Hide controls when the slideshow is playing
hideControlsOnSlideshow:false,
// Close the gallery when clicking on an empty slide area:
closeOnSlideClick: true,
// Close the gallery by swiping up or down:
closeOnSwipeUpOrDown: true,
// Emulate touch events on mouse-pointer devices such as desktop browsers:
emulateTouchEvents: true,
// Stop touch events from bubbling up to ancestor elements of the Gallery:
stopTouchEventsPropagation: false,
// Hide the page scrollbars:
hidePageScrollbars: true,
// Stops any touches on the container from scrolling the page:
disableScroll: true,
// Carousel mode (shortcut for carousel specific options):
carousel: false,
// Allow continuous navigation, moving from last to first
// and from first to last slide:
continuous: true,
// Remove elements outside of the preload range from the DOM:
unloadElements: true,
// Start with the automatic slideshow:
startSlideshow: false,
// Delay in milliseconds between slides for the automatic slideshow:
slideshowInterval: 5000,
// The starting index as integer.
// Can also be an object of the given list,
// or an equal object with the same url property:
index: 0,
// The number of elements to load around the current index:
preloadRange: 2,
// The transition speed between slide changes in milliseconds:
transitionSpeed: 400,
// The transition speed for automatic slide changes, set to an integer
// greater 0 to override the default transition speed:
slideshowTransitionSpeed: undefined,
// The event object for which the default action will be canceled
// on Gallery initialization (e.g. the click event to open the Gallery):
event: undefined,
// Callback function executed when the Gallery is initialized.
// Is called with the gallery instance as "this" object:
onopen: undefined,
// Callback function executed when the Gallery has been initialized
// and the initialization transition has been completed.
// Is called with the gallery instance as "this" object:
onopened: undefined,
// Callback function executed on slide change.
// Is called with the gallery instance as "this" object and the
// current index and slide as arguments:
onslide: undefined,
// Callback function executed after the slide change transition.
// Is called with the gallery instance as "this" object and the
// current index and slide as arguments:
onslideend: undefined,
// Callback function executed on slide content load.
// Is called with the gallery instance as "this" object and the
// slide index and slide element as arguments:
onslidecomplete: undefined,
// Callback function executed when the Gallery is about to be closed.
// Is called with the gallery instance as "this" object:
onclose: undefined,
// Callback function executed when the Gallery has been closed
// and the closing transition has been completed.
// Is called with the gallery instance as "this" object:
onclosed: undefined
},
carouselOptions: {
hidePageScrollbars: false,
toggleControlsOnReturn: false,
toggleSlideshowOnSpace: false,
enableKeyboardNavigation: false,
closeOnEscape: false,
closeOnSlideClick: false,
closeOnSwipeUpOrDown: false,
disableScroll: false,
startSlideshow: true
},
console: window.console && typeof window.console.log === 'function' ?
window.console :
{log: function () {}},
// Detect touch, transition, transform and background-size support:
support: (function (element) {
var support = {
touch: window.ontouchstart !== undefined ||
(window.DocumentTouch && document instanceof DocumentTouch)
},
transitions = {
webkitTransition: {
end: 'webkitTransitionEnd',
prefix: '-webkit-'
},
MozTransition: {
end: 'transitionend',
prefix: '-moz-'
},
OTransition: {
end: 'otransitionend',
prefix: '-o-'
},
transition: {
end: 'transitionend',
prefix: ''
}
},
elementTests = function () {
var transition = support.transition,
prop,
translateZ;
document.body.appendChild(element);
if (transition) {
prop = transition.name.slice(0, -9) + 'ransform';
if (element.style[prop] !== undefined) {
element.style[prop] = 'translateZ(0)';
translateZ = window.getComputedStyle(element)
.getPropertyValue(transition.prefix + 'transform');
support.transform = {
prefix: transition.prefix,
name: prop,
translate: true,
translateZ: !!translateZ && translateZ !== 'none'
};
}
}
if (element.style.backgroundSize !== undefined) {
support.backgroundSize = {};
element.style.backgroundSize = 'contain';
support.backgroundSize.contain = window
.getComputedStyle(element)
.getPropertyValue('background-size') === 'contain';
element.style.backgroundSize = 'cover';
support.backgroundSize.cover = window
.getComputedStyle(element)
.getPropertyValue('background-size') === 'cover';
}
document.body.removeChild(element);
};
(function (support, transitions) {
var prop;
for (prop in transitions) {
if (transitions.hasOwnProperty(prop) &&
element.style[prop] !== undefined) {
support.transition = transitions[prop];
support.transition.name = prop;
break;
}
}
}(support, transitions));
if (document.body) {
elementTests();
} else {
$(document).on('DOMContentLoaded', elementTests);
}
return support;
// Test element, has to be standard HTML and must not be hidden
// for the CSS3 tests using window.getComputedStyle to be applicable:
}(document.createElement('div'))),
requestAnimationFrame: window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame,
initialize: function () {
this.initStartIndex();
if (this.initWidget() === false) {
return false;
}
this.initEventListeners();
// Load the slide at the given index:
this.onslide(this.index);
// Manually trigger the slideend event for the initial slide:
this.ontransitionend();
// Start the automatic slideshow if applicable:
if (this.options.startSlideshow) {
this.play();
}
},
slide: function (to, speed) {
window.clearTimeout(this.timeout);
var index = this.index,
direction,
naturalDirection,
diff;
if (index === to || this.num === 1) {
return;
}
if (!speed) {
speed = this.options.transitionSpeed;
}
if (this.support.transform) {
if (!this.options.continuous) {
to = this.circle(to);
}
// 1: backward, -1: forward:
direction = Math.abs(index - to) / (index - to);
// Get the actual position of the slide:
if (this.options.continuous) {
naturalDirection = direction;
direction = -this.positions[this.circle(to)] / this.slideWidth;
// If going forward but to < index, use to = slides.length + to
// If going backward but to > index, use to = -slides.length + to
if (direction !== naturalDirection) {
to = -direction * this.num + to;
}
}
diff = Math.abs(index - to) - 1;
// Move all the slides between index and to in the right direction:
while (diff) {
diff -= 1;
this.move(
this.circle((to > index ? to : index) - diff - 1),
this.slideWidth * direction,
0
);
}
to = this.circle(to);
this.move(index, this.slideWidth * direction, speed);
this.move(to, 0, speed);
if (this.options.continuous) {
this.move(
this.circle(to - direction),
-(this.slideWidth * direction),
0
);
}
} else {
to = this.circle(to);
this.animate(index * -this.slideWidth, to * -this.slideWidth, speed);
}
this.onslide(to);
},
getIndex: function () {
return this.index;
},
getNumber: function () {
return this.num;
},
prev: function () {
if (this.options.continuous || this.index) {
this.slide(this.index - 1);
}
},
next: function () {
if (this.options.continuous || this.index < this.num - 1) {
this.slide(this.index + 1);
}
},
play: function (time) {
var that = this;
if (this.options.hideControlsOnSlideshow) this.container.removeClass(this.options.controlsClass);
window.clearTimeout(this.timeout);
this.interval = time || this.options.slideshowInterval;
if (this.elements[this.index] > 1) {
this.timeout = this.setTimeout(
(!this.requestAnimationFrame && this.slide) || function (to, speed) {
that.animationFrameId = that.requestAnimationFrame.call(
window,
function () {
that.slide(to, speed);
}
);
},
[this.index + 1, this.options.slideshowTransitionSpeed],
this.interval
);
}
this.container.addClass(this.options.playingClass);
},
pause: function () {
window.clearTimeout(this.timeout);
this.interval = null;
this.container.removeClass(this.options.playingClass);
},
add: function (list) {
var i;
if (!list.concat) {
// Make a real array out of the list to add:
list = Array.prototype.slice.call(list);
}
if (!this.list.concat) {
// Make a real array out of the Gallery list:
this.list = Array.prototype.slice.call(this.list);
}
this.list = this.list.concat(list);
this.num = this.list.length;
if (this.num > 2 && this.options.continuous === null) {
this.options.continuous = true;
this.container.removeClass(this.options.leftEdgeClass);
}
this.container
.removeClass(this.options.rightEdgeClass)
.removeClass(this.options.singleClass);
for (i = this.num - list.length; i < this.num; i += 1) {
this.addSlide(i);
this.positionSlide(i);
}
this.positions.length = this.num;
this.initSlides(true);
},
resetSlides: function () {
this.slidesContainer.empty();
this.slides = [];
},
handleClose: function () {
var options = this.options;
this.destroyEventListeners();
// Cancel the slideshow:
this.pause();
this.container[0].style.display = 'none';
this.container
.removeClass(options.displayClass)
.removeClass(options.singleClass)
.removeClass(options.leftEdgeClass)
.removeClass(options.rightEdgeClass);
if (options.hidePageScrollbars) {
document.body.style.overflow = this.bodyOverflowStyle;
}
if (this.options.clearSlides) {
this.resetSlides();
}
if (this.options.onclosed) {
this.options.onclosed.call(this);
}
},
close: function () {
var that = this,
closeHandler = function (event) {
if (event.target === that.container[0]) {
that.container.off(
that.support.transition.end,
closeHandler
);
that.handleClose();
}
};
if (this.options.onclose) {
this.options.onclose.call(this);
}
if (this.support.transition && this.options.displayTransition) {
this.container.on(
this.support.transition.end,
closeHandler
);
this.container.removeClass(this.options.displayClass);
} else {
this.handleClose();
}
},
circle: function (index) {
// Always return a number inside of the slides index range:
return (this.num + (index % this.num)) % this.num;
},
move: function (index, dist, speed) {
this.translateX(index, dist, speed);
this.positions[index] = dist;
},
translate: function (index, x, y, speed) {
var style = this.slides[index].style,
transition = this.support.transition,
transform = this.support.transform;
style[transition.name + 'Duration'] = speed + 'ms';
style[transform.name] = 'translate(' + x + 'px, ' + y + 'px)' +
(transform.translateZ ? ' translateZ(0)' : '');
},
translateX: function (index, x, speed) {
this.translate(index, x, 0, speed);
},
translateY: function (index, y, speed) {
this.translate(index, 0, y, speed);
},
animate: function (from, to, speed) {
if (!speed) {
this.slidesContainer[0].style.left = to + 'px';
return;
}
var that = this,
start = new Date().getTime(),
timer = window.setInterval(function () {
var timeElap = new Date().getTime() - start;
if (timeElap > speed) {
that.slidesContainer[0].style.left = to + 'px';
that.ontransitionend();
window.clearInterval(timer);
return;
}
that.slidesContainer[0].style.left = (((to - from) *
(Math.floor((timeElap / speed) * 100) / 100)) +
from) + 'px';
}, 4);
},
preventDefault: function (event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
stopPropagation: function (event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
},
onresize: function () {
this.initSlides(true);
},
onmousedown: function (event) {
// Trigger on clicks of the left mouse button only
// and exclude video elements:
if (event.which && event.which === 1 &&
event.target.nodeName !== 'VIDEO') {
// Preventing the default mousedown action is required
// to make touch emulation work with Firefox:
event.preventDefault();
(event.originalEvent || event).touches = [{
pageX: event.pageX,
pageY: event.pageY
}];
this.ontouchstart(event);
}
},
onmousemove: function (event) {
if (this.touchStart) {
(event.originalEvent || event).touches = [{
pageX: event.pageX,
pageY: event.pageY
}];
this.ontouchmove(event);
}
},
onmouseup: function (event) {
if (this.touchStart) {
this.ontouchend(event);
delete this.touchStart;
}
},
onmouseout: function (event) {
if (this.touchStart) {
var target = event.target,
related = event.relatedTarget;
if (!related || (related !== target &&
!$.contains(target, related))) {
this.onmouseup(event);
}
}
},
ontouchstart: function (event) {
if (this.options.stopTouchEventsPropagation) {
this.stopPropagation(event);
}
// jQuery doesn't copy touch event properties by default,
// so we have to access the originalEvent object:
var touches = (event.originalEvent || event).touches[0];
this.touchStart = {
// Remember the initial touch coordinates:
x: touches.pageX,
y: touches.pageY,
// Store the time to determine touch duration:
time: Date.now()
};
// Helper variable to detect scroll movement:
this.isScrolling = undefined;
// Reset delta values:
this.touchDelta = {};
},
ontouchmove: function (event) {
if (this.options.stopTouchEventsPropagation) {
this.stopPropagation(event);
}
// jQuery doesn't copy touch event properties by default,
// so we have to access the originalEvent object:
var touches = (event.originalEvent || event).touches[0],
scale = (event.originalEvent || event).scale,
index = this.index,
touchDeltaX,
indices;
// Ensure this is a one touch swipe and not, e.g. a pinch:
if (touches.length > 1 || (scale && scale !== 1)) {
return;
}
if (this.options.disableScroll) {
event.preventDefault();
}
// Measure change in x and y coordinates:
this.touchDelta = {
x: touches.pageX - this.touchStart.x,
y: touches.pageY - this.touchStart.y
};
touchDeltaX = this.touchDelta.x;
// Detect if this is a vertical scroll movement (run only once per touch):
if (this.isScrolling === undefined) {
this.isScrolling = this.isScrolling ||
Math.abs(touchDeltaX) < Math.abs(this.touchDelta.y);
}
if (!this.isScrolling) {
// Always prevent horizontal scroll:
event.preventDefault();
// Stop the slideshow:
window.clearTimeout(this.timeout);
if (this.options.continuous) {
indices = [
this.circle(index + 1),
index,
this.circle(index - 1)
];
} else {
// Increase resistance if first slide and sliding left
// or last slide and sliding right:
this.touchDelta.x = touchDeltaX =
touchDeltaX /
(((!index && touchDeltaX > 0) ||
(index === this.num - 1 && touchDeltaX < 0)) ?
(Math.abs(touchDeltaX) / this.slideWidth + 1) : 1);
indices = [index];
if (index) {
indices.push(index - 1);
}
if (index < this.num - 1) {
indices.unshift(index + 1);
}
}
while (indices.length) {
index = indices.pop();
this.translateX(index, touchDeltaX + this.positions[index], 0);
}
} else if (this.options.closeOnSwipeUpOrDown) {
this.translateY(index, this.touchDelta.y + this.positions[index], 0);
}
},
ontouchend: function (event) {
if (this.options.stopTouchEventsPropagation) {
this.stopPropagation(event);
}
var index = this.index,
speed = this.options.transitionSpeed,
slideWidth = this.slideWidth,
isShortDuration = Number(Date.now() - this.touchStart.time) < 250,
// Determine if slide attempt triggers next/prev slide:
isValidSlide = (isShortDuration && Math.abs(this.touchDelta.x) > 20) ||
Math.abs(this.touchDelta.x) > slideWidth / 2,
// Determine if slide attempt is past start or end:
isPastBounds = (!index && this.touchDelta.x > 0) ||
(index === this.num - 1 && this.touchDelta.x < 0),
isValidClose = !isValidSlide && this.options.closeOnSwipeUpOrDown &&
((isShortDuration && Math.abs(this.touchDelta.y) > 20) ||
Math.abs(this.touchDelta.y) > this.slideHeight / 2),
direction,
indexForward,
indexBackward,
distanceForward,
distanceBackward;
if (this.options.continuous) {
isPastBounds = false;
}
// Determine direction of swipe (true: right, false: left):
direction = this.touchDelta.x < 0 ? -1 : 1;
if (!this.isScrolling) {
if (isValidSlide && !isPastBounds) {
indexForward = index + direction;
indexBackward = index - direction;
distanceForward = slideWidth * direction;
distanceBackward = -slideWidth * direction;
if (this.options.continuous) {
this.move(this.circle(indexForward), distanceForward, 0);
this.move(this.circle(index - 2 * direction), distanceBackward, 0);
} else if (indexForward >= 0 &&
indexForward < this.num) {
this.move(indexForward, distanceForward, 0);
}
this.move(index, this.positions[index] + distanceForward, speed);
this.move(
this.circle(indexBackward),
this.positions[this.circle(indexBackward)] + distanceForward,
speed
);
index = this.circle(indexBackward);
this.onslide(index);
} else {
// Move back into position
if (this.options.continuous) {
this.move(this.circle(index - 1), -slideWidth, speed);
this.move(index, 0, speed);
this.move(this.circle(index + 1), slideWidth, speed);
} else {
if (index) {
this.move(index - 1, -slideWidth, speed);
}
this.move(index, 0, speed);
if (index < this.num - 1) {
this.move(index + 1, slideWidth, speed);
}
}
}
} else {
if (isValidClose) {
this.close();
} else {
// Move back into position
this.translateY(index, 0, speed);
}
}
},
ontouchcancel: function (event) {
if (this.touchStart) {
this.ontouchend(event);
delete this.touchStart;
}
},
ontransitionend: function (event) {
var slide = this.slides[this.index];
if (!event || slide === event.target) {
if (this.interval) {
this.play();
}
this.setTimeout(
this.options.onslideend,
[this.index, slide]
);
}
},
oncomplete: function (event) {
var target = event.target || event.srcElement,
parent = target && target.parentNode,
index;
if (!target || !parent) {
return;
}
index = this.getNodeIndex(parent);
$(parent).removeClass(this.options.slideLoadingClass);
if (event.type === 'error') {
$(parent).addClass(this.options.slideErrorClass);
this.elements[index] = 3; // Fail
} else {
this.elements[index] = 2; // Done
}
// Fix for IE7's lack of support for percentage max-height:
if (target.clientHeight > this.container[0].clientHeight) {
target.style.maxHeight = this.container[0].clientHeight;
}
if (this.interval && this.slides[this.index] === parent) {
this.play();
}
this.setTimeout(
this.options.onslidecomplete,
[index, parent]
);
},
onload: function (event) {
this.oncomplete(event);
},
onerror: function (event) {
this.oncomplete(event);
},
onkeydown: function (event) {
switch (event.which || event.keyCode) {
case 13: // Return
if (this.options.toggleControlsOnReturn) {
this.preventDefault(event);
this.toggleControls();
}
break;
case 27: // Esc
if (this.options.closeOnEscape) {
this.close();
}
break;
case 32: // Space
if (this.options.toggleSlideshowOnSpace) {
this.preventDefault(event);
this.toggleSlideshow();
}
break;
case 37: // Left
if (this.options.enableKeyboardNavigation) {
this.preventDefault(event);
this.prev();
}
break;
case 39: // Right
if (this.options.enableKeyboardNavigation) {
this.preventDefault(event);
this.next();
}
break;
}
},
handleClick: function (event) {
var options = this.options,
target = event.target || event.srcElement,
parent = target.parentNode,
isTarget = function (className) {
return $(target).hasClass(className) ||
$(parent).hasClass(className);
};
if (isTarget(options.toggleClass)) {
// Click on "toggle" control
this.preventDefault(event);
this.toggleControls();
} else if (isTarget(options.prevClass)) {
// Click on "prev" control
this.preventDefault(event);
this.prev();
} else if (isTarget(options.nextClass)) {
// Click on "next" control
this.preventDefault(event);
this.next();
} else if (isTarget(options.closeClass)) {
// Click on "close" control
this.preventDefault(event);
this.close();
} else if (isTarget(options.playPauseClass)) {
// Click on "play-pause" control
this.preventDefault(event);
this.toggleSlideshow();
}else if (isTarget(options.fullscreenClass)) {
// Click on "fullscreen" control
this.preventDefault(event);
this.toggleFullscreen();
}
else if (isTarget(options.downloadClass)) {
// Click on "download" control
if (this.list[this.getIndex()] && typeof this.list[this.getIndex()].download_href != 'undefined')
{
event.target.href = this.list[this.getIndex()].download_href;
}
else
{
event.target.href = this.list[this.getIndex()].href;
}
if (typeof event.target.download != 'undefined') event.target.download = this.list[this.getIndex()].title;
}
else if (parent === this.slidesContainer[0]) {
// Click on slide background
this.preventDefault(event);
if (options.closeOnSlideClick) {
this.close();
} else {
this.toggleControls();
}
} else if (parent.parentNode &&
parent.parentNode === this.slidesContainer[0]) {
// Click on displayed element
this.preventDefault(event);
this.toggleControls();
}
},
onclick: function (event) {
if (this.options.emulateTouchEvents &&
this.touchDelta && (Math.abs(this.touchDelta.x) > 20 ||
Math.abs(this.touchDelta.y) > 20)) {
delete this.touchDelta;
return;
}
return this.handleClick(event);
},
updateEdgeClasses: function (index) {
if (!index) {
this.container.addClass(this.options.leftEdgeClass);
} else {
this.container.removeClass(this.options.leftEdgeClass);
}
if (index === this.num - 1) {
this.container.addClass(this.options.rightEdgeClass);
} else {
this.container.removeClass(this.options.rightEdgeClass);
}
},
handleSlide: function (index) {
if (!this.options.continuous) {
this.updateEdgeClasses(index);
}
this.loadElements(index);
if (this.options.unloadElements) {
this.unloadElements(index);
}
this.setTitle(index);
},
onslide: function (index) {
this.index = index;
this.handleSlide(index);
this.setTimeout(this.options.onslide, [index, this.slides[index]]);
},
setTitle: function (index) {
var text = this.slides[index].firstChild.title,
titleElement = this.titleElement;
if (titleElement.length) {
this.titleElement.empty();
if (text) {
titleElement[0].appendChild(document.createTextNode(text));
}
}
},
setTimeout: function (func, args, wait) {
var that = this;
return func && window.setTimeout(function () {
func.apply(that, args || []);
}, wait || 0);
},
imageFactory: function (obj, callback) {
var that = this,
img = this.imagePrototype.cloneNode(false),
url = obj,
backgroundSize = this.options.stretchImages,
called,
element,
callbackWrapper = function (event) {
if (!called) {
event = {
type: event.type,
target: element
};
if (!element.parentNode) {
// Fix for IE7 firing the load event for
// cached images before the element could
// be added to the DOM:
return that.setTimeout(callbackWrapper, [event]);
}
called = true;
$(img).off('load error', callbackWrapper);
if (backgroundSize) {
if (event.type === 'load') {
element.style.background = 'url("' + url +
'") center no-repeat';
element.style.backgroundSize = backgroundSize;
}
}
callback(event);
}
},
title;
if (typeof url !== 'string') {
url = this.getItemProperty(obj, this.options.urlProperty);
title = this.getItemProperty(obj, this.options.titleProperty);
}
if (backgroundSize === true) {
backgroundSize = 'contain';
}
backgroundSize = this.support.backgroundSize &&
this.support.backgroundSize[backgroundSize] && backgroundSize;
if (backgroundSize) {
element = this.elementPrototype.cloneNode(false);
} else {
element = img;
img.draggable = false;
}
if (title) {
element.title = title;
}
$(img).on('load error', callbackWrapper);
img.src = url;
return element;
},
createElement: function (obj, callback) {
var type = obj && this.getItemProperty(obj, this.options.typeProperty),
factory = (type && this[type.split('/')[0] + 'Factory']) ||
this.imageFactory,
element = obj && factory.call(this, obj, callback);
if (!element) {
element = this.elementPrototype.cloneNode(false);
this.setTimeout(callback, [{
type: 'error',
target: element
}]);
}
$(element).addClass(this.options.slideContentClass);
return element;
},
loadElement: function (index) {
if (!this.elements[index]) {
if (this.slides[index].firstChild) {
this.elements[index] = $(this.slides[index])
.hasClass(this.options.slideErrorClass) ? 3 : 2;
} else {
this.elements[index] = 1; // Loading
$(this.slides[index]).addClass(this.options.slideLoadingClass);
this.slides[index].appendChild(this.createElement(
this.list[index],
this.proxyListener
));
}
}
},
loadElements: function (index) {
var limit = Math.min(this.num, this.options.preloadRange * 2 + 1),
j = index,
i;
for (i = 0; i < limit; i += 1) {
// First load the current slide element (0),
// then the next one (+1),
// then the previous one (-2),
// then the next after next (+2), etc.:
j += i * (i % 2 === 0 ? -1 : 1);
// Connect the ends of the list to load slide elements for
// continuous navigation:
j = this.circle(j);
this.loadElement(j);
}
},
unloadElements: function (index) {
var i,
slide,
diff;
for (i in this.elements) {
if (this.elements.hasOwnProperty(i)) {
diff = Math.abs(index - i);
if (diff > this.options.preloadRange &&
diff + this.options.preloadRange < this.num) {
slide = this.slides[i];
slide.removeChild(slide.firstChild);
delete this.elements[i];
}
}
}
},
addSlide: function (index) {
var slide = this.slidePrototype.cloneNode(false);
slide.setAttribute('data-index', index);
this.slidesContainer[0].appendChild(slide);
this.slides.push(slide);
},
positionSlide: function (index) {
var slide = this.slides[index];
slide.style.width = this.slideWidth + 'px';
if (this.support.transform) {
slide.style.left = (index * -this.slideWidth) + 'px';
this.move(index, this.index > index ? -this.slideWidth :
(this.index < index ? this.slideWidth : 0), 0);
}
},
initSlides: function (reload) {
var clearSlides,
i;
if (!reload) {
this.positions = [];
this.positions.length = this.num;
this.elements = {};
this.imagePrototype = document.createElement('img');
this.elementPrototype = document.createElement('div');
this.slidePrototype = document.createElement('div');
$(this.slidePrototype).addClass(this.options.slideClass);
this.slides = this.slidesContainer[0].children;
clearSlides = this.options.clearSlides ||
this.slides.length !== this.num;
}
this.slideWidth = this.container[0].offsetWidth;
this.slideHeight = this.container[0].offsetHeight;
this.slidesContainer[0].style.width =
(this.num * this.slideWidth) + 'px';
if (clearSlides) {
this.resetSlides();
}
for (i = 0; i < this.num; i += 1) {
if (clearSlides) {
this.addSlide(i);
}
this.positionSlide(i);
}
// Reposition the slides before and after the given index:
if (this.options.continuous && this.support.transform) {
this.move(this.circle(this.index - 1), -this.slideWidth, 0);
this.move(this.circle(this.index + 1), this.slideWidth, 0);
}
if (!this.support.transform) {
this.slidesContainer[0].style.left =
(this.index * -this.slideWidth) + 'px';
}
},
toggleControls: function () {
var controlsClass = this.options.controlsClass;
if (this.container.hasClass(controlsClass)) {
this.container.removeClass(controlsClass);
} else {
this.container.addClass(controlsClass);
}
},
toggleSlideshow: function () {
if (!this.interval) {
this.play();
if (this.options.toggleFullscreenOnSlideShow) this.requestFullScreen(this.container[0]);
} else {
this.pause();
}
},
toggleFullscreen: function ()
{
if (!this.getFullScreenElement())
{
this.requestFullScreen(this.container[0]);
}
else
{
this.exitFullScreen();
}
},
getNodeIndex: function (element) {
return parseInt(element.getAttribute('data-index'), 10);
},
getNestedProperty: function (obj, property) {
property.replace(
// Matches native JavaScript notation in a String,
// e.g. '["doubleQuoteProp"].dotProp[2]'
/\[(?:'([^']+)'|"([^"]+)"|(\d+))\]|(?:(?:^|\.)([^\.\[]+))/g,
function (str, singleQuoteProp, doubleQuoteProp, arrayIndex, dotProp) {
var prop = dotProp || singleQuoteProp || doubleQuoteProp ||
(arrayIndex && parseInt(arrayIndex, 10));
if (str && obj) {
obj = obj[prop];
}
}
);
return obj;
},
getDataProperty: function (obj, property) {
if (obj.getAttribute) {
var prop = obj.getAttribute('data-' +
property.replace(/([A-Z])/g, '-$1').toLowerCase());
if (typeof prop === 'string') {
if (/^(true|false|null|-?\d+(\.\d+)?|\{[\s\S]*\}|\[[\s\S]*\])$/
.test(prop)) {
try {
return $.parseJSON(prop);
} catch (ignore) {}
}
return prop;
}
}
},
getItemProperty: function (obj, property) {
var prop = obj[property];
if (prop === undefined) {
prop = this.getDataProperty(obj, property);
if (prop === undefined) {
prop = this.getNestedProperty(obj, property);
}
}
return prop;
},
initStartIndex: function () {
var index = this.options.index,
urlProperty = this.options.urlProperty,
i;
// Check if the index is given as a list object:
if (index && typeof index !== 'number') {
for (i = 0; i < this.num; i += 1) {
if (this.list[i] === index ||
this.getItemProperty(this.list[i], urlProperty) ===
this.getItemProperty(index, urlProperty)) {
index = i;
break;
}
}
}
// Make sure the index is in the list range:
this.index = this.circle(parseInt(index, 10) || 0);
},
initEventListeners: function () {
var that = this,
slidesContainer = this.slidesContainer,
proxyListener = function (event) {
var type = that.support.transition &&
that.support.transition.end === event.type ?
'transitionend' : event.type;
that['on' + type](event);
};
$(window).on('resize', proxyListener);
$(document.body).on('keydown', proxyListener);
this.container.on('click', proxyListener);
if (this.support.touch) {
slidesContainer
.on('touchstart touchmove touchend touchcancel', proxyListener);
} else if (this.options.emulateTouchEvents &&
this.support.transition) {
slidesContainer
.on('mousedown mousemove mouseup mouseout', proxyListener);
}
if (this.support.transition) {
slidesContainer.on(
this.support.transition.end,
proxyListener
);
}
this.proxyListener = proxyListener;
},
destroyEventListeners: function () {
var slidesContainer = this.slidesContainer,
proxyListener = this.proxyListener;
$(window).off('resize', proxyListener);
$(document.body).off('keydown', proxyListener);
this.container.off('click', proxyListener);
if (this.support.touch) {
slidesContainer
.off('touchstart touchmove touchend touchcancel', proxyListener);
} else if (this.options.emulateTouchEvents &&
this.support.transition) {
slidesContainer
.off('mousedown mousemove mouseup mouseout', proxyListener);
}
if (this.support.transition) {
slidesContainer.off(
this.support.transition.end,
proxyListener
);
}
},
handleOpen: function () {
if (this.options.onopened) {
this.options.onopened.call(this);
}
},
initWidget: function () {
var that = this,
openHandler = function (event) {
if (event.target === that.container[0]) {
that.container.off(
that.support.transition.end,
openHandler
);
that.handleOpen();
}
};
this.container = $(this.options.container);
if (!this.container.length) {
this.console.log(
'blueimp Gallery: Widget container not found.',
this.options.container
);
return false;
}
this.slidesContainer = this.container.find(
this.options.slidesContainer
).first();
if (!this.slidesContainer.length) {
this.console.log(
'blueimp Gallery: Slides container not found.',
this.options.slidesContainer
);
return false;
}
this.titleElement = this.container.find(
this.options.titleElement
).first();
if (this.num === 1) {
this.container.addClass(this.options.singleClass);
}
if (this.options.onopen) {
this.options.onopen.call(this);
}
if (this.support.transition && this.options.displayTransition) {
this.container.on(
this.support.transition.end,
openHandler
);
} else {
this.handleOpen();
}
if (this.options.hidePageScrollbars) {
// Hide the page scrollbars:
this.bodyOverflowStyle = document.body.style.overflow;
document.body.style.overflow = 'hidden';
}
this.container[0].style.display = 'block';
this.initSlides();
this.container.addClass(this.options.displayClass);
},
initOptions: function (options) {
// Create a copy of the prototype options:
this.options = $.extend({}, this.options);
// Check if carousel mode is enabled:
if ((options && options.carousel) ||
(this.options.carousel && (!options || options.carousel !== false))) {
$.extend(this.options, this.carouselOptions);
}
// Override any given options:
$.extend(this.options, options);
if (this.num < 3) {
// 1 or 2 slides cannot be displayed continuous,
// remember the original option by setting to null instead of false:
this.options.continuous = this.options.continuous ? null : false;
}
if (!this.support.transition) {
this.options.emulateTouchEvents = false;
}
if (this.options.event) {
this.preventDefault(this.options.event);
}
}
});
return Gallery;
}));