3 Lightbox for Bootstrap 3 by @ashleydw
4 https://github.com/ashleydw/lightbox
6 License: https://github.com/ashleydw/lightbox/blob/master/LICENSE
15 EkkoLightbox = function(element, options) {
16 var content, footer, header;
17 this.options = $.extend({
21 }, $.fn.ekkoLightbox.defaults, options || {});
22 this.$element = $(element);
24 this.modal_id = this.options.modal_id ? this.options.modal_id : 'ekkoLightbox-' + Math.floor((Math.random() * 1000) + 1);
25 header = '<div class="modal-header"' + (this.options.title || this.options.always_show_close ? '' : ' style="display:none"') + '><button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button><h4 class="modal-title">' + (this.options.title || " ") + '</h4></div>';
26 footer = '<div class="modal-footer"' + (this.options.footer ? '' : ' style="display:none"') + '>' + this.options.footer + '</div>';
27 $(document.body).append('<div id="' + this.modal_id + '" class="ekko-lightbox modal fade" tabindex="-1"><div class="modal-dialog"><div class="modal-content">' + header + '<div class="modal-body"><div class="ekko-lightbox-container"><div></div></div></div>' + footer + '</div></div></div>');
28 this.modal = $('#' + this.modal_id);
29 this.modal_dialog = this.modal.find('.modal-dialog').first();
30 this.modal_content = this.modal.find('.modal-content').first();
31 this.modal_body = this.modal.find('.modal-body').first();
32 this.modal_header = this.modal.find('.modal-header').first();
33 this.modal_footer = this.modal.find('.modal-footer').first();
34 this.lightbox_container = this.modal_body.find('.ekko-lightbox-container').first();
35 this.lightbox_body = this.lightbox_container.find('> div:first-child').first();
37 this.modal_arrows = null;
39 top: parseFloat(this.modal_dialog.css('border-top-width')) + parseFloat(this.modal_content.css('border-top-width')) + parseFloat(this.modal_body.css('border-top-width')),
40 right: parseFloat(this.modal_dialog.css('border-right-width')) + parseFloat(this.modal_content.css('border-right-width')) + parseFloat(this.modal_body.css('border-right-width')),
41 bottom: parseFloat(this.modal_dialog.css('border-bottom-width')) + parseFloat(this.modal_content.css('border-bottom-width')) + parseFloat(this.modal_body.css('border-bottom-width')),
42 left: parseFloat(this.modal_dialog.css('border-left-width')) + parseFloat(this.modal_content.css('border-left-width')) + parseFloat(this.modal_body.css('border-left-width'))
45 top: parseFloat(this.modal_dialog.css('padding-top')) + parseFloat(this.modal_content.css('padding-top')) + parseFloat(this.modal_body.css('padding-top')),
46 right: parseFloat(this.modal_dialog.css('padding-right')) + parseFloat(this.modal_content.css('padding-right')) + parseFloat(this.modal_body.css('padding-right')),
47 bottom: parseFloat(this.modal_dialog.css('padding-bottom')) + parseFloat(this.modal_content.css('padding-bottom')) + parseFloat(this.modal_body.css('padding-bottom')),
48 left: parseFloat(this.modal_dialog.css('padding-left')) + parseFloat(this.modal_content.css('padding-left')) + parseFloat(this.modal_body.css('padding-left'))
50 this.modal.on('show.bs.modal', this.options.onShow.bind(this)).on('shown.bs.modal', (function(_this) {
53 return _this.options.onShown.call(_this);
55 })(this)).on('hide.bs.modal', this.options.onHide.bind(this)).on('hidden.bs.modal', (function(_this) {
58 $(document).off('keydown.ekkoLightbox');
61 return _this.options.onHidden.call(_this);
63 })(this)).modal('show', options);
67 EkkoLightbox.prototype = {
68 modal_shown: function() {
70 if (!this.options.remote) {
71 return this.error('No remote target given');
73 this.gallery = this.$element.data('gallery');
75 if (this.options.gallery_parent_selector === 'document.body' || this.options.gallery_parent_selector === '') {
76 this.gallery_items = $(document.body).find('*[data-gallery="' + this.gallery + '"]');
78 this.gallery_items = this.$element.parents(this.options.gallery_parent_selector).first().find('*[data-gallery="' + this.gallery + '"]');
80 this.gallery_index = this.gallery_items.index(this.$element);
81 $(document).on('keydown.ekkoLightbox', this.navigate.bind(this));
82 if (this.options.directional_arrows && this.gallery_items.length > 1) {
83 this.lightbox_container.append('<div class="ekko-lightbox-nav-overlay"><a href="#" class="' + this.strip_stops(this.options.left_arrow_class) + '"></a><a href="#" class="' + this.strip_stops(this.options.right_arrow_class) + '"></a></div>');
84 this.modal_arrows = this.lightbox_container.find('div.ekko-lightbox-nav-overlay').first();
85 this.lightbox_container.find('a' + this.strip_spaces(this.options.left_arrow_class)).on('click', (function(_this) {
86 return function(event) {
87 event.preventDefault();
88 return _this.navigate_left();
91 this.lightbox_container.find('a' + this.strip_spaces(this.options.right_arrow_class)).on('click', (function(_this) {
92 return function(event) {
93 event.preventDefault();
94 return _this.navigate_right();
99 if (this.options.type) {
100 if (this.options.type === 'image') {
101 return this.preloadImage(this.options.remote, true);
102 } else if (this.options.type === 'youtube' && (video_id = this.getYoutubeId(this.options.remote))) {
103 return this.showYoutubeVideo(video_id);
104 } else if (this.options.type === 'vimeo') {
105 return this.showVimeoVideo(this.options.remote);
106 } else if (this.options.type === 'instagram') {
107 return this.showInstagramVideo(this.options.remote);
108 } else if (this.options.type === 'url') {
109 return this.loadRemoteContent(this.options.remote);
110 } else if (this.options.type === 'video') {
111 return this.showVideoIframe(this.options.remote);
113 return this.error("Could not detect remote target type. Force the type using data-type=\"image|youtube|vimeo|instagram|url|video\"");
116 return this.detectRemoteType(this.options.remote);
120 strip_stops: function(str) {
121 return str.replace(/\./g, '');
123 strip_spaces: function(str) {
124 return str.replace(/\s/g, '');
126 isImage: function(str) {
127 return str.match(/(^data:image\/.*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg)((\?|#).*)?$)/i);
129 isSwf: function(str) {
130 return str.match(/\.(swf)((\?|#).*)?$/i);
132 getYoutubeId: function(str) {
134 match = str.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/);
135 if (match && match[2].length === 11) {
141 getVimeoId: function(str) {
142 if (str.indexOf('vimeo') > 0) {
148 getInstagramId: function(str) {
149 if (str.indexOf('instagram') > 0) {
155 navigate: function(event) {
156 event = event || window.event;
157 if (event.keyCode === 39 || event.keyCode === 37) {
158 if (event.keyCode === 39) {
159 return this.navigate_right();
160 } else if (event.keyCode === 37) {
161 return this.navigate_left();
165 navigateTo: function(index) {
167 if (index < 0 || index > this.gallery_items.length - 1) {
171 this.gallery_index = index;
172 this.$element = $(this.gallery_items.get(this.gallery_index));
173 this.updateTitleAndFooter();
174 src = this.$element.attr('data-remote') || this.$element.attr('href');
175 this.detectRemoteType(src, this.$element.attr('data-type') || false);
176 if (this.gallery_index + 1 < this.gallery_items.length) {
177 next = $(this.gallery_items.get(this.gallery_index + 1), false);
178 src = next.attr('data-remote') || next.attr('href');
179 if (next.attr('data-type') === 'image' || this.isImage(src)) {
180 return this.preloadImage(src, false);
184 navigate_left: function() {
185 if (this.gallery_items.length === 1) {
188 if (this.gallery_index === 0) {
189 this.gallery_index = this.gallery_items.length - 1;
191 this.gallery_index--;
193 this.options.onNavigate.call(this, 'left', this.gallery_index);
194 return this.navigateTo(this.gallery_index);
196 navigate_right: function() {
197 if (this.gallery_items.length === 1) {
200 if (this.gallery_index === this.gallery_items.length - 1) {
201 this.gallery_index = 0;
203 this.gallery_index++;
205 this.options.onNavigate.call(this, 'right', this.gallery_index);
206 return this.navigateTo(this.gallery_index);
208 detectRemoteType: function(src, type) {
210 type = type || false;
211 if (type === 'image' || this.isImage(src)) {
212 this.options.type = 'image';
213 return this.preloadImage(src, true);
214 } else if (type === 'youtube' || (video_id = this.getYoutubeId(src))) {
215 this.options.type = 'youtube';
216 return this.showYoutubeVideo(video_id);
217 } else if (type === 'vimeo' || (video_id = this.getVimeoId(src))) {
218 this.options.type = 'vimeo';
219 return this.showVimeoVideo(video_id);
220 } else if (type === 'instagram' || (video_id = this.getInstagramId(src))) {
221 this.options.type = 'instagram';
222 return this.showInstagramVideo(video_id);
223 } else if (type === 'video') {
224 this.options.type = 'video';
225 return this.showVideoIframe(video_id);
227 this.options.type = 'url';
228 return this.loadRemoteContent(src);
231 updateTitleAndFooter: function() {
232 var caption, footer, header, title;
233 header = this.modal_content.find('.modal-header');
234 footer = this.modal_content.find('.modal-footer');
235 title = this.$element.data('title') || "";
236 caption = this.$element.data('footer') || "";
237 if (title || this.options.always_show_close) {
238 header.css('display', '').find('.modal-title').html(title || " ");
240 header.css('display', 'none');
243 footer.css('display', '').html(caption);
245 footer.css('display', 'none');
249 showLoading: function() {
250 this.lightbox_body.html('<div class="modal-loading">' + this.options.loadingMessage + '</div>');
253 showYoutubeVideo: function(id) {
254 var height, rel, width;
255 if ((this.$element.attr('data-norelated') != null) || this.options.no_related) {
260 width = this.checkDimensions(this.$element.data('width') || 560);
261 height = width / (560 / 315);
262 return this.showVideoIframe('//www.youtube.com/embed/' + id + '?badge=0&autoplay=1&html5=1' + rel, width, height);
264 showVimeoVideo: function(id) {
266 width = this.checkDimensions(this.$element.data('width') || 560);
267 height = width / (500 / 281);
268 return this.showVideoIframe(id + '?autoplay=1', width, height);
270 showInstagramVideo: function(id) {
272 width = this.checkDimensions(this.$element.data('width') || 612);
275 this.lightbox_body.html('<iframe width="' + width + '" height="' + height + '" src="' + this.addTrailingSlash(id) + 'embed/" frameborder="0" allowfullscreen></iframe>');
276 this.options.onContentLoaded.call(this);
277 if (this.modal_arrows) {
278 return this.modal_arrows.css('display', 'none');
281 showVideoIframe: function(url, width, height) {
282 height = height || width;
284 this.lightbox_body.html('<div class="embed-responsive embed-responsive-16by9"><iframe width="' + width + '" height="' + height + '" src="' + url + '" frameborder="0" allowfullscreen class="embed-responsive-item"></iframe></div>');
285 this.options.onContentLoaded.call(this);
286 if (this.modal_arrows) {
287 this.modal_arrows.css('display', 'none');
291 loadRemoteContent: function(url) {
292 var disableExternalCheck, width;
293 width = this.$element.data('width') || 560;
295 disableExternalCheck = this.$element.data('disableExternalCheck') || false;
296 if (!disableExternalCheck && !this.isExternal(url)) {
297 this.lightbox_body.load(url, $.proxy((function(_this) {
299 return _this.$element.trigger('loaded.bs.modal');
303 this.lightbox_body.html('<iframe width="' + width + '" height="' + width + '" src="' + url + '" frameborder="0" allowfullscreen></iframe>');
304 this.options.onContentLoaded.call(this);
306 if (this.modal_arrows) {
307 this.modal_arrows.css('display', 'none');
311 isExternal: function(url) {
313 match = url.match(/^([^:\/?#]+:)?(?:\/\/([^\/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/);
314 if (typeof match[1] === "string" && match[1].length > 0 && match[1].toLowerCase() !== location.protocol) {
317 if (typeof match[2] === "string" && match[2].length > 0 && match[2].replace(new RegExp(":(" + {
320 }[location.protocol] + ")?$"), "") !== location.host) {
325 error: function(message) {
326 this.lightbox_body.html(message);
329 preloadImage: function(src, onLoadShowImage) {
332 if ((onLoadShowImage == null) || onLoadShowImage === true) {
333 img.onload = (function(_this) {
336 image = $('<img />');
337 image.attr('src', img.src);
338 image.addClass('img-responsive');
339 _this.lightbox_body.html(image);
340 if (_this.modal_arrows) {
341 _this.modal_arrows.css('display', 'block');
343 return image.load(function() {
344 if (_this.options.scale_height) {
345 _this.scaleHeight(img.height, img.width);
347 _this.resize(img.width);
349 return _this.options.onContentLoaded.call(_this);
353 img.onerror = (function(_this) {
355 return _this.error('Failed to load image: ' + src);
362 scaleHeight: function(height, width) {
363 var border_padding, factor, footer_height, header_height, margins, max_height;
364 header_height = this.modal_header.outerHeight(true) || 0;
365 footer_height = this.modal_footer.outerHeight(true) || 0;
366 if (!this.modal_footer.is(':visible')) {
369 if (!this.modal_header.is(':visible')) {
372 border_padding = this.border.top + this.border.bottom + this.padding.top + this.padding.bottom;
373 margins = parseFloat(this.modal_dialog.css('margin-top')) + parseFloat(this.modal_dialog.css('margin-bottom'));
374 max_height = $(window).height() - border_padding - margins - header_height - footer_height;
375 factor = Math.min(max_height / height, 1);
376 this.modal_dialog.css('height', 'auto').css('max-height', max_height);
377 return this.resize(factor * width);
379 resize: function(width) {
381 width_total = width + this.border.left + this.padding.left + this.padding.right + this.border.right;
382 this.modal_dialog.css('width', 'auto').css('max-width', width_total);
383 this.lightbox_container.find('a').css('line-height', function() {
384 return $(this).parent().height() + 'px';
388 checkDimensions: function(width) {
389 var body_width, width_total;
390 width_total = width + this.border.left + this.padding.left + this.padding.right + this.border.right;
391 body_width = document.body.clientWidth;
392 if (width_total > body_width) {
393 width = this.modal_body.width();
398 return this.modal.modal('hide');
400 addTrailingSlash: function(url) {
401 if (url.substr(-1) !== '/') {
408 $.fn.ekkoLightbox = function(options) {
409 return this.each(function() {
413 remote: $this.attr('data-remote') || $this.attr('href'),
414 gallery_parent_selector: $this.attr('data-parent'),
415 type: $this.attr('data-type')
416 }, options, $this.data());
417 new EkkoLightbox(this, options);
422 $.fn.ekkoLightbox.defaults = {
423 gallery_parent_selector: 'document.body',
424 left_arrow_class: '.glyphicon .glyphicon-chevron-left',
425 right_arrow_class: '.glyphicon .glyphicon-chevron-right',
426 directional_arrows: true,
428 always_show_close: true,
431 loadingMessage: 'Loading...',
432 onShow: function() {},
433 onShown: function() {},
434 onHide: function() {},
435 onHidden: function() {},
436 onNavigate: function() {},
437 onContentLoaded: function() {}