;(function ($) { 'use strict'; var _self = { cache: {}, support: {}, objects: {}, /** * Initializes the plugin * * @param {object} options * @return {object} */ init: function (options) { return this.each(function () { $(this).unbind('click.lightcase').bind('click.lightcase', function (event) { event.preventDefault(); $(this).lightcase('start', options); }); }); }, /** * Starts the plugin * * @param {object} options * @return {void} */ start: function (options) { _self.origin = lightcase.origin = this; _self.settings = lightcase.settings = $.extend(true, { idPrefix: 'lightcase-', classPrefix: 'lightcase-', attrPrefix: 'lc-', transition: 'elastic', transitionOpen: null, transitionClose: null, transitionIn: null, transitionOut: null, cssTransitions: true, speedIn: 250, speedOut: 250, width: null, height: null, maxWidth: 800, maxHeight: 500, forceWidth: false, forceHeight: false, liveResize: true, fullScreenModeForMobile: true, mobileMatchExpression: /(iphone|ipod|ipad|android|blackberry|symbian)/, disableShrink: false, fixedRatio: true, shrinkFactor: .75, overlayOpacity: .9, slideshow: false, slideshowAutoStart: true, breakBeforeShow: false, timeout: 5000, swipe: true, useKeys: true, useCategories: true, useAsCollection: false, navigateEndless: true, closeOnOverlayClick: true, title: null, caption: null, showTitle: true, showCaption: true, showSequenceInfo: true, inline: { width: 'auto', height: 'auto' }, ajax: { width: 'auto', height: 'auto', type: 'get', dataType: 'html', data: {} }, iframe: { width: 800, height: 500, frameborder: 0 }, flash: { width: 400, height: 205, wmode: 'transparent' }, video: { width: 400, height: 225, poster: '', preload: 'auto', controls: true, autobuffer: true, autoplay: true, loop: false }, attr: 'data-rel', href: null, type: null, typeMapping: { 'image': 'jpg,jpeg,gif,png,bmp', 'flash': 'swf', 'video': 'mp4,mov,ogv,ogg,webm', 'iframe': 'html,php', 'ajax': 'json,txt', 'inline': '#' }, errorMessage: function () { return '
' + _self.settings.labels['errorMessage'] + '
'; }, labels: { 'errorMessage': 'Source could not be found...', 'sequenceInfo.of': ' of ', 'close': 'Close', 'navigator.prev': 'Prev', 'navigator.next': 'Next', 'navigator.play': 'Play', 'navigator.pause': 'Pause' }, markup: function () { _self.objects.body.append( _self.objects.overlay = $(''), _self.objects.loading = $(''), _self.objects.case = $('') ); _self.objects.case.after( _self.objects.close = $('' + _self.settings.labels['close'] + ''), _self.objects.nav = $('') ); _self.objects.nav.append( _self.objects.prev = $('' + _self.settings.labels['navigator.prev'] + '').hide(), _self.objects.next = $('' + _self.settings.labels['navigator.next'] + '').hide(), _self.objects.play = $('' + _self.settings.labels['navigator.play'] + '').hide(), _self.objects.pause = $('' + _self.settings.labels['navigator.pause'] + '').hide() ); _self.objects.case.append( _self.objects.content = $(''), _self.objects.info = $('') ); _self.objects.content.append( _self.objects.contentInner = $('') ); _self.objects.info.append( _self.objects.sequenceInfo = $(''), _self.objects.title = $(''), _self.objects.caption = $('') ); }, onInit: {}, onStart: {}, onBeforeCalculateDimensions: {}, onAfterCalculateDimensions: {}, onBeforeShow: {}, onFinish: {}, onResize: {}, onClose: {}, onCleanup: {} }, options, // Load options from data-lc-options attribute _self.origin.data ? _self.origin.data('lc-options') : {}); _self.objects.document = $('html'); _self.objects.body = $('body'); // Call onInit hook functions _self._callHooks(_self.settings.onInit); _self.objectData = _self._setObjectData(this); _self._addElements(); _self._open(); _self.dimensions = _self.getViewportDimensions(); }, /** * Getter method for objects * * @param {string} name * @return {object} */ get: function (name) { return _self.objects[name]; }, /** * Getter method for objectData * * @return {object} */ getObjectData: function () { return _self.objectData; }, /** * Sets the object data * * @param {object} object * @return {object} objectData */ _setObjectData: function (object) { var $object = $(object), objectData = { this: $(object), title: _self.settings.title || $object.attr(_self._prefixAttributeName('title')) || $object.attr('title'), caption: _self.settings.caption || $object.attr(_self._prefixAttributeName('caption')) || $object.children('img').attr('alt'), url: _self._determineUrl(), requestType: _self.settings.ajax.type, requestData: _self.settings.ajax.data, requestDataType: _self.settings.ajax.dataType, rel: $object.attr(_self._determineAttributeSelector()), type: _self.settings.type || _self._verifyDataType(_self._determineUrl()), isPartOfSequence: _self.settings.useAsCollection || _self._isPartOfSequence($object.attr(_self.settings.attr), ':'), isPartOfSequenceWithSlideshow: _self._isPartOfSequence($object.attr(_self.settings.attr), ':slideshow'), currentIndex: $(_self._determineAttributeSelector()).index($object), sequenceLength: $(_self._determineAttributeSelector()).length }; // Add sequence info to objectData objectData.sequenceInfo = (objectData.currentIndex + 1) + _self.settings.labels['sequenceInfo.of'] + objectData.sequenceLength; // Add next/prev index objectData.prevIndex = objectData.currentIndex - 1; objectData.nextIndex = objectData.currentIndex + 1; return objectData; }, /** * Prefixes a data attribute name with defined name from 'settings.attrPrefix' * to ensure more uniqueness for all lightcase related/used attributes. * * @param {string} name * @return {string} */ _prefixAttributeName: function (name) { return 'data-' + _self.settings.attrPrefix + name; }, /** * Determines the link target considering 'settings.href' and data attributes * but also with a fallback to the default 'href' value. * * @return {string} */ _determineLinkTarget: function () { return _self.settings.href || $(_self.origin).attr(_self._prefixAttributeName('href')) || $(_self.origin).attr('href'); }, /** * Determines the attribute selector to use, depending on * whether categorized collections are beeing used or not. * * @return {string} selector */ _determineAttributeSelector: function () { var $origin = $(_self.origin), selector = ''; if (typeof _self.cache.selector !== 'undefined') { selector = _self.cache.selector; } else if (_self.settings.useCategories === true && $origin.attr(_self._prefixAttributeName('categories'))) { var categories = $origin.attr(_self._prefixAttributeName('categories')).split(' '); $.each(categories, function (index, category) { if (index > 0) { selector += ','; } selector += '[' + _self._prefixAttributeName('categories') + '~="' + category + '"]'; }); } else { selector = '[' + _self.settings.attr + '="' + $origin.attr(_self.settings.attr) + '"]'; } _self.cache.selector = selector; return selector; }, /** * Determines the correct resource according to the * current viewport and density. * * @return {string} url */ _determineUrl: function () { var dataUrl = _self._verifyDataUrl(_self._determineLinkTarget()), width = 0, density = 0, supportLevel = '', url; $.each(dataUrl, function (index, src) { switch (_self._verifyDataType(src.url)) { case 'video': var video = document.createElement('video'), videoType = _self._verifyDataType(src.url) + '/' + _self._getFileUrlSuffix(src.url); // Check if browser can play this type of video format if (supportLevel !== 'probably' && supportLevel !== video.canPlayType(videoType) && video.canPlayType(videoType) !== '') { supportLevel = video.canPlayType(videoType); url = src.url; } break; default: if ( // Check density _self._devicePixelRatio() >= src.density && src.density >= density && // Check viewport width _self._matchMedia()('screen and (min-width:' + src.width + 'px)').matches && src.width >= width ) { width = src.width; density = src.density; url = src.url; } break; } }); return url; }, /** * Normalizes an url and returns information about the resource path, * the viewport width as well as density if defined. * * @param {string} url Path to resource in format of an url or srcset * @return {object} */ _normalizeUrl: function (url) { var srcExp = /^\d+$/; return url.split(',').map(function (str) { var src = { width: 0, density: 0 }; str.trim().split(/\s+/).forEach(function (url, i) { if (i === 0) { return src.url = url; } var value = url.substring(0, url.length - 1), lastChar = url[url.length - 1], intVal = parseInt(value, 10), floatVal = parseFloat(value); if (lastChar === 'w' && srcExp.test(value)) { src.width = intVal; } else if (lastChar === 'h' && srcExp.test(value)) { src.height = intVal; } else if (lastChar === 'x' && !isNaN(floatVal)) { src.density = floatVal; } }); return src; }); }, /** * Verifies if the link is part of a sequence * * @param {string} rel * @param {string} expression * @return {boolean} */ _isPartOfSequence: function (rel, expression) { var getSimilarLinks = $('[' + _self.settings.attr + '="' + rel + '"]'), regexp = new RegExp(expression); return (regexp.test(rel) && getSimilarLinks.length > 1); }, /** * Verifies if the slideshow should be enabled * * @return {boolean} */ isSlideshowEnabled: function () { return (_self.objectData.isPartOfSequence && (_self.settings.slideshow === true || _self.objectData.isPartOfSequenceWithSlideshow === true)); }, /** * Loads the new content to show * * @return {void} */ _loadContent: function () { if (_self.cache.originalObject) { _self._restoreObject(); } _self._createObject(); }, /** * Creates a new object * * @return {void} */ _createObject: function () { var $object; // Create object switch (_self.objectData.type) { case 'image': $object = $(new Image()); $object.attr({ // The time expression is required to prevent the binding of an image load 'src': _self.objectData.url, 'alt': _self.objectData.title }); break; case 'inline': $object = $(''); $object.html(_self._cloneObject($(_self.objectData.url))); // Add custom attributes from _self.settings $.each(_self.settings.inline, function (name, value) { $object.attr(_self._prefixAttributeName(name), value); }); break; case 'ajax': $object = $(''); // Add custom attributes from _self.settings $.each(_self.settings.ajax, function (name, value) { if (name !== 'data') { $object.attr(_self._prefixAttributeName(name), value); } }); break; case 'flash': $object = $(''); // Add custom attributes from _self.settings $.each(_self.settings.flash, function (name, value) { $object.attr(name, value); }); break; case 'video': $object = $(''); $object.attr('src', _self.objectData.url); // Add custom attributes from _self.settings $.each(_self.settings.video, function (name, value) { $object.attr(name, value); }); break; default: $object = $(''); $object.attr({ 'src': _self.objectData.url }); // Add custom attributes from _self.settings $.each(_self.settings.iframe, function (name, value) { $object.attr(name, value); }); break; } _self._addObject($object); _self._loadObject($object); }, /** * Adds the new object to the markup * * @param {object} $object * @return {void} */ _addObject: function ($object) { // Add object to content holder _self.objects.contentInner.html($object); // Start loading _self._loading('start'); // Call onStart hook functions _self._callHooks(_self.settings.onStart); // Add sequenceInfo to the content holder or hide if its empty if (_self.settings.showSequenceInfo === true && _self.objectData.isPartOfSequence) { _self.objects.sequenceInfo.html(_self.objectData.sequenceInfo); _self.objects.sequenceInfo.show(); } else { _self.objects.sequenceInfo.empty(); _self.objects.sequenceInfo.hide(); } // Add title to the content holder or hide if its empty if (_self.settings.showTitle === true && _self.objectData.title !== undefined && _self.objectData.title !== '') { _self.objects.title.html(_self.objectData.title); _self.objects.title.show(); } else { _self.objects.title.empty(); _self.objects.title.hide(); } // Add caption to the content holder or hide if its empty if (_self.settings.showCaption === true && _self.objectData.caption !== undefined && _self.objectData.caption !== '') { _self.objects.caption.html(_self.objectData.caption); _self.objects.caption.show(); } else { _self.objects.caption.empty(); _self.objects.caption.hide(); } }, /** * Loads the new object * * @param {object} $object * @return {void} */ _loadObject: function ($object) { // Load the object switch (_self.objectData.type) { case 'inline': if ($(_self.objectData.url)) { _self._showContent($object); } else { _self.error(); } break; case 'ajax': $.ajax( $.extend({}, _self.settings.ajax, { url: _self.objectData.url, type: _self.objectData.requestType, dataType: _self.objectData.requestDataType, data: _self.objectData.requestData, success: function (data, textStatus, jqXHR) { // Check for X-Ajax-Location if (jqXHR.getResponseHeader('X-Ajax-Location')) { _self.objectData.url = jqXHR.getResponseHeader('X-Ajax-Location'); _self._loadObject($object); } else { // Unserialize if data is transferred as json if (_self.objectData.requestDataType === 'json') { _self.objectData.data = data; } else { $object.html(data); } _self._showContent($object); } }, error: function (jqXHR, textStatus, errorThrown) { _self.error(); } }) ); break; case 'flash': _self._showContent($object); break; case 'video': if (typeof($object.get(0).canPlayType) === 'function' || _self.objects.case.find('video').length === 0) { _self._showContent($object); } else { _self.error(); } break; default: if (_self.objectData.url) { $object.on('load', function () { _self._showContent($object); }); $object.on('error', function () { _self.error(); }); } else { _self.error(); } break; } }, /** * Throws an error message if something went wrong * * @return {void} */ error: function () { _self.objectData.type = 'error'; var $object = $(''); $object.html(_self.settings.errorMessage); _self.objects.contentInner.html($object); _self._showContent(_self.objects.contentInner); }, /** * Calculates the dimensions to fit content * * @param {object} $object * @return {void} */ _calculateDimensions: function ($object) { _self._cleanupDimensions(); if (!$object) return; // Set default dimensions var dimensions = { ratio: 1, objectWidth: $object.attr('width') ? $object.attr('width') : $object.attr(_self._prefixAttributeName('width')), objectHeight: $object.attr('height') ? $object.attr('height') : $object.attr(_self._prefixAttributeName('height')) }; if (!_self.settings.disableShrink) { // Add calculated maximum width/height to dimensions dimensions.maxWidth = parseInt(_self.dimensions.windowWidth * _self.settings.shrinkFactor); dimensions.maxHeight = parseInt(_self.dimensions.windowHeight * _self.settings.shrinkFactor); // If the auto calculated maxWidth/maxHeight greather than the user-defined one, use that. if (dimensions.maxWidth > _self.settings.maxWidth) { dimensions.maxWidth = _self.settings.maxWidth; } if (dimensions.maxHeight > _self.settings.maxHeight) { dimensions.maxHeight = _self.settings.maxHeight; } // Calculate the difference between screen width/height and image width/height dimensions.differenceWidthAsPercent = parseInt(100 / dimensions.maxWidth * dimensions.objectWidth); dimensions.differenceHeightAsPercent = parseInt(100 / dimensions.maxHeight * dimensions.objectHeight); switch (_self.objectData.type) { case 'image': case 'flash': case 'video': case 'iframe': case 'ajax': case 'inline': if (_self.objectData.type === 'image' || _self.settings.fixedRatio === true) { if (dimensions.differenceWidthAsPercent > 100 && dimensions.differenceWidthAsPercent > dimensions.differenceHeightAsPercent) { dimensions.objectWidth = dimensions.maxWidth; dimensions.objectHeight = parseInt(dimensions.objectHeight / dimensions.differenceWidthAsPercent * 100); } if (dimensions.differenceHeightAsPercent > 100 && dimensions.differenceHeightAsPercent > dimensions.differenceWidthAsPercent) { dimensions.objectWidth = parseInt(dimensions.objectWidth / dimensions.differenceHeightAsPercent * 100); dimensions.objectHeight = dimensions.maxHeight; } if (dimensions.differenceHeightAsPercent > 100 && dimensions.differenceWidthAsPercent < dimensions.differenceHeightAsPercent) { dimensions.objectWidth = parseInt(dimensions.maxWidth / dimensions.differenceHeightAsPercent * dimensions.differenceWidthAsPercent); dimensions.objectHeight = dimensions.maxHeight; } break; } case 'error': if (!isNaN(dimensions.objectWidth) && dimensions.objectWidth > dimensions.maxWidth) { dimensions.objectWidth = dimensions.maxWidth; } break; default: if ((isNaN(dimensions.objectWidth) || dimensions.objectWidth > dimensions.maxWidth) && !_self.settings.forceWidth) { dimensions.objectWidth = dimensions.maxWidth; } if (((isNaN(dimensions.objectHeight) && dimensions.objectHeight !== 'auto') || dimensions.objectHeight > dimensions.maxHeight) && !_self.settings.forceHeight) { dimensions.objectHeight = dimensions.maxHeight; } break; } } if (_self.settings.forceWidth) { try { dimensions.objectWidth = _self.settings[_self.objectData.type].width; } catch (e) { dimensions.objectWidth = _self.settings.width || dimensions.objectWidth; } dimensions.maxWidth = null; } if ($object.attr(_self._prefixAttributeName('max-width'))) { dimensions.maxWidth = $object.attr(_self._prefixAttributeName('max-width')); } if (_self.settings.forceHeight) { try { dimensions.objectHeight = _self.settings[_self.objectData.type].height; } catch (e) { dimensions.objectHeight = _self.settings.height || dimensions.objectHeight; } dimensions.maxHeight = null; } if ($object.attr(_self._prefixAttributeName('max-height'))) { dimensions.maxHeight = $object.attr(_self._prefixAttributeName('max-height')); } _self._adjustDimensions($object, dimensions); }, /** * Adjusts the dimensions * * @param {object} $object * @param {object} dimensions * @return {void} */ _adjustDimensions: function ($object, dimensions) { // Adjust width and height $object.css({ 'width': dimensions.objectWidth, 'height': dimensions.objectHeight, 'max-width': dimensions.maxWidth, 'max-height': dimensions.maxHeight }); _self.objects.contentInner.css({ 'width': $object.outerWidth(), 'height': $object.outerHeight(), 'max-width': '100%' }); _self.objects.case.css({ 'width': _self.objects.contentInner.outerWidth(), 'max-width': '100%' }); // Adjust margin _self.objects.case.css({ 'margin-top': parseInt(-(_self.objects.case.outerHeight() / 2)), 'margin-left': parseInt(-(_self.objects.case.outerWidth() / 2)) }); }, /** * Handles the _loading * * @param {string} process * @return {void} */ _loading: function (process) { if (process === 'start') { _self.objects.case.addClass(_self.settings.classPrefix + 'loading'); _self.objects.loading.show(); } else if (process === 'end') { _self.objects.case.removeClass(_self.settings.classPrefix + 'loading'); _self.objects.loading.hide(); } }, /** * Gets the client screen dimensions * * @return {object} dimensions */ getViewportDimensions: function () { return { windowWidth: $(window).innerWidth(), windowHeight: $(window).innerHeight() }; }, /** * Verifies the url * * @param {string} dataUrl * @return {object} dataUrl Clean url for processing content */ _verifyDataUrl: function (dataUrl) { if (!dataUrl || dataUrl === undefined || dataUrl === '') { return false; } if (dataUrl.indexOf('#') > -1) { dataUrl = dataUrl.split('#'); dataUrl = '#' + dataUrl[dataUrl.length - 1]; } return _self._normalizeUrl(dataUrl.toString()); }, // /** * Tries to get the (file) suffix of an url * * @param {string} url * @return {string} */ _getFileUrlSuffix: function (url) { var re = /(?:\.([^.]+))?$/; return re.exec(url.toLowerCase())[1]; }, /** * Verifies the data type of the content to load * * @param {string} url * @return {string|boolean} Array key if expression matched, else false */ _verifyDataType: function (url) { var typeMapping = _self.settings.typeMapping; // Early abort if dataUrl couldn't be verified if (!url) { return false; } // Verify the dataType of url according to typeMapping which // has been defined in settings. for (var key in typeMapping) { if (typeMapping.hasOwnProperty(key)) { var suffixArr = typeMapping[key].split(','); for (var i = 0; i < suffixArr.length; i++) { var suffix = suffixArr[i].toLowerCase(), regexp = new RegExp('\.(' + suffix + ')$', 'i'), str = url.toLowerCase().split('?')[0].substr(-5); if (regexp.test(str) === true || (key === 'inline' && (url.indexOf(suffix) > -1))) { return key; } } } } // If no expression matched, return 'iframe'. return 'iframe'; }, /** * Extends html markup with the essential tags * * @return {void} */ _addElements: function () { if (typeof _self.objects.case !== 'undefined' && $('#' + _self.objects.case.attr('id')).length) { return; } _self.settings.markup(); }, /** * Shows the loaded content * * @param {object} $object * @return {void} */ _showContent: function ($object) { // Add data attribute with the object type _self.objects.document.attr(_self._prefixAttributeName('type'), _self.objectData.type); _self.cache.object = $object; // Call onBeforeShow hook functions _self._callHooks(_self.settings.onBeforeShow); if (_self.settings.breakBeforeShow) return; _self.show(); }, /** * Starts the 'inTransition' * @return {void} */ _startInTransition: function () { switch (_self.transition.in()) { case 'scrollTop': case 'scrollRight': case 'scrollBottom': case 'scrollLeft': case 'scrollHorizontal': case 'scrollVertical': _self.transition.scroll(_self.objects.case, 'in', _self.settings.speedIn); _self.transition.fade(_self.objects.contentInner, 'in', _self.settings.speedIn); break; case 'elastic': if (_self.objects.case.css('opacity') < 1) { _self.transition.zoom(_self.objects.case, 'in', _self.settings.speedIn); _self.transition.fade(_self.objects.contentInner, 'in', _self.settings.speedIn); } case 'fade': case 'fadeInline': _self.transition.fade(_self.objects.case, 'in', _self.settings.speedIn); _self.transition.fade(_self.objects.contentInner, 'in', _self.settings.speedIn); break; default: _self.transition.fade(_self.objects.case, 'in', 0); break; } // End loading. _self._loading('end'); _self.isBusy = false; // Set index of the first item opened if (!_self.cache.firstOpened) { _self.cache.firstOpened = _self.objectData.this; } // Fade in the info with delay _self.objects.info.hide(); setTimeout(function () { _self.transition.fade(_self.objects.info, 'in', _self.settings.speedIn); }, _self.settings.speedIn); // Call onFinish hook functions _self._callHooks(_self.settings.onFinish); }, /** * Processes the content to show * * @return {void} */ _processContent: function () { _self.isBusy = true; // Fade out the info at first _self.transition.fade(_self.objects.info, 'out', 0); switch (_self.settings.transitionOut) { case 'scrollTop': case 'scrollRight': case 'scrollBottom': case 'scrollLeft': case 'scrollVertical': case 'scrollHorizontal': if (_self.objects.case.is(':hidden')) { _self.transition.fade(_self.objects.contentInner, 'out', 0); _self.transition.fade(_self.objects.case, 'out', 0, 0, function () { _self._loadContent(); }); } else { _self.transition.scroll(_self.objects.case, 'out', _self.settings.speedOut, function () { _self._loadContent(); }); } break; case 'fade': if (_self.objects.case.is(':hidden')) { _self.transition.fade(_self.objects.case, 'out', 0, 0, function () { _self._loadContent(); }); } else { _self.transition.fade(_self.objects.case, 'out', _self.settings.speedOut, 0, function () { _self._loadContent(); }); } break; case 'fadeInline': case 'elastic': if (_self.objects.case.is(':hidden')) { _self.transition.fade(_self.objects.case, 'out', 0, 0, function () { _self._loadContent(); }); } else { _self.transition.fade(_self.objects.contentInner, 'out', _self.settings.speedOut, 0, function () { _self._loadContent(); }); } break; default: _self.transition.fade(_self.objects.case, 'out', 0, 0, function () { _self._loadContent(); }); break; } }, /** * Handles events for gallery buttons * * @return {void} */ _handleEvents: function () { _self._unbindEvents(); _self.objects.nav.children().not(_self.objects.close).hide(); // If slideshow is enabled, show play/pause and start timeout. if (_self.isSlideshowEnabled()) { // Only start the timeout if slideshow autostart is enabled and slideshow is not pausing if ( (_self.settings.slideshowAutoStart === true || _self.isSlideshowStarted) && !_self.objects.nav.hasClass(_self.settings.classPrefix + 'paused') ) { _self._startTimeout(); } else { _self._stopTimeout(); } } if (_self.settings.liveResize) { _self._watchResizeInteraction(); } _self.objects.close.click(function (event) { event.preventDefault(); _self.close(); }); if (_self.settings.closeOnOverlayClick === true) { _self.objects.overlay.css('cursor', 'pointer').click(function (event) { event.preventDefault(); _self.close(); }); } if (_self.settings.useKeys === true) { _self._addKeyEvents(); } if (_self.objectData.isPartOfSequence) { _self.objects.nav.attr(_self._prefixAttributeName('ispartofsequence'), true); _self.objects.nav.data('items', _self._setNavigation()); _self.objects.prev.click(function (event) { event.preventDefault(); if (_self.settings.navigateEndless === true || !_self.item.isFirst()) { _self.objects.prev.unbind('click'); _self.cache.action = 'prev'; _self.objects.nav.data('items').prev.click(); if (_self.isSlideshowEnabled()) { _self._stopTimeout(); } } }); _self.objects.next.click(function (event) { event.preventDefault(); if (_self.settings.navigateEndless === true || !_self.item.isLast()) { _self.objects.next.unbind('click'); _self.cache.action = 'next'; _self.objects.nav.data('items').next.click(); if (_self.isSlideshowEnabled()) { _self._stopTimeout(); } } }); if (_self.isSlideshowEnabled()) { _self.objects.play.click(function (event) { event.preventDefault(); _self._startTimeout(); }); _self.objects.pause.click(function (event) { event.preventDefault(); _self._stopTimeout(); }); } // Enable swiping if activated if (_self.settings.swipe === true) { if ($.isPlainObject($.event.special.swipeleft)) { _self.objects.case.on('swipeleft', function (event) { event.preventDefault(); _self.objects.next.click(); if (_self.isSlideshowEnabled()) { _self._stopTimeout(); } }); } if ($.isPlainObject($.event.special.swiperight)) { _self.objects.case.on('swiperight', function (event) { event.preventDefault(); _self.objects.prev.click(); if (_self.isSlideshowEnabled()) { _self._stopTimeout(); } }); } } } }, /** * Adds the key events * * @return {void} */ _addKeyEvents: function () { $(document).bind('keyup.lightcase', function (event) { // Do nothing if lightcase is in process if (_self.isBusy) { return; } switch (event.keyCode) { // Escape key case 27: _self.objects.close.click(); break; // Backward key case 37: if (_self.objectData.isPartOfSequence) { _self.objects.prev.click(); } break; // Forward key case 39: if (_self.objectData.isPartOfSequence) { _self.objects.next.click(); } break; } }); }, /** * Starts the slideshow timeout * * @return {void} */ _startTimeout: function () { _self.isSlideshowStarted = true; _self.objects.play.hide(); _self.objects.pause.show(); _self.cache.action = 'next'; _self.objects.nav.removeClass(_self.settings.classPrefix + 'paused'); _self.timeout = setTimeout(function () { _self.objects.nav.data('items').next.click(); }, _self.settings.timeout); }, /** * Stops the slideshow timeout * * @return {void} */ _stopTimeout: function () { _self.objects.play.show(); _self.objects.pause.hide(); _self.objects.nav.addClass(_self.settings.classPrefix + 'paused'); clearTimeout(_self.timeout); }, /** * Sets the navigator buttons (prev/next) * * @return {object} items */ _setNavigation: function () { var $links = $((_self.cache.selector || _self.settings.attr)), sequenceLength = _self.objectData.sequenceLength - 1, items = { prev: $links.eq(_self.objectData.prevIndex), next: $links.eq(_self.objectData.nextIndex) }; if (_self.objectData.currentIndex > 0) { _self.objects.prev.show(); } else { items.prevItem = $links.eq(sequenceLength); } if (_self.objectData.nextIndex <= sequenceLength) { _self.objects.next.show(); } else { items.next = $links.eq(0); } if (_self.settings.navigateEndless === true) { _self.objects.prev.show(); _self.objects.next.show(); } return items; }, /** * Item information/status * */ item: { /** * Verifies if the current item is first item. * * @return {boolean} */ isFirst: function () { return (_self.objectData.currentIndex === 0); }, /** * Verifies if the current item is first item opened. * * @return {boolean} */ isFirstOpened: function () { return _self.objectData.this.is(_self.cache.firstOpened); }, /** * Verifies if the current item is last item. * * @return {boolean} */ isLast: function () { return (_self.objectData.currentIndex === (_self.objectData.sequenceLength - 1)); } }, /** * Clones the object for inline elements * * @param {object} $object * @return {object} $clone */ _cloneObject: function ($object) { var $clone = $object.clone(), objectId = $object.attr('id'); // If element is hidden, cache the object and remove if ($object.is(':hidden')) { _self._cacheObjectData($object); $object.attr('id', _self.settings.idPrefix + 'temp-' + objectId).empty(); } else { // Prevent duplicated id's $clone.removeAttr('id'); } return $clone.show(); }, /** * Verifies if it is a mobile device * * @return {boolean} */ isMobileDevice: function () { var deviceAgent = navigator.userAgent.toLowerCase(), agentId = deviceAgent.match(_self.settings.mobileMatchExpression); return agentId ? true : false; }, /** * Verifies if css transitions are supported * * @return {string|boolean} The transition prefix if supported, else false. */ isTransitionSupported: function () { var body = _self.objects.body.get(0), isTransitionSupported = false, transitionMapping = { 'transition': '', 'WebkitTransition': '-webkit-', 'MozTransition': '-moz-', 'OTransition': '-o-', 'MsTransition': '-ms-' }; for (var key in transitionMapping) { if (transitionMapping.hasOwnProperty(key) && key in body.style) { _self.support.transition = transitionMapping[key]; isTransitionSupported = true; } } return isTransitionSupported; }, /** * Transition types * */ transition: { /** * Returns the correct transition type according to the status of interaction. * * @return {string} Transition type */ in: function () { if (_self.settings.transitionOpen && !_self.cache.firstOpened) { return _self.settings.transitionOpen; } return _self.settings.transitionIn; }, /** * Fades in/out the object * * @param {object} $object * @param {string} type * @param {number} speed * @param {number} opacity * @param {function} callback * @return {void} Animates an object */ fade: function ($object, type, speed, opacity, callback) { var isInTransition = type === 'in', startTransition = {}, startOpacity = $object.css('opacity'), endTransition = {}, endOpacity = opacity ? opacity: isInTransition ? 1 : 0; if (!_self.isOpen && isInTransition) return; startTransition['opacity'] = startOpacity; endTransition['opacity'] = endOpacity; $object.css(_self.support.transition + 'transition', 'none'); $object.css(startTransition).show(); // Css transition if (_self.support.transitions) { endTransition[_self.support.transition + 'transition'] = speed + 'ms ease'; setTimeout(function () { $object.css(endTransition); setTimeout(function () { $object.css(_self.support.transition + 'transition', ''); if (callback && (_self.isOpen || !isInTransition)) { callback(); } }, speed); }, 15); } else { // Fallback to js transition $object.stop(); $object.animate(endTransition, speed, callback); } }, /** * Scrolls in/out the object * * @param {object} $object * @param {string} type * @param {number} speed * @param {function} callback * @return {void} Animates an object */ scroll: function ($object, type, speed, callback) { var isInTransition = type === 'in', transition = isInTransition ? _self.settings.transitionIn : _self.settings.transitionOut, direction = 'left', startTransition = {}, startOpacity = isInTransition ? 0 : 1, startOffset = isInTransition ? '-50%' : '50%', endTransition = {}, endOpacity = isInTransition ? 1 : 0, endOffset = isInTransition ? '50%' : '-50%'; if (!_self.isOpen && isInTransition) return; switch (transition) { case 'scrollTop': direction = 'top'; break; case 'scrollRight': startOffset = isInTransition ? '150%' : '50%'; endOffset = isInTransition ? '50%' : '150%'; break; case 'scrollBottom': direction = 'top'; startOffset = isInTransition ? '150%' : '50%'; endOffset = isInTransition ? '50%' : '150%'; break; case 'scrollHorizontal': startOffset = isInTransition ? '150%' : '50%'; endOffset = isInTransition ? '50%' : '-50%'; break; case 'scrollVertical': direction = 'top'; startOffset = isInTransition ? '-50%' : '50%'; endOffset = isInTransition ? '50%' : '150%'; break; } if (_self.cache.action === 'prev') { switch (transition) { case 'scrollHorizontal': startOffset = isInTransition ? '-50%' : '50%'; endOffset = isInTransition ? '50%' : '150%'; break; case 'scrollVertical': startOffset = isInTransition ? '150%' : '50%'; endOffset = isInTransition ? '50%' : '-50%'; break; } } startTransition['opacity'] = startOpacity; startTransition[direction] = startOffset; endTransition['opacity'] = endOpacity; endTransition[direction] = endOffset; $object.css(_self.support.transition + 'transition', 'none'); $object.css(startTransition).show(); // Css transition if (_self.support.transitions) { endTransition[_self.support.transition + 'transition'] = speed + 'ms ease'; setTimeout(function () { $object.css(endTransition); setTimeout(function () { $object.css(_self.support.transition + 'transition', ''); if (callback && (_self.isOpen || !isInTransition)) { callback(); } }, speed); }, 15); } else { // Fallback to js transition $object.stop(); $object.animate(endTransition, speed, callback); } }, /** * Zooms in/out the object * * @param {object} $object * @param {string} type * @param {number} speed * @param {function} callback * @return {void} Animates an object */ zoom: function ($object, type, speed, callback) { var isInTransition = type === 'in', startTransition = {}, startOpacity = $object.css('opacity'), startScale = isInTransition ? 'scale(0.75)' : 'scale(1)', endTransition = {}, endOpacity = isInTransition ? 1 : 0, endScale = isInTransition ? 'scale(1)' : 'scale(0.75)'; if (!_self.isOpen && isInTransition) return; startTransition['opacity'] = startOpacity; startTransition[_self.support.transition + 'transform'] = startScale; endTransition['opacity'] = endOpacity; $object.css(_self.support.transition + 'transition', 'none'); $object.css(startTransition).show(); // Css transition if (_self.support.transitions) { endTransition[_self.support.transition + 'transform'] = endScale; endTransition[_self.support.transition + 'transition'] = speed + 'ms ease'; setTimeout(function () { $object.css(endTransition); setTimeout(function () { $object.css(_self.support.transition + 'transform', ''); $object.css(_self.support.transition + 'transition', ''); if (callback && (_self.isOpen || !isInTransition)) { callback(); } }, speed); }, 15); } else { // Fallback to js transition $object.stop(); $object.animate(endTransition, speed, callback); } } }, /** * Calls all the registered functions of a specific hook * * @param {object} hooks * @return {void} */ _callHooks: function (hooks) { if (typeof(hooks) === 'object') { $.each(hooks, function(index, hook) { if (typeof(hook) === 'function') { hook.call(_self.origin); } }); } }, /** * Caches the object data * * @param {object} $object * @return {void} */ _cacheObjectData: function ($object) { $.data($object, 'cache', { id: $object.attr('id'), content: $object.html() }); _self.cache.originalObject = $object; }, /** * Restores the object from cache * * @return void */ _restoreObject: function () { var $object = $('[id^="' + _self.settings.idPrefix + 'temp-"]'); $object.attr('id', $.data(_self.cache.originalObject, 'cache').id); $object.html($.data(_self.cache.originalObject, 'cache').content); }, /** * Executes functions for a window resize. * It stops an eventual timeout and recalculates dimensions. * * @param {object} dimensions * @return {void} */ resize: function (event, dimensions) { if (!_self.isOpen) return; if (_self.isSlideshowEnabled()) { _self._stopTimeout(); } if (typeof dimensions === 'object' && dimensions !== null) { if (dimensions.width) { _self.cache.object.attr( _self._prefixAttributeName('width'), dimensions.width ); } if (dimensions.maxWidth) { _self.cache.object.attr( _self._prefixAttributeName('max-width'), dimensions.maxWidth ); } if (dimensions.height) { _self.cache.object.attr( _self._prefixAttributeName('height'), dimensions.height ); } if (dimensions.maxHeight) { _self.cache.object.attr( _self._prefixAttributeName('max-height'), dimensions.maxHeight ); } } _self.dimensions = _self.getViewportDimensions(); _self._calculateDimensions(_self.cache.object); // Call onResize hook functions _self._callHooks(_self.settings.onResize); }, /** * Watches for any resize interaction and caches the new sizes. * * @return {void} */ _watchResizeInteraction: function () { $(window).resize(_self.resize); }, /** * Stop watching any resize interaction related to _self. * * @return {void} */ _unwatchResizeInteraction: function () { $(window).off('resize', _self.resize); }, /** * Switches to the fullscreen mode * * @return {void} */ _switchToFullScreenMode: function () { _self.settings.shrinkFactor = 1; _self.settings.overlayOpacity = 1; $('html').addClass(_self.settings.classPrefix + 'fullScreenMode'); }, /** * Enters into the lightcase view * * @return {void} */ _open: function () { _self.isOpen = true; _self.support.transitions = _self.settings.cssTransitions ? _self.isTransitionSupported() : false; _self.support.mobileDevice = _self.isMobileDevice(); if (_self.support.mobileDevice) { $('html').addClass(_self.settings.classPrefix + 'isMobileDevice'); if (_self.settings.fullScreenModeForMobile) { _self._switchToFullScreenMode(); } } if (!_self.settings.transitionIn) { _self.settings.transitionIn = _self.settings.transition; } if (!_self.settings.transitionOut) { _self.settings.transitionOut = _self.settings.transition; } switch (_self.transition.in()) { case 'fade': case 'fadeInline': case 'elastic': case 'scrollTop': case 'scrollRight': case 'scrollBottom': case 'scrollLeft': case 'scrollVertical': case 'scrollHorizontal': if (_self.objects.case.is(':hidden')) { _self.objects.close.css('opacity', 0); _self.objects.overlay.css('opacity', 0); _self.objects.case.css('opacity', 0); _self.objects.contentInner.css('opacity', 0); } _self.transition.fade(_self.objects.overlay, 'in', _self.settings.speedIn, _self.settings.overlayOpacity, function () { _self.transition.fade(_self.objects.close, 'in', _self.settings.speedIn); _self._handleEvents(); _self._processContent(); }); break; default: _self.transition.fade(_self.objects.overlay, 'in', 0, _self.settings.overlayOpacity, function () { _self.transition.fade(_self.objects.close, 'in', 0); _self._handleEvents(); _self._processContent(); }); break; } _self.objects.document.addClass(_self.settings.classPrefix + 'open'); _self.objects.case.attr('aria-hidden', 'false'); }, /** * Shows the lightcase by starting the transition */ show: function () { // Call onCalculateDimensions hook functions _self._callHooks(_self.settings.onBeforeCalculateDimensions); _self._calculateDimensions(_self.cache.object); // Call onAfterCalculateDimensions hook functions _self._callHooks(_self.settings.onAfterCalculateDimensions); _self._startInTransition(); }, /** * Escapes from the lightcase view * * @return {void} */ close: function () { _self.isOpen = false; if (_self.isSlideshowEnabled()) { _self._stopTimeout(); _self.isSlideshowStarted = false; _self.objects.nav.removeClass(_self.settings.classPrefix + 'paused'); } _self.objects.loading.hide(); _self._unbindEvents(); _self._unwatchResizeInteraction(); $('html').removeClass(_self.settings.classPrefix + 'open'); _self.objects.case.attr('aria-hidden', 'true'); _self.objects.nav.children().hide(); _self.objects.close.hide(); // Call onClose hook functions _self._callHooks(_self.settings.onClose); // Fade out the info at first _self.transition.fade(_self.objects.info, 'out', 0); switch (_self.settings.transitionClose || _self.settings.transitionOut) { case 'fade': case 'fadeInline': case 'scrollTop': case 'scrollRight': case 'scrollBottom': case 'scrollLeft': case 'scrollHorizontal': case 'scrollVertical': _self.transition.fade(_self.objects.case, 'out', _self.settings.speedOut, 0, function () { _self.transition.fade(_self.objects.overlay, 'out', _self.settings.speedOut, 0, function () { _self.cleanup(); }); }); break; case 'elastic': _self.transition.zoom(_self.objects.case, 'out', _self.settings.speedOut, function () { _self.transition.fade(_self.objects.overlay, 'out', _self.settings.speedOut, 0, function () { _self.cleanup(); }); }); break; default: _self.cleanup(); break; } }, /** * Unbinds all given events * * @return {void} */ _unbindEvents: function () { // Unbind overlay event _self.objects.overlay.unbind('click'); // Unbind key events $(document).unbind('keyup.lightcase'); // Unbind swipe events _self.objects.case.unbind('swipeleft').unbind('swiperight'); // Unbind navigator events _self.objects.prev.unbind('click'); _self.objects.next.unbind('click'); _self.objects.play.unbind('click'); _self.objects.pause.unbind('click'); // Unbind close event _self.objects.close.unbind('click'); }, /** * Cleans up the dimensions * * @return {void} */ _cleanupDimensions: function () { var opacity = _self.objects.contentInner.css('opacity'); _self.objects.case.css({ 'width': '', 'height': '', 'top': '', 'left': '', 'margin-top': '', 'margin-left': '' }); _self.objects.contentInner.removeAttr('style').css('opacity', opacity); _self.objects.contentInner.children().removeAttr('style'); }, /** * Cleanup after aborting lightcase * * @return {void} */ cleanup: function () { _self._cleanupDimensions(); _self.objects.loading.hide(); _self.objects.overlay.hide(); _self.objects.case.hide(); _self.objects.prev.hide(); _self.objects.next.hide(); _self.objects.play.hide(); _self.objects.pause.hide(); _self.objects.document.removeAttr(_self._prefixAttributeName('type')); _self.objects.nav.removeAttr(_self._prefixAttributeName('ispartofsequence')); _self.objects.contentInner.empty().hide(); _self.objects.info.children().empty(); if (_self.cache.originalObject) { _self._restoreObject(); } // Call onCleanup hook functions _self._callHooks(_self.settings.onCleanup); // Restore cache _self.cache = {}; }, /** * Returns the supported match media or undefined if the browser * doesn't support match media. * * @return {mixed} */ _matchMedia: function () { return window.matchMedia || window.msMatchMedia; }, /** * Returns the devicePixelRatio if supported. Else, it simply returns * 1 as the default. * * @return {number} */ _devicePixelRatio: function () { return window.devicePixelRatio || 1; }, /** * Checks if method is public * * @return {boolean} */ _isPublicMethod: function (method) { return (typeof _self[method] === 'function' && method.charAt(0) !== '_'); }, /** * Exports all public methods to be accessible, callable * from global scope. * * @return {void} */ _export: function () { window.lightcase = {}; $.each(_self, function (property) { if (_self._isPublicMethod(property)) { lightcase[property] = _self[property]; } }); } }; _self._export(); $.fn.lightcase = function (method) { // Method calling logic (only public methods are applied) if (_self._isPublicMethod(method)) { return _self[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (typeof method === 'object' || !method) { return _self.init.apply(this, arguments); } else { $.error('Method ' + method + ' does not exist on jQuery.lightcase'); } }; })(jQuery);