var _____WB$wombat$assign$function_____ = function(name) {return (self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init(name)) || self[name]; };
if (!self.__WB_pmw) { self.__WB_pmw = function(obj) { this.__WB_source = obj; return this; } }
{
let window = _____WB$wombat$assign$function_____("window");
let self = _____WB$wombat$assign$function_____("self");
let document = _____WB$wombat$assign$function_____("document");
let location = _____WB$wombat$assign$function_____("location");
let top = _____WB$wombat$assign$function_____("top");
let parent = _____WB$wombat$assign$function_____("parent");
let frames = _____WB$wombat$assign$function_____("frames");
let opener = _____WB$wombat$assign$function_____("opener");
/**
* A media-viewer script for web pages that allows content to be viewed without
* navigating away from the original linking page.
*
* This file is part of Shadowbox.
*
* Shadowbox is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* Shadowbox is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Shadowbox. If not, see .
*
* @author Michael J. I. Jackson
* @copyright 2007 Michael J. I. Jackson
* @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL 3.0
* @version SVN: $Id: shadowbox.js 63 2008-02-06 14:28:42Z mjijackson $
*/
if(typeof Shadowbox == 'undefined'){
throw 'Unable to load Shadowbox, no base library adapter found.';
}
/**
* The Shadowbox class. Used to display different media on a web page using a
* Lightbox-like effect.
*
* Useful resources:
* - http://www.alistapart.com/articles/byebyeembed
* - http://www.w3.org/TR/html401/struct/objects.html
* - http://www.dyn-web.com/dhtml/iframes/
* - http://support.microsoft.com/kb/316992
* - http://www.apple.com/quicktime/player/specs.html
* - http://www.howtocreate.co.uk/wrongWithIE/?chapter=navigator.plugins
*
* @class Shadowbox
* @author Michael J. I. Jackson
* @singleton
* @todo Find a way to tell when movies and iframes are loaded
* @todo Incorporate slideshow functionality
*/
(function(){
/**
* The current version of Shadowbox.
*
* @property {String} version
* @private
*/
var version = '1.0b7';
/**
* Contains the default options for Shadowbox. This object is almost
* entirely customizable.
*
* @property {Object} options
* @private
*/
var options = {
/**
* The path to the image to display while loading.
*
* @var {String} loadingImage
*/
loadingImage: '/images/plugins/shadowbox/loading.gif',
/**
* Enable animations.
*
* @var {Boolean} animate
*/
animate: true,
/**
* Specifies the sequence of the height and width animations. May be
* 'wh' (width then height), 'hw' (height then width), or 'sync' (both
* at the same time). Of course this will only work if animate is true.
*
* @var {String} animSequence
*/
animSequence: 'wh',
/**
* The path to flvplayer.swf.
*
* @var {String} flvPlayer
*/
flvPlayer: '/flash/flvplayer/flvplayer.swf',
/**
* The background color and opacity of the overlay. Note: When viewing
* movie files on FF Mac, the default background image will be used
* because that browser has problems displaying movies above layers
* that aren't 100% opaque.
*
* @var {String} overlayColor
*/
overlayColor: '#000',
/**
* The background opacity to use for the overlay.
*
* @var {Number} overlayOpacity
*/
overlayOpacity: 0.85,
/**
* A background image to use for browsers such as FF Mac that don't
* support displaying movie content over backgrounds that aren't 100%
* opaque.
*
* @var {String} overlayBgImage
*/
overlayBgImage: '/images/plugins/shadowbox/overlay-85.png',
/**
* Automatically play movies.
*
* @var {Boolean} autoplayMovies
*/
autoplayMovies: true,
/**
* Enable movie controllers on movie players.
*
* @var {Boolean} showMovieControls
*/
showMovieControls: true,
/**
* The duration of the resizing animations (in seconds).
*
* @var {Number} resizeDuration
*/
resizeDuration: 0.35,
/**
* The duration of the overlay fade animation (in seconds).
*
* @var {Number} fadeDuration
*/
fadeDuration: 0.35,
/**
* Show the navigation controls.
*
* @var {Boolean} displayNav
*/
displayNav: true,
/**
* Enable continuous galleries. When this is true, users will be able
* to skip to the first gallery image from the last using next and vice
* versa.
*
* @var {Boolean} continuous
*/
continuous: false,
/**
* Display the gallery counter.
*
* @var {Boolean} displayCounter
*/
displayCounter: true,
/**
* This option may be either 'default' or 'skip'. The default counter is
* a simple '1 of 5' message. The skip counter displays a link for each
* piece in the gallery that enables a user to skip directly to any
* piece.
*
* @var {String} counterType
*/
counterType: 'default',
/**
* The amount of padding to maintain around the viewport edge (in
* pixels). This only applies when the image is very large and takes up
* the entire viewport.
*
* @var {Number} viewportPadding
*/
viewportPadding: 20,
/**
* How to handle images that are too large for the viewport. 'resize'
* will resize the image while preserving aspect ratio and display it at
* the smaller resolution. 'drag' will display the image at its native
* resolution but it will be draggable within the Shadowbox. 'none' will
* display the image at its native resolution but it may be cropped.
*
* @var {String} handleLgImages
*/
handleLgImages: 'resize',
/**
* The initial height of Shadowbox (in pixels).
*
* @var {Number} initialHeight
*/
initialHeight: 160,
/**
* The initial width of Shadowbox (in pixels).
*
* @var {Number} initialWidth
*/
initialWidth: 320,
/**
* Enable keyboard control. Note: If you disable the keys, you may want
* to change the visual styles for the navigation elements that suggest
* keyboard shortcuts.
*
* @var {Boolean} enableKeys
*/
enableKeys: true,
/**
* The keys used to control Shadowbox. Note: In order to use these,
* enableKeys must be true. Key values or key codes may be used.
*
* @var {Array}
*/
keysClose: ['c', 'q', 27], // c, q, or esc
keysNext: ['n', 39], // n or right arrow
keysPrev: ['p', 37], // p or left arrow
/**
* Hook functions that will be fired at various stages in the script
* execution. The single parameter passed to the function will be a link
* (DOM) element. In the case of onOpen, it will be the link element
* that was clicked. In onClose, it will be the link element corresponding
* to the last gallery piece that was displayed.
*
* @var {Function}
*/
onOpen: null,
onFinish: null,
onClose: null,
/**
* The mode to use when handling unsupported media. May be either
* 'remove' or 'link'. If it is 'remove', the unsupported gallery item
* will merely be removed from the gallery. If it is the only item in
* the gallery, the link will simply be followed. If it is 'link', a
* link will be provided to the appropriate plugin page in place of the
* gallery element.
*
* @var {String} handleUnsupported
*/
handleUnsupported: 'link',
/**
* Skips calling Shadowbox.setup() in init(). This means that it must
* be called later manually.
*
* @var {Boolean} skipSetup
*/
skipSetup: false,
/**
* Text messages to use for Shadowbox. These are provided so they may be
* translated into different languages.
*
* @var {Object} text
*/
text: {
cancel: 'Cancel',
loading: 'loading',
close: 'Close',
next: 'Next',
prev: 'Previous',
errors: {
single: 'You must install the {1} browser plugin to view this content.',
shared: 'You must install both the {1} and {3} browser plugins to view this content.',
either: 'You must install either the {1} or the {3} browser plugin to view this content.'
}
},
/**
* An object containing names of plugins and links to their respective
* download pages.
*
* @var {Object} errors
*/
errors: {
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 HTML markup to use for Shadowbox. Note: The script depends on
* most of these elements being present, so don't modify this variable
* unless you know what you're doing.
*
* @var {Object} skin
*/
skin: {
main: '' +
'',
loading: '
' +
'{2}',
counter: '{0}
',
close: '',
next: '',
prev: ''
},
/**
* An object containing arrays of all supported file extensions. Each
* property of this object contains an array. If this object is to be
* modified, it must be done before calling init().
*
* - img: Supported image file extensions
* - qt: Movie file extensions supported by QuickTime
* - wmp: Movie file extensions supported by Windows Media Player
* - qtwmp: Movie file extensions supported by both QuickTime and Windows Media Player
* - iframe: File extensions that will be display in an iframe
*
* @var {Object} ext
*/
ext: {
img: ['png', 'jpg', 'jpeg', 'gif', 'bmp'],
qt: ['dv', 'mov', 'moov', 'movie', 'mp4'],
wmp: ['asf', 'wm', 'wmv'],
qtwmp: ['avi', 'mpg', 'mpeg'],
iframe: ['asp', 'aspx', 'cgi', 'cfm', 'htm', 'html', 'pl', 'php',
'php3', 'php4', 'php5', 'phtml', 'rb', 'rhtml', 'shtml',
'txt', 'vbs']
}
};
/**
* Stores the default set of options in case a custom set of options is used
* on a link-by-link basis so we can restore them later.
*
* @property {Object} default_options
* @private
*/
var default_options = null;
/**
* Shorthand for Shadowbox.lib.
*
* @property {Object} SL
* @private
*/
var SL = Shadowbox.lib;
/**
* An object containing some regular expressions we'll need later. Compiled
* up front for speed.
*
* @property {Object} RE
* @private
*/
var RE = {
resize: /(img|swf|flv)/, // file types to resize
swf: /\.swf\s*$/i, // swf file extension
flv: /\.flv\s*$/i, // flv file extension
domain: /:\/\/(.*?)[:\/]/, // domain prefix
inline: /#(.+)$/, // inline element id
rel: /^shadowbox/i, // rel attribute format
gallery: /^shadowbox\[(.*?)\]/i, // rel attribute format for gallery link
unsupported: /^unsupported-(\w+)/, // unsupported media type
param: /\s*([a-z_]*?)\s*=\s*(.+)\s*/, // rel string parameter
empty: /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i // elements that don't have children
};
/**
* A cache of options for links that have been set up for use with
* Shadowbox.
*
* @property {Array} cache
* @private
*/
var cache = [];
/**
* An array of pieces currently being viewed. In the case of non-gallery
* pieces, this will only hold one object.
*
* @property {Array} current_gallery
* @private
*/
var current_gallery;
/**
* The array index of the current_gallery that is currently being viewed.
*
* @property {Number} current
* @private
*/
var current;
/**
* Keeps track of the current optimal height of the box. We use this so that
* if the user resizes the browser window to get a better view, and we're
* currently at a size smaller than the optimal, we can resize easily.
*
* @see resizeContent()
* @property {Number} optimal_height
* @private
*/
var optimal_height = options.initialHeight;
/**
* Keeps track of the current optimal width of the box. See optimal_height
* explanation (above).
*
* @property {Number} optimal_width
* @private
*/
var optimal_width = options.initialWidth;
/**
* Keeps track of the current height of the box. This is useful in drag
* calculations.
*
* @property {Number} current_height
* @private
*/
var current_height = 0;
/**
* Keeps track of the current width of the box. Useful in drag calculations.
*
* @property {Number} current_width
* @private
*/
var current_width = 0;
/**
* 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.
*
* @property {HTMLElement} preloader
* @private
*/
var preloader;
/**
* Keeps track of whether or not Shadowbox has been initialized. We never
* want to initialize twice.
*
* @property {Boolean} initialized
* @private
*/
var initialized = false;
/**
* Keeps track of whether or not Shadowbox is activated.
*
* @property {Boolean} activated
* @private
*/
var activated = false;
/**
* Keeps track of 4 floating values (x, y, start_x, & start_y) that are used
* in the drag calculations.
*
* @property {Object} drag
* @private
*/
var drag;
/**
* Holds the draggable element so we don't have to fetch it every time
* the mouse moves.
*
* @property {HTMLElement} draggable
* @private
*/
var draggable;
/**
* Keeps track of whether or not we're currently using the overlay
* background image to display the current gallery. We do this because we
* use different methods for fading the overlay in and out. The color fill
* overlay fades in and out nicely, but the image overlay stutters. By
* keeping track of the type of overlay in use, we don't have to check again
* what type of overlay we're using when it's time to get rid of it later.
*
* @property {Boolean} overlay_img_needed
* @private
*/
var overlay_img_needed;
/**
* These parameters for simple browser detection. Used in Ext.js.
*
* @ignore
*/
var ua = navigator.userAgent.toLowerCase();
var isStrict = document.compatMode == 'CSS1Compat',
isOpera = ua.indexOf("opera") > -1,
isIE = ua.indexOf('msie') > -1,
isIE7 = ua.indexOf('msie 7') > -1,
isBorderBox = isIE && !isStrict,
isSafari = (/webkit|khtml/).test(ua),
isSafari3 = isSafari && !!(document.evaluate),
isGecko = !isSafari && ua.indexOf('gecko') > -1,
isWindows = (ua.indexOf('windows') != -1 || ua.indexOf('win32') != -1),
isMac = (ua.indexOf('macintosh') != -1 || ua.indexOf('mac os x') != -1),
isLinux = (ua.indexOf('linux') != -1);
/**
* Do we need to hack the position to make Shadowbox appear fixed? We could
* hack this using CSS, but let's just get over all the hacks and let IE6
* users get what they deserve! Down with hacks! Hmm...now that I think
* about it, I should just flash all kinds of alerts and annoying popups on
* their screens, and then redirect them to some foreign spyware site that
* will upload a nasty virus...
*
* @property {Boolean} absolute_pos
* @private
*/
var absolute_pos = isIE && !isIE7;
/**
* Contains plugin support information. Each property of this object is a
* boolean indicating whether that plugin is supported.
*
* - fla: Flash player
* - qt: QuickTime player
* - wmp: Windows Media player
* - f4m: Flip4Mac plugin
*
* @property {Object} plugins
* @private
*/
var plugins = null;
// detect plugin support
if(navigator.plugins && navigator.plugins.length){
var detectPlugin = function(plugin_name){
var detected = false;
for (var i = 0, len = navigator.plugins.length; i < len; ++i){
if(navigator.plugins[i].name.indexOf(plugin_name) > -1){
detected = true;
break;
}
}
return detected;
};
var f4m = detectPlugin('Flip4Mac');
var plugins = {
fla: detectPlugin('Shockwave Flash'),
qt: detectPlugin('QuickTime'),
wmp: !f4m && detectPlugin('Windows Media'), // if it's Flip4Mac, it's not really WMP
f4m: f4m
};
}else{
var detectPlugin = function(plugin_name){
var detected = false;
try {
var axo = new ActiveXObject(plugin_name);
if(axo){
detected = true;
}
} catch (e) {}
return detected;
};
var plugins = {
fla: detectPlugin('ShockwaveFlash.ShockwaveFlash'),
qt: detectPlugin('QuickTime.QuickTime'),
wmp: detectPlugin('wmplayer.ocx'),
f4m: false
};
}
/**
* Applies all properties of e to o. This function is recursive so that if
* any properties of e are themselves objects, those objects will be applied
* to objects with the same key that may exist in o.
*
* @param {Object} o The original object
* @param {Object} e The extension object
* @return {Object} The original object with all properties
* of the extension object applied (deep)
* @private
*/
var apply = function(o, e){
if(o && e && typeof e == 'object'){
for (var p in e) o[p] = e[p];
}
return o;
};
/**
* Gets the height of the viewport in pixels. Note: This function includes
* scrollbars in Safari 3.
*
* @return {Number} The height of the viewport
* @public
* @static
*/
SL.getViewportHeight = function(){
var height = window.innerHeight; // Safari
var mode = document.compatMode;
if((mode || isIE) && !isOpera){
height = isStrict ? document.documentElement.clientHeight : document.body.clientHeight;
}
return height;
};
/**
* Gets the width of the viewport in pixels. Note: This function includes
* scrollbars in Safari 3.
*
* @return {Number} The width of the viewport
* @public
* @static
*/
SL.getViewportWidth = function(){
var width = window.innerWidth; // Safari
var mode = document.compatMode;
if(mode || isIE){
width = isStrict ? document.documentElement.clientWidth : document.body.clientWidth;
}
return width;
};
/**
* Gets the height of the document (body and its margins) in pixels.
*
* @return {Number} The height of the document
* @public
* @static
*/
SL.getDocumentHeight = function(){
var scrollHeight = isStrict ? document.documentElement.scrollHeight : document.body.scrollHeight;
return Math.max(scrollHeight, SL.getViewportHeight());
};
/**
* Gets the width of the document (body and its margins) in pixels.
*
* @return {Number} The width of the document
* @public
* @static
*/
SL.getDocumentWidth = function(){
var scrollWidth = isStrict ? document.documentElement.scrollWidth : document.body.scrollWidth;
return Math.max(scrollWidth, SL.getViewportWidth());
};
/**
* A utility function used by the fade functions to clear the opacity
* style setting of the given element. Required in some cases for IE.
* Based on Ext.Element's clearOpacity.
*
* @param {HTMLElement} el The DOM element
* @return void
* @private
*/
var clearOpacity = function(el){
if(isIE){
if(typeof el.style.filter == 'string' && (/alpha/i).test(el.style.filter)){
el.style.filter = '';
}
}else{
el.style.opacity = '';
el.style['-moz-opacity'] = '';
el.style['-khtml-opacity'] = '';
}
};
/**
* Fades the given element from 0 to the specified opacity.
*
* @param {HTMLElement} el The DOM element to fade
* @param {Number} endingOpacity The final opacity to animate to
* @param {Number} duration The duration of the animation
* (in seconds)
* @param {Function} callback A callback function to call
* when the animation completes
* @return void
* @private
*/
var fadeIn = function(el, endingOpacity, duration, callback){
if(options.animate){
SL.setStyle(el, 'opacity', 0);
el.style.visibility = 'visible';
SL.animate(el, {
opacity: { to: endingOpacity }
}, duration, function(){
if(endingOpacity == 1) clearOpacity(el);
if(typeof callback == 'function') callback();
});
}else{
if(endingOpacity == 1){
clearOpacity(el);
}else{
SL.setStyle(el, 'opacity', endingOpacity);
}
el.style.visibility = 'visible';
if(typeof callback == 'function') callback();
}
};
/**
* Fades the given element from its current opacity to 0.
*
* @param {HTMLElement} el The DOM element to fade
* @param {Number} duration The duration of the fade animation
* @param {Function} callback A callback function to call when
* the animation completes
* @return void
* @private
*/
var fadeOut = function(el, duration, callback){
var cb = function(){
el.style.visibility = 'hidden';
clearOpacity(el);
if(typeof callback == 'function') callback();
};
if(options.animate){
SL.animate(el, {
opacity: { to: 0 }
}, duration, cb);
}else{
cb();
}
};
/**
* Appends an HTML fragment to the given element.
*
* @param {String/HTMLElement} el The element to append to
* @param {String} html The HTML fragment to use
* @return {HTMLElement} The newly appended element
* @private
*/
var appendHTML = function(el, html){
el = SL.get(el);
if(el.insertAdjacentHTML){
el.insertAdjacentHTML('BeforeEnd', html);
return el.lastChild;
}
if(el.lastChild){
var range = el.ownerDocument.createRange();
range.setStartAfter(el.lastChild);
var frag = range.createContextualFragment(html);
el.appendChild(frag);
return el.lastChild;
}else{
el.innerHTML = html;
return el.lastChild;
}
};
/**
* Overwrites the HTML of the given element.
*
* @param {String/HTMLElement} el The element to overwrite
* @param {String} html The new HTML to use
* @return {HTMLElement} The new firstChild element
* @private
*/
var overwriteHTML = function(el, html){
el = SL.get(el);
el.innerHTML = html;
return el.firstChild;
};
/**
* Gets either the offsetHeight or the height of the given element plus
* padding and borders (when offsetHeight is not available). Based on
* Ext.Element's getComputedHeight.
*
* @return {Number} The computed height of the element
* @private
*/
var getComputedHeight = function(el){
var h = Math.max(el.offsetHeight, el.clientHeight);
if(!h){
h = parseInt(SL.getStyle(el, 'height'), 10) || 0;
if(!isBorderBox){
h += parseInt(SL.getStyle(el, 'padding-top'), 10)
+ parseInt(SL.getStyle(el, 'padding-bottom'), 10)
+ parseInt(SL.getStyle(el, 'border-top-width'), 10)
+ parseInt(SL.getStyle(el, 'border-bottom-width'), 10);
}
}
return h;
};
/**
* Gets either the offsetWidth or the width of the given element plus
* padding and borders (when offsetWidth is not available). Based on
* Ext.Element's getComputedWidth.
*
* @return {Number} The computed width of the element
* @private
*/
var getComputedWidth = function(el){
var w = Math.max(el.offsetWidth, el.clientWidth);
if(!w){
w = parseInt(SL.getStyle(el, 'width'), 10) || 0;
if(!isBorderBox){
w += parseInt(SL.getStyle(el, 'padding-left'), 10)
+ parseInt(SL.getStyle(el, 'padding-right'), 10)
+ parseInt(SL.getStyle(el, 'border-left-width'), 10)
+ parseInt(SL.getStyle(el, 'border-right-width'), 10);
}
}
return w;
};
/**
* Determines the player needed to display the file at the given URL. If
* the file type is not supported, the return value will be 'unsupported-*'
* where * will be the player abbreviation.
*
* @param {String} url The url of the file
* @return {String} The name of the player to use
* @private
*/
var getPlayerType = function(url){
if(RE.img.test(url)){
return 'img';
}
var this_domain = (domain_match = url.match(RE.domain))
? (document.domain == domain_match[1])
: false;
if(url.indexOf('#') > -1 && this_domain) return 'html';
var q_index = url.indexOf('?');
if(q_index > -1){
url = url.substring(0, q_index);
}
if(RE.swf.test(url)) return (plugins.fla) ? 'swf' : 'unsupported-swf';
if(RE.flv.test(url)) return (plugins.fla) ? 'flv' : 'unsupported-flv';
if(RE.qt.test(url)) return (plugins.qt) ? 'qt' : 'unsupported-qt';
if(RE.wmp.test(url)){
if(plugins.wmp){
return 'wmp';
}else if(plugins.f4m){
return 'qt';
}else{
if(isMac) return (plugins.qt ? 'unsupported-f4m' : 'unsupported-qtf4m');
return 'unsupported-wmp';
}
}else if(RE.qtwmp.test(url)){
if(plugins.qt){
return 'qt';
}else if(plugins.wmp){
return 'wmp';
}else{
return (isMac ? 'unsupported-qt' : 'unsupported-qtwmp');
}
}else if(!this_domain || RE.iframe.test(url)){
return 'iframe';
}
return 'unsupported';
};
/**
* Gets an array of information regarding the gallery for the given link
* element. The first element of this array will itself be an array of link
* objects that share the same gallery as the given link. The second element
* of the returned array will be the index in the first array of the given
* link element. This represents the starting point of the gallery. Note: We
* create copies of objects in the cache (using apply()) so that we don't
* permanently modify them later in setupGallery().
*
* @param {HTMLElement} link The link that was clicked
* @return {Array} The gallery information as detailed above
* @private
*/
var getGallery = function(link){
var key = link.shadowboxCacheKey;
var name = cache[key].gallery;
if(!name){
return [[apply({}, cache[key])], 0]; // single item, no gallery
}else{
var gallery = [], index;
for(var i = 0, len = cache.length; i < len; ++i){
if(key == i){
index = gallery.length; // key element found
gallery[gallery.length] = apply({}, cache[i]);
}else if(cache[i].gallery && cache[i].gallery == name){
gallery[gallery.length] = apply({}, cache[i]);
}
}
if(index == null) throw 'No Shadowbox cache item with index ' + key;
return [gallery, index];
}
};
/**
* Sets up the current gallery and checks to see if any of the gallery
* pieces are not supported by the user's browser. If there are, they will
* be handled according to the handleUnsupported option.
*
* @param {HTMLElement} link The link to set up the gallery for
* @return void
* @private
*/
var setupGallery = function(link){
// update current & current_gallery
var gallery_info = getGallery(link);
current_gallery = gallery_info[0];
current = gallery_info[1];
// are any media in the current gallery supported?
var match;
for(var i = 0; i < current_gallery.length; ++i){
if(match = RE.unsupported.exec(current_gallery[i].type)){ // handle unsupported elements
if(options.handleUnsupported == 'link'){
// generate a link to the appropriate plugin download page(s)
current_gallery[i].type = 'html';
var m;
switch(match[1]){
case 'qtwmp':
m = String.format(
options.text.errors.either,
options.errors.qt.url,
options.errors.qt.name,
options.errors.wmp.url,
options.errors.wmp.name);
break;
case 'qtf4m':
m = String.format(
options.text.errors.shared,
options.errors.qt.url,
options.errors.qt.name,
options.errors.f4m.url,
options.errors.f4m.name);
break;
default:
if(match[1] == 'swf' || match[1] == 'flv'){
match[1] = 'fla';
}
m = String.format(
options.text.errors.single,
options.errors[match[1]].url,
options.errors[match[1]].name);
}
current_gallery[i] = apply(current_gallery[i], {
height: options.initialHeight,
width: options.initialWidth,
html: Shadowbox.createHTML({
tag: 'div',
cls: 'shadowbox_message',
html: m
})
});
}else{
// remove the element from the gallery
current_gallery.splice(i, 1);
if(i < current) --current;
--i;
}
}else if(current_gallery[i].type == 'html'){ // handle inline elements
var match = RE.inline.exec(current_gallery[i].href);
if(match){
var el;
if(el = SL.get(match[1])){
current_gallery[i].html = el.innerHTML;
}else{
throw 'No element found with id ' + match[1];
}
}else{
throw 'No element id found for inline content';
}
}
}
};
/**
* Handles all clicks on links that have been set up to work with Shadowbox.
* Determines if the type of medium is supported. If so, stops the browser
* from navigating away and opens Shadowbox.
*
* @param {Event} ev The click event object
* @return void
* @private
*/
var handleClick = function(ev){
if(activated) return; // already open
activated = true;
// get link (anchor) element
var link;
if(typeof this.tagName == 'string' && this.tagName.toUpperCase() == 'A'){
link = this; // jQuery, Prototype, YUI
}else{
link = SL.getTarget(ev); // Ext
while(link.tagName.toUpperCase() != 'A' && link.parentNode){
link = link.parentNode;
}
}
// setup current gallery
setupGallery(link);
// if so, don't follow the link and open Shadowbox
if(current_gallery.length){
SL.preventDefault(ev);
openContent(link);
}
};
/**
* Hides the title bar and toolbar and populates them with the proper
* content.
*
* @return void
* @private
*/
var buildBars = function(){
var link = current_gallery[current];
if(!link) return; // nothing to build
// build the title
var title_i = SL.get('shadowbox_title_inner');
title_i.innerHTML = (link.title) ? link.title : '';
// empty the toolbar
var tool_i = SL.get('shadowbox_toolbar_inner');
tool_i.innerHTML = '';
// build the nav
if(options.displayNav){
tool_i.innerHTML = String.format(options.skin.close, options.text.close);
if(current_gallery.length > 1){
if(options.continuous){
// show both
appendHTML(tool_i, String.format(options.skin.next, options.text.next));
appendHTML(tool_i, String.format(options.skin.prev, options.text.prev));
}else{
// not last in the gallery, show the next link
if((current_gallery.length - 1) > current){
appendHTML(tool_i, String.format(options.skin.next, options.text.next));
}
// not first in the gallery, show the previous link
if(current > 0){
appendHTML(tool_i, String.format(options.skin.prev, options.text.prev));
}
}
}
}
// build the counter
if(current_gallery.length > 1 && options.displayCounter){
// append the counter div
var counter = '';
if(options.counterType == 'skip'){
for(var i = 0, len = current_gallery.length; i < len; ++i){
counter += '' + (i + 1) + '';
}
}else{
counter = (current + 1) + ' of ' + current_gallery.length;
}
appendHTML(tool_i, String.format(options.skin.counter, counter));
}
};
/**
* Hides the title and tool bars.
*
* @param {Function} callback A function to call on finish
* @return void
* @private
*/
var hideBars = function(callback){
var title_m = getComputedHeight(SL.get('shadowbox_title'));
var tool_m = 0 - getComputedHeight(SL.get('shadowbox_toolbar'));
var title_i = SL.get('shadowbox_title_inner');
var tool_i = SL.get('shadowbox_toolbar_inner');
if(options.animate && callback){
// animate the transition
SL.animate(title_i, {
marginTop: { to: title_m }
}, 0.2);
SL.animate(tool_i, {
marginTop: { to: tool_m }
}, 0.2, callback);
}else{
SL.setStyle(title_i, 'marginTop', title_m + 'px');
SL.setStyle(tool_i, 'marginTop', tool_m + 'px');
}
};
/**
* Shows the title and tool bars.
*
* @param {Function} callback A callback function to execute after
* the animation completes
* @return void
* @private
*/
var showBars = function(callback){
var title_i = SL.get('shadowbox_title_inner');
if(options.animate){
if(title_i.innerHTML != ''){
SL.animate(title_i, { marginTop: { to: 0 } }, 0.35);
}
SL.animate(SL.get('shadowbox_toolbar_inner'), {
marginTop: { to: 0 }
}, 0.35, callback);
}else{
if(title_i.innerHTML != ''){
SL.setStyle(title_i, 'margin-top', '0px');
}
SL.setStyle(SL.get('shadowbox_toolbar_inner'), 'margin-top', '0px');
callback();
}
};
/**
* Resets the class drag variable.
*
* @return void
* @private
*/
var resetDrag = function(){
drag = {
x: 0,
y: 0,
start_x: null,
start_y: null
};
};
/**
* Toggles the drag function on and off.
*
* @param {Boolean} on True to toggle on, false to toggle off
* @return void
* @private
*/
var toggleDrag = function(on){
if(on){
resetDrag();
// add drag layer to prevent browser dragging of actual image
var styles = [
'position:absolute',
'cursor:' + (isGecko ? '-moz-grab' : 'move')
];
// make drag layer transparent
styles.push(isIE ? 'background-color:#fff;filter:alpha(opacity=0)' : 'background-color:transparent');
appendHTML('shadowbox_body_inner', '');
SL.addEvent(SL.get('shadowbox_drag_layer'), 'mousedown', listenDrag);
}else{
var d = SL.get('shadowbox_drag_layer');
if(d){
SL.removeEvent(d, 'mousedown', listenDrag);
SL.remove(d);
}
}
};
/**
* Sets up a drag listener on the document. Called when the mouse button is
* pressed (mousedown).
*
* @param {mixed} ev The mousedown event
* @return void
* @private
*/
var listenDrag = function(ev){
drag.start_x = ev.clientX;
drag.start_y = ev.clientY;
draggable = SL.get('shadowbox_content');
SL.addEvent(document, 'mousemove', positionDrag);
SL.addEvent(document, 'mouseup', unlistenDrag);
if(isGecko) SL.setStyle(SL.get('shadowbox_drag_layer'), 'cursor', '-moz-grabbing');
};
/**
* Removes the drag listener. Called when the mouse button is released
* (mouseup).
*
* @return void
* @private
*/
var unlistenDrag = function(){
SL.removeEvent(document, 'mousemove', positionDrag);
SL.removeEvent(document, 'mouseup', unlistenDrag); // clean up
if(isGecko) SL.setStyle(SL.get('shadowbox_drag_layer'), 'cursor', '-moz-grab');
};
/**
* Positions an oversized image on drag.
*
* @param {mixed} ev The drag event
* @return void
* @private
*/
var positionDrag = function(ev){
var move_y = ev.clientY - drag.start_y;
drag.start_y = drag.start_y + move_y;
drag.y = Math.max(Math.min(0, drag.y + move_y), current_height - optimal_height); // y boundaries
SL.setStyle(draggable, 'top', drag.y + 'px');
var move_x = ev.clientX - drag.start_x;
drag.start_x = drag.start_x + move_x;
drag.x = Math.max(Math.min(0, drag.x + move_x), current_width - optimal_width); // x boundaries
SL.setStyle(draggable, 'left', drag.x + 'px');
};
/**
* Opens the content linked to by the given link element. By this point,
* setupGallery() should have been called either by the click handler or
* by Shadowbox.open() directly.
*
* @param {HTMLElement} link The link to open
* @return void
* @private
*/
var openContent = function(link){
// revert to default options
if(default_options){
options = default_options;
default_options = null; // erase for next time
}
// apply custom options
if(current_gallery[current].options){
default_options = apply({}, options); // store default options
options = apply(options, current_gallery[current].options);
}
// fire onOpen hook
if(options.onOpen && typeof options.onOpen == 'function'){
options.onOpen(link);
}
// display:block here helps with correct dimension calculations
SL.setStyle(SL.get('shadowbox'), 'display', 'block');
toggleTroubleElements(false);
var dims = getDimensions(options.initialHeight, options.initialWidth);
adjustHeight(dims.height, dims.top);
adjustWidth(dims.width);
hideBars(false);
// show the overlay and load the content
toggleOverlay(function(){
SL.setStyle(SL.get('shadowbox'), 'visibility', 'visible');
showLoading();
loadContent();
});
};
/**
* Removes old content and sets the new content of the Shadowbox.
*
* @param {Object} obj The content to set (appropriate to pass
* directly to Shadowbox.createHTML())
* @return {HTMLElement} The newly appended element (or null if
* none is provided)
* @private
*/
var setContent = function(obj){
var id = 'shadowbox_content';
var content = SL.get(id);
if(content){
// remove old content first
switch(content.tagName.toUpperCase()){
case 'OBJECT':
// if we're in a gallery (i.e. changing and there's a new
// object) we want the LAST link object
var link = current_gallery[(obj ? current - 1 : current)];
if(link.type == 'wmp' && isIE){
try{
shadowbox_content.controls.stop(); // stop the movie
shadowbox_content.URL = 'non-existent.wmv'; // force player refresh
window.shadowbox_content = function(){}; // remove from window
}catch(e){}
}else if(link.type == 'qt' && isSafari){
try{
document.shadowbox_content.Stop(); // stop QT movie
}catch(e){}
// stop QT audio stream for movies that have not yet loaded
content.innerHTML = '';
// console.log(document.shadowbox_content);
}
setTimeout(function(){ // using setTimeout prevents browser crashes with WMP
SL.remove(content);
}, 10);
break;
case 'IFRAME':
SL.remove(content);
if(isGecko) delete window.frames[id]; // needed for Firefox
break;
default:
SL.remove(content);
}
}
if(obj){
if(!obj.id) obj.id = id;
return appendHTML('shadowbox_body_inner', Shadowbox.createHTML(obj));
}
return null;
};
/**
* Loads the Shadowbox with the current piece.
*
* @return void
* @private
*/
var loadContent = function(){
var link = current_gallery[current];
if(!link) return; // invalid
buildBars();
switch(link.type){
case 'img':
// preload the image
preloader = new Image();
preloader.onload = function(){
// images default to image height and width
var h = link.height ? parseInt(link.height, 10) : preloader.height;
var w = link.width ? parseInt(link.width, 10) : preloader.width;
resizeContent(preloader.height, preloader.width, function(dims){
showBars(function(){
setContent({
tag: 'img',
height: dims.i_height,
width: dims.i_width,
src: link.href,
style: 'position:absolute'
});
if(dims.enableDrag && options.handleLgImages == 'drag'){
// listen for drag
toggleDrag(true);
SL.setStyle(SL.get('shadowbox_drag_layer'), {
height: dims.i_height + 'px',
width: dims.i_width + 'px'
});
}
finishContent();
});
});
preloader.onload = function(){}; // clear onload for IE
};
preloader.src = link.href;
break;
case 'swf':
case 'flv':
case 'qt':
case 'wmp':
var markup = Shadowbox.movieMarkup(link);
resizeContent(markup.height, markup.width, function(){
showBars(function(){
setContent(markup);
finishContent();
});
});
break;
case 'iframe':
// iframes default to full viewport height and width
var h = link.height ? parseInt(link.height, 10) : SL.getViewportHeight();
var w = link.width ? parseInt(link.width, 10) : SL.getViewportWidth();
var content = {
tag: 'iframe',
name: 'shadowbox_content',
height: '100%',
width: '100%',
frameborder: '0',
marginwidth: '0',
marginheight: '0',
scrolling: 'auto'
};
resizeContent(h, w, function(dims){
showBars(function(){
setContent(content);
var win = (isIE)
? SL.get('shadowbox_content').contentWindow
: window.frames['shadowbox_content'];
win.location = link.href;
finishContent();
});
});
break;
case 'html':
// HTML content defaults to full viewport height and width
var h = link.height ? parseInt(link.height, 10) : SL.getViewportHeight();
var w = link.width ? parseInt(link.width, 10) : SL.getViewportWidth();
var content = {
tag: 'div',
cls: 'html', /* give special class to make scrollable */
html: link.html
};
resizeContent(h, w, function(){
showBars(function(){
setContent(content);
finishContent();
});
});
break;
case 'unsupported':
// should never happen because links to unsupported media are
// removed or taken care of with an error message in setupGallery()
throw 'Content type cannot be determined for ' + link.href;
break;
}
// preload neighboring images
if(current_gallery.length > 0){
var next = current_gallery[current + 1];
if(!next){
next = current_gallery[0];
}
if(next.type == 'img'){
var preload_next = new Image();
preload_next.src = next.href;
}
var prev = current_gallery[current - 1];
if(!prev){
prev = current_gallery[current_gallery.length - 1];
}
if(prev.type == 'img'){
var preload_prev = new Image();
preload_prev.src = prev.href;
}
}
};
/**
* This function is used as the callback after the Shadowbox has been
* positioned, resized, and loaded with content.
*
* @return void
* @private
*/
var finishContent = function(){
var link = current_gallery[current];
if(!link) return; // invalid
hideLoading(function(){
listenKeyboard(true);
// fire onFinish hook
if(options.onFinish && typeof options.onFinish == 'function'){
options.onFinish(link.el);
}
});
};
/**
* Resizes and positions the content box using the given height and width.
* If the callback parameter is missing, the transition will not be
* animated. If the callback parameter is present, it will be passed the
* new calculated dimensions object as its first parameter. Note: the height
* and width here should represent the optimal height and width of the box.
*
* @param {Function} callback A callback function to use when the
* resize completes
* @return void
* @private
*/
var resizeContent = function(height, width, callback){
// update optimal height and width
optimal_height = height;
optimal_width = width;
var resizable = RE.resize.test(current_gallery[current].type);
var dims = getDimensions(optimal_height, optimal_width, resizable);
if(callback){
var cb = function(){ callback(dims); };
switch(options.animSequence){
case 'hw':
adjustHeight(dims.height, dims.top, true, function(){
adjustWidth(dims.width, true, cb);
});
break;
case 'wh':
adjustWidth(dims.width, true, function(){
adjustHeight(dims.height, dims.top, true, cb);
});
break;
default: // sync
adjustWidth(dims.width, true);
adjustHeight(dims.height, dims.top, true, cb);
}
}else{ // window resize
adjustWidth(dims.width, false);
adjustHeight(dims.height, dims.top, false);
// resize content images & flash in 'resize' mode
if(options.handleLgImages == 'resize' && resizable){
var content = SL.get('shadowbox_content');
if(content){ // may be animating, not present
content.height = dims.i_height;
content.width = dims.i_width;
}
}
}
};
/**
* Calculates the dimensions for Shadowbox, taking into account the borders,
* margins, and surrounding elements of the shadowbox_body. If the image
* is still to large for Shadowbox, and options.handleLgImages is 'resize',
* the resized dimensions will be returned (preserving the original aspect
* ratio). Otherwise, the originally calculated dimensions will be returned.
* The returned object will have the following properties:
*
* - height: The height to use for shadowbox_body_inner
* - width: The width to use for shadowbox
* - i_height: The height to use for resizable content
* - i_width: The width to use for resizable content
* - top: The top to use for shadowbox
* - enableDrag: True if dragging should be enabled (image is oversized)
*
* @param {Number} o_height The optimal height
* @param {Number} o_width The optimal width
* @param {Boolean} resizable True if the content is able to be
* resized. Defaults to false.
* @return {Object} The resize dimensions (see above)
* @private
*/
var getDimensions = function(o_height, o_width, resizable){
if(typeof resizable == 'undefined') resizable = false;
var height = o_height = parseInt(o_height);
var width = o_width = parseInt(o_width);
var shadowbox_b = SL.get('shadowbox_body');
// calculate the max height
var view_height = SL.getViewportHeight();
var extra_height = parseInt(SL.getStyle(shadowbox_b, 'border-top-width'), 10)
+ parseInt(SL.getStyle(shadowbox_b, 'border-bottom-width'), 10)
+ parseInt(SL.getStyle(shadowbox_b, 'margin-top'), 10)
+ parseInt(SL.getStyle(shadowbox_b, 'margin-bottom'), 10)
+ getComputedHeight(SL.get('shadowbox_title'))
+ getComputedHeight(SL.get('shadowbox_toolbar'))
+ (2 * options.viewportPadding);
if((height + extra_height) >= view_height){
height = view_height - extra_height;
}
// calculate the max width
var view_width = SL.getViewportWidth();
var extra_body_width = parseInt(SL.getStyle(shadowbox_b, 'border-left-width'), 10)
+ parseInt(SL.getStyle(shadowbox_b, 'border-right-width'), 10)
+ parseInt(SL.getStyle(shadowbox_b, 'margin-left'), 10)
+ parseInt(SL.getStyle(shadowbox_b, 'margin-right'), 10);
var extra_width = extra_body_width + (2 * options.viewportPadding);
if((width + extra_width) >= view_width){
width = view_width - extra_width;
}
// handle oversized images & flash
var enableDrag = false;
var i_height = o_height;
var i_width = o_width;
var handle = options.handleLgImages;
if(resizable && (handle == 'resize' || handle == 'drag')){
var change_h = (o_height - height) / o_height;
var change_w = (o_width - width) / o_width;
if(handle == 'resize'){
if(change_h > change_w){
width = Math.round((o_width / o_height) * height);
}else if(change_w > change_h){
height = Math.round((o_height / o_width) * width);
}
// adjust image height or width accordingly
i_width = width;
i_height = height;
}else{
// drag on oversized images only
var link = current_gallery[current];
if(link) enableDrag = link.type == 'img' && (change_h > 0 || change_w > 0);
}
}
return {
height: height,
width: width + extra_body_width,
i_height: i_height,
i_width: i_width,
top: ((view_height - (height + extra_height)) / 2) + options.viewportPadding,
enableDrag: enableDrag
};
};
/**
* Centers Shadowbox vertically in the viewport. Needs to be called on
* scroll in IE6 because it does not support fixed positioning.
*
* @return void
* @private
*/
var centerVertically = function(){
var shadowbox = SL.get('shadowbox');
var scroll = document.documentElement.scrollTop;
var s_top = scroll + Math.round((SL.getViewportHeight() - (shadowbox.offsetHeight || 0)) / 2);
SL.setStyle(shadowbox, 'top', s_top + 'px');
};
/**
* Adjusts the height of shadowbox_body_inner and centers Shadowbox
* vertically in the viewport.
*
* @param {Number} height The height of shadowbox_body_inner
* @param {Number} top The top of the Shadowbox
* @param {Boolean} animate True to animate the transition
* @param {Function} callback A callback to use when the animation completes
* @return void
* @private
*/
var adjustHeight = function(height, top, animate, callback){
height = parseInt(height);
// update current_height
current_height = height;
// adjust the height
var sbi = SL.get('shadowbox_body_inner');
if(animate && options.animate){
SL.animate(sbi, {
height: { to: height }
}, options.resizeDuration, callback);
}else{
SL.setStyle(sbi, 'height', height + 'px');
if(typeof callback == 'function') callback();
}
// manually adjust the top because we're using fixed positioning in IE6
if(absolute_pos){
// listen for scroll so we can adjust
centerVertically();
SL.addEvent(window, 'scroll', centerVertically);
// add scroll to top
top += document.documentElement.scrollTop;
}
// adjust the top
var shadowbox = SL.get('shadowbox');
if(animate && options.animate){
SL.animate(shadowbox, {
top: { to: top }
}, options.resizeDuration);
}else{
SL.setStyle(shadowbox, 'top', top + 'px');
}
};
/**
* Adjusts the width of shadowbox.
*
* @param {Number} width The width to use
* @param {Boolean} animate True to animate the transition
* @param {Function} callback A callback to use when the animation completes
* @return void
* @private
*/
var adjustWidth = function(width, animate, callback){
width = parseInt(width);
// update current_width
current_width = width;
var shadowbox = SL.get('shadowbox');
if(animate && options.animate){
SL.animate(shadowbox, {
width: { to: width }
}, options.resizeDuration, callback);
}else{
SL.setStyle(shadowbox, 'width', width + 'px');
if(typeof callback == 'function') callback();
}
};
/**
* Sets up a listener on the document for keystrokes.
*
* @param {Boolean} on True to enable the listner, false to turn
* it off
* @return void
* @private
*/
var listenKeyboard = function(on){
if(!options.enableKeys) return;
if(on){
document.onkeydown = handleKey;
}else{
document.onkeydown = '';
}
};
/**
* Asserts the given key or code is present in the array of valid keys.
*
* @param {Array} valid An array of valid keys and codes
* @param {String} key The character that was pressed
* @param {Number} code The key code that was pressed
* @return {Boolean} True if the key is valid
* @private
*/
var assertKey = function(valid, key, code){
return (valid.indexOf(key) != -1 || valid.indexOf(code) != -1);
};
/**
* A listener function that will act on a key pressed.
*
* @param {Event} e The event object
* @return void
* @private
*/
var handleKey = function(e){
var code = e ? e.which : event.keyCode;
var key = String.fromCharCode(code).toLowerCase();
if(assertKey(options.keysClose, key, code)){
Shadowbox.close();
}else if(assertKey(options.keysPrev, key, code)){
Shadowbox.previous();
}else if(assertKey(options.keysNext, key, code)){
Shadowbox.next();
}
};
/**
* Shows and hides elements that are troublesome for modal overlays.
*
* @param {Boolean} on True to show the elements, false otherwise
* @return void
* @private
*/
var toggleTroubleElements = function(on){
var vis = (on ? 'visible' : 'hidden');
var selects = document.getElementsByTagName('select');
for(i = 0, len = selects.length; i < len; ++i){
selects[i].style.visibility = vis;
}
var objects = document.getElementsByTagName('object');
for(i = 0, len = objects.length; i < len; ++i){
objects[i].style.visibility = vis;
}
var embeds = document.getElementsByTagName('embed');
for(i = 0, len = embeds.length; i < len; ++i){
embeds[i].style.visibility = vis;
}
};
/**
* Fills the Shadowbox with the loading skin.
*
* @return void
* @private
*/
var showLoading = function(){
var loading = SL.get('shadowbox_loading');
overwriteHTML(loading, String.format(options.skin.loading,
options.loadingImage,
options.text.loading,
options.text.cancel));
loading.style.visibility = 'visible';
};
/**
* Hides the Shadowbox loading skin.
*
* @param {Function} callback The callback function to call after
* hiding the loading skin
* @return void
* @private
*/
var hideLoading = function(callback){
var t = current_gallery[current].type;
var anim = (t == 'img' || t == 'html'); // fade on images & html
var loading = SL.get('shadowbox_loading');
if(anim){
fadeOut(loading, 0.35, callback);
}else{
loading.style.visibility = 'hidden';
callback();
}
};
/**
* Sets the size of the overlay to the size of the document.
*
* @return void
* @private
*/
var resizeOverlay = function(){
var overlay = SL.get('shadowbox_overlay');
SL.setStyle(overlay, {
height: '100%',
width: '100%'
});
SL.setStyle(overlay, 'height', SL.getDocumentHeight() + 'px');
if(!isSafari3){
// Safari3 includes vertical scrollbar in SL.getDocumentWidth()!
// Leave overlay width at 100% for now...
SL.setStyle(overlay, 'width', SL.getDocumentWidth() + 'px');
}
};
/**
* Used to determine if the pre-made overlay background image is needed
* instead of using the trasparent background overlay. A pre-made background
* image is used for all but image pieces in FF Mac because it has problems
* displaying correctly if the background layer is not 100% opaque. When
* displaying a gallery, if any piece in the gallery meets these criteria,
* the pre-made background image will be used.
*
* @return {Boolean} Whether or not an overlay image is needed
* @private
*/
var checkOverlayImgNeeded = function(){
if(!(isGecko && isMac)) return false;
var t;
for(var i = 0, len = current_gallery.length; i < len; ++i){
t = current_gallery[i].type;
if(t != 'img' && t != 'html') return true;
}
return false;
};
/**
* Activates (or deactivates) the Shadowbox overlay. If a callback function
* is provided, we know we're activating. Otherwise, deactivate the overlay.
*
* @param {Function} callback A callback to call after activation
* @return void
* @private
*/
var toggleOverlay = function(callback){
var overlay = SL.get('shadowbox_overlay');
if(overlay_img_needed == null){
overlay_img_needed = checkOverlayImgNeeded();
}
if(callback){
resizeOverlay(); // size the overlay before showing
if(overlay_img_needed){
SL.setStyle(overlay, {
visibility: 'visible',
backgroundColor: 'transparent',
backgroundImage: 'url(' + options.overlayBgImage + ')',
backgroundRepeat: 'repeat',
opacity: 1
});
callback();
}else{
SL.setStyle(overlay, {
visibility: 'visible',
backgroundColor: options.overlayColor,
backgroundImage: 'none'
});
fadeIn(overlay, options.overlayOpacity, options.fadeDuration,
callback);
}
}else{
if(overlay_img_needed){
SL.setStyle(overlay, 'visibility', 'hidden');
}else{
fadeOut(overlay, options.fadeDuration);
}
// reset for next time
overlay_img_needed = null;
}
};
/**
* Initializes the Shadowbox environment.
*
* @param {Object} opts The default options to use
* @return void
* @public
*/
Shadowbox.init = function(opts){
if(initialized) return; // don't initialize twice
options = apply(options, opts || {});
// add markup
appendHTML(document.body, options.skin.main);
// compile file type regular expressions here for speed
RE.img = new RegExp('\.(' + options.ext.img.join('|') + ')\s*$', 'i');
RE.qt = new RegExp('\.(' + options.ext.qt.join('|') + ')\s*$', 'i');
RE.wmp = new RegExp('\.(' + options.ext.wmp.join('|') + ')\s*$', 'i');
RE.qtwmp = new RegExp('\.(' + options.ext.qtwmp.join('|') + ')\s*$', 'i');
RE.iframe = new RegExp('\.(' + options.ext.iframe.join('|') + ')\s*$', 'i');
// handle window resize events
var id = null;
var resize = function(){
clearInterval(id);
id = null;
resizeOverlay();
resizeContent(optimal_height, optimal_width);
};
SL.addEvent(window, 'resize', function(){
if(activated){
// use event buffering to prevent jerky window resizing
if(id){
clearInterval(id);
id = null;
}
if(!id) id = setInterval(resize, 50);
}
});
// add a listener to the overlay
SL.addEvent(SL.get('shadowbox_overlay'), 'click', function(){
Shadowbox.close();
});
// adjust some positioning if needed
if(absolute_pos){
// give the container absolute positioning
SL.setStyle(SL.get('shadowbox_container'), 'position', 'absolute');
// give shadowbox_body "layout"...whatever that is
SL.setStyle('shadowbox_body', 'zoom', 1);
// need to listen to the container element because it covers the top
// half of the page
SL.addEvent(SL.get('shadowbox_container'), 'click', function(e){
var target = SL.getTarget(e);
if(target.id && target.id == 'shadowbox_container') Shadowbox.close();
});
}
// skip setup, will need to be done manually later
if(!options.skipSetup) Shadowbox.setup();
initialized = true;
};
/**
* Grabs all relevant anchor elements on the page and sets them up for use
* with Shadowbox. Note: This method may be used to reset Shadowbox if links
* on a page change after initialization.
*
* @param {Array} links An array (or array-like) list of link
* elements to set up
* @param {Object} opts Some options to use for the given links
* @return void
* @public
*/
Shadowbox.setup = function(links, opts){
// get links if none specified
if(!links){
var links = [];
var a = document.getElementsByTagName('a'), rel;
for(var i = 0, len = a.length; i < len; ++i){
rel = a[i].getAttribute('rel');
if(rel && RE.rel.test(rel)) links[links.length] = a[i];
}
}else if(!links.length){
links = [links]; // one link
}
var link, key;
for(var i = 0, len = links.length; i < len; ++i){
link = links[i];
if(typeof link.shadowboxCacheKey == 'undefined'){
// assign cache key expando
// use integer primitive so no memory leak in IE
link.shadowboxCacheKey = cache.length;
SL.addEvent(link, 'click', handleClick); // add listener
}
cache[link.shadowboxCacheKey] = this.buildCacheObj(link, opts);
}
};
/**
* Builds an object from the original element data. That will be stored in
* the cache. These objects contain (most of) the following keys:
*
* - id: the link's id
* - title: the linked file title
* - href: the linked file location
* - type: the linked file type
* - gallery: the gallery the file belongs to
* - height: the height of the linked file (only necessary for movies)
* - width: the width of the linked file (only necessary for movies)
* - options: custom options to use (optional)
*
* @param {HTMLElement} link The link to process
* @return {Object} An object representing the link
* @public
*/
Shadowbox.buildCacheObj = function(link, opts){
var href = link.href; // don't use getAttribute() here
var o = {
el: link,
title: link.getAttribute('title'),
href: href,
type: getPlayerType(href),
options: apply({}, opts || {})
};
// remove link-level options from top-level options
var opt, l_opts = ['height', 'width', 'gallery'];
for(var i = 0, len = l_opts.length; i < len; ++i){
opt = l_opts[i];
if(typeof o.options[opt] != 'undefined'){
o[opt] = o.options[opt];
delete o.options[opt];
}
}
// 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(RE.gallery);
if(match) o.gallery = escape(match[1]);
// other parameters
var params = rel.split(';');
for(var i = 0, len = params.length; i < len; ++i){
match = params[i].match(RE.param);
if(match){
if(match[1] == 'options'){
eval('o.options = apply(o.options, ' + match[2] + ')');
}else{
o[match[1]] = match[2];
}
}
}
}
return o;
};
/**
* Activates the Shadowbox with the given link element's content.
*
* @param {HTMLElement} link The link that was clicked
* @return void
* @public
*/
Shadowbox.open = function(link){
if(activated) return; // already open
activated = true;
// setup the current gallery
setupGallery(link);
// anything to display?
if(current_gallery.length){
openContent(link);
}else{
throw 'Shadowbox unable to open link, run setup() first';
}
};
/**
* Changes the view to the picture in the current gallery specified by
* num.
*
* @param {Number} num The gallery index to view
* @return void
* @public
*/
Shadowbox.change = function(num){
if(!current_gallery) return; // no current gallery
if(!current_gallery[num]){ // index does not exist
if(!options.continuous){
return;
}else{
num = (num < 0) ? (current_gallery.length - 1) : 0; // loop
}
}
// update current
current = num;
// stop listening for drag
toggleDrag(false);
// empty the content
setContent(null);
// turn this back on when done
listenKeyboard(false);
showLoading();
hideBars(loadContent);
};
/**
* Attempts to forward the gallery to the next image.
*
* @return {Boolean} True if the gallery changed to next item, false
* otherwise
* @public
*/
Shadowbox.next = function(){
return this.change(current + 1);
};
/**
* Attempts to rewind the gallery to the previous image.
*
* @return {Boolean} True if the gallery changed to previous item,
* false otherwise
* @public
*/
Shadowbox.previous = function(){
return this.change(current - 1);
};
/**
* Deactivates the Shadowbox.
*
* @return void
* @public
*/
Shadowbox.close = function(){
if(!activated) return; // already closed
// stop listening for keys
listenKeyboard(false);
// hide
SL.setStyle(SL.get('shadowbox'), {
display: 'none',
visibility: 'hidden'
});
// stop listening for scroll on IE
if(absolute_pos) SL.removeEvent(window, 'scroll', centerVertically);
// stop listening for drag
toggleDrag(false);
// empty the content
setContent(null);
// prevent old image requests from loading
if(preloader){
preloader.onload = function(){};
preloader = null;
}
// hide the overlay
toggleOverlay(false);
// turn on trouble elements
toggleTroubleElements(true);
// fire onClose hook
if(options.onClose && typeof options.onClose == 'function'){
var link = current_gallery[current];
if(link) options.onClose(link.el);
}
activated = false;
};
/**
* Generates the markup necessary to embed the movie file with the given
* link element. This markup will be browser-specific. Useful for generating
* the media test suite.
*
* @param {HTMLElement} link The link to the media file
* @return {Object} The proper markup to use (see above)
* @public
*/
Shadowbox.movieMarkup = function(link){
// movies default to 300x300 pixels
var h = link.height ? parseInt(link.height, 10) : 300;
var w = link.width ? parseInt(link.width, 10) : 300;
var autoplay = options.autoplayMovies;
var controls = options.showMovieControls;
if(link.options){
if(link.options.autoplayMovies != null){
autoplay = link.options.autoplayMovies;
}
if(link.options.showMovieControls != null){
controls = link.options.showMovieControls;
}
}
var markup = {
tag: 'object',
name: 'shadowbox_content'
};
switch(link.type){
case 'swf':
var dims = getDimensions(h, w, true);
h = dims.height;
w = dims.width;
markup.type = 'application/x-shockwave-flash';
markup.data = link.href;
markup.children = [
{ tag: 'param', name: 'movie', value: link.href }
];
break;
case 'flv':
autoplay = autoplay ? 'true' : 'false';
var showicons = 'false';
var a = h/w; // aspect ratio
if(controls){
showicons = 'true';
h += 20; // height of JW FLV player controller
}
var dims = getDimensions(h, h/a, true); // resize
h = dims.height;
w = (h-(controls?20:0))/a; // maintain aspect ratio
var flashvars = [
'file=' + link.href,
'height=' + h,
'width=' + w,
'autostart=' + autoplay,
'displayheight=' + (h - (controls?20:0)),
'showicons=' + showicons,
'backcolor=0x000000&frontcolor=0xCCCCCC&lightcolor=0x557722'
];
markup.type = 'application/x-shockwave-flash';
markup.data = options.flvPlayer;
markup.children = [
{ tag: 'param', name: 'movie', value: options.flvPlayer },
{ tag: 'param', name: 'flashvars', value: flashvars.join('&') },
{ tag: 'param', name: 'allowfullscreen', value: 'true' }
];
break;
case 'qt':
autoplay = autoplay ? 'true' : 'false';
if(controls){
controls = 'true';
h += 16; // height of QuickTime controller
}else{
controls = 'false';
}
markup.children = [
{ tag: 'param', name: 'src', value: link.href },
{ tag: 'param', name: 'scale', value: 'aspect' },
{ tag: 'param', name: 'controller', value: controls },
{ tag: 'param', name: 'autoplay', value: autoplay }
];
if(isIE){
markup.classid = 'clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B';
markup.codebase = 'http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0';
}else{
markup.type = 'video/quicktime';
markup.data = link.href;
}
break;
case 'wmp':
autoplay = autoplay ? 1 : 0;
markup.children = [
{ tag: 'param', name: 'autostart', value: autoplay }
];
if(isIE){
if(controls){
controls = 'full';
h += 70; // height of WMP controller in IE
}else{
controls = 'none';
}
// markup.type = 'application/x-oleobject';
markup.classid = 'clsid:6BF52A52-394A-11d3-B153-00C04F79FAA6';
markup.children[markup.children.length] = { tag: 'param', name: 'url', value: link.href };
markup.children[markup.children.length] = { tag: 'param', name: 'uimode', value: controls };
}else{
if(controls){
controls = 1;
h += 45; // height of WMP controller in non-IE
}else{
controls = 0;
}
markup.type = 'video/x-ms-wmv';
markup.data = link.href;
markup.children[markup.children.length] = { tag: 'param', name: 'showcontrols', value: controls };
}
break;
}
markup.height = h; // new height includes controller
markup.width = w;
return markup;
};
/**
* Creates an HTML string from an object representing HTML elements. Based
* on Ext.DomHelper's createHtml.
*
* @param {Object} obj The HTML definition object
* @return {String} An HTML string
* @public
*/
Shadowbox.createHTML = function(obj){
var html = '<' + obj.tag;
for(var attr in obj){
if(attr == 'tag' || attr == 'html' || attr == 'children') continue;
if(attr == 'cls'){
html += ' class="' + obj['cls'] + '"';
}else{
html += ' ' + attr + '="' + obj[attr] + '"';
}
}
if(RE.empty.test(obj.tag)){
html += '/>\n';
}else{
html += '>\n';
var cn = obj.children;
if(cn){
for(var i = 0, len = cn.length; i < len; ++i){
html += this.createHTML(cn[i]);
}
}
if(obj.html) html += obj.html;
html += '' + obj.tag + '>\n';
}
return html;
};
/**
* Gets an object that lists which plugins are supported on this platform.
* The keys of this object will be:
*
* - fla: Adobe Flash Player
* - qt: QuickTime Player
* - wmp: Windows Media Player
* - f4m: Flip4Mac QuickTime Player
*
* @return {Object} The plugins object
* @public
*/
Shadowbox.getPlugins = function(){
return plugins;
};
/**
* Gets the current options object in use.
*
* @return {Object} The options object
* @public
*/
Shadowbox.getOptions = function(){
return options;
};
/**
* Gets the current gallery item.
*
* @return {Object} The current gallery item
* @public
*/
Shadowbox.getCurrent = function(){
return current_gallery[current];
};
/**
* Gets the current version number of Shadowbox.
*
* @return {String} The current version
* @public
*/
Shadowbox.version = function(){
return version;
};
})();
/**
* Finds the index of the given object in this array.
*
* @param {mixed} o The object to search for
* @return {Number} The index of the given object
* @public
*/
Array.prototype.indexOf = Array.prototype.indexOf || function(o){
for (var i = 0, len = this.length; i < len; ++i){
if(this[i] == o) return i;
}
return -1;
};
/**
* Formats a string with the given parameters. The string for format must have
* placeholders that correspond to the numerical index of the arguments passed
* in surrounded by curly braces (e.g. 'Some {0} string {1}').
*
* @param {String} format The string to format
* @param ... The parameters to put inside the string
* @return {String} The string with the specified parameters
* replaced
* @public
* @static
*/
String.format = String.format || function(format){
var args = Array.prototype.slice.call(arguments, 1);
return format.replace(/\{(\d+)\}/g, function(m, i){
return args[i];
});
};
}