2973 lines
73 KiB
JavaScript
2973 lines
73 KiB
JavaScript
/*!
|
|
* Shadowbox.js, version 3.0.3
|
|
* http://shadowbox-js.com/
|
|
*
|
|
* Copyright 2007-2010, Michael J. I. Jackson
|
|
* Date: 2026-01-26 15:30:20.2317822 +1100
|
|
*/
|
|
(function(window, undefined) {
|
|
/**
|
|
* The Shadowbox object.
|
|
*
|
|
* @type {Object}
|
|
* @public
|
|
*/
|
|
var S = {
|
|
|
|
/**
|
|
* The current version of Shadowbox.
|
|
*
|
|
* @type {String}
|
|
* @public
|
|
*/
|
|
version: "3.0.3"
|
|
|
|
}
|
|
|
|
var ua = navigator.userAgent.toLowerCase();
|
|
|
|
// operating system detection
|
|
if (ua.indexOf('windows') > -1 || ua.indexOf('win32') > -1) {
|
|
S.isWindows = true;
|
|
} else if (ua.indexOf('macintosh') > -1 || ua.indexOf('mac os x') > -1) {
|
|
S.isMac = true;
|
|
} else if (ua.indexOf('linux') > -1) {
|
|
S.isLinux = true;
|
|
}
|
|
|
|
// browser detection -- deprecated. the goal is to use object detection
|
|
// instead of the user agent string
|
|
S.isIE = ua.indexOf('msie') > -1;
|
|
S.isIE6 = ua.indexOf('msie 6') > -1;
|
|
S.isIE7 = ua.indexOf('msie 7') > -1;
|
|
S.isGecko = ua.indexOf('gecko') > -1 && ua.indexOf('safari') == -1;
|
|
S.isWebKit = ua.indexOf('applewebkit/') > -1;
|
|
|
|
var inlineId = /#(.+)$/,
|
|
galleryName = /^(light|shadow)box\[(.*?)\]/i,
|
|
inlineParam = /\s*([a-z_]*?)\s*=\s*(.+)\s*/,
|
|
fileExtension = /[0-9a-z]+$/i,
|
|
scriptPath = /(.+\/)shadowbox\.js/i;
|
|
|
|
/**
|
|
* True if Shadowbox is currently open, false otherwise.
|
|
*
|
|
* @type {Boolean}
|
|
* @private
|
|
*/
|
|
var open = false,
|
|
|
|
/**
|
|
* True if Shadowbox has been initialized, false otherwise.
|
|
*
|
|
* @type {Boolean}
|
|
* @private
|
|
*/
|
|
initialized = false,
|
|
|
|
/**
|
|
* The previous set of options that were used before Shadowbox.applyOptions was
|
|
* called.
|
|
*
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
lastOptions = {},
|
|
|
|
/**
|
|
* The delay in milliseconds that the current gallery uses.
|
|
*
|
|
* @type {Number}
|
|
* @private
|
|
*/
|
|
slideDelay = 0,
|
|
|
|
/**
|
|
* The time at which the current slideshow frame appeared.
|
|
*
|
|
* @type {Number}
|
|
* @private
|
|
*/
|
|
slideStart,
|
|
|
|
/**
|
|
* The timeout id for the slideshow transition function.
|
|
*
|
|
* @type {Number}
|
|
* @private
|
|
*/
|
|
slideTimer;
|
|
|
|
/**
|
|
* The index of the current object in the gallery array.
|
|
*
|
|
* @type {Number}
|
|
* @public
|
|
*/
|
|
S.current = -1;
|
|
|
|
/**
|
|
* The current dimensions of Shadowbox.
|
|
*
|
|
* @type {Object}
|
|
* @public
|
|
*/
|
|
S.dimensions = null;
|
|
|
|
/**
|
|
* Easing function used for animations. Based on a cubic polynomial.
|
|
*
|
|
* @param {Number} state The state of the animation (% complete)
|
|
* @return {Number} The adjusted easing value
|
|
* @public
|
|
*/
|
|
S.ease = function(state) {
|
|
return 1 + Math.pow(state - 1, 3);
|
|
}
|
|
|
|
/**
|
|
* An object containing names of plugins and links to their respective download pages.
|
|
*
|
|
* @type {Object}
|
|
* @public
|
|
*/
|
|
S.errorInfo = {
|
|
fla: {
|
|
name: "Flash",
|
|
url: "http://www.adobe.com/products/flashplayer/"
|
|
},
|
|
qt: {
|
|
name: "QuickTime",
|
|
url: "http://www.apple.com/quicktime/download/"
|
|
},
|
|
wmp: {
|
|
name: "Windows Media Player",
|
|
url: "http://www.microsoft.com/windows/windowsmedia/"
|
|
},
|
|
f4m: {
|
|
name: "Flip4Mac",
|
|
url: "http://www.flip4mac.com/wmv_download.htm"
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The content objects in the current set.
|
|
*
|
|
* @type {Array}
|
|
* @public
|
|
*/
|
|
S.gallery = [];
|
|
|
|
/**
|
|
* A function that will be called as soon as the DOM is ready.
|
|
*
|
|
* @type {Function}
|
|
* @public
|
|
*/
|
|
S.onReady = noop;
|
|
|
|
/**
|
|
* The URL path to the Shadowbox script.
|
|
*
|
|
* @type {String}
|
|
* @public
|
|
*/
|
|
S.path = null;
|
|
|
|
/**
|
|
* The current player object.
|
|
*
|
|
* @type {Object}
|
|
* @public
|
|
*/
|
|
S.player = null;
|
|
|
|
/**
|
|
* The id to use for the Shadowbox player element.
|
|
*
|
|
* @type {String}
|
|
* @public
|
|
*/
|
|
S.playerId = "sb-player";
|
|
|
|
/**
|
|
* Various options that control Shadowbox' behavior.
|
|
*
|
|
* @type {Object}
|
|
* @public
|
|
*/
|
|
S.options = {
|
|
|
|
/**
|
|
* True to enable animations.
|
|
*
|
|
* @type {Boolean}
|
|
*/
|
|
animate: true,
|
|
|
|
/**
|
|
* True to enable opacity animations.
|
|
*
|
|
* @type {Boolean}
|
|
*/
|
|
animateFade: true,
|
|
|
|
/**
|
|
* True to automatically play movies when the load.
|
|
*
|
|
* @type {Boolean}
|
|
*/
|
|
autoplayMovies: true,
|
|
|
|
/**
|
|
* True to enable the user to skip to the first item in a gallery from the last using
|
|
* next.
|
|
*
|
|
* @type {Boolean}
|
|
*/
|
|
continuous: false,
|
|
|
|
/**
|
|
* True to enable keyboard navigation.
|
|
*
|
|
* @type {Boolean}
|
|
*/
|
|
enableKeys: true,
|
|
|
|
/**
|
|
* Parameters to pass to flash <object>'s.
|
|
*
|
|
* @type {Object}
|
|
*/
|
|
flashParams: {
|
|
bgcolor: "#000000",
|
|
allowfullscreen: true
|
|
},
|
|
|
|
/**
|
|
* Variables to pass to flash <object>'s.
|
|
*
|
|
* @type {Object}
|
|
*/
|
|
flashVars: {},
|
|
|
|
/**
|
|
* The minimum required Flash version.
|
|
*
|
|
* Note: The default is 9.0.115. This is the minimum version suggested by
|
|
* the JW FLV player.
|
|
*
|
|
* @type {String}
|
|
*/
|
|
flashVersion: "9.0.115",
|
|
|
|
/**
|
|
* Determines how oversized content is handled. If set to "resize" the
|
|
* content will be resized while preserving aspect ratio. If "drag" will display
|
|
* the image at its original resolution but it will be draggable. If "none" will
|
|
* display the content at its original resolution but it may be cropped.
|
|
*
|
|
* @type {String}
|
|
*/
|
|
handleOversize: "resize",
|
|
|
|
/**
|
|
* Determines how unsupported content is handled. If set to "remove" will
|
|
* remove the content from the gallery. If "link" will display a helpful
|
|
* link to a page where the necessary browser plugin can be installed.
|
|
*
|
|
* @type {String}
|
|
*/
|
|
handleUnsupported: "link",
|
|
|
|
/**
|
|
* A hook function to be fired when changing from one gallery item to the
|
|
* next. Is passed the item that is about to be displayed as its only argument.
|
|
*
|
|
* @type {Function}
|
|
*/
|
|
onChange: noop,
|
|
|
|
/**
|
|
* A hook function to be fired when closing. Is passed the most recent item
|
|
* as its only argument.
|
|
*
|
|
* @type {Function}
|
|
*/
|
|
onClose: noop,
|
|
|
|
/**
|
|
* A hook funciton to be fires when content is finished loading. Is passed the
|
|
* current gallery item as its only argument.
|
|
*
|
|
* @type {Function}
|
|
*/
|
|
onFinish: noop,
|
|
|
|
/**
|
|
* A hook function to be fired when opening. Is passed the current gallery item
|
|
* as its only argument.
|
|
*
|
|
* @type {Function}
|
|
*/
|
|
onOpen: noop,
|
|
|
|
/**
|
|
* True to enable movie controls on movie players.
|
|
*
|
|
* @type {Boolean}
|
|
*/
|
|
showMovieControls: true,
|
|
|
|
/**
|
|
* True to skip calling setup during init.
|
|
*
|
|
* @type {Boolean}
|
|
*/
|
|
skipSetup: false,
|
|
|
|
/**
|
|
* The delay (in seconds) to use when displaying a gallery in slideshow mode. Setting
|
|
* this option to any value other than 0 will trigger slideshow mode.
|
|
*
|
|
* @type {Number}
|
|
*/
|
|
slideshowDelay: 0,
|
|
|
|
/**
|
|
* The ammount of padding (in pixels) to maintain around the edge of the viewport at all
|
|
* times.
|
|
*
|
|
* @type {Number}
|
|
*/
|
|
viewportPadding: 20
|
|
|
|
};
|
|
|
|
/**
|
|
* Gets the object that is currently being displayed.
|
|
*
|
|
* @return {Object}
|
|
* @public
|
|
*/
|
|
S.getCurrent = function() {
|
|
return S.current > -1 ? S.gallery[S.current] : null;
|
|
}
|
|
|
|
/**
|
|
* Returns true if there is another object to display after the current.
|
|
*
|
|
* @return {Boolean}
|
|
* @public
|
|
*/
|
|
S.hasNext = function() {
|
|
return S.gallery.length > 1 && (S.current != S.gallery.length - 1 || S.options.continuous);
|
|
}
|
|
|
|
/**
|
|
* Returns true if Shadowbox is currently open.
|
|
*
|
|
* @return {Boolean}
|
|
* @public
|
|
*/
|
|
S.isOpen = function() {
|
|
return open;
|
|
}
|
|
|
|
/**
|
|
* Returns true if Shadowbox is currently paused.
|
|
*
|
|
* @return {Boolean}
|
|
* @public
|
|
*/
|
|
S.isPaused = function() {
|
|
return slideTimer == "pause";
|
|
}
|
|
|
|
/**
|
|
* Applies the given set of options to Shadowbox' options. May be undone with revertOptions().
|
|
*
|
|
* @param {Object} options
|
|
* @public
|
|
*/
|
|
S.applyOptions = function(options) {
|
|
lastOptions = apply({}, S.options);
|
|
apply(S.options, options);
|
|
}
|
|
|
|
/**
|
|
* Reverts to whatever the options were before applyOptions() was called.
|
|
*
|
|
* @public
|
|
*/
|
|
S.revertOptions = function() {
|
|
apply(S.options, lastOptions);
|
|
}
|
|
|
|
/**
|
|
* Initializes the Shadowbox environment. If options are given here, they
|
|
* will override the defaults. A callback may be provided that will be called
|
|
* when the document is ready. This function can be used for setting up links
|
|
* using Shadowbox.setup.
|
|
*
|
|
* @param {Object} options
|
|
* @param {Function} callback
|
|
* @public
|
|
*/
|
|
S.init = function(options, callback) {
|
|
if (initialized)
|
|
return;
|
|
|
|
initialized = true;
|
|
|
|
if (S.skin.options)
|
|
apply(S.options, S.skin.options);
|
|
|
|
if (options)
|
|
apply(S.options, options);
|
|
|
|
if (!S.path) {
|
|
// determine script path automatically
|
|
var path, scripts = document.getElementsByTagName("script");
|
|
for (var i = 0, len = scripts.length; i < len; ++i) {
|
|
path = scriptPath.exec(scripts[i].src);
|
|
if (path) {
|
|
S.path = path[1];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (callback)
|
|
S.onReady = callback;
|
|
|
|
bindLoad();
|
|
}
|
|
|
|
/**
|
|
* Opens the given object in Shadowbox. This object may be any of the following:
|
|
*
|
|
* - A URL specifying the location of some content to display
|
|
* - An HTML link object (A or AREA tag) that links to some content
|
|
* - A custom object similar to one produced by Shadowbox.makeObject
|
|
* - An array of any of the above
|
|
*
|
|
* Note: When a single link object is given, Shadowbox will automatically search
|
|
* for other cached link objects that have been set up in the same gallery and
|
|
* display them all together.
|
|
*
|
|
* @param {mixed} obj
|
|
* @public
|
|
*/
|
|
S.open = function(obj) {
|
|
if (open)
|
|
return;
|
|
|
|
var gc = S.makeGallery(obj);
|
|
S.gallery = gc[0];
|
|
S.current = gc[1];
|
|
|
|
obj = S.getCurrent();
|
|
|
|
if (obj == null)
|
|
return;
|
|
|
|
S.applyOptions(obj.options || {});
|
|
|
|
filterGallery();
|
|
|
|
// anything left to display?
|
|
if (S.gallery.length) {
|
|
obj = S.getCurrent();
|
|
|
|
if (S.options.onOpen(obj) === false)
|
|
return;
|
|
|
|
open = true;
|
|
|
|
S.skin.onOpen(obj, load);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Closes Shadowbox.
|
|
*
|
|
* @public
|
|
*/
|
|
S.close = function() {
|
|
if (!open)
|
|
return;
|
|
|
|
open = false;
|
|
|
|
if (S.player) {
|
|
S.player.remove();
|
|
S.player = null;
|
|
}
|
|
|
|
if (typeof slideTimer == "number") {
|
|
clearTimeout(slideTimer);
|
|
slideTimer = null;
|
|
}
|
|
slideDelay = 0;
|
|
|
|
listenKeys(false);
|
|
|
|
S.options.onClose(S.getCurrent());
|
|
|
|
S.skin.onClose();
|
|
|
|
S.revertOptions();
|
|
}
|
|
|
|
/**
|
|
* Starts a slideshow when a gallery is being displayed. Is called automatically
|
|
* when the slideshowDelay option is set to anything other than 0.
|
|
*
|
|
* @public
|
|
*/
|
|
S.play = function() {
|
|
if (!S.hasNext())
|
|
return;
|
|
|
|
if (!slideDelay)
|
|
slideDelay = S.options.slideshowDelay * 1000;
|
|
|
|
if (slideDelay) {
|
|
slideStart = now();
|
|
slideTimer = setTimeout(function(){
|
|
slideDelay = slideStart = 0; // reset slideshow
|
|
S.next();
|
|
}, slideDelay);
|
|
|
|
if(S.skin.onPlay)
|
|
S.skin.onPlay();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pauses a slideshow on the current object.
|
|
*
|
|
* @public
|
|
*/
|
|
S.pause = function() {
|
|
if (typeof slideTimer != "number")
|
|
return;
|
|
|
|
slideDelay = Math.max(0, slideDelay - (now() - slideStart));
|
|
|
|
// if there's any time left on current slide, pause the timer
|
|
if (slideDelay) {
|
|
clearTimeout(slideTimer);
|
|
slideTimer = "pause";
|
|
|
|
if(S.skin.onPause)
|
|
S.skin.onPause();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Changes Shadowbox to display the item in the gallery specified by index.
|
|
*
|
|
* @param {Number} index
|
|
* @public
|
|
*/
|
|
S.change = function(index) {
|
|
if (!(index in S.gallery)) {
|
|
if (S.options.continuous) {
|
|
index = (index < 0 ? S.gallery.length + index : 0); // loop
|
|
if (!(index in S.gallery))
|
|
return;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
S.current = index;
|
|
|
|
if (typeof slideTimer == "number") {
|
|
clearTimeout(slideTimer);
|
|
slideTimer = null;
|
|
slideDelay = slideStart = 0;
|
|
}
|
|
|
|
S.options.onChange(S.getCurrent());
|
|
|
|
load(true);
|
|
}
|
|
|
|
/**
|
|
* Advances to the next item in the gallery.
|
|
*
|
|
* @public
|
|
*/
|
|
S.next = function() {
|
|
S.change(S.current + 1);
|
|
}
|
|
|
|
/**
|
|
* Rewinds to the previous gallery item.
|
|
*
|
|
* @public
|
|
*/
|
|
S.previous = function() {
|
|
S.change(S.current - 1);
|
|
}
|
|
|
|
/**
|
|
* Calculates the dimensions for Shadowbox.
|
|
*
|
|
* @param {Number} height The height of the object
|
|
* @param {Number} width The width of the object
|
|
* @param {Number} maxHeight The maximum available height
|
|
* @param {Number} maxWidth The maximum available width
|
|
* @param {Number} topBottom The extra top/bottom required for borders/toolbars
|
|
* @param {Number} leftRight The extra left/right required for borders/toolbars
|
|
* @param {Number} padding The amount of padding (in pixels) to maintain around
|
|
* the edge of the viewport
|
|
* @param {Boolean} preserveAspect True to preserve the original aspect ratio when the
|
|
* given dimensions are too large
|
|
* @return {Object} The new dimensions object
|
|
* @public
|
|
*/
|
|
S.setDimensions = function(height, width, maxHeight, maxWidth, topBottom, leftRight, padding, preserveAspect) {
|
|
var originalHeight = height,
|
|
originalWidth = width;
|
|
|
|
// constrain height/width to max
|
|
var extraHeight = 2 * padding + topBottom;
|
|
if (height + extraHeight > maxHeight)
|
|
height = maxHeight - extraHeight;
|
|
var extraWidth = 2 * padding + leftRight;
|
|
if (width + extraWidth > maxWidth)
|
|
width = maxWidth - extraWidth;
|
|
|
|
// determine if object is oversized
|
|
var changeHeight = (originalHeight - height) / originalHeight,
|
|
changeWidth = (originalWidth - width) / originalWidth,
|
|
oversized = (changeHeight > 0 || changeWidth > 0);
|
|
|
|
// adjust height/width if too large
|
|
if (preserveAspect && oversized) {
|
|
// preserve aspect ratio according to greatest change
|
|
if (changeHeight > changeWidth) {
|
|
width = Math.round((originalWidth / originalHeight) * height);
|
|
} else if (changeWidth > changeHeight) {
|
|
height = Math.round((originalHeight / originalWidth) * width);
|
|
}
|
|
}
|
|
|
|
S.dimensions = {
|
|
height: height + topBottom,
|
|
width: width + leftRight,
|
|
innerHeight: height,
|
|
innerWidth: width,
|
|
top: Math.floor((maxHeight - (height + extraHeight)) / 2 + padding),
|
|
left: Math.floor((maxWidth - (width + extraWidth)) / 2 + padding),
|
|
oversized: oversized
|
|
};
|
|
|
|
return S.dimensions;
|
|
}
|
|
|
|
/**
|
|
* Returns an array with two elements. The first is an array of objects that
|
|
* constitutes the gallery, and the second is the index of the given object in
|
|
* that array.
|
|
*
|
|
* @param {mixed} obj
|
|
* @return {Array} An array containing the gallery and current index
|
|
* @public
|
|
*/
|
|
S.makeGallery = function(obj) {
|
|
var gallery = [], current = -1;
|
|
|
|
if (typeof obj == "string")
|
|
obj = [obj];
|
|
|
|
if (typeof obj.length == "number") {
|
|
each(obj, function(i, o) {
|
|
if (o.content) {
|
|
gallery[i] = o;
|
|
} else {
|
|
gallery[i] = {content: o};
|
|
}
|
|
});
|
|
current = 0;
|
|
} else {
|
|
if (obj.tagName) {
|
|
// check the cache for this object before building one on the fly
|
|
var cacheObj = S.getCache(obj);
|
|
obj = cacheObj ? cacheObj : S.makeObject(obj);
|
|
}
|
|
|
|
if (obj.gallery) {
|
|
// gallery object, build gallery from cached gallery objects
|
|
gallery = [];
|
|
|
|
var o;
|
|
for (var key in S.cache) {
|
|
o = S.cache[key];
|
|
if (o.gallery && o.gallery == obj.gallery) {
|
|
if (current == -1 && o.content == obj.content)
|
|
current = gallery.length;
|
|
gallery.push(o);
|
|
}
|
|
}
|
|
|
|
if (current == -1) {
|
|
gallery.unshift(obj);
|
|
current = 0;
|
|
}
|
|
} else {
|
|
// single object, no gallery
|
|
gallery = [obj];
|
|
current = 0;
|
|
}
|
|
}
|
|
|
|
// use apply to break references to each gallery object here because
|
|
// the code may modify certain properties of these objects from here
|
|
// on out and we want to preserve the original in case the same object
|
|
// is used again in a future call
|
|
each(gallery, function(i, o) {
|
|
gallery[i] = apply({}, o);
|
|
});
|
|
|
|
return [gallery, current];
|
|
}
|
|
|
|
/**
|
|
* Extracts parameters from a link element and returns an object containing
|
|
* (most of) the following keys:
|
|
*
|
|
* - content: The URL of the linked to content
|
|
* - player: The abbreviated name of the player to use for the object (can automatically
|
|
* be determined in most cases)
|
|
* - title: The title to use for the object (optional)
|
|
* - gallery: The name of the gallery the object belongs to (optional)
|
|
* - height: The height of the object (in pixels, only required for movies and Flash)
|
|
* - width: The width of the object (in pixels, only required for movies and Flash)
|
|
* - options: A set of options to use for this object (optional)
|
|
* - link: A reference to the original link element
|
|
*
|
|
* A custom set of options may be passed in here that will be applied when
|
|
* this object is displayed. However, any options that are specified in
|
|
* the link's HTML markup will trump options given here.
|
|
*
|
|
* @param {HTMLElement} link
|
|
* @param {Object} options
|
|
* @return {Object} An object representing the link
|
|
* @public
|
|
*/
|
|
S.makeObject = function(link, options) {
|
|
var obj = {
|
|
// accessing the href attribute directly here (instead of using
|
|
// getAttribute) should give a full URL instead of a relative one
|
|
content: link.href,
|
|
title: link.getAttribute("title") || "",
|
|
link: link
|
|
};
|
|
|
|
// remove link-level options from top-level options
|
|
if (options) {
|
|
options = apply({}, options);
|
|
each(["player", "title", "height", "width", "gallery"], function(i, o) {
|
|
if (typeof options[o] != "undefined") {
|
|
obj[o] = options[o];
|
|
delete options[o];
|
|
}
|
|
});
|
|
obj.options = options;
|
|
} else {
|
|
obj.options = {};
|
|
}
|
|
|
|
if (!obj.player)
|
|
obj.player = S.getPlayer(obj.content);
|
|
|
|
// HTML options always trump JavaScript options, so do these last
|
|
var rel = link.getAttribute("rel");
|
|
if (rel) {
|
|
// extract gallery name from shadowbox[name] format
|
|
var match = rel.match(galleryName);
|
|
if (match)
|
|
obj.gallery = escape(match[2]);
|
|
|
|
// extract any other parameters
|
|
each(rel.split(';'), function(i, p) {
|
|
match = p.match(inlineParam);
|
|
if (match)
|
|
obj[match[1]] = match[2];
|
|
});
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
/**
|
|
* Attempts to automatically determine the correct player to use for an object based
|
|
* on its content attribute. Defaults to "iframe" when the content type cannot
|
|
* automatically be determined.
|
|
*
|
|
* @param {String} content The content attribute of the object
|
|
* @return {String} The name of the player to use
|
|
* @public
|
|
*/
|
|
S.getPlayer = function(content) {
|
|
if (content.indexOf("#") > -1 && content.indexOf(document.location.href) == 0)
|
|
return "inline";
|
|
|
|
// strip query string for player detection purposes
|
|
var q = content.indexOf("?");
|
|
if (q > -1)
|
|
content = content.substring(0, q);
|
|
|
|
// get file extension
|
|
var ext, m = content.match(fileExtension);
|
|
if (m)
|
|
ext = m[0].toLowerCase();
|
|
|
|
if (ext) {
|
|
if (S.img && S.img.ext.indexOf(ext) > -1)
|
|
return "img";
|
|
if (S.swf && S.swf.ext.indexOf(ext) > -1)
|
|
return "swf";
|
|
if (S.flv && S.flv.ext.indexOf(ext) > -1)
|
|
return "flv";
|
|
if (S.qt && S.qt.ext.indexOf(ext) > -1) {
|
|
if (S.wmp && S.wmp.ext.indexOf(ext) > -1) {
|
|
return "qtwmp"; // can be played by either QuickTime or Windows Media Player
|
|
} else {
|
|
return "qt";
|
|
}
|
|
}
|
|
if (S.wmp && S.wmp.ext.indexOf(ext) > -1)
|
|
return "wmp";
|
|
}
|
|
|
|
return "iframe";
|
|
}
|
|
|
|
/**
|
|
* Filters the current gallery for unsupported objects.
|
|
*
|
|
* @private
|
|
*/
|
|
function filterGallery() {
|
|
var err = S.errorInfo, plugins = S.plugins, obj, remove, needed,
|
|
m, format, replace, inlineEl, flashVersion;
|
|
|
|
for (var i = 0; i < S.gallery.length; ++i) {
|
|
obj = S.gallery[i]
|
|
|
|
remove = false; // remove the object?
|
|
needed = null; // what plugins are needed?
|
|
|
|
switch (obj.player) {
|
|
case "flv":
|
|
case "swf":
|
|
if (!plugins.fla)
|
|
needed = "fla";
|
|
break;
|
|
case "qt":
|
|
if (!plugins.qt)
|
|
needed = "qt";
|
|
break;
|
|
case "wmp":
|
|
if (S.isMac) {
|
|
if (plugins.qt && plugins.f4m) {
|
|
obj.player = "qt";
|
|
} else {
|
|
needed = "qtf4m";
|
|
}
|
|
} else if (!plugins.wmp) {
|
|
needed = "wmp";
|
|
}
|
|
break;
|
|
case "qtwmp":
|
|
if (plugins.qt) {
|
|
obj.player = "qt";
|
|
} else if (plugins.wmp) {
|
|
obj.player = "wmp";
|
|
} else {
|
|
needed = "qtwmp";
|
|
}
|
|
break;
|
|
}
|
|
|
|
// handle unsupported elements
|
|
if (needed) {
|
|
if (S.options.handleUnsupported == "link") {
|
|
// generate a link to the appropriate plugin download page(s)
|
|
switch (needed) {
|
|
case "qtf4m":
|
|
format = "shared";
|
|
replace = [err.qt.url, err.qt.name, err.f4m.url, err.f4m.name];
|
|
break;
|
|
case "qtwmp":
|
|
format = "either";
|
|
replace = [err.qt.url, err.qt.name, err.wmp.url, err.wmp.name];
|
|
break;
|
|
default:
|
|
format = "single";
|
|
replace = [err[needed].url, err[needed].name];
|
|
}
|
|
|
|
obj.player = "html";
|
|
obj.content = '<div class="sb-message">' + sprintf(S.lang.errors[format], replace) + '</div>';
|
|
} else {
|
|
remove = true;
|
|
}
|
|
} else if (obj.player == "inline") {
|
|
// inline element, retrieve innerHTML
|
|
m = inlineId.exec(obj.content);
|
|
if (m) {
|
|
inlineEl = get(m[1]);
|
|
if (inlineEl) {
|
|
obj.content = inlineEl.innerHTML;
|
|
} else {
|
|
// cannot find element with id
|
|
remove = true;
|
|
}
|
|
} else {
|
|
// cannot determine element id from content string
|
|
remove = true;
|
|
}
|
|
} else if (obj.player == "swf" || obj.player == "flv") {
|
|
flashVersion = (obj.options && obj.options.flashVersion) || S.options.flashVersion;
|
|
|
|
if (S.flash && !S.flash.hasFlashPlayerVersion(flashVersion)) {
|
|
// express install will be triggered because the client does not meet the
|
|
// minimum required version of Flash. set height and width to those of expressInstall.swf
|
|
obj.width = 310;
|
|
// minimum height is 127, but +20 pixels on top and bottom looks better
|
|
obj.height = 177;
|
|
}
|
|
}
|
|
|
|
if (remove) {
|
|
S.gallery.splice(i, 1);
|
|
|
|
if (i < S.current) {
|
|
--S.current; // maintain integrity of S.current
|
|
} else if (i == S.current) {
|
|
S.current = i > 0 ? i - 1 : i; // look for supported neighbor
|
|
}
|
|
|
|
// decrement index for next loop
|
|
--i;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets up a listener on the document for keydown events.
|
|
*
|
|
* @param {Boolean} on True to enable the listener, false to disable
|
|
* @private
|
|
*/
|
|
function listenKeys(on) {
|
|
if (!S.options.enableKeys)
|
|
return;
|
|
|
|
(on ? addEvent : removeEvent)(document, "keydown", handleKey);
|
|
}
|
|
|
|
/**
|
|
* A listener function that is fired when a key is pressed.
|
|
*
|
|
* @param {Event} e The keydown event
|
|
* @private
|
|
*/
|
|
function handleKey(e) {
|
|
// don't handle events with modifier keys
|
|
if (e.metaKey || e.shiftKey || e.altKey || e.ctrlKey)
|
|
return;
|
|
|
|
var code = keyCode(e), handler;
|
|
|
|
switch (code) {
|
|
case 81: // q
|
|
case 88: // x
|
|
case 27: // esc
|
|
handler = S.close;
|
|
break;
|
|
case 37: // left
|
|
handler = S.previous;
|
|
break;
|
|
case 39: // right
|
|
handler = S.next;
|
|
break;
|
|
case 32: // space
|
|
handler = typeof slideTimer == "number" ? S.pause : S.play;
|
|
break;
|
|
}
|
|
|
|
if (handler) {
|
|
preventDefault(e);
|
|
handler();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads the current object.
|
|
*
|
|
* @param {Boolean} True if changing from a previous object
|
|
* @private
|
|
*/
|
|
function load(changing) {
|
|
listenKeys(false);
|
|
|
|
var obj = S.getCurrent();
|
|
|
|
// determine player, inline is really just html
|
|
var player = (obj.player == "inline" ? "html" : obj.player);
|
|
|
|
if (typeof S[player] != "function")
|
|
throw "unknown player " + player;
|
|
|
|
if (changing) {
|
|
S.player.remove();
|
|
S.revertOptions();
|
|
S.applyOptions(obj.options || {});
|
|
}
|
|
|
|
S.player = new S[player](obj, S.playerId);
|
|
|
|
// preload neighboring gallery images
|
|
if (S.gallery.length > 1) {
|
|
var next = S.gallery[S.current + 1] || S.gallery[0];
|
|
if (next.player == "img") {
|
|
var a = new Image();
|
|
a.src = next.content;
|
|
}
|
|
var prev = S.gallery[S.current - 1] || S.gallery[S.gallery.length - 1];
|
|
if (prev.player == "img") {
|
|
var b = new Image();
|
|
b.src = prev.content;
|
|
}
|
|
}
|
|
|
|
S.skin.onLoad(changing, waitReady);
|
|
}
|
|
|
|
/**
|
|
* Waits until the current object is ready to be displayed.
|
|
*
|
|
* @private
|
|
*/
|
|
function waitReady() {
|
|
if (!open)
|
|
return;
|
|
|
|
if (typeof S.player.ready != "undefined") {
|
|
// wait for content to be ready before loading
|
|
var timer = setInterval(function() {
|
|
if (open) {
|
|
if (S.player.ready) {
|
|
clearInterval(timer);
|
|
timer = null;
|
|
S.skin.onReady(show);
|
|
}
|
|
} else {
|
|
clearInterval(timer);
|
|
timer = null;
|
|
}
|
|
}, 10);
|
|
} else {
|
|
S.skin.onReady(show);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Displays the current object.
|
|
*
|
|
* @private
|
|
*/
|
|
function show() {
|
|
if (!open)
|
|
return;
|
|
|
|
S.player.append(S.skin.body, S.dimensions);
|
|
|
|
S.skin.onShow(finish);
|
|
}
|
|
|
|
/**
|
|
* Finishes up any remaining tasks after the object is displayed.
|
|
*
|
|
* @private
|
|
*/
|
|
function finish() {
|
|
if (!open)
|
|
return;
|
|
|
|
if (S.player.onLoad)
|
|
S.player.onLoad();
|
|
|
|
S.options.onFinish(S.getCurrent());
|
|
|
|
if (!S.isPaused())
|
|
S.play(); // kick off next slide
|
|
|
|
listenKeys(true);
|
|
}
|
|
if (!Array.prototype.indexOf) {
|
|
Array.prototype.indexOf = function(obj, from) {
|
|
var len = this.length >>> 0;
|
|
|
|
from = from || 0;
|
|
if (from < 0)
|
|
from += len;
|
|
|
|
for (; from < len; ++from) {
|
|
if (from in this && this[from] === obj)
|
|
return from;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the current time in milliseconds.
|
|
*
|
|
* @return {Number}
|
|
* @private
|
|
*/
|
|
function now() {
|
|
return (new Date).getTime();
|
|
}
|
|
|
|
/**
|
|
* Applies all properties of extension to original.
|
|
*
|
|
* @param {Object} original
|
|
* @param {Object} extension
|
|
* @return {Object} The original object
|
|
* @private
|
|
*/
|
|
function apply(original, extension) {
|
|
for (var property in extension)
|
|
original[property] = extension[property];
|
|
return original;
|
|
}
|
|
|
|
/**
|
|
* Calls the given callback function for each element in obj. Note: obj must be an array-like
|
|
* object.
|
|
*
|
|
* @param {Array|mixed} obj
|
|
* @param {Function} callback
|
|
* @private
|
|
*/
|
|
function each(obj, callback) {
|
|
var i = 0, len = obj.length;
|
|
for (var value = obj[0]; i < len && callback.call(value, i, value) !== false; value = obj[++i]) {}
|
|
}
|
|
|
|
/**
|
|
* Formats a string with the elements in the replacement array. The string should contain
|
|
* tokens in the format {n} where n corresponds to the index of property name of the replacement
|
|
* in the replace object.
|
|
*
|
|
* Example:
|
|
*
|
|
* format('Hello {0}', ['World']); // "Hello World"
|
|
* format('Hello {world}', {world: "World"}); // "Hello World"
|
|
*
|
|
* @param {String} str The format spec string
|
|
* @param {Array|Object} replace The array/object of replacement values
|
|
* @return {String} The formatted string
|
|
* @private
|
|
*/
|
|
function sprintf(str, replace) {
|
|
return str.replace(/\{(\w+?)\}/g, function(match, i) {
|
|
return replace[i];
|
|
});
|
|
}
|
|
|
|
/**
|
|
* A no-op function.
|
|
*
|
|
* @private
|
|
*/
|
|
function noop() {}
|
|
|
|
/**
|
|
* Gets the element with the given id.
|
|
*
|
|
* @param {String} id
|
|
* @return {HTMLElement}
|
|
* @private
|
|
*/
|
|
function get(id) {
|
|
return document.getElementById(id);
|
|
}
|
|
|
|
/**
|
|
* Removes an element from the DOM.
|
|
*
|
|
* @param {HTMLElement} el The element to remove
|
|
* @private
|
|
*/
|
|
function remove(el) {
|
|
el.parentNode.removeChild(el);
|
|
}
|
|
|
|
/**
|
|
* True if this browser supports opacity.
|
|
*
|
|
* @type {Boolean}
|
|
* @private
|
|
*/
|
|
var supportsOpacity = true,
|
|
|
|
/**
|
|
* True if the browser supports fixed positioning.
|
|
*
|
|
* @type {Boolean}
|
|
* @private
|
|
*/
|
|
supportsFixed = true;
|
|
|
|
/**
|
|
* Checks the level of support the browser provides. Should be called when
|
|
* the DOM is ready to be manipulated.
|
|
*
|
|
* @private
|
|
*/
|
|
function checkSupport() {
|
|
var body = document.body,
|
|
div = document.createElement("div");
|
|
|
|
// detect opacity support
|
|
supportsOpacity = typeof div.style.opacity === "string";
|
|
|
|
// detect support for fixed positioning
|
|
div.style.position = "fixed";
|
|
div.style.margin = 0;
|
|
div.style.top = "20px";
|
|
body.appendChild(div, body.firstChild);
|
|
supportsFixed = div.offsetTop == 20;
|
|
body.removeChild(div);
|
|
}
|
|
|
|
/**
|
|
* Gets the computed value of the style on the given element.
|
|
*
|
|
* Note: This function is not safe for retrieving float values or non-pixel values
|
|
* in IE.
|
|
*
|
|
* @param {HTMLElement} el The element
|
|
* @param {String} style The camel-cased name of the style
|
|
* @return {mixed} The computed value of the given style
|
|
* @public
|
|
*/
|
|
S.getStyle = (function() {
|
|
var opacity = /opacity=([^)]*)/,
|
|
getComputedStyle = document.defaultView && document.defaultView.getComputedStyle;
|
|
|
|
return function(el, style) {
|
|
var ret;
|
|
|
|
if (!supportsOpacity && style == "opacity" && el.currentStyle) {
|
|
ret = opacity.test(el.currentStyle.filter || "") ? (parseFloat(RegExp.$1) / 100) + "" : "";
|
|
return ret === "" ? "1" : ret;
|
|
}
|
|
|
|
if (getComputedStyle) {
|
|
var computedStyle = getComputedStyle(el, null);
|
|
|
|
if (computedStyle)
|
|
ret = computedStyle[style];
|
|
|
|
if (style == "opacity" && ret == "")
|
|
ret = "1";
|
|
} else {
|
|
ret = el.currentStyle[style];
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
})();
|
|
|
|
/**
|
|
* Appends an HTML fragment to the given element.
|
|
*
|
|
* @param {HTMLElement} el
|
|
* @param {String} html The HTML fragment to use
|
|
* @public
|
|
*/
|
|
S.appendHTML = function(el, html) {
|
|
if (el.insertAdjacentHTML) {
|
|
el.insertAdjacentHTML("BeforeEnd", html);
|
|
} else if (el.lastChild) {
|
|
var range = el.ownerDocument.createRange();
|
|
range.setStartAfter(el.lastChild);
|
|
var frag = range.createContextualFragment(html);
|
|
el.appendChild(frag);
|
|
} else {
|
|
el.innerHTML = html;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the window size. The dimension may be either "Height" or "Width".
|
|
*
|
|
* @param {String} dimension
|
|
* @return {Number}
|
|
* @public
|
|
*/
|
|
S.getWindowSize = function(dimension) {
|
|
if (document.compatMode === "CSS1Compat")
|
|
return document.documentElement["client" + dimension];
|
|
|
|
return document.body["client" + dimension];
|
|
}
|
|
|
|
/**
|
|
* Sets an element's opacity.
|
|
*
|
|
* @param {HTMLElement} el
|
|
* @param {Number} opacity
|
|
* @public
|
|
*/
|
|
S.setOpacity = function(el, opacity) {
|
|
var style = el.style;
|
|
if (supportsOpacity) {
|
|
style.opacity = (opacity == 1 ? "" : opacity);
|
|
} else {
|
|
style.zoom = 1; // trigger hasLayout
|
|
if (opacity == 1) {
|
|
if (typeof style.filter == "string" && (/alpha/i).test(style.filter))
|
|
style.filter = style.filter.replace(/\s*[\w\.]*alpha\([^\)]*\);?/gi, "");
|
|
} else {
|
|
style.filter = (style.filter || "").replace(/\s*[\w\.]*alpha\([^\)]*\)/gi, "") +
|
|
" alpha(opacity=" + (opacity * 100) + ")";
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clears the opacity setting on the given element. Needed for some cases in IE.
|
|
*
|
|
* @param {HTMLElement} el
|
|
* @public
|
|
*/
|
|
S.clearOpacity = function(el) {
|
|
S.setOpacity(el, 1);
|
|
}
|
|
/**
|
|
* The jQuery adapter for Shadowbox.
|
|
*/
|
|
|
|
/**
|
|
* Gets the target of the given event. The event object passed will be
|
|
* the same object that is passed to listeners registered with
|
|
* addEvent().
|
|
*
|
|
* @param {Event} e The event object
|
|
* @return {HTMLElement} The event's target element
|
|
* @private
|
|
*/
|
|
function getTarget(e) {
|
|
return e.target;
|
|
}
|
|
|
|
/**
|
|
* Gets the page X/Y coordinates of the mouse event in an [x, y] array.
|
|
* The page coordinates should be relative to the document, and not the
|
|
* viewport. The event object provided here will be the same object that
|
|
* is passed to listeners registered with addEvent().
|
|
*
|
|
* @param {Event} e The event object
|
|
* @return {Array} The page X/Y coordinates
|
|
* @private
|
|
*/
|
|
function getPageXY(e) {
|
|
return [e.pageX, e.pageY];
|
|
}
|
|
|
|
/**
|
|
* Prevents the event's default behavior. The event object passed will
|
|
* be the same object that is passed to listeners registered with
|
|
* addEvent().
|
|
*
|
|
* @param {Event} e The event object
|
|
* @private
|
|
*/
|
|
function preventDefault(e) {
|
|
e.preventDefault();
|
|
}
|
|
|
|
/**
|
|
* Gets the key code of the given event object (keydown). The event
|
|
* object here will be the same object that is passed to listeners
|
|
* registered with addEvent().
|
|
*
|
|
* @param {Event} e The event object
|
|
* @return {Number} The key code of the event
|
|
* @private
|
|
*/
|
|
function keyCode(e) {
|
|
return e.keyCode;
|
|
}
|
|
|
|
/**
|
|
* Adds an event handler to the given element. The handler should be called
|
|
* in the scope of the element with the event object as its only argument.
|
|
*
|
|
* @param {HTMLElement} el The element to listen to
|
|
* @param {String} type The type of the event to add
|
|
* @param {Function} handler The event handler function
|
|
* @private
|
|
*/
|
|
function addEvent(el, type, handler) {
|
|
jQuery(el).bind(type, handler);
|
|
}
|
|
|
|
/**
|
|
* Removes an event handler from the given element.
|
|
*
|
|
* @param {HTMLElement} el The DOM element to stop listening to
|
|
* @param {String} type The type of the event to remove
|
|
* @param {Function} handler The event handler function
|
|
* @private
|
|
*/
|
|
function removeEvent(el, type, handler) {
|
|
jQuery(el).unbind(type, handler);
|
|
}
|
|
|
|
/**
|
|
* Passes the selected elements to the Shadowbox.setup() function. Supports
|
|
* embedded height and width attributes within the class attribute.
|
|
*
|
|
* @param {Object} options The options to pass to setup() for all selected elements
|
|
* @return {Object} The jQuery object
|
|
* @public
|
|
* @author Mike Alsup
|
|
* @author Roger Barrett
|
|
*/
|
|
jQuery.fn.shadowbox = function(options) {
|
|
return this.each(function() {
|
|
var el = jQuery(this);
|
|
// support jQuery metadata plugin
|
|
var opts = jQuery.extend({}, options || {}, jQuery.metadata ? el.metadata() : jQuery.meta ? el.data() : {});
|
|
// support embedded opts (for w/h) within the class attr
|
|
var cls = this.className || '';
|
|
opts.width = parseInt((cls.match(/w:(\d+)/)||[])[1]) || opts.width;
|
|
opts.height = parseInt((cls.match(/h:(\d+)/)||[])[1]) || opts.height;
|
|
Shadowbox.setup(el, opts);
|
|
});
|
|
}
|
|
// The code in this file is adapted for Shadowbox from the jQuery JavaScript library
|
|
|
|
/**
|
|
* True if Shadowbox has been loaded into the DOM, false otherwise.
|
|
*
|
|
* @type {Boolean}
|
|
* @private
|
|
*/
|
|
var loaded = false,
|
|
|
|
/**
|
|
* The callback function for the DOMContentLoaded browser event.
|
|
*
|
|
* @type {Function}
|
|
* @private
|
|
*/
|
|
DOMContentLoaded;
|
|
|
|
if (document.addEventListener) {
|
|
DOMContentLoaded = function() {
|
|
document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false);
|
|
S.load();
|
|
}
|
|
} else if (document.attachEvent) {
|
|
DOMContentLoaded = function() {
|
|
if (document.readyState === "complete") {
|
|
document.detachEvent("onreadystatechange", DOMContentLoaded);
|
|
S.load();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A DOM ready check for IE.
|
|
*
|
|
* @private
|
|
*/
|
|
function doScrollCheck() {
|
|
if (loaded)
|
|
return;
|
|
|
|
try {
|
|
document.documentElement.doScroll("left");
|
|
} catch (e) {
|
|
setTimeout(doScrollCheck, 1);
|
|
return;
|
|
}
|
|
|
|
S.load();
|
|
}
|
|
|
|
/**
|
|
* Waits for the DOM to be ready before firing the given callback function.
|
|
*
|
|
* @param {Function} callback
|
|
* @private
|
|
*/
|
|
function bindLoad() {
|
|
if (document.readyState === "complete")
|
|
return S.load();
|
|
|
|
if (document.addEventListener) {
|
|
document.addEventListener("DOMContentLoaded", DOMContentLoaded, false);
|
|
window.addEventListener("load", S.load, false);
|
|
} else if (document.attachEvent) {
|
|
document.attachEvent("onreadystatechange", DOMContentLoaded);
|
|
window.attachEvent("onload", S.load);
|
|
|
|
var topLevel = false;
|
|
try {
|
|
topLevel = window.frameElement === null;
|
|
} catch (e) {}
|
|
|
|
if (document.documentElement.doScroll && topLevel)
|
|
doScrollCheck();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads the Shadowbox code into the DOM. Is called automatically when the document
|
|
* is ready.
|
|
*
|
|
* @public
|
|
*/
|
|
S.load = function() {
|
|
if (loaded)
|
|
return;
|
|
|
|
if (!document.body)
|
|
return setTimeout(S.load, 13);
|
|
|
|
loaded = true;
|
|
|
|
checkSupport();
|
|
|
|
S.onReady();
|
|
|
|
if (!S.options.skipSetup)
|
|
S.setup();
|
|
|
|
S.skin.init();
|
|
}
|
|
/**
|
|
* Contains plugin support information. Each property of this object is a
|
|
* boolean indicating whether that plugin is supported. Keys are:
|
|
*
|
|
* - fla: Flash player
|
|
* - qt: QuickTime player
|
|
* - wmp: Windows Media player
|
|
* - f4m: Flip4Mac plugin
|
|
*
|
|
* @type {Object}
|
|
* @public
|
|
*/
|
|
S.plugins = {};
|
|
|
|
if (navigator.plugins && navigator.plugins.length) {
|
|
var names = [];
|
|
each(navigator.plugins, function(i, p) {
|
|
names.push(p.name);
|
|
});
|
|
names = names.join(',');
|
|
|
|
var f4m = names.indexOf('Flip4Mac') > -1;
|
|
|
|
S.plugins = {
|
|
fla: names.indexOf('Shockwave Flash') > -1,
|
|
qt: names.indexOf('QuickTime') > -1,
|
|
wmp: !f4m && names.indexOf('Windows Media') > -1, // if it's Flip4Mac, it's not really WMP
|
|
f4m: f4m
|
|
};
|
|
} else {
|
|
var detectPlugin = function(name) {
|
|
var axo;
|
|
try {
|
|
axo = new ActiveXObject(name);
|
|
} catch(e) {}
|
|
return !!axo;
|
|
}
|
|
|
|
S.plugins = {
|
|
fla: detectPlugin('ShockwaveFlash.ShockwaveFlash'),
|
|
qt: detectPlugin('QuickTime.QuickTime'),
|
|
wmp: detectPlugin('wmplayer.ocx'),
|
|
f4m: false
|
|
};
|
|
}
|
|
// used to match the rel attribute of links
|
|
var relAttr = /^(light|shadow)box/i,
|
|
|
|
/**
|
|
* The name of the expando property that Shadowbox uses on HTML elements
|
|
* to store the cache index of that element.
|
|
*
|
|
* @type {String}
|
|
* @private
|
|
*/
|
|
expando = "shadowboxCacheKey",
|
|
|
|
/**
|
|
* A unique id counter.
|
|
*
|
|
* @type {Number}
|
|
* @private
|
|
*/
|
|
cacheKey = 1;
|
|
|
|
/**
|
|
* Contains all link objects that have been cached.
|
|
*
|
|
* @type {Object}
|
|
* @public
|
|
*/
|
|
S.cache = {};
|
|
|
|
/**
|
|
* Resolves a link selector. The selector may be omitted to select all anchor elements
|
|
* on the page with rel="shadowbox" or, if Shadowbox.find is used, it may be a single CSS
|
|
* selector or an array of [selector, [context]].
|
|
*
|
|
* @param {mixed} selector
|
|
* @return {Array} An array of matching link elements
|
|
* @public
|
|
*/
|
|
S.select = function(selector) {
|
|
var links = [];
|
|
|
|
if (!selector) {
|
|
var rel;
|
|
each(document.getElementsByTagName("a"), function(i, el) {
|
|
rel = el.getAttribute("rel");
|
|
if (rel && relAttr.test(rel))
|
|
links.push(el);
|
|
});
|
|
} else {
|
|
var length = selector.length;
|
|
if (length) {
|
|
if (typeof selector == "string") {
|
|
if (S.find)
|
|
links = S.find(selector); // css selector
|
|
} else if (length == 2 && typeof selector[0] == "string" && selector[1].nodeType) {
|
|
if (S.find)
|
|
links = S.find(selector[0], selector[1]); // css selector + context
|
|
} else {
|
|
// array of links (or node list)
|
|
for (var i = 0; i < length; ++i)
|
|
links[i] = selector[i];
|
|
}
|
|
} else {
|
|
links.push(selector); // single link
|
|
}
|
|
}
|
|
|
|
return links;
|
|
}
|
|
|
|
/**
|
|
* Adds all links specified by the given selector to the cache. If no selector
|
|
* is provided, will select every anchor element on the page with rel="shadowbox".
|
|
*
|
|
* Note: Options given here apply only to links selected by the given selector.
|
|
* Also, because <area> elements do not support the rel attribute, they must be
|
|
* explicitly passed to this method.
|
|
*
|
|
* @param {mixed} selector
|
|
* @param {Object} options Some options to use for the given links
|
|
* @public
|
|
*/
|
|
S.setup = function(selector, options) {
|
|
each(S.select(selector), function(i, link) {
|
|
S.addCache(link, options);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Removes all links specified by the given selector from the cache.
|
|
*
|
|
* @param {mixed} selector
|
|
* @public
|
|
*/
|
|
S.teardown = function(selector) {
|
|
each(S.select(selector), function(i, link) {
|
|
S.removeCache(link);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Adds the given link element to the cache with the given options.
|
|
*
|
|
* @param {HTMLElement} link
|
|
* @param {Object} options
|
|
* @public
|
|
*/
|
|
S.addCache = function(link, options) {
|
|
var key = link[expando];
|
|
|
|
if (key == undefined) {
|
|
key = cacheKey++;
|
|
// assign cache key expando, use integer primitive to avoid memory leak in IE
|
|
link[expando] = key;
|
|
// add onclick listener
|
|
addEvent(link, "click", handleClick);
|
|
}
|
|
|
|
S.cache[key] = S.makeObject(link, options);
|
|
}
|
|
|
|
/**
|
|
* Removes the given link element from the cache.
|
|
*
|
|
* @param {HTMLElement} link
|
|
* @public
|
|
*/
|
|
S.removeCache = function(link) {
|
|
removeEvent(link, "click", handleClick);
|
|
delete S.cache[link[expando]];
|
|
link[expando] = null;
|
|
}
|
|
|
|
/**
|
|
* Gets the object from cache representative of the given link element (if there is one).
|
|
*
|
|
* @param {HTMLElement} link
|
|
* @return {Object}
|
|
* @public
|
|
*/
|
|
S.getCache = function(link) {
|
|
var key = link[expando];
|
|
return (key in S.cache && S.cache[key]);
|
|
}
|
|
|
|
/**
|
|
* Removes all onclick listeners from elements that have previously been setup with
|
|
* Shadowbox and clears all objects from cache.
|
|
*
|
|
* @public
|
|
*/
|
|
S.clearCache = function() {
|
|
for (var key in S.cache)
|
|
S.removeCache(S.cache[key].link);
|
|
|
|
S.cache = {};
|
|
}
|
|
|
|
/**
|
|
* Handles all clicks on links that have been set up to work with Shadowbox
|
|
* and cancels the default event behavior when appropriate.
|
|
*
|
|
* @param {Event} e The click event
|
|
* @private
|
|
*/
|
|
function handleClick(e) {
|
|
//preventDefault(e); // good for debugging
|
|
|
|
S.open(this);
|
|
|
|
if (S.gallery.length)
|
|
preventDefault(e);
|
|
}
|
|
/**
|
|
* The English language translation for Shadowbox.
|
|
*/
|
|
|
|
S.lang = {
|
|
code: 'en',
|
|
of: 'of',
|
|
loading: 'loading',
|
|
cancel: 'Cancel',
|
|
next: 'Next',
|
|
previous: 'Previous',
|
|
play: 'Play',
|
|
pause: 'Pause',
|
|
close: 'Close',
|
|
errors: {
|
|
single: 'You must install the <a href="{0}">{1}</a> browser plugin to view this content.',
|
|
shared: 'You must install both the <a href="{0}">{1}</a> and <a href="{2}">{3}</a> browser plugins to view this content.',
|
|
either: 'You must install either the <a href="{0}">{1}</a> or the <a href="{2}">{3}</a> browser plugin to view this content.'
|
|
}
|
|
}
|
|
/**
|
|
* The iframe player for Shadowbox.
|
|
*/
|
|
|
|
/**
|
|
* Constructor. The iframe player class for Shadowbox.
|
|
*
|
|
* @constructor
|
|
* @param {Object} obj The content object
|
|
* @param {String} id The player id
|
|
* @public
|
|
*/
|
|
S.iframe = function(obj, id) {
|
|
this.obj = obj;
|
|
this.id = id;
|
|
|
|
// height/width default to full viewport height/width
|
|
var overlay = get("sb-overlay");
|
|
this.height = obj.height ? parseInt(obj.height, 10) : overlay.offsetHeight;
|
|
this.width = obj.width ? parseInt(obj.width, 10) : overlay.offsetWidth;
|
|
}
|
|
|
|
S.iframe.prototype = {
|
|
|
|
/**
|
|
* Appends this iframe to the DOM.
|
|
*
|
|
* @param {HTMLElement} body The body element
|
|
* @param {Object} dims The current Shadowbox dimensions
|
|
* @public
|
|
*/
|
|
append: function(body, dims) {
|
|
var html = '<iframe id="' + this.id + '" name="' + this.id + '" height="100%" ' +
|
|
'width="100%" frameborder="0" marginwidth="0" marginheight="0" ' +
|
|
'style="visibility:hidden" onload="this.style.visibility=\'visible\'" ' +
|
|
'scrolling="auto"';
|
|
|
|
if (S.isIE) {
|
|
// prevent brief whiteout while loading iframe source
|
|
html += ' allowtransparency="true"';
|
|
|
|
// prevent "secure content" warning for https on IE6
|
|
// see http://www.zachleat.com/web/2007/04/24/adventures-in-i-frame-shims-or-how-i-learned-to-love-the-bomb/
|
|
if (S.isIE6)
|
|
html += ' src="javascript:false;document.write(\'\');"';
|
|
}
|
|
|
|
html += '></iframe>';
|
|
|
|
// use innerHTML method of insertion here instead of appendChild
|
|
// because IE renders frameborder otherwise
|
|
body.innerHTML = html;
|
|
},
|
|
|
|
/**
|
|
* Removes this iframe from the DOM.
|
|
*
|
|
* @public
|
|
*/
|
|
remove: function() {
|
|
var el = get(this.id);
|
|
if (el) {
|
|
remove(el);
|
|
if (S.isGecko)
|
|
delete window.frames[this.id]; // needed for Firefox
|
|
}
|
|
},
|
|
|
|
/**
|
|
* An optional callback function to process after this content has been loaded.
|
|
*
|
|
* @public
|
|
*/
|
|
onLoad: function() {
|
|
var win = S.isIE ? get(this.id).contentWindow : window.frames[this.id];
|
|
win.location.href = this.obj.content;
|
|
}
|
|
|
|
}
|
|
/**
|
|
* The image player for Shadowbox.
|
|
*/
|
|
|
|
/**
|
|
* Resource used to preload images. It's class-level so that when a new image is requested,
|
|
* the same resource can be reassigned, cancelling the original's callback.
|
|
*
|
|
* @type {Image}
|
|
* @private
|
|
*/
|
|
var pre,
|
|
|
|
/**
|
|
* The id to use for the drag proxy element.
|
|
*
|
|
* @type {String}
|
|
* @private
|
|
*/
|
|
proxyId = "sb-drag-proxy",
|
|
|
|
/**
|
|
* Keeps track of 4 floating values (x, y, startx, & starty) that are used in the drag calculations.
|
|
*
|
|
* @type {Object}
|
|
* @private
|
|
*/
|
|
dragData,
|
|
|
|
/**
|
|
* The transparent element that is used to listen for drag events.
|
|
*
|
|
* @type {HTMLElement}
|
|
* @private
|
|
*/
|
|
dragProxy,
|
|
|
|
/**
|
|
* The draggable element.
|
|
*
|
|
* @type {HTMLElement}
|
|
* @private
|
|
*/
|
|
dragTarget;
|
|
|
|
/**
|
|
* Resets the class drag variable.
|
|
*
|
|
* @private
|
|
*/
|
|
function resetDrag() {
|
|
dragData = {
|
|
x: 0,
|
|
y: 0,
|
|
startX: null,
|
|
startY: null
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Updates the drag proxy dimensions.
|
|
*
|
|
* @private
|
|
*/
|
|
function updateProxy() {
|
|
var dims = S.dimensions;
|
|
apply(dragProxy.style, {
|
|
height: dims.innerHeight + "px",
|
|
width: dims.innerWidth + "px"
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Enables a transparent drag layer on top of images.
|
|
*
|
|
* @private
|
|
*/
|
|
function enableDrag() {
|
|
resetDrag();
|
|
|
|
// add transparent proxy layer to prevent browser dragging of actual image
|
|
var style = [
|
|
"position:absolute",
|
|
"cursor:" + (S.isGecko ? "-moz-grab" : "move"),
|
|
"background-color:" + (S.isIE ? "#fff;filter:alpha(opacity=0)" : "transparent")
|
|
].join(";");
|
|
S.appendHTML(S.skin.body, '<div id="' + proxyId + '" style="' + style + '"></div>');
|
|
|
|
dragProxy = get(proxyId);
|
|
updateProxy();
|
|
|
|
addEvent(dragProxy, "mousedown", startDrag);
|
|
}
|
|
|
|
/**
|
|
* Disables the drag layer.
|
|
*
|
|
* @private
|
|
*/
|
|
function disableDrag() {
|
|
if (dragProxy) {
|
|
removeEvent(dragProxy, "mousedown", startDrag);
|
|
remove(dragProxy);
|
|
dragProxy = null;
|
|
}
|
|
|
|
dragTarget = null;
|
|
}
|
|
|
|
/**
|
|
* Sets up a drag listener on the document.
|
|
*
|
|
* @param {Event} e The mousedown event
|
|
* @private
|
|
*/
|
|
function startDrag(e) {
|
|
// prevent browser dragging
|
|
preventDefault(e);
|
|
|
|
var xy = getPageXY(e);
|
|
dragData.startX = xy[0];
|
|
dragData.startY = xy[1];
|
|
|
|
dragTarget = get(S.player.id);
|
|
|
|
addEvent(document, "mousemove", positionDrag);
|
|
addEvent(document, "mouseup", endDrag);
|
|
|
|
if (S.isGecko)
|
|
dragProxy.style.cursor = "-moz-grabbing";
|
|
}
|
|
|
|
/**
|
|
* Positions an oversized image on drag.
|
|
*
|
|
* @param {Event} e The mousemove event
|
|
* @private
|
|
*/
|
|
function positionDrag(e) {
|
|
var player = S.player,
|
|
dims = S.dimensions,
|
|
xy = getPageXY(e);
|
|
|
|
var moveX = xy[0] - dragData.startX;
|
|
dragData.startX += moveX;
|
|
dragData.x = Math.max(Math.min(0, dragData.x + moveX), dims.innerWidth - player.width);
|
|
|
|
var moveY = xy[1] - dragData.startY;
|
|
dragData.startY += moveY;
|
|
dragData.y = Math.max(Math.min(0, dragData.y + moveY), dims.innerHeight - player.height);
|
|
|
|
apply(dragTarget.style, {
|
|
left: dragData.x + "px",
|
|
top: dragData.y + "px"
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Removes the drag listener from the document.
|
|
*
|
|
* @private
|
|
*/
|
|
function endDrag() {
|
|
removeEvent(document, "mousemove", positionDrag);
|
|
removeEvent(document, "mouseup", endDrag);
|
|
|
|
if (S.isGecko)
|
|
dragProxy.style.cursor = "-moz-grab";
|
|
}
|
|
|
|
/**
|
|
* Constructor. The image player class for Shadowbox.
|
|
*
|
|
* @constructor
|
|
* @param {Object} obj The content object
|
|
* @param {String} id The player id
|
|
* @public
|
|
*/
|
|
S.img = function(obj, id) {
|
|
this.obj = obj;
|
|
this.id = id;
|
|
|
|
// preload the image
|
|
this.ready = false;
|
|
var self = this;
|
|
pre = new Image();
|
|
pre.onload = function() {
|
|
// height/width defaults to image height/width
|
|
self.height = obj.height ? parseInt(obj.height, 10) : pre.height;
|
|
self.width = obj.width ? parseInt(obj.width, 10) : pre.width;
|
|
|
|
// ready to go
|
|
self.ready = true;
|
|
|
|
// clean up to prevent memory leak in IE
|
|
pre.onload = null;
|
|
pre = null;
|
|
}
|
|
pre.src = obj.content;
|
|
}
|
|
|
|
S.img.ext = ["bmp", "gif", "jpg", "jpeg", "png"];
|
|
|
|
S.img.prototype = {
|
|
|
|
/**
|
|
* Appends this image to the document.
|
|
*
|
|
* @param {HTMLElement} body The body element
|
|
* @param {Object} dims The current Shadowbox dimensions
|
|
* @public
|
|
*/
|
|
append: function(body, dims) {
|
|
var img = document.createElement("img");
|
|
img.id = this.id;
|
|
img.src = this.obj.content;
|
|
img.style.position = "absolute";
|
|
|
|
var height, width;
|
|
if (dims.oversized && S.options.handleOversize == "resize") {
|
|
height = dims.innerHeight;
|
|
width = dims.innerWidth;
|
|
} else {
|
|
height = this.height;
|
|
width = this.width;
|
|
}
|
|
|
|
// need to use setAttribute here for IE's sake
|
|
img.setAttribute("height", height);
|
|
img.setAttribute("width", width);
|
|
|
|
body.appendChild(img);
|
|
},
|
|
|
|
/**
|
|
* Removes this image from the document.
|
|
*
|
|
* @public
|
|
*/
|
|
remove: function() {
|
|
var el = get(this.id);
|
|
if (el)
|
|
remove(el);
|
|
|
|
disableDrag();
|
|
|
|
// prevent old image requests from loading
|
|
if (pre) {
|
|
pre.onload = null;
|
|
pre = null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* An optional callback function to process after this content has been
|
|
* loaded.
|
|
*
|
|
* @public
|
|
*/
|
|
onLoad: function() {
|
|
var dims = S.dimensions;
|
|
|
|
// listen for drag when image is oversized
|
|
if (dims.oversized && S.options.handleOversize == "drag")
|
|
enableDrag();
|
|
},
|
|
|
|
/**
|
|
* Called when the window is resized.
|
|
*
|
|
* @public
|
|
*/
|
|
onWindowResize: function() {
|
|
var dims = S.dimensions;
|
|
|
|
switch (S.options.handleOversize) {
|
|
case "resize":
|
|
var el = get(this.id);
|
|
el.height = dims.innerHeight;
|
|
el.width = dims.innerWidth;
|
|
break;
|
|
case "drag":
|
|
if (dragTarget) {
|
|
var top = parseInt(S.getStyle(dragTarget, "top")),
|
|
left = parseInt(S.getStyle(dragTarget, "left"));
|
|
|
|
// fix positioning when viewport is enlarged
|
|
if (top + this.height < dims.innerHeight)
|
|
dragTarget.style.top = dims.innerHeight - this.height + "px";
|
|
if (left + this.width < dims.innerWidth)
|
|
dragTarget.style.left = dims.innerWidth - this.width + "px";
|
|
|
|
updateProxy();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
/**
|
|
* Keeps track of whether or not the overlay is activated.
|
|
*
|
|
* @type {Boolean}
|
|
* @private
|
|
*/
|
|
var overlayOn = false,
|
|
|
|
/**
|
|
* A cache of elements that are troublesome for modal overlays.
|
|
*
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
visibilityCache = [],
|
|
|
|
/**
|
|
* Id's of elements that need transparent PNG support.
|
|
*
|
|
* @type {Array}
|
|
* @private
|
|
*/
|
|
pngIds = [
|
|
"sb-nav-close",
|
|
"sb-nav-next",
|
|
"sb-nav-play",
|
|
"sb-nav-pause",
|
|
"sb-nav-previous"
|
|
],
|
|
|
|
/**
|
|
* The container element.
|
|
*
|
|
* @type {HTMLElement}
|
|
* @private
|
|
*/
|
|
container,
|
|
|
|
/**
|
|
* The overlay element.
|
|
*
|
|
* @type {HTMLElement}
|
|
* @private
|
|
*/
|
|
overlay,
|
|
|
|
/**
|
|
* The wrapper element.
|
|
*
|
|
* @type {HTMLElement}
|
|
* @private
|
|
*/
|
|
wrapper,
|
|
|
|
/**
|
|
* True if the window resize event is allowed to fire.
|
|
*
|
|
* @type {Boolean}
|
|
* @private
|
|
*/
|
|
doWindowResize = true;
|
|
|
|
/**
|
|
* Animates the given property of el to the given value over a specified duration. If a
|
|
* callback is provided, it will be called when the animation is finished.
|
|
*
|
|
* @param {HTMLElement} el
|
|
* @param {String} property
|
|
* @param {mixed} to
|
|
* @param {Number} duration
|
|
* @param {Function} callback
|
|
* @private
|
|
*/
|
|
function animate(el, property, to, duration, callback) {
|
|
var isOpacity = (property == "opacity"),
|
|
anim = isOpacity ? S.setOpacity : function(el, value) {
|
|
// default unit is px for properties other than opacity
|
|
el.style[property] = "" +
|
|
value + "px";
|
|
};
|
|
|
|
if (duration == 0 || (!isOpacity && !S.options.animate) || (isOpacity && !S.options.animateFade)) {
|
|
anim(el, to);
|
|
if (callback)
|
|
callback();
|
|
return;
|
|
}
|
|
|
|
var from = parseFloat(S.getStyle(el, property)) || 0;
|
|
var delta = to - from;
|
|
if (delta == 0) {
|
|
if (callback)
|
|
callback();
|
|
return; // nothing to animate
|
|
}
|
|
|
|
duration *= 1000; // convert to milliseconds
|
|
|
|
var begin = now(),
|
|
ease = S.ease,
|
|
end = begin + duration,
|
|
time;
|
|
|
|
var interval = setInterval(function() {
|
|
time = now();
|
|
if (time >= end) {
|
|
clearInterval(interval);
|
|
interval = null;
|
|
anim(el, to);
|
|
if (callback)
|
|
callback();
|
|
} else {
|
|
anim(el, from + ease((time - begin) / duration) * delta);
|
|
}
|
|
}, 10); // 10 ms interval is minimum on WebKit
|
|
}
|
|
|
|
/**
|
|
* Sets the size of the container element.
|
|
*
|
|
* @private
|
|
*/
|
|
function setSize() {
|
|
container.style.height = S.getWindowSize("Height") + "px";
|
|
container.style.width = S.getWindowSize("Width") + "px";
|
|
}
|
|
|
|
/**
|
|
* Sets the top of the container element. This is only necessary in browsers that
|
|
* don't support fixed positioning, such as IE6.
|
|
*
|
|
* @private
|
|
*/
|
|
function setPosition() {
|
|
container.style.top = document.documentElement.scrollTop + "px";
|
|
container.style.left = document.documentElement.scrollLeft + "px";
|
|
}
|
|
|
|
/**
|
|
* Toggles the visibility of elements that are troublesome for overlays.
|
|
*
|
|
* @param {Boolean} on True to make visible, false to hide
|
|
* @private
|
|
*/
|
|
function toggleTroubleElements(on) {
|
|
if (on) {
|
|
each(visibilityCache, function(i, el){
|
|
el[0].style.visibility = el[1] || '';
|
|
});
|
|
} else {
|
|
visibilityCache = [];
|
|
each(S.options.troubleElements, function(i, tag) {
|
|
each(document.getElementsByTagName(tag), function(j, el) {
|
|
visibilityCache.push([el, el.style.visibility]);
|
|
el.style.visibility = "hidden";
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Toggles the display of the nav control with the given id.
|
|
*
|
|
* @param {String} id The id of the navigation control
|
|
* @param {Boolean} on True to toggle on, false to toggle off
|
|
* @private
|
|
*/
|
|
function toggleNav(id, on) {
|
|
var el = get("sb-nav-" + id);
|
|
if (el)
|
|
el.style.display = on ? "" : "none";
|
|
}
|
|
|
|
/**
|
|
* Toggles the visibility of the loading layer.
|
|
*
|
|
* @param {Boolean} on True to toggle on, false to toggle off
|
|
* @param {Function} callback The callback to use when finished
|
|
* @private
|
|
*/
|
|
function toggleLoading(on, callback) {
|
|
var loading = get("sb-loading"),
|
|
playerName = S.getCurrent().player,
|
|
anim = (playerName == "img" || playerName == "html"); // fade on images & html
|
|
|
|
if (on) {
|
|
S.setOpacity(loading, 0);
|
|
loading.style.display = "block";
|
|
|
|
var wrapped = function() {
|
|
S.clearOpacity(loading);
|
|
if (callback)
|
|
callback();
|
|
}
|
|
|
|
if (anim) {
|
|
animate(loading, "opacity", 1, S.options.fadeDuration, wrapped);
|
|
} else {
|
|
wrapped();
|
|
}
|
|
} else {
|
|
var wrapped = function() {
|
|
loading.style.display = "none";
|
|
S.clearOpacity(loading);
|
|
if (callback)
|
|
callback();
|
|
}
|
|
|
|
if (anim) {
|
|
animate(loading, "opacity", 0, S.options.fadeDuration, wrapped);
|
|
} else {
|
|
wrapped();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builds the content for the title and information bars.
|
|
*
|
|
* @param {Function} callback The callback to use when finished
|
|
* @private
|
|
*/
|
|
function buildBars(callback) {
|
|
var obj = S.getCurrent();
|
|
|
|
get("sb-title-inner").innerHTML = obj.title || "";
|
|
|
|
// build the nav
|
|
var close, next, play, pause, previous;
|
|
if (S.options.displayNav) {
|
|
close = true;
|
|
var len = S.gallery.length;
|
|
if (len > 1) {
|
|
if (S.options.continuous) {
|
|
next = previous = true;
|
|
} else {
|
|
next = (len - 1) > S.current; // not last in gallery, show next
|
|
previous = S.current > 0; // not first in gallery, show previous
|
|
}
|
|
}
|
|
// in a slideshow?
|
|
if (S.options.slideshowDelay > 0 && S.hasNext()) {
|
|
pause = !S.isPaused();
|
|
play = !pause;
|
|
}
|
|
} else {
|
|
close = next = play = pause = previous = false;
|
|
}
|
|
toggleNav("close", close);
|
|
toggleNav("next", next);
|
|
toggleNav("play", play);
|
|
toggleNav("pause", pause);
|
|
toggleNav("previous", previous);
|
|
|
|
// build the counter
|
|
var counter = "";
|
|
if (S.options.displayCounter && S.gallery.length > 1) {
|
|
var len = S.gallery.length;
|
|
if (S.options.counterType == "skip") {
|
|
// limit the counter?
|
|
var i = 0,
|
|
end = len,
|
|
limit = parseInt(S.options.counterLimit) || 0;
|
|
|
|
if (limit < len && limit > 2) { // support large galleries
|
|
var h = Math.floor(limit / 2);
|
|
i = S.current - h;
|
|
if (i < 0)
|
|
i += len;
|
|
end = S.current + (limit - h);
|
|
if (end > len)
|
|
end -= len;
|
|
}
|
|
|
|
while (i != end) {
|
|
if (i == len)
|
|
i = 0;
|
|
counter += '<a onclick="Shadowbox.change(' + i + ');"'
|
|
if (i == S.current)
|
|
counter += ' class="sb-counter-current"';
|
|
counter += ">" + (++i) + "</a>";
|
|
}
|
|
} else {
|
|
counter = [S.current + 1, S.lang.of, len].join(' ');
|
|
}
|
|
}
|
|
|
|
get("sb-counter").innerHTML = counter;
|
|
|
|
callback();
|
|
}
|
|
|
|
/**
|
|
* Shows the title and info bars.
|
|
*
|
|
* @param {Function} callback The callback to use when finished
|
|
* @private
|
|
*/
|
|
function showBars(callback) {
|
|
var titleInner = get("sb-title-inner"),
|
|
infoInner = get("sb-info-inner"),
|
|
duration = 0.35;
|
|
|
|
// clear visibility before animating into view
|
|
titleInner.style.visibility = infoInner.style.visibility = "";
|
|
|
|
if (titleInner.innerHTML != "")
|
|
animate(titleInner, "marginTop", 0, duration);
|
|
animate(infoInner, "marginTop", 0, duration, callback);
|
|
}
|
|
|
|
/**
|
|
* Hides the title and info bars.
|
|
*
|
|
* @param {Boolean} anim True to animate the transition
|
|
* @param {Function} callback The callback to use when finished
|
|
* @private
|
|
*/
|
|
function hideBars(anim, callback) {
|
|
var title = get("sb-title"),
|
|
info = get("sb-info"),
|
|
titleHeight = title.offsetHeight,
|
|
infoHeight = info.offsetHeight,
|
|
titleInner = get("sb-title-inner"),
|
|
infoInner = get("sb-info-inner"),
|
|
duration = (anim ? 0.35 : 0);
|
|
|
|
animate(titleInner, "marginTop", titleHeight, duration);
|
|
animate(infoInner, "marginTop", infoHeight * -1, duration, function() {
|
|
titleInner.style.visibility = infoInner.style.visibility = "hidden";
|
|
callback();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Adjusts the height of #sb-wrapper-inner and centers #sb-wrapper vertically
|
|
* in the viewport.
|
|
*
|
|
* @param {Number} height The height (in pixels)
|
|
* @param {Number} top The top (in pixels)
|
|
* @param {Boolean} anim True to animate the transition
|
|
* @param {Function} callback The callback to use when finished
|
|
* @private
|
|
*/
|
|
function adjustHeight(height, top, anim, callback) {
|
|
var wrapperInner = get("sb-wrapper-inner"),
|
|
duration = (anim ? S.options.resizeDuration : 0);
|
|
|
|
animate(wrapper, "top", top, duration);
|
|
animate(wrapperInner, "height", height, duration, callback);
|
|
}
|
|
|
|
/**
|
|
* Adjusts the width and left position of #sb-wrapper.
|
|
*
|
|
* @param {Number} width The width (in pixels)
|
|
* @param {Number} left The left (in pixels)
|
|
* @param {Boolean} anim True to animate the transition
|
|
* @param {Function} callback The callback to use when finished
|
|
* @private
|
|
*/
|
|
function adjustWidth(width, left, anim, callback) {
|
|
var duration = (anim ? S.options.resizeDuration : 0);
|
|
|
|
animate(wrapper, "left", left, duration);
|
|
animate(wrapper, "width", width, duration, callback);
|
|
}
|
|
|
|
/**
|
|
* Calculates the dimensions for Shadowbox.
|
|
*
|
|
* @param {Number} height The content height
|
|
* @param {Number} width The content width
|
|
* @return {Object} The new dimensions object
|
|
* @private
|
|
*/
|
|
function setDimensions(height, width) {
|
|
var bodyInner = get("sb-body-inner"),
|
|
height = parseInt(height),
|
|
width = parseInt(width),
|
|
topBottom = wrapper.offsetHeight - bodyInner.offsetHeight,
|
|
leftRight = wrapper.offsetWidth - bodyInner.offsetWidth,
|
|
|
|
// overlay should provide proper window dimensions here
|
|
maxHeight = overlay.offsetHeight,
|
|
maxWidth = overlay.offsetWidth,
|
|
|
|
// default to the default viewport padding
|
|
padding = parseInt(S.options.viewportPadding) || 20,
|
|
|
|
// only preserve aspect ratio if there is something to display and
|
|
// it's not draggable
|
|
preserveAspect = (S.player && S.options.handleOversize != "drag");
|
|
|
|
return S.setDimensions(height, width, maxHeight, maxWidth, topBottom, leftRight, padding, preserveAspect);
|
|
}
|
|
|
|
/**
|
|
* The Shadowbox.skin object.
|
|
*
|
|
* @type {Object}
|
|
* @public
|
|
*/
|
|
var K = {};
|
|
|
|
/**
|
|
* The HTML markup to use.
|
|
*
|
|
* @type {String}
|
|
* @public
|
|
*/
|
|
K.markup = "" +
|
|
'<div id="sb-container">' +
|
|
'<div id="sb-overlay"></div>' +
|
|
'<div id="sb-wrapper">' +
|
|
'<div id="sb-title">' +
|
|
'<div id="sb-title-inner"></div>' +
|
|
'</div>' +
|
|
'<div id="sb-wrapper-inner">' +
|
|
'<div id="sb-body">' +
|
|
'<div id="sb-body-inner"></div>' +
|
|
'<div id="sb-loading">' +
|
|
'<div id="sb-loading-inner"><span>{loading}</span></div>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div id="sb-info">' +
|
|
'<div id="sb-info-inner">' +
|
|
'<div id="sb-counter"></div>' +
|
|
'<div id="sb-nav">' +
|
|
'<a id="sb-nav-close" title="{close}" onclick="Shadowbox.close()"></a>' +
|
|
'<a id="sb-nav-next" title="{next}" onclick="Shadowbox.next()"></a>' +
|
|
'<a id="sb-nav-play" title="{play}" onclick="Shadowbox.play()"></a>' +
|
|
'<a id="sb-nav-pause" title="{pause}" onclick="Shadowbox.pause()"></a>' +
|
|
'<a id="sb-nav-previous" title="{previous}" onclick="Shadowbox.previous()"></a>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'</div>';
|
|
|
|
/**
|
|
* Various options that control the behavior of Shadowbox' skin.
|
|
*
|
|
* @type {Object}
|
|
* @public
|
|
*/
|
|
K.options = {
|
|
|
|
/**
|
|
* The sequence of the resizing animations. "hw" will resize height, then width. "wh" resizes
|
|
* width, then height. "sync" resizes both simultaneously.
|
|
*
|
|
* @type {String}
|
|
*/
|
|
animSequence: "sync",
|
|
|
|
/**
|
|
* The limit to the number of counter links that are displayed in a "skip"-style counter.
|
|
*
|
|
* @type {Number}
|
|
*/
|
|
counterLimit: 10,
|
|
|
|
/**
|
|
* The counter type to use. May be either "default" or "skip". A skip counter displays a
|
|
* link for each object in the gallery.
|
|
*
|
|
* @type {String}
|
|
*/
|
|
counterType: "default",
|
|
|
|
/**
|
|
* True to display the gallery counter.
|
|
*
|
|
* @type {Boolean}
|
|
*/
|
|
displayCounter: true,
|
|
|
|
/**
|
|
* True to show the navigation controls.
|
|
*
|
|
* @type {Boolean}
|
|
*/
|
|
displayNav: true,
|
|
|
|
/**
|
|
* The duration (in seconds) of opacity animations.
|
|
*
|
|
* @type {Number}
|
|
*/
|
|
fadeDuration: 0.35,
|
|
|
|
/**
|
|
* The initial height (in pixels).
|
|
*
|
|
* @type {Number}
|
|
*/
|
|
initialHeight: 160,
|
|
|
|
/**
|
|
* The initial width (in pixels).
|
|
*
|
|
* @type {Number}
|
|
*/
|
|
initialWidth: 320,
|
|
|
|
/**
|
|
* True to trigger Shadowbox.close when the overlay is clicked.
|
|
*
|
|
* @type {Boolean}
|
|
*/
|
|
modal: false,
|
|
|
|
/**
|
|
* The color (in hex) to use for the overlay.
|
|
*
|
|
* @type {String}
|
|
*/
|
|
overlayColor: "#000",
|
|
|
|
/**
|
|
* The opacity to use for the overlay.
|
|
*
|
|
* @type {Number}
|
|
*/
|
|
overlayOpacity: 0.5,
|
|
|
|
/**
|
|
* The duration (in seconds) to use for resizing animations.
|
|
*
|
|
* @type {Number}
|
|
*/
|
|
resizeDuration: 0.35,
|
|
|
|
/**
|
|
* True to show the overlay, false to hide it.
|
|
*
|
|
* @type {Boolean}
|
|
*/
|
|
showOverlay: true,
|
|
|
|
/**
|
|
* Names of elements that should be hidden when the overlay is enabled.
|
|
*
|
|
* @type {String}
|
|
*/
|
|
troubleElements: ["select", "object", "embed", "canvas"]
|
|
|
|
};
|
|
|
|
/**
|
|
* Initialization function. Called immediately after this skin's markup has been
|
|
* appended to the document with all of the necessary language replacements done.
|
|
*
|
|
* @public
|
|
*/
|
|
K.init = function() {
|
|
S.appendHTML(document.body, sprintf(K.markup, S.lang));
|
|
|
|
K.body = get("sb-body-inner");
|
|
|
|
// cache oft-used elements
|
|
container = get("sb-container");
|
|
overlay = get("sb-overlay");
|
|
wrapper = get("sb-wrapper");
|
|
|
|
// use absolute positioning in browsers that don't support fixed
|
|
if (!supportsFixed)
|
|
container.style.position = "absolute";
|
|
|
|
if (!supportsOpacity) {
|
|
// support transparent PNG's via AlphaImageLoader
|
|
var el, m, re = /url\("(.*\.png)"\)/;
|
|
each(pngIds, function(i, id) {
|
|
el = get(id);
|
|
if (el) {
|
|
m = S.getStyle(el, "backgroundImage").match(re);
|
|
if (m) {
|
|
el.style.backgroundImage = "none";
|
|
el.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true,src=" +
|
|
m[1] + ",sizingMethod=scale);";
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// add window resize event handler, use 10 ms buffer to prevent jerky resizing
|
|
var timer;
|
|
addEvent(window, "resize", function() {
|
|
if (timer) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
|
|
if (open)
|
|
timer = setTimeout(K.onWindowResize, 10);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Called when Shadowbox opens.
|
|
*
|
|
* @param {Object} obj The object to open
|
|
* @param {Function} callback The callback to use when finished
|
|
* @public
|
|
*/
|
|
K.onOpen = function(obj, callback) {
|
|
// prevent window resize events from firing until we're finished
|
|
doWindowResize = false;
|
|
|
|
container.style.display = "block";
|
|
|
|
setSize();
|
|
|
|
var dims = setDimensions(S.options.initialHeight, S.options.initialWidth);
|
|
adjustHeight(dims.innerHeight, dims.top);
|
|
adjustWidth(dims.width, dims.left);
|
|
|
|
if (S.options.showOverlay) {
|
|
overlay.style.backgroundColor = S.options.overlayColor;
|
|
S.setOpacity(overlay, 0);
|
|
|
|
if (!S.options.modal)
|
|
addEvent(overlay, "click", S.close);
|
|
|
|
overlayOn = true;
|
|
}
|
|
|
|
if (!supportsFixed) {
|
|
setPosition();
|
|
addEvent(window, "scroll", setPosition);
|
|
}
|
|
|
|
toggleTroubleElements();
|
|
container.style.visibility = "visible";
|
|
|
|
if (overlayOn) {
|
|
animate(overlay, "opacity", S.options.overlayOpacity, S.options.fadeDuration, callback);
|
|
} else {
|
|
callback();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when a new object is being loaded.
|
|
*
|
|
* @param {Boolean} changing True if the content is changing from some
|
|
* previous object
|
|
* @param {Function} callback The callback to use when finished
|
|
* @public
|
|
*/
|
|
K.onLoad = function(changing, callback) {
|
|
toggleLoading(true);
|
|
|
|
// make sure the body doesn't have any children
|
|
while (K.body.firstChild)
|
|
remove(K.body.firstChild);
|
|
|
|
hideBars(changing, function() {
|
|
if (!open)
|
|
return;
|
|
|
|
if (!changing)
|
|
wrapper.style.visibility = "visible";
|
|
|
|
buildBars(callback);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Called when the content is ready to be loaded (e.g. when the image has finished
|
|
* loading). Should resize the content box and make any other necessary adjustments.
|
|
*
|
|
* @param {Function} callback The callback to use when finished
|
|
* @public
|
|
*/
|
|
K.onReady = function(callback) {
|
|
if (!open)
|
|
return;
|
|
|
|
var player = S.player,
|
|
dims = setDimensions(player.height, player.width);
|
|
|
|
var wrapped = function() {
|
|
showBars(callback);
|
|
}
|
|
|
|
switch (S.options.animSequence) {
|
|
case "hw":
|
|
adjustHeight(dims.innerHeight, dims.top, true, function() {
|
|
adjustWidth(dims.width, dims.left, true, wrapped);
|
|
});
|
|
break;
|
|
case "wh":
|
|
adjustWidth(dims.width, dims.left, true, function() {
|
|
adjustHeight(dims.innerHeight, dims.top, true, wrapped);
|
|
});
|
|
break;
|
|
default: // sync
|
|
adjustWidth(dims.width, dims.left, true);
|
|
adjustHeight(dims.innerHeight, dims.top, true, wrapped);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when the content is loaded into the box and is ready to be displayed.
|
|
*
|
|
* @param {Function} callback The callback to use when finished
|
|
* @public
|
|
*/
|
|
K.onShow = function(callback) {
|
|
toggleLoading(false, callback);
|
|
|
|
// re-enable window resize events
|
|
doWindowResize = true;
|
|
}
|
|
|
|
/**
|
|
* Called in Shadowbox.close.
|
|
*
|
|
* @public
|
|
*/
|
|
K.onClose = function() {
|
|
if (!supportsFixed)
|
|
removeEvent(window, "scroll", setPosition);
|
|
|
|
removeEvent(overlay, "click", S.close);
|
|
|
|
wrapper.style.visibility = "hidden";
|
|
|
|
var callback = function() {
|
|
container.style.visibility = "hidden";
|
|
container.style.display = "none";
|
|
toggleTroubleElements(true);
|
|
}
|
|
|
|
if (overlayOn) {
|
|
animate(overlay, "opacity", 0, S.options.fadeDuration, callback);
|
|
} else {
|
|
callback();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called in Shadowbox.play.
|
|
*
|
|
* @public
|
|
*/
|
|
K.onPlay = function() {
|
|
toggleNav("play", false);
|
|
toggleNav("pause", true);
|
|
}
|
|
|
|
/**
|
|
* Called in Shadowbox.pause.
|
|
*
|
|
* @public
|
|
*/
|
|
K.onPause = function() {
|
|
toggleNav("pause", false);
|
|
toggleNav("play", true);
|
|
}
|
|
|
|
/**
|
|
* Called when the window is resized.
|
|
*
|
|
* @public
|
|
*/
|
|
K.onWindowResize = function() {
|
|
if (!doWindowResize)
|
|
return;
|
|
|
|
setSize();
|
|
|
|
var player = S.player,
|
|
dims = setDimensions(player.height, player.width);
|
|
|
|
// adjust width first to eliminate horizontal scroll bar
|
|
adjustWidth(dims.width, dims.left);
|
|
adjustHeight(dims.innerHeight, dims.top);
|
|
|
|
if (player.onWindowResize)
|
|
player.onWindowResize();
|
|
}
|
|
|
|
S.skin = K;
|
|
// expose
|
|
window['Shadowbox'] = S;
|
|
|
|
})(window);
|