app/assets/javascripts/bootstrap-image-gallery.js
/*
* Bootstrap Image Gallery 2.10
* https://github.com/blueimp/Bootstrap-Image-Gallery
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*jslint nomen: true, regexp: true */
/*global define, window, document, jQuery */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define([
'jquery',
'load-image',
'bootstrap'
], factory);
} else {
// Browser globals:
factory(
window.jQuery,
window.loadImage
);
}
}(function ($, loadImage) {
'use strict';
// Bootstrap Image Gallery is an extension to the Modal dialog of Twitter's
// Bootstrap toolkit, to ease navigation between a set of gallery images.
// It features transition effects, fullscreen mode and slideshow functionality.
$.extend($.fn.modal.defaults, {
// Delegate to search gallery links from, can be anything that
// is accepted as parameter for $():
delegate: document,
// Selector for gallery links:
selector: null,
// The filter for the selected gallery links (e.g. set to ":odd" to
// filter out label and thumbnail linking twice to the same image):
filter: '*',
// The index of the first gallery image to show:
index: 0,
// The href of the first gallery image to show (overrides index):
href: null,
// The range of images around the current one to preload:
preloadRange: 2,
// Offset of image width to viewport width:
offsetWidth: 100,
// Offset of image height to viewport height:
offsetHeight: 200,
// Set to true to display images as canvas elements:
canvas: false,
// Shows the next image after the given time in ms (0 = disabled):
slideshow: 0,
// Defines the image division for previous/next clicks:
imageClickDivision: 0.5
});
var originalShow = $.fn.modal.Constructor.prototype.show,
originalHide = $.fn.modal.Constructor.prototype.hide;
$.extend($.fn.modal.Constructor.prototype, {
initLinks: function () {
var $this = this,
options = this.options,
selector = options.selector ||
'a[data-target=' + options.target + ']';
this.$links = $(options.delegate).find(selector)
.filter(options.filter).each(function (index) {
if ($this.getUrl(this) === options.href) {
options.index = index;
}
});
if (!this.$links[options.index]) {
options.index = 0;
}
},
getUrl: function (element) {
return element.href || $(element).data('href');
},
getDownloadUrl: function (element) {
return $(element).data('download');
},
startSlideShow: function () {
var $this = this;
if (this.options.slideshow) {
this._slideShow = window.setTimeout(
function () {
$this.next();
},
this.options.slideshow
);
}
},
stopSlideShow: function () {
window.clearTimeout(this._slideShow);
},
toggleSlideShow: function () {
var node = this.$element.find('.modal-slideshow');
if (this.options.slideshow) {
this.options.slideshow = 0;
this.stopSlideShow();
} else {
this.options.slideshow = node.data('slideshow') || 5000;
this.startSlideShow();
}
node.find('i').toggleClass('icon-play icon-pause');
},
preloadImages: function () {
var options = this.options,
range = options.index + options.preloadRange + 1,
link,
i;
for (i = options.index - options.preloadRange; i < range; i += 1) {
link = this.$links[i];
if (link && i !== options.index) {
$('<img>').prop('src', this.getUrl(link));
}
}
},
loadImage: function () {
var $this = this,
modal = this.$element,
index = this.options.index,
url = this.getUrl(this.$links[index]),
download = this.getDownloadUrl(this.$links[index]),
oldImg;
this.abortLoad();
this.stopSlideShow();
modal.trigger('beforeLoad');
// The timeout prevents displaying a loading status,
// if the image has already been loaded:
this._loadingTimeout = window.setTimeout(function () {
modal.addClass('modal-loading');
}, 100);
oldImg = modal.find('.modal-image').children().removeClass('in');
// The timeout allows transition effects to finish:
window.setTimeout(function () {
oldImg.remove();
}, 3000);
modal.find('.modal-title').text(this.$links[index].title);
modal.find('.modal-download').prop(
'href',
download || url
);
this._loadingImage = loadImage(
url,
function (img) {
$this.img = img;
window.clearTimeout($this._loadingTimeout);
modal.removeClass('modal-loading');
modal.trigger('load');
$this.showImage(img);
$this.startSlideShow();
},
this._loadImageOptions
);
this.preloadImages();
},
showImage: function (img) {
var modal = this.$element,
transition = $.support.transition && modal.hasClass('fade'),
method = transition ? modal.animate : modal.css,
modalImage = modal.find('.modal-image'),
clone,
forceReflow;
modalImage.css({
width: img.width,
height: img.height
});
modal.find('.modal-title').css({ width: Math.max(img.width, 380) });
if (transition) {
clone = modal.clone().hide().appendTo(document.body);
}
if ($(window).width() > 767) {
method.call(modal.stop(), {
'margin-top': -((clone || modal).outerHeight() / 2),
'margin-left': -((clone || modal).outerWidth() / 2)
});
} else {
modal.css({
top: ($(window).height() - (clone || modal).outerHeight()) / 2
});
}
if (clone) {
clone.remove();
}
modalImage.append(img);
forceReflow = img.offsetWidth;
modal.trigger('display');
if (transition) {
if (modal.is(':visible')) {
$(img).on(
$.support.transition.end,
function (e) {
// Make sure we don't respond to other transitions events
// in the container element, e.g. from button elements:
if (e.target === img) {
$(img).off($.support.transition.end);
modal.trigger('displayed');
}
}
).addClass('in');
} else {
$(img).addClass('in');
modal.one('shown', function () {
modal.trigger('displayed');
});
}
} else {
$(img).addClass('in');
modal.trigger('displayed');
}
},
abortLoad: function () {
if (this._loadingImage) {
this._loadingImage.onload = this._loadingImage.onerror = null;
}
window.clearTimeout(this._loadingTimeout);
},
prev: function () {
var options = this.options;
options.index -= 1;
if (options.index < 0) {
options.index = this.$links.length - 1;
}
this.loadImage();
},
next: function () {
var options = this.options;
options.index += 1;
if (options.index > this.$links.length - 1) {
options.index = 0;
}
this.loadImage();
},
keyHandler: function (e) {
switch (e.which) {
case 37: // left
case 38: // up
e.preventDefault();
this.prev();
break;
case 39: // right
case 40: // down
e.preventDefault();
this.next();
break;
}
},
wheelHandler: function (e) {
e.preventDefault();
e = e.originalEvent;
this._wheelCounter = this._wheelCounter || 0;
this._wheelCounter += (e.wheelDelta || e.detail || 0);
if ((e.wheelDelta && this._wheelCounter >= 120) ||
(!e.wheelDelta && this._wheelCounter < 0)) {
this.prev();
this._wheelCounter = 0;
} else if ((e.wheelDelta && this._wheelCounter <= -120) ||
(!e.wheelDelta && this._wheelCounter > 0)) {
this.next();
this._wheelCounter = 0;
}
},
initGalleryEvents: function () {
var $this = this,
modal = this.$element;
modal.find('.modal-image').on('click.modal-gallery', function (e) {
var modalImage = $(this);
if ($this.$links.length === 1) {
$this.hide();
} else {
if ((e.pageX - modalImage.offset().left) / modalImage.width() <
$this.options.imageClickDivision) {
$this.prev(e);
} else {
$this.next(e);
}
}
});
modal.find('.modal-prev').on('click.modal-gallery', function (e) {
$this.prev(e);
});
modal.find('.modal-next').on('click.modal-gallery', function (e) {
$this.next(e);
});
modal.find('.modal-slideshow').on('click.modal-gallery', function (e) {
$this.toggleSlideShow(e);
});
$(document)
.on('keydown.modal-gallery', function (e) {
$this.keyHandler(e);
})
.on(
'mousewheel.modal-gallery, DOMMouseScroll.modal-gallery',
function (e) {
$this.wheelHandler(e);
}
);
},
destroyGalleryEvents: function () {
var modal = this.$element;
this.abortLoad();
this.stopSlideShow();
modal.find('.modal-image, .modal-prev, .modal-next, .modal-slideshow')
.off('click.modal-gallery');
$(document)
.off('keydown.modal-gallery')
.off('mousewheel.modal-gallery, DOMMouseScroll.modal-gallery');
},
show: function () {
if (!this.isShown && this.$element.hasClass('modal-gallery')) {
var modal = this.$element,
options = this.options,
windowWidth = $(window).width(),
windowHeight = $(window).height();
if (modal.hasClass('modal-fullscreen')) {
this._loadImageOptions = {
maxWidth: windowWidth,
maxHeight: windowHeight,
canvas: options.canvas
};
if (modal.hasClass('modal-fullscreen-stretch')) {
this._loadImageOptions.minWidth = windowWidth;
this._loadImageOptions.minHeight = windowHeight;
}
} else {
this._loadImageOptions = {
maxWidth: windowWidth - options.offsetWidth,
maxHeight: windowHeight - options.offsetHeight,
canvas: options.canvas
};
}
if (windowWidth > 767) {
modal.css({
'margin-top': -(modal.outerHeight() / 2),
'margin-left': -(modal.outerWidth() / 2)
});
} else {
modal.css({
top: ($(window).height() - modal.outerHeight()) / 2
});
}
this.initGalleryEvents();
this.initLinks();
if (this.$links.length) {
modal.find('.modal-slideshow, .modal-prev, .modal-next')
.toggle(this.$links.length !== 1);
modal.toggleClass(
'modal-single',
this.$links.length === 1
);
this.loadImage();
}
}
originalShow.apply(this, arguments);
},
hide: function () {
if (this.isShown && this.$element.hasClass('modal-gallery')) {
this.options.delegate = document;
this.options.href = null;
this.destroyGalleryEvents();
}
originalHide.apply(this, arguments);
}
});
$(function () {
$(document.body).on(
'click.modal-gallery.data-api',
'[data-toggle="modal-gallery"]',
function (e) {
var $this = $(this),
options = $this.data(),
modal = $(options.target),
data = modal.data('modal'),
link;
if (!data) {
options = $.extend(modal.data(), options);
}
if (!options.selector) {
options.selector = 'a[data-gallery=gallery]';
}
link = $(e.target).closest(options.selector);
if (link.length && modal.length) {
e.preventDefault();
options.href = link.prop('href') || link.data('href');
options.delegate = link[0] !== this ? this : document;
if (data) {
$.extend(data.options, options);
}
modal.modal(options);
}
}
);
});
}));