UNPKG

formstone

Version:

Library of modular front end components.

1,543 lines (1,261 loc) 52.2 kB
/* global define */ (function(factory) { if (typeof define === "function" && define.amd) { define([ "jquery", "./core", "./touch", "./transition", "./viewer" ], factory); } else { factory(jQuery, Formstone); } }(function($, Formstone) { "use strict"; /** * @method private * @name setup * @description Setup plugin. */ function setup() { $Body = Formstone.$body; $Locks = $("html, body"); OnLoad = Formstone.window.location.hash.replace("#", ""); } /** * @method private * @name resize * @description Handles window resize */ function resize() { if (Instance) { resizeLightbox(); } } /** * @method private * @name construct * @description Builds instance. * @param data [object] "Instance data" */ function construct(data) { this.on(Events.click, data, buildLightbox); var gallery = this.data(Namespace + "-gallery"); if (!OnLoaded && OnLoad && gallery === OnLoad) { OnLoaded = true; this.trigger(Events.click); } } /** * @method private * @name destruct * @description Tears down instance. * @param data [object] "Instance data" */ function destruct(data) { closeLightbox(); this.off(Events.namespace); } /** * @method private * @name initialize * @description Builds instance from $target. * @param $target [jQuery] "Target jQuery object" */ function initialize($target, options) { if ($target instanceof $) { // Emulate event buildLightbox.apply(Window, [{ data: $.extend(true, {}, { $object: $target }, Defaults, options || {}) }]); } } /** * @method private * @name buildLightbox * @description Builds new lightbox. * @param e [object] "Event data" */ function buildLightbox(e) { if (!Instance) { var data = e.data; if (data.overlay === true) { data.mobile = true; // Backwards compatibility } // Cache internal data Instance = $.extend({}, { visible: false, gallery: { active: false }, isMobile: (Formstone.isMobile || data.mobile), isTouch: Formstone.support.touch, isAnimating: true, isZooming: false, oldContentHeight: 0, oldContentWidth: 0, metaHeight: 0, thumbnailHeight: 0, captionOpen: false, thumbnailsOpen: false, tapTimer: null }, data); Instance.isViewer = (Instance.isMobile && data.viewer && typeof $.fn.fsViewer !== "undefined"); // Check target type var $el = data.$el, $object = data.$object, source = ($el && $el[0].href) ? $el[0].href || "" : "", hash = ($el && $el[0].hash) ? $el[0].hash || "" : "", sourceParts = source.toLowerCase().split(".").pop().split(/\#|\?/), type = ($el) ? $el.data(Namespace + "-type") : "", isImage = ((type === "image") || (source.match(data.fileTypes) || source.substr(0, 10) === "data:image")), isVideo = checkVideo(source), isUrl = ((type === "url") || (!isImage && !isVideo && source.substr(0, 4) === "http" && !hash)), isElement = ((type === "element") || (!isImage && !isVideo && !isUrl && (hash.substr(0, 1) === "#"))), isObject = ((typeof $object !== "undefined")); if (isElement) { source = hash; } // Retain default click if (!(isImage || isVideo || isUrl || isElement || isObject)) { Instance = null; return; } // Kill event Functions.killEvent(e); // Double the margin Instance.margin *= 2; if (isImage) { Instance.type = "image"; } else if (isVideo) { Instance.type = "video"; } else { Instance.type = "element"; } if (isImage || isVideo) { // Check for gallery var id = $el.data(Namespace + "-gallery"); if (id) { Instance.gallery.active = true; Instance.gallery.id = id; Instance.gallery.$items = $("a[data-lightbox-gallery= " + Instance.gallery.id + "], a[rel= " + Instance.gallery.id + "]"); // backwards compatibility Instance.gallery.index = Instance.gallery.$items.index(Instance.$el); Instance.gallery.total = Instance.gallery.$items.length - 1; } } // Thumbnails support if (!(Instance.thumbnails && (isImage || isVideo) && Instance.gallery.active)) { Instance.thumbnails = false; } // Assemble HTML var html = ''; if (!Instance.isMobile) { html += '<div class="' + [RawClasses.overlay, Instance.theme, Instance.customClass].join(" ") + '"></div>'; } var lightboxClasses = [ RawClasses.base, RawClasses.loading, RawClasses.animating, Instance.theme, Instance.customClass ]; if (Instance.fixed) { lightboxClasses.push(RawClasses.fixed); } if (Instance.isMobile) { lightboxClasses.push(RawClasses.mobile); } if (Instance.isTouch) { lightboxClasses.push(RawClasses.touch); } if (isUrl) { lightboxClasses.push(RawClasses.iframed); } if (isElement || isObject) { lightboxClasses.push(RawClasses.inline); } if (Instance.thumbnails) { lightboxClasses.push(RawClasses.thumbnailed); } Instance.labels.lightbox = Instance.labels.lightbox.replace('{guid}', data.numGuid); html += '<div class="' + lightboxClasses.join(" ") + '" role="dialog" aria-label="' + Instance.labels.lightbox + '" tabindex="-1">'; html += '<button type="button" class="' + RawClasses.close + '">' + Instance.labels.close + '</button>'; html += '<span class="' + RawClasses.loading_icon + '"></span>'; html += '<div class="' + RawClasses.container + '">'; // Thumbnails if (Instance.gallery.active && Instance.thumbnails) { html += '<div class="' + [RawClasses.thumbnails] + '">'; html += '<div class="' + [RawClasses.thumbnail_container] + '">'; var $item, thumb; for (var i = 0, count = Instance.gallery.$items.length; i < count; i++) { $item = Instance.gallery.$items.eq(i); thumb = $item.data("lightbox-thumbnail"); if (!thumb) { thumb = $item.find("img").attr("src"); } html += '<button class="' + [RawClasses.thumbnail_item] + '">'; html += '<img src="' + thumb + '" alt="">'; html += '</button>'; } html += '</div></div>'; } html += '<div class="' + RawClasses.content + '"></div>'; if (isImage || isVideo) { html += '<div class="' + RawClasses.tools + '">'; html += '<div class="' + RawClasses.controls + '">'; if (Instance.gallery.active) { html += '<button type="button" class="' + [RawClasses.control, RawClasses.control_previous].join(" ") + '">' + Instance.labels.previous + '</button>'; html += '<button type="button" class="' + [RawClasses.control, RawClasses.control_next].join(" ") + '">' + Instance.labels.next + '</button>'; } if (Instance.isMobile && Instance.isTouch) { html += '<button type="button" class="' + [RawClasses.toggle, RawClasses.caption_toggle].join(" ") + '">' + Instance.labels.captionClosed + '</button>'; if (Instance.gallery.active && Instance.thumbnails) { html += '<button type="button" class="' + [RawClasses.toggle, RawClasses.thumbnail_toggle].join(" ") + '">' + Instance.labels.thumbnailsClosed + '</button>'; } } html += '</div>'; // controls html += '<div class="' + RawClasses.meta + '">'; html += '<div class="' + RawClasses.meta_content + '">'; if (Instance.gallery.active) { html += '<p class="' + RawClasses.position + '"'; if (Instance.gallery.total < 1) { html += ' style="display: none;"'; } html += '>'; html += '<span class="' + RawClasses.position_current + '">' + (Instance.gallery.index + 1) + '</span> '; html += Instance.labels.count; html += ' <span class="' + RawClasses.position_total + '">' + (Instance.gallery.total + 1) + '</span>'; html += '</p>'; } html += '<div class="' + RawClasses.caption + '">'; html += Instance.formatter.call($el, data); html += '</div></div></div>'; // caption, meta_content, meta html += '</div>'; // tools } html += '</div></div>'; //container, content, lightbox // Modify Dom $Body.append(html); // Cache jquery objects Instance.$overlay = $(Classes.overlay); Instance.$lightbox = $(Classes.base); Instance.$close = $(Classes.close); Instance.$container = $(Classes.container); Instance.$content = $(Classes.content); Instance.$tools = $(Classes.tools); Instance.$meta = $(Classes.meta); Instance.$metaContent = $(Classes.meta_content); Instance.$position = $(Classes.position); Instance.$caption = $(Classes.caption); Instance.$controlBox = $(Classes.controls); Instance.$controls = $(Classes.control); Instance.$thumbnails = $(Classes.thumbnails); Instance.$thumbnailContainer = $(Classes.thumbnail_container); Instance.$thumbnailItems = $(Classes.thumbnail_item); if (Instance.isMobile) { Instance.paddingVertical = Instance.$close.outerHeight(); Instance.paddingHorizontal = 0; Instance.mobilePaddingVertical = parseInt(Instance.$content.css("paddingTop"), 10) + parseInt(Instance.$content.css("paddingBottom"), 10); Instance.mobilePaddingHorizontal = parseInt(Instance.$content.css("paddingLeft"), 10) + parseInt(Instance.$content.css("paddingRight"), 10); } else { Instance.paddingVertical = parseInt(Instance.$lightbox.css("paddingTop"), 10) + parseInt(Instance.$lightbox.css("paddingBottom"), 10); Instance.paddingHorizontal = parseInt(Instance.$lightbox.css("paddingLeft"), 10) + parseInt(Instance.$lightbox.css("paddingRight"), 10); Instance.mobilePaddingVertical = 0; Instance.mobilePaddingHorizontal = 0; } Instance.contentHeight = Instance.$lightbox.outerHeight() - Instance.paddingVertical; Instance.contentWidth = Instance.$lightbox.outerWidth() - Instance.paddingHorizontal; Instance.controlHeight = Instance.$controls.outerHeight(); // Center centerLightbox(); // Update gallery if (Instance.gallery.active) { Instance.$lightbox.addClass(RawClasses.has_controls); updateGalleryControls(); } // Bind events $Window.on(Events.keyDown, onKeyDown); $Body.on(Events.click, [Classes.overlay, Classes.close].join(", "), closeLightbox) .on([Events.focus, Events.focusIn].join(" "), onDocumentFocus); if (Instance.gallery.active) { Instance.$lightbox.on(Events.click, Classes.control, advanceGallery); } if (Instance.thumbnails) { Instance.$lightbox.on(Events.click, Classes.thumbnail_item, jumpGallery); } if (Instance.isMobile && Instance.isTouch) { Instance.$lightbox.on(Events.click, Classes.caption_toggle, toggleCaption) .on(Events.click, Classes.thumbnail_toggle, toggleThumbnails); } Instance.$lightbox.fsTransition({ property: "opacity" }, function() { if (isImage) { loadImage(source); } else if (isVideo) { loadVideo(source); } else if (isUrl) { loadURL(source); } else if (isElement) { appendContents(source); } else if (isObject) { appendObject(Instance.$object); } }).addClass(RawClasses.open); Instance.$overlay.addClass(RawClasses.open); } } /** * @method * @name resize * @description Resizes lightbox. * @example $.lightbox("resize"); * @param height [int | false] "Target height or false to auto size" * @param width [int | false] "Target width or false to auto size" */ /** * @method private * @name resizeLightbox * @description Triggers resize of instance. */ function resizeLightbox(e) { if (typeof e !== "object") { Instance.targetHeight = arguments[0]; Instance.targetWidth = arguments[1]; } if (Instance.type === "element") { sizeContent(Instance.$content.find("> :first-child")); } else if (Instance.type === "image") { sizeImage(); } else if (Instance.type === "video") { sizeVideo(); } sizeLightbox(); } /** * @method * @name close * @description Closes active instance. * @example $.lightbox("close"); */ /** * @method private * @name closeLightbox * @description Closes active instance. * @param e [object] "Event data" */ function closeLightbox(e) { Functions.killEvent(e); if (Instance) { Instance.$lightbox.fsTransition("destroy"); Instance.$content.fsTransition("destroy"); Instance.$lightbox.addClass(RawClasses.animating).fsTransition({ property: "opacity" }, function(e) { // Clean up if (typeof Instance.$inlineTarget !== 'undefined' && Instance.$inlineTarget.length) { restoreContents(); } if (Instance.isViewer && Instance.$imageContainer && Instance.$imageContainer.length) { Instance.$imageContainer.fsViewer("destroy"); } Instance.$lightbox.off(Events.namespace); Instance.$container.off(Events.namespace); $Window.off(Events.keyDown); $Body.off(Events.namespace); $Body.off(Events.namespace); Instance.$overlay.remove(); Instance.$lightbox.remove(); if (typeof Instance.$el !== "undefined" && Instance.$el && Instance.$el.length) { Instance.$el.focus(); } // Reset Instance Instance = null; $Window.trigger(Events.close); }); Instance.$lightbox.removeClass(RawClasses.open); Instance.$overlay.removeClass(RawClasses.open); if (Instance.isMobile) { $Locks.removeClass(RawClasses.lock); Functions.unlockViewport(Namespace); } } } /** * @method private * @name openLightbox * @description Opens active instance. */ function openLightbox() { var position = calculatePosition(), durration = Instance.isMobile ? 0 : Instance.duration; if (Instance.isMobile) { Functions.lockViewport(Namespace); } else { Instance.$controls.css({ marginTop: ((Instance.contentHeight - Instance.controlHeight - Instance.metaHeight + Instance.thumbnailHeight) / 2) }); } if (Instance.$caption.html() === "") { Instance.$caption.hide(); Instance.$lightbox.removeClass(RawClasses.has_caption); if (!Instance.gallery.active) { Instance.$tools.hide(); } } else { Instance.$caption.show(); Instance.$lightbox.addClass(RawClasses.has_caption); } Instance.$lightbox.fsTransition({ property: (Instance.contentHeight !== Instance.oldContentHeight) ? "height" : "width" }, function() { Instance.$content.fsTransition({ property: "opacity" }, function() { Instance.$lightbox.removeClass(RawClasses.animating); Instance.isAnimating = false; }); Instance.$lightbox.removeClass(RawClasses.loading) .addClass(RawClasses.ready); Instance.visible = true; // Fire open event $Window.trigger(Events.open); // Start preloading if (Instance.gallery.active) { preloadGallery(); updateThumbnails(); positionThumbnails(); } // Focus Instance.$lightbox.focus(); }); if (!Instance.isMobile) { Instance.$lightbox.css({ height: Instance.contentHeight + Instance.paddingVertical, width: Instance.contentWidth + Instance.paddingHorizontal, top: (!Instance.fixed) ? position.top : 0 }); } // Trigger event in case the content size hasn't changed var contentHasChanged = (Instance.oldContentHeight !== Instance.contentHeight || Instance.oldContentWidth !== Instance.contentWidth); if (Instance.isMobile || !contentHasChanged) { Instance.$lightbox.fsTransition("resolve"); } // Track content size changes Instance.oldContentHeight = Instance.contentHeight; Instance.oldContentWidth = Instance.contentWidth; if (Instance.isMobile) { $Locks.addClass(RawClasses.lock); } } /** * @method private * @name sizeLightbox * @description Sizes active instance. */ function sizeLightbox() { if (Instance.visible && !Instance.isMobile) { var position = calculatePosition(); Instance.$controls.css({ marginTop: ((Instance.contentHeight - Instance.controlHeight - Instance.metaHeight + Instance.thumbnailHeight) / 2) }); Instance.$lightbox.css({ height: Instance.contentHeight + Instance.paddingVertical, width: Instance.contentWidth + Instance.paddingHorizontal, top: (!Instance.fixed) ? position.top : 0 }); Instance.oldContentHeight = Instance.contentHeight; Instance.oldContentWidth = Instance.contentWidth; } } /** * @method private * @name centerLightbox * @description Centers instance. */ function centerLightbox() { var position = calculatePosition(); Instance.$lightbox.css({ top: (!Instance.fixed) ? position.top : 0 }); } /** * @method private * @name calculatePosition * @description Calculates positions. * @return [object] "Object containing top and left positions" */ function calculatePosition() { if (Instance.isMobile) { return { left: 0, top: 0 }; } var pos = { left: (Formstone.windowWidth - Instance.contentWidth - Instance.paddingHorizontal) / 2, top: (Instance.top <= 0) ? ((Formstone.windowHeight - Instance.contentHeight - Instance.paddingVertical) / 2) : Instance.top }; if (Instance.fixed !== true) { pos.top += $Window.scrollTop(); } return pos; } /** * @method private * @name toggleCaption * @description Toggle caption. */ function toggleCaption(e) { Functions.killEvent(e); if (Instance.captionOpen) { closeCaption(); } else { closeThumbnails(); var height = parseInt(Instance.$metaContent.outerHeight(true)); height += parseInt(Instance.$meta.css("padding-top")); height += parseInt(Instance.$meta.css("padding-bottom")); Instance.$meta.css({ height: height }); Instance.$lightbox.addClass(RawClasses.caption_open) .find(Classes.caption_toggle).text(Instance.labels.captionOpen); Instance.captionOpen = true; } } /** * @method private * @name closeCaption * @description Close caption. */ function closeCaption() { Instance.$lightbox.removeClass(RawClasses.caption_open) .find(Classes.caption_toggle).text(Instance.labels.captionClosed); Instance.captionOpen = false; } /** * @method private * @name formatCaption * @description Formats caption. * @param $target [jQuery object] "Target element" */ function formatCaption() { var title = this.attr("title"), t = (title !== undefined && title) ? title.replace(/^\s+|\s+$/g, '') : false; return t ? '<p class="caption">' + t + '</p>' : ""; } /** * @method private * @name toggleThumbnails * @description Toggle thumbnails. */ function toggleThumbnails(e) { Functions.killEvent(e); if (Instance.thumbnailsOpen) { closeThumbnails(); } else { closeCaption(); Instance.$lightbox.addClass(RawClasses.thumbnails_open) .find(Classes.thumbnail_toggle).text(Instance.labels.thumbnailsOpen); Instance.thumbnailsOpen = true; } } /** * @method private * @name closeThumbnails * @description Close thumbnails. */ function closeThumbnails() { Instance.$lightbox.removeClass(RawClasses.thumbnails_open) .find(Classes.thumbnail_toggle).text(Instance.labels.thumbnailsClosed); Instance.thumbnailsOpen = false; } /** * @method private * @name loadImage * @description Loads source image. * @param source [string] "Source image URL" */ function loadImage(source) { if (Instance.isViewer) { Instance.$imageContainer = $('<div class="' + RawClasses.image_container + '"><img></div>'); Instance.$image = Instance.$imageContainer.find("img"); Instance.$image.attr("src", source) .addClass(RawClasses.image); Instance.$content.prepend(Instance.$imageContainer); sizeImage(); Instance.$imageContainer.one("loaded.viewer", function() { openLightbox(); }).fsViewer({ theme: Instance.theme }); } else { // Cache current image Instance.$imageContainer = $('<div class="' + RawClasses.image_container + '"><img></div>'); Instance.$image = Instance.$imageContainer.find("img"); Instance.$image.one(Events.load, function() { var naturalSize = calculateNaturalSize(Instance.$image); Instance.naturalHeight = naturalSize.naturalHeight; Instance.naturalWidth = naturalSize.naturalWidth; if (Instance.retina) { Instance.naturalHeight /= 2; Instance.naturalWidth /= 2; } Instance.$content.prepend(Instance.$imageContainer); // Size content to be sure it fits the viewport sizeImage(); openLightbox(); }).on(Events.error, loadError) .attr("src", source) .addClass(RawClasses.image); // If image has already loaded into cache, trigger load event if (Instance.$image[0].complete || Instance.$image[0].readyState === 4) { Instance.$image.trigger(Events.load); } } } /** * @method private * @name sizeImage * @description Sizes image to fit in viewport. * @param count [int] "Number of resize attempts" */ function sizeImage() { if (Instance.$image) { var count = 0; Instance.windowHeight = Instance.viewportHeight = Formstone.windowHeight - Instance.mobilePaddingVertical - Instance.paddingVertical; Instance.windowWidth = Instance.viewportWidth = Formstone.windowWidth - Instance.mobilePaddingHorizontal - Instance.paddingHorizontal; Instance.contentHeight = Infinity; Instance.contentWidth = Infinity; Instance.imageMarginTop = 0; Instance.imageMarginLeft = 0; while (Instance.contentHeight > Instance.viewportHeight && count < 2) { Instance.imageHeight = (count === 0) ? Instance.naturalHeight : Instance.$image.outerHeight(); Instance.imageWidth = (count === 0) ? Instance.naturalWidth : Instance.$image.outerWidth(); Instance.metaHeight = (count === 0) ? 0 : Instance.metaHeight; Instance.spacerHeight = (count === 0) ? 0 : Instance.spacerHeight; Instance.thumbnailHeight = (count === 0) ? 0 : Instance.thumbnailHeight; if (count === 0) { Instance.ratioHorizontal = Instance.imageHeight / Instance.imageWidth; Instance.ratioVertical = Instance.imageWidth / Instance.imageHeight; Instance.isWide = (Instance.imageWidth > Instance.imageHeight); } // Double check min and max if (Instance.imageHeight < Instance.minHeight) { Instance.minHeight = Instance.imageHeight; } if (Instance.imageWidth < Instance.minWidth) { Instance.minWidth = Instance.imageWidth; } if (Instance.isMobile) { if (Instance.isTouch) { Instance.$controlBox.css({ width: Formstone.windowWidth }); Instance.spacerHeight = Instance.$controls.outerHeight(true); } else { Instance.$tools.css({ width: Formstone.windowWidth }); Instance.spacerHeight = Instance.$tools.outerHeight(true); } // Content match viewport Instance.contentHeight = Instance.viewportHeight; Instance.contentWidth = Instance.viewportWidth; if (!Instance.isTouch) { Instance.$content.css({ height: Instance.contentHeight - Instance.spacerHeight // - 10 }); } if (Instance.$thumbnails.length) { Instance.spacerHeight += Instance.$thumbnails.outerHeight(true); } Instance.spacerHeight += 10; fitImage(); Instance.imageMarginTop = (Instance.contentHeight - Instance.targetImageHeight - Instance.spacerHeight) / 2; Instance.imageMarginLeft = (Instance.contentWidth - Instance.targetImageWidth) / 2; } else { // Viewport should match window, less margin, padding and meta if (count === 0) { Instance.viewportHeight -= (Instance.margin + Instance.paddingVertical); Instance.viewportWidth -= (Instance.margin + Instance.paddingHorizontal); } Instance.viewportHeight -= Instance.metaHeight; if (Instance.thumbnails) { Instance.viewportHeight -= Instance.thumbnailHeight; } fitImage(); Instance.contentHeight = Instance.targetImageHeight; Instance.contentWidth = Instance.targetImageWidth; } // Modify DOM if (!Instance.isMobile && !Instance.isTouch) { Instance.$meta.css({ width: Instance.contentWidth }); } Instance.$image.css({ height: Instance.targetImageHeight, width: Instance.targetImageWidth, marginTop: Instance.imageMarginTop, marginLeft: Instance.imageMarginLeft }); if (!Instance.isMobile) { Instance.metaHeight = Instance.$meta.outerHeight(true); Instance.contentHeight += Instance.metaHeight; } if (Instance.thumbnails) { Instance.thumbnailHeight = Instance.$thumbnails.outerHeight(true); Instance.contentHeight += Instance.thumbnailHeight; } count++; } } } /** * @method private * @name fitImage * @description Calculates target image size. */ function fitImage() { var height = (!Instance.isMobile) ? Instance.viewportHeight : Instance.contentHeight - Instance.spacerHeight, width = (!Instance.isMobile) ? Instance.viewportWidth : Instance.contentWidth; if (Instance.isWide) { // WIDE Instance.targetImageWidth = width; Instance.targetImageHeight = Instance.targetImageWidth * Instance.ratioHorizontal; if (Instance.targetImageHeight > height) { Instance.targetImageHeight = height; Instance.targetImageWidth = Instance.targetImageHeight * Instance.ratioVertical; } } else { // TALL Instance.targetImageHeight = height; Instance.targetImageWidth = Instance.targetImageHeight * Instance.ratioVertical; if (Instance.targetImageWidth > width) { Instance.targetImageWidth = width; Instance.targetImageHeight = Instance.targetImageWidth * Instance.ratioHorizontal; } } // MAX if (Instance.targetImageWidth > Instance.imageWidth || Instance.targetImageHeight > Instance.imageHeight) { Instance.targetImageHeight = Instance.imageHeight; Instance.targetImageWidth = Instance.imageWidth; } // MIN if (Instance.targetImageWidth < Instance.minWidth || Instance.targetImageHeight < Instance.minHeight) { if (Instance.targetImageWidth < Instance.minWidth) { Instance.targetImageWidth = Instance.minWidth; Instance.targetImageHeight = Instance.targetImageWidth * Instance.ratioHorizontal; } else { Instance.targetImageHeight = Instance.minHeight; Instance.targetImageWidth = Instance.targetImageHeight * Instance.ratioVertical; } } } /** * @method private * @name loadVideo * @description Loads source video. * @param source [string] "Source video URL" */ function formatYouTube(parts) { return "//www.youtube.com/embed/" + parts[1]; } function formatVimeo(parts) { return "//player.vimeo.com/video/" + parts[3]; } function loadVideo(source) { var parts, url = checkVideo(source), queryString = source.split("?"); if (url) { // if we have a query string if (queryString.length >= 2) { url += "?" + queryString.slice(1)[0].trim(); } Instance.$videoWrapper = $('<div class="' + RawClasses.video_wrapper + '"></div>'); Instance.$video = $('<iframe class="' + RawClasses.video + '" frameborder="0" seamless="seamless" allowfullscreen></iframe>'); Instance.$video.attr("src", url) .addClass(RawClasses.video) .prependTo(Instance.$videoWrapper); Instance.$content.prepend(Instance.$videoWrapper); sizeVideo(); openLightbox(); } else { loadError(); } } /** * @method private * @name sizeVideo * @description Sizes video to fit in viewport. */ function sizeVideo() { // Set initial vars Instance.windowHeight = Instance.viewportHeight = Formstone.windowHeight - Instance.mobilePaddingVertical - Instance.paddingVertical; Instance.windowWidth = Instance.viewportWidth = Formstone.windowWidth - Instance.mobilePaddingHorizontal - Instance.paddingHorizontal; Instance.videoMarginTop = 0; Instance.videoMarginLeft = 0; if (Instance.isMobile) { if (Instance.isTouch) { Instance.$controlBox.css({ width: Formstone.windowWidth }); Instance.spacerHeight = Instance.$controls.outerHeight(true) + 10; } else { Instance.$tools.css({ width: Formstone.windowWidth }); Instance.spacerHeight = Instance.$tools.outerHeight(true); Instance.spacerHeight += Instance.$thumbnails.outerHeight(true) + 10; } Instance.viewportHeight -= Instance.spacerHeight; Instance.targetVideoWidth = Instance.viewportWidth; Instance.targetVideoHeight = Instance.targetVideoWidth * Instance.videoRatio; if (Instance.targetVideoHeight > Instance.viewportHeight) { Instance.targetVideoHeight = Instance.viewportHeight; Instance.targetVideoWidth = Instance.targetVideoHeight / Instance.videoRatio; } Instance.videoMarginTop = (Instance.viewportHeight - Instance.targetVideoHeight) / 2; Instance.videoMarginLeft = (Instance.viewportWidth - Instance.targetVideoWidth) / 2; } else { Instance.viewportHeight = Instance.windowHeight - Instance.margin; Instance.viewportWidth = Instance.windowWidth - Instance.margin; Instance.targetVideoWidth = (Instance.videoWidth > Instance.viewportWidth) ? Instance.viewportWidth : Instance.videoWidth; if (Instance.targetVideoWidth < Instance.minWidth) { Instance.targetVideoWidth = Instance.minWidth; } Instance.targetVideoHeight = Instance.targetVideoWidth * Instance.videoRatio; Instance.contentHeight = Instance.targetVideoHeight; Instance.contentWidth = Instance.targetVideoWidth; } // Update dom if (!Instance.isMobile && !Instance.isTouch) { Instance.$meta.css({ width: Instance.contentWidth }); } Instance.$videoWrapper.css({ height: Instance.targetVideoHeight, width: Instance.targetVideoWidth, marginTop: Instance.videoMarginTop, marginLeft: Instance.videoMarginLeft }); if (!Instance.isMobile) { Instance.metaHeight = Instance.$meta.outerHeight(true); Instance.contentHeight += Instance.metaHeight; } if (Instance.thumbnails) { Instance.thumbnailHeight = Instance.$thumbnails.outerHeight(true); Instance.contentHeight += Instance.thumbnailHeight; } } /** * @method private * @name preloadGallery * @description Preloads previous and next images in gallery for faster rendering. * @param e [object] "Event Data" */ function preloadGallery(e) { var source = ''; if (Instance.gallery.index > 0) { source = Instance.gallery.$items.eq(Instance.gallery.index - 1).attr("href"); if (!checkVideo(source)) { $('<img src="' + source + '">'); } } if (Instance.gallery.index < Instance.gallery.total) { source = Instance.gallery.$items.eq(Instance.gallery.index + 1).attr("href"); if (!checkVideo(source)) { $('<img src="' + source + '">'); } } } /** * @method private * @name advanceGallery * @description Advances gallery base on direction. * @param e [object] "Event Data" */ function advanceGallery(e) { Functions.killEvent(e); var $control = $(e.currentTarget); if (!Instance.isAnimating && !$control.hasClass(RawClasses.control_disabled)) { Instance.isAnimating = true; closeCaption(); Instance.gallery.index += ($control.hasClass(RawClasses.control_next)) ? 1 : -1; if (Instance.gallery.index > Instance.gallery.total) { Instance.gallery.index = (Instance.infinite) ? 0 : Instance.gallery.total; } if (Instance.gallery.index < 0) { Instance.gallery.index = (Instance.infinite) ? Instance.gallery.total : 0; } updateThumbnails(); Instance.$lightbox.addClass(RawClasses.animating); Instance.$content.fsTransition({ property: "opacity" }, cleanGallery); Instance.$lightbox.addClass(RawClasses.loading); } } /** * @method private * @name jumpGallery * @description Jumps gallery base on thumbnail click. * @param e [object] "Event Data" */ function jumpGallery(e) { Functions.killEvent(e); var $thumbnail = $(e.currentTarget); if (!Instance.isAnimating && !$thumbnail.hasClass(RawClasses.active)) { Instance.isAnimating = true; closeCaption(); Instance.gallery.index = Instance.$thumbnailItems.index($thumbnail); updateThumbnails(); Instance.$lightbox.addClass(RawClasses.animating); Instance.$content.fsTransition({ property: "opacity" }, cleanGallery); Instance.$lightbox.addClass(RawClasses.loading); } } /** * @method private * @name jumpGallery * @description */ function updateThumbnails() { // Thumbnails if (Instance.thumbnails) { var $thumb = Instance.$thumbnailItems.eq(Instance.gallery.index); Instance.$thumbnailItems.removeClass(RawClasses.active); $thumb.addClass(RawClasses.active); } } /** * @method private * @name jumpGallery * @description */ function positionThumbnails() { // Thumbnails if (Instance.thumbnails) { var $thumb = Instance.$thumbnailItems.eq(Instance.gallery.index), scrollLeft = $thumb.position().left + ($thumb.outerWidth(false) / 2) - (Instance.$thumbnailContainer.outerWidth(true) / 2); Instance.$thumbnailContainer.stop().animate({ scrollLeft: scrollLeft }, 200, "linear"); } } /** * @method private * @name cleanGallery * @description Cleans gallery. */ function cleanGallery() { if (typeof Instance.$imageContainer !== 'undefined') { if (Instance.isViewer) { Instance.$imageContainer.fsViewer("destroy"); } Instance.$imageContainer.remove(); } if (typeof Instance.$videoWrapper !== 'undefined') { Instance.$videoWrapper.remove(); } Instance.$el = Instance.gallery.$items.eq(Instance.gallery.index); Instance.$caption.html(Instance.formatter.call(Instance.$el, Instance)); Instance.$position.find(Classes.position_current).html(Instance.gallery.index + 1); var source = Instance.$el.attr("href"), isVideo = checkVideo(source); if (isVideo) { Instance.type = "video"; loadVideo(source); } else { Instance.type = "image"; loadImage(source); } updateGalleryControls(); } /** * @method private * @name updateGalleryControls * @description Updates gallery control states. */ function updateGalleryControls() { Instance.$controls.removeClass(RawClasses.control_disabled); if (!Instance.infinite) { if (Instance.gallery.index === 0) { Instance.$controls.filter(Classes.control_previous).addClass(RawClasses.control_disabled); } if (Instance.gallery.index === Instance.gallery.total) { Instance.$controls.filter(Classes.control_next).addClass(RawClasses.control_disabled); } } } /** * @method private * @name onKeyDown * @description Handles keypress in gallery. * @param e [object] "Event data" */ function onKeyDown(e) { if (Instance.gallery.active && (e.keyCode === 37 || e.keyCode === 39)) { Functions.killEvent(e); Instance.$controls.filter((e.keyCode === 37) ? Classes.control_previous : Classes.control_next).trigger(Events.click); } else if (e.keyCode === 27) { Instance.$close.trigger(Events.click); } } /** * @method private * @name appendContents * @description Moves target inline element. * @param id [string] "Target element id" */ function appendContents(id) { Instance.$inlineTarget = $(id); Instance.$inlineContents = Instance.$inlineTarget.children().detach(); appendObject(Instance.$inlineContents); } /** * @method private * @name restoreContents * @description Restores inline element. */ function restoreContents() { Instance.$inlineTarget.append(Instance.$inlineContents.detach()); } /** * @method private * @name loadURL * @description Load URL into iframe. * @param source [string] "Target URL" */ function loadURL(source) { source = source + ((source.indexOf("?") > -1) ? "&" + Instance.requestKey + "=true" : "?" + Instance.requestKey + "=true"); var $iframe = $('<iframe class="' + RawClasses.iframe + '" src="' + source + '"></iframe>'); appendObject($iframe); } /** * @method private * @name appendObject * @description Appends and sizes object. * @param $object [jQuery Object] "Object to append" */ function appendObject($object) { Instance.$content.append($object); sizeContent($object); openLightbox(); } /** * @method private * @name sizeContent * @description Sizes jQuery object to fir in viewport. * @param $object [jQuery Object] "Object to size" */ function sizeContent($object) { Instance.windowHeight = Formstone.windowHeight - Instance.mobilePaddingVertical - Instance.paddingVertical; Instance.windowWidth = Formstone.windowWidth - Instance.mobilePaddingHorizontal - Instance.paddingHorizontal; Instance.objectHeight = $object.outerHeight(true); Instance.objectWidth = $object.outerWidth(true); Instance.targetHeight = Instance.targetHeight || (Instance.$el ? Instance.$el.data(Namespace + "-height") : null); Instance.targetWidth = Instance.targetWidth || (Instance.$el ? Instance.$el.data(Namespace + "-width") : null); Instance.maxHeight = (Instance.windowHeight < 0) ? Instance.minHeight : Instance.windowHeight; Instance.isIframe = $object.is("iframe"); Instance.objectMarginTop = 0; Instance.objectMarginLeft = 0; if (!Instance.isMobile) { Instance.windowHeight -= Instance.margin; Instance.windowWidth -= Instance.margin; } Instance.contentHeight = (Instance.targetHeight) ? Instance.targetHeight : (Instance.isIframe || Instance.isMobile) ? Instance.windowHeight : Instance.objectHeight; Instance.contentWidth = (Instance.targetWidth) ? Instance.targetWidth : (Instance.isIframe || Instance.isMobile) ? Instance.windowWidth : Instance.objectWidth; if ((Instance.isIframe || Instance.isObject) && Instance.isMobile) { Instance.contentHeight = Instance.windowHeight; Instance.contentWidth = Instance.windowWidth; } else if (Instance.isObject) { Instance.contentHeight = (Instance.contentHeight > Instance.windowHeight) ? Instance.windowHeight : Instance.contentHeight; Instance.contentWidth = (Instance.contentWidth > Instance.windowWidth) ? Instance.windowWidth : Instance.contentWidth; } if (!Instance.isMobile) { if (Instance.contentHeight > Instance.maxHeight) { Instance.contentHeight = Instance.maxHeight; } if (Instance.contentWidth > Instance.maxWidth) { Instance.contentWidth = Instance.maxWidth; } } } /** * @method private * @name loadError * @description Error when resource fails to load. */ function loadError() { var $error = $('<div class="' + RawClasses.error + '"><p>Error Loading Resource</p></div>'); // Clean up Instance.type = "element"; Instance.$tools.remove(); Instance.$image.off(Events.namespace); appendObject($error); $Window.trigger(Events.error); } /** * @method private * @name calculateNaturalSize * @description Determines natural size of target image. * @param $img [jQuery object] "Source image object" * @return [object | boolean] "Object containing natural height and width values or false" */ function calculateNaturalSize($img) { var node = $img[0], img = new Image(); if (typeof node.naturalHeight !== "undefined") { return { naturalHeight: node.naturalHeight, naturalWidth: node.naturalWidth }; } else { if (node.tagName.toLowerCase() === 'img') { img.src = node.src; return { naturalHeight: img.height, naturalWidth: img.width }; } } return false; } /** * @method private * @name checkVideo * @description Determines if url is a YouTube or Vimeo url. * @param source [string] "Source url" * @return [boolean] "True if YouTube or Vimeo url" */ function checkVideo(source) { var formats = Instance.videoFormatter, parts; for (var i in formats) { if (formats.hasOwnProperty(i)) { parts = source.match(formats[i].pattern); if (parts !== null) { return formats[i].format.call(Instance, parts); } } } return false; } /** * @method private * @name onDocumentFocus * @description Hanle document focus * @param e [object] "Event data" */ function onDocumentFocus(e) { var target = e.target; if (!$.contains(Instance.$lightbox[0], target) && target !== Instance.$lightbox[0] && target !== Instance.$overlay[0]) { Functions.killEvent(e); Instance.$lightbox.focus(); } } /** * @plugin * @name Lightbox * @description A jQuery plugin for simple modals. * @type widget * @main lightbox.js * @main lightbox.css * @dependency jQuery * @dependency core.js * @dependency touch.js * @dependency transition.js * @dependency viewer.js (optional) */ var Plugin = Formstone.Plugin("lightbox", { widget: true, /** * @options * @param customClass [string] <''> "Class applied to instance" * @param fileTypes [regex] <> "Image file types" * @param fixed [boolean] <false> "Flag for fixed positioning" * @param formatter [function] <$.noop> "Caption format function" * @param infinite [boolean] <false> "Flag for infinite galleries" * @param labels.close [string] <'Close'> "Close button text" * @param labels.count [string] <'of'> "Gallery count separator text" * @param labels.next [string] <'Next'> "Gallery control text" * @param labels.previous [string] <'Previous'> "Gallery control text" * @param labels.captionClosed [string] <'Close Caption'> "Mobile caption toggle text, closed state" * @param labels.captionOpen [string] <'View Caption'> "Mobile caption toggle text, open state" * @param labels.thumbnailsClosed [string] <'Close Thumbnails'> "Mobile thumbnails toggle text, closed state" * @param labels.thumbnailsOpen [string] <'View Thumbnails'> "Mobile thumbnails toggle text, open state" * @param labels.lightbox [string] <'Lightbox {guid}'> "Lightbox aria label; {guid} replaced with instance GUID" * @param margin [int] <50> "Margin used when sizing (single side)" * @param maxHeight [int] <10000> "Maximum height of element modal" * @param maxWidth [int] <10000> "Maximum width of element modal" * @param minHeight [int] <100> "Minimum height of modal" * @param minWidth [int] <100> "Minimum width of modal" * @param overlay [boolean] <false> "Flag to force 'overlay' ('mobile') rendering" * @param retina [boolean] <false> "Flag to use 'retina' sizing (halves natural sizes)" * @param requestKey [string] <'fs-lightbox'> "GET variable for ajax / iframe requests" * @param theme [string] <"fs-light"> "Theme class name" * @param thumbnails [boolean] <false> "Flag to display thumbnail strip" * @param top [int] <0> "Target top position; over-rides centering" * @param videoFormatter [array] <[]> "Object of video formatter objects containing a 'pattern' regex and 'format' callback" * @param videoRatio [number] <0.5625> "Video height / width ratio (9 / 16 = 0.5625)" * @param videoWidth [int] <800> "Video max width" * @param viewer [boolean] <false> "Flag to force 'Viewer' rendering, if available" */ defaults: { customClass: "", fileTypes: /\.(jpg|sjpg|jpeg|png|gif)/i, fixed: false, formatter: formatCaption, infinite: false, labels: { close: "Close", count: "of", next: "Next", previous: "Previous", captionClosed: "View Caption", captionOpen: "Close Caption", thumbnailsClosed: "View Thumbnails", thumbnailsOpen: "Close Thumbnails", lightbox: "Lightbox {guid}" }, margin: 50, maxHeight: 10000, maxWidth: 10000, minHeight: 100, minWidth: 100, mobile: false, overlay: false, retina: false, requestKey: "fs-lightbox", theme: "fs-light", thumbnails: false, top: 0, videoFormatter: { "youtube": { pattern: /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/ ]{11})/, format: formatYouTube }, "vimeo": { pattern: /(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^\/]*)\/videos\/|album\/(\d+)\/video\/|video\/|)(\d+)(?:$|\/|\?)/, format: formatVimeo } }, videoRatio: 0.5625, videoWidth: 800, viewer: true }, classes: [ "loading",