(function (global) {
var documentElement = document.documentElement;
// Detect support for opacity.
var supportsOpacity = "opacity" in documentElement.style && typeof documentElement.style.opacity === "string";
// Detect support for fixed positioning.
var fixedDiv = document.createElement("div");
fixedDiv.style.position = "fixed";
fixedDiv.style.margin = 0;
fixedDiv.style.top = "20px";
documentElement.appendChild(fixedDiv, documentElement.firstChild);
var supportsFixed = (fixedDiv.offsetTop == 20);
documentElement.removeChild(fixedDiv);
// Detect touch-based devices.
var supportsTouch = ("createTouch" in document);
/**
* The current version of Shadowbox.
*/
shadowbox.version = "4.0.0";
shadowbox.guid = 1;
shadowbox.K = function () {
return this;
};
/**
* The default set of options.
*/
shadowbox.options = {
// Animate height/width transitions.
animate: true,
// Automatically close when done playing movies.
autoClose: false,
// Able to navigate from one end of a gallery to the other (i.e. from
// last item to first or vice versa) by choosing next/previous?
continuous: false,
// Easing function for animations. Based on a cubic polynomial.
ease: function (state) {
return 1 + Math.pow(state - 1, 3);
},
// Enable control of Shadowbox via the keyboard?
enableKeys: !supportsTouch,
// The space to maintain around the edge of Shadowbox at all times.
margin: 40,
// A hook function that is called when closing.
onClose: shadowbox.K,
// A hook function that is called when a player is finished loading and
// all display transitions are complete. Receives the player object as
// its only argument.
onDone: shadowbox.K,
// A hook function that is called when opening.
onOpen: shadowbox.K,
// A hook function that is called when a player is ready to be
// displayed. Receives the player object as its only argument.
onShow: shadowbox.K,
// Background color for the overlay.
overlayColor: "black",
// Opacity for the overlay.
overlayOpacity: 0.8,
// The index in the current gallery at which to start when first opening.
startIndex: 0
};
/**
* A map of file extensions to the player class that should be used to play
* files with that extension.
*/
shadowbox.players = {};
/**
* Registers the given player class to be used with the given file
* extensions.
*
* shadowbox.registerPlayer(shadowbox.VideoPlayer, "mov");
* shadowbox.registerPlayer(shadowbox.PhotoPlayer, [ "jpg", "jpeg" ]);
*/
shadowbox.registerPlayer = function (playerClass, extensions) {
extensions = extensions || [];
if (!isArray(extensions))
extensions = [ extensions ];
forEach(extensions, function (extension) {
shadowbox.players[extension] = playerClass;
});
};
// Cache references to oft-used DOM elements.
var containerElement, overlayElement, wrapperElement, bodyElement, contentElement, coverElement;
/**
* Appends Shadowbox to the DOM and initializes DOM references.
*/
function initialize() {
if (containerElement)
return; // Don't initialize twice!
// The Shadowbox markup:
//
//
//
//
//
//
//
//
//
//
//
//
//
containerElement = makeDom("div", { id: "shadowbox" });
overlayElement = makeDom("div", { id: "sb-overlay" });
wrapperElement = makeDom("div", { id: "sb-wrapper" });
bodyElement = makeDom("div", { id: "sb-body" });
contentElement = makeDom("div", { id: "sb-content" });
coverElement = makeDom("div", { id: "sb-cover" });
var closeElement = makeDom("div", { id: "sb-close" });
var nextElement = makeDom("div", { id: "sb-next" });
var previousElement = makeDom("div", { id: "sb-prev" });
// Append #shadowbox to the DOM.
makeDom(document.body, [
makeDom(containerElement, [
overlayElement,
makeDom(wrapperElement, [
makeDom(bodyElement, [ contentElement, coverElement ]),
closeElement,
nextElement,
previousElement
])
])
]);
// Use an absolutely positioned container in browsers that don't
// support fixed positioning.
if (!supportsFixed)
setStyle(containerElement, "position", "absolute");
// Setup a click listener on the overlay to close Shadowbox.
addEvent(overlayElement, "click", shadowbox.close);
// Setup callbacks on navigation elements.
addEvent(closeElement, "click", cancel(shadowbox.close));
addEvent(nextElement, "click", cancel(shadowbox.showNext));
addEvent(previousElement, "click", cancel(shadowbox.showPrevious));
}
var currentIndex = -1,
currentGallery,
currentPlayer,
currentOptions;
/**
* Opens an object (or an array of objects) in Shadowbox. Takes options as
* the second argument.
*
* shadowbox("myphoto.jpg");
* shadowbox([ "myphoto1.jpg", "myphoto2.jpg" ]);
* shadowbox([ "myphoto1.jpg", "myphoto2.jpg" ], {
* animate: false,
* overlayColor: "white",
* overlayOpacity: 0.8
* });
*
* Options may be any of shadowbox.options. Returns the number of objects
* that were able to be opened.
*/
function shadowbox(objects, options) {
if (typeof options === 'number')
options = { startIndex: options };
if (!isArray(objects))
objects = [ objects ];
currentOptions = mergeProperties({}, shadowbox.options);
if (options)
mergeProperties(currentOptions, options);
currentGallery = [];
// Normalize into player objects and append them to the gallery.
var startIndex = currentOptions.startIndex;
forEach(objects, function (object, index) {
var player = shadowbox.makePlayer(object);
if (player) {
currentGallery.push(player);
} else {
if (index < startIndex) {
startIndex -= 1;
} else if (index === startIndex) {
startIndex = 0;
}
}
});
// Display the first item in the gallery, if there's anything left.
if (currentGallery.length > 0) {
if (currentIndex == -1) {
initialize();
if (isFunction(currentOptions.onOpen))
currentOptions.onOpen();
setStyle(containerElement, "display", "block");
setContainerPosition();
setContainerSize();
toggleTroubleElements(0);
setStyle(overlayElement, "backgroundColor", currentOptions.overlayColor);
setStyle(overlayElement, "opacity", 0);
setStyle(containerElement, "visibility", "visible");
animateStyle(overlayElement, "opacity", currentOptions.overlayOpacity, 0.35, function () {
setWrapperSize({ width: 340, height: 200 });
setStyle(wrapperElement, "visibility", "visible");
shadowbox.show(startIndex);
});
} else {
shadowbox.show(startIndex);
}
}
return currentGallery.length;
}
// Alias.
shadowbox.open = shadowbox;
/**
* Displays the gallery item at the given index in Shadowbox. Assumes that
* Shadowbox is already initialized and open.
*/
shadowbox.show = function (index) {
// Guard against invalid indices and no-ops.
if (index < 0 || !currentGallery[index] || currentIndex === index)
return;
toggleControls(0);
toggleWindowHandlers(0);
toggleMouseMoveHandler(0);
toggleKeyDownHandler(0);
setStyle(coverElement, "display", "block");
setStyle(coverElement, "opacity", 1);
if (currentPlayer)
currentPlayer.remove();
currentIndex = index;
currentPlayer = currentGallery[currentIndex];
function playerIsReady() {
return !currentPlayer || currentPlayer.isReady !== false;
}
waitUntil(playerIsReady, function () {
if (!currentPlayer)
return; // Shadowbox was closed.
if (isFunction(currentOptions.onShow))
currentOptions.onShow(currentPlayer);
var size = getWrapperSize();
var fromWidth = parseInt(getStyle(wrapperElement, "width")) || 0,
fromHeight = parseInt(getStyle(wrapperElement, "height")) || 0,
toWidth = size.width,
toHeight = size.height,
changeWidth = toWidth - fromWidth,
changeHeight = toHeight - fromHeight;
function frameHandler(value) {
if (!currentPlayer)
return false; // Shadowbox was closed, cancel the animation.
setWrapperSize({
width: fromWidth + (changeWidth * value),
height: fromHeight + (changeHeight * value)
});
}
// Open to the correct dimensions. Use the low-level animation
// primitive to make this transition as smooth as possible.
animate(0, 1, 0.5, frameHandler, function () {
if (currentPlayer) {
currentPlayer.injectInto(contentElement);
if (currentPlayer.fadeCover) {
animateStyle(coverElement, "opacity", 0, 0.5, finishShow);
} else {
finishShow();
}
}
});
});
};
function finishShow() {
if (currentPlayer) {
setStyle(coverElement, "display", "none");
toggleWindowHandlers(1);
toggleMouseMoveHandler(1);
toggleKeyDownHandler(1);
if (isFunction(currentOptions.onDone))
currentOptions.onDone(currentPlayer);
}
}
/**
* Opens the previous item in the gallery.
*/
shadowbox.showPrevious = function () {
shadowbox.show(getPreviousIndex());
};
/**
* Gets the index of the previous item in the gallery, -1 if there is none.
*/
function getPreviousIndex() {
if (currentIndex === 0)
return currentOptions.continuous ? (currentGallery.length - 1) : -1;
return currentIndex - 1;
}
/**
* Opens the next item in the gallery.
*/
shadowbox.showNext = function () {
shadowbox.show(getNextIndex());
};
/**
* Gets the index of the next item in the gallery, -1 if there is none.
*/
function getNextIndex() {
if (currentIndex === currentGallery.length - 1)
return (currentOptions.continuous && currentIndex !== 0) ? 0 : -1;
return currentIndex + 1;
}
/**
* Closes Shadowbox immediately.
*/
shadowbox.close = function () {
if (shadowbox.isOpen()) {
currentIndex = -1;
currentPlayer = null;
setStyle(wrapperElement, "visibility", "hidden");
setStyle(coverElement, "opacity", 1);
contentElement.innerHTML = "";
toggleControls(0);
toggleWindowHandlers(0);
toggleMouseMoveHandler(0);
toggleKeyDownHandler(0);
animateStyle(overlayElement, "opacity", 0, 0.5, function () {
setStyle(containerElement, "visibility", "hidden");
setStyle(containerElement, "display", "none");
toggleTroubleElements(1);
if (isFunction(currentOptions.onClose))
currentOptions.onClose();
});
}
};
/**
* Returns true if Shadowbox is currently open.
*/
shadowbox.isOpen = function () {
return currentIndex !== -1;
};
/**
* Gets the current player instance.
*/
shadowbox.getPlayer = function () {
return currentPlayer;
};
/**
* Gets the size that should be used for the wrapper element. Should be
* called when Shadowbox is open and has a player that is ready.
*/
function getWrapperSize() {
var margin = Math.max(currentOptions.margin, 20); // Minimum 20px margin.
return constrainSize(currentPlayer.width, currentPlayer.height,
overlayElement.offsetWidth, overlayElement.offsetHeight, margin);
}
/**
* Sets the size and position of the wrapper.
*/
function setWrapperSize(size) {
setStyle(wrapperElement, "width", size.width + "px");
setStyle(wrapperElement, "marginLeft", (-size.width / 2) + "px");
setStyle(wrapperElement, "height", size.height + "px");
setStyle(wrapperElement, "marginTop", (-size.height / 2) + "px");
}
/**
* Scales the given width and height to be within the bounds of the given
* maximum width and height, allowing for margin. Returns an array of the
* constrained [width, height].
*/
function constrainSize(width, height, maxWidth, maxHeight, margin) {
var originalWidth = width, originalHeight = height;
// Constrain width/height to max.
var marginWidth = 2 * margin;
if (width + marginWidth > maxWidth)
width = maxWidth - marginWidth;
var marginHeight = 2 * margin;
if (height + marginHeight > maxHeight)
height = maxHeight - marginHeight;
var changeWidth = (originalWidth - width) / originalWidth;
var changeHeight = (originalHeight - height) / originalHeight;
// Adjust width/height if oversized.
if (changeWidth > 0 || changeHeight > 0) {
// Preserve original aspect ratio according to greatest change.
if (changeWidth > changeHeight) {
height = Math.round((originalHeight / originalWidth) * width);
} else if (changeHeight > changeWidth) {
width = Math.round((originalWidth / originalHeight) * height);
}
}
return { width: width, height: height };
}
/**
* Sets the size of the container element to the size of the window.
*/
function setContainerSize() {
setStyle(containerElement, "width", documentElement.clientWidth + "px");
setStyle(containerElement, "height", documentElement.clientHeight + "px");
if (currentPlayer)
setWrapperSize(getWrapperSize());
}
/**
* Sets the position of the container element to the top left corner of
* the window. Necessary when using absolute positioning instead of fixed.
*/
function setContainerPosition() {
setStyle(containerElement, "left", documentElement.scrollLeft + "px");
setStyle(containerElement, "top", documentElement.scrollTop + "px");
}
var troubleElementTagNames = [ "select", "object", "embed", "canvas" ];
var troubleVisibilityCache = [];
/**
* Toggles the visibility of elements that are troublesome for overlays.
*/
function toggleTroubleElements(on) {
if (on) {
forEach(troubleVisibilityCache, function (item) {
setStyle(item.element, "visibility", item.visibility || "");
});
} else {
troubleVisibilityCache = [];
forEach(troubleElementTagNames, function (tagName) {
forEach(document.getElementsByTagName(tagName), function (element) {
troubleVisibilityCache.push({
element: element,
visibility: getStyle(element, "visibility")
});
setStyle(element, "visibility", "hidden");
});
});
}
}
/**
* Creates a new player object based on the properties of the given object.
* Valid properties include:
*
* - url The URL of the content to display
* - width (optional) The width of the content
* - height (optional) The height of the content
* - playerClass (optional) The player class to use to play the content.
* Can be guessed in most cases from the URL
* - encodings (video only) Encoding name/URL pairs of alternate URL's
* for the video. Possible encoding names are "h264", "ogg"
* "webm", and "flv"
* - posterUrl (video only) The URL to a poster image of the video
* - flashParams (flash only) Name/value pairs of 's to use for
* the Flash