mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-23 07:09:20 +01:00
1381 lines
54 KiB
JavaScript
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;
|
|
})); |