/*** The Shadowbox class.** This file is part of Shadowbox.** Shadowbox is an online media viewer application that supports all of the* web's most popular media publishing formats. Shadowbox is written entirely* in JavaScript and CSS and is highly customizable. Using Shadowbox, website* authors can showcase a wide assortment of media in all major browsers without* navigating users away from the linking page.** Shadowbox is released under version 3.0 of the Creative Commons Attribution-* Noncommercial-Share Alike license. This means that it is absolutely free* for personal, noncommercial use provided that you 1) make attribution to the* author and 2) release any derivative work under the same or a similar* license.** If you wish to use Shadowbox for commercial purposes, licensing information* can be found at http://mjijackson.com/shadowbox/.** @author      Michael J. I. Jackson <mjijackson@gmail.com>* @copyright   2007-2008 Michael J. I. Jackson* @license     http://creativecommons.org/licenses/by-nc-sa/3.0/* @version     SVN: $Id: shadowbox.js 108 2008-07-11 04:19:01Z 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://www.apple.com/quicktime/player/specs.html*-http://www.apple.com/quicktime/tutorials/embed2.html*-http://www.howtocreate.co.uk/wrongWithIE/?chapter=navigator.plugins*-http://msdn.microsoft.com/en-us/library/ms532969.aspx*-http://support.microsoft.com/kb/316992** @class       Shadowbox* @author      Michael J. I. Jackson <mjijackson@gmail.com>* @singleton*/(function(){/*** The current version of Shadowbox.** @var         String* @private*/
var version = '2.0';/*** Contains the default options for Shadowbox.** @var         Object* @private*/
var options = {/*** Enable all animations besides fades.** @var     Boolean*/
animate:            true,/*** Enable fade animations.** @var     Boolean*/
animateFade:        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:       'wh',/*** The path to flvplayer.swf.** @var     String*/
flvPlayer:          'flvplayer.swf',/*** Listen to the overlay for clicks. If the user clicks the overlay,* it will trigger Shadowbox.close().** @var     Boolean*/
modal:              false,/*** The color to use for the modal overlay (in hex).** @var     String*/
overlayColor:       '#000',/*** The opacity to use for the modal overlay.** @var     Number*/
overlayOpacity:     0.8,/*** The default background color to use for Flash movies (in hex).** @var     String*/
flashBgColor:       '#000000',/*** Automatically play movies.** @var     Boolean*/
autoplayMovies:     true,/*** Enable movie controllers on movie players.** @var     Boolean*/
showMovieControls:  true,/*** A delay (in seconds) to use for slideshows. If set to anything other* than 0, this value determines an interval at which Shadowbox will* automatically proceed to the next piece in the gallery.** @var     Number*/
slideshowDelay:     0,/*** The duration of the resizing animations (in seconds).** @var     Number*/
resizeDuration:     0.55,/*** The duration of the fading animations (in seconds).** @var     Number*/
fadeDuration:       0.35,/*** Show the navigation controls.** @var     Boolean*/
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:         false,/*** Display the gallery counter.** @var     Boolean*/
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:        'default',/*** Limits the number of counter links that will be displayed in a "skip"* style counter. If the actual number of gallery elements is greater* than this value, the counter will be restrained to the elements* immediately preceeding and following the current element.** @var     Number*/
counterLimit:       10,/*** 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:    20,/*** How to handle content that is too large to display in its entirety* (and is resizable). A value of 'resize' will resize the content while* preserving aspect ratio and display it at the smaller resolution. If* the content is an image, a value of 'drag' will display the image at* its original resolution but it will be draggable within Shadowbox. A* value of 'none' will display the content at its original resolution* but it may be cropped.** @var     String*/
handleOversize:     'resize',/*** An exception handling function that will be called whenever* Shadowbox should throw an exception. Will be passed the error* message as its first argument.** @var     Function*/
handleException:    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:  'link',/*** The initial height of Shadowbox (in pixels).** @var     Number*/
initialHeight:      160,/*** The initial width of Shadowbox (in pixels).** @var     Number*/
initialWidth:       320,/*** Enable keyboard control.** @var     Boolean*/
enableKeys:         true,/*** A hook function to be fired when Shadowbox opens. The single argument* will be the current gallery element.** @var     Function*/
onOpen:             null,/*** A hook function to be fired when Shadowbox finishes loading its* content. The single argument will be the current gallery element on* display.** @var     Function*/
onFinish:           null,/*** A hook function to be fired when Shadowbox changes from one gallery* element to the next. The single argument will be the current gallery* element that is about to be displayed.** @var     Function*/
onChange:           null,/*** A hook function that will be fired when Shadowbox closes. The single* argument will be the gallery element most recently displayed.** @var     Function*/
onClose:            null,/*** Skips calling Shadowbox.setup() in init(). This means that it must* be called later manually.** @var     Boolean*/
skipSetup:          false,/*** An object containing names of plugins and links to their respective* download pages.** @var     Object*/
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'}},/*** A map of players to the file extensions they support. Each member of* this object is the name of a player (with one exception), whose value* is an array of file extensions that player will "play". The one* exception to this rule is the "qtwmp" member, which contains extensions* that may be played using either QuickTime or Windows Media Player.**-img: Image file extensions*-swf: Flash SWF file extensions*-flv: Flash video file extensions (will be played by JW FLV player)*-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** IMPORTANT: If this object is to be modified, it must be copied in its* entirety and tweaked because it is not merged recursively with the* default. Also, any modifications must be passed into Shadowbox.init* for speed reasons.** @var     Object      ext*/
ext:     {
img:        ['png', 'jpg', 'jpeg', 'gif', 'bmp'],
swf:        ['swf'],
flv:        ['flv'],
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']}};
var SB = Shadowbox;
var SL = SB.lib;/*** 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.** @var         Object* @private*/
var default_options;/*** An object containing some regular expressions we'll need later. Compiled* up front for speed.** @var         Object* @private*/
var RE = {
domain:         /:\/\/(.*?)[:\/]/, // domain prefix
inline:         /#(.+)$/, // inline element id
rel:            /^(light|shadow)box/i, // rel attribute format
gallery:        /^(light|shadow)box\[(.*?)\]/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.** @var         Array* @private*/
var cache = [];/*** An array containing the gallery objects currently being viewed. In the* case of non-gallery items, this will only hold one object.** @var         Array* @private*/
var gallery;/*** The array index of the current gallery that is currently being viewed.** @var         Number* @private*/
var current;/*** The current content object.** @var         Object* @private*/
var content;/*** The id to use for content objects.** @var         String* @private*/
var content_id = 'shadowbox_content';/*** Holds the current dimensions of Shadowbox as calculated by* setDimensions(). Contains the following properties:**-height: The total height of #shadowbox*-width: The total width of #shadowbox*-inner_h: The height of #shadowbox_body*-inner_w: The width of #shadowbox_body*-top: The top to use for #shadowbox*-resize_h: The height to use for resizable content*-resize_w: The width to use for resizable content*-drag: True if dragging should be enabled (oversized image)** @var         Object* @private*/
var dims;/*** Keeps track of whether or not Shadowbox has been initialized. We never* want to initialize twice.** @var         Boolean* @private*/
var initialized = false;/*** Keeps track of whether or not Shadowbox is activated.** @var         Boolean* @private*/
var activated = false;/*** The timeout id for the slideshow transition function.** @var         Number* @private*/
var slide_timer;/*** Keeps track of the time at which the current slideshow frame was* displayed.** @var         Number* @private*/
var slide_start;/*** The delay on which the next slide will display.** @var         Number* @private*/
var slide_delay = 0;/*** These parameters for simple browser detection. Adapted from Ext.js.** @var         Object* @private*/
var ua = navigator.userAgent.toLowerCase();
var client = {
isStrict:   document.compatMode == 'CSS1Compat',
isOpera:    ua.indexOf('opera') >-1,
isIE:       ua.indexOf('msie') >-1,
isIE7:      ua.indexOf('msie 7') >-1,
isSafari:   /webkit|khtml/.test(ua),
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};
client.isBorderBox = client.isIE && !client.isStrict;
client.isSafari3 = client.isSafari && !!(document.evaluate);
client.isGecko = ua.indexOf('gecko') !=-1 && !client.isSafari;/*** You're not sill using IE6 are you?** @var         Boolean* @private*/
var ltIE7 = client.isIE && !client.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** @var         Object* @private*/
var plugins;
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');
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;};
plugins = {
fla:    detectPlugin('ShockwaveFlash.ShockwaveFlash'),
qt:     detectPlugin('QuickTime.QuickTime'),
wmp:    detectPlugin('wmplayer.ocx'),
f4m:    false};}/*** Applies all properties of e to 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* @private*/
var apply = function(o, e){
for(var p in e) o[p] = e[p];
return o;};/*** Determines if the given object is an anchor/area element.** @param   mixed       el      The object to check* @return  Boolean             True if the object is a link element* @private*/
var isLink = function(el){
return el && typeof el.tagName == 'string' && (el.tagName.toUpperCase() == 'A' || el.tagName.toUpperCase() == 'AREA');};/*** 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 h = window.innerHeight; // Safari
var mode = document.compatMode;
if((mode || client.isIE) && !client.isOpera){
h = client.isStrict ? document.documentElement.clientHeight : document.body.clientHeight;}
return h;};/*** 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 w = window.innerWidth; // Safari
var mode = document.compatMode;
if(mode || client.isIE){
w = client.isStrict ? document.documentElement.clientWidth : document.body.clientWidth;}
return w;};/*** 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* @static*/
SL.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+= '/>';}else{
html+= '>';
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+'>';}
return html;};/*** Easing function used for animations. Based on a cubic polynomial.** @param   Number      x       The state of the animation (% complete)* @return  Number              The adjusted easing value* @private* @static*/
var ease = function(x){
return 1+Math.pow(x-1, 3);};/*** Animates any numeric (not color) style of the given element from its* current state to the given value. Defaults to using pixel-based* measurements.** @param   HTMLElement     el      The DOM element to animate* @param   String          p       The property to animate (in camelCase)* @param   mixed           to      The value to animate to* @param   Number          d       The duration of the animation (in*                                  seconds)* @param   Function        cb      A callback function to call when the*                                  animation completes* @return  void* @private* @static*/
var animate = function(el, p, to, d, cb){
var from = parseFloat(SL.getStyle(el, p));
if(isNaN(from)) from = 0;
if(from == to){
if(typeof cb == 'function') cb();
return; // nothing to animate}
var delta = to-from;
var op = p == 'opacity';
var unit = op ? '' : 'px'; // default unit is px
var fn = function(ease){
SL.setStyle(el, p, from+ease * delta+unit);};
if(!options.animate && !op || op && !options.animateFade){
fn(1);
if(typeof cb == 'function') cb();
return;}
d *= 1000; // convert to milliseconds
var begin = new Date().getTime();
var end = begin+d;
var timer = setInterval(function(){
var time = new Date().getTime();
if(time >= end){ // end of animation
clearInterval(timer);
fn(1);
if(typeof cb == 'function') cb();}else{
fn(ease((time-begin) / d));}}, 10); // 10 ms interval is minimum on WebKit};/*** A utility function used by the fade functions to clear the opacity* style setting of the given element. Required in some cases for IE.** @param   HTMLElement     el      The DOM element* @return  void* @private*/
var clearOpacity = function(el){
var s = el.style;
if(client.isIE){
if(typeof s.filter == 'string' && (/alpha/i).test(s.filter)){
s.filter = s.filter.replace(/[\w\.]*alpha\(.*?\);?/i, '');}}else{
s.opacity = '';
s['-moz-opacity'] = '';
s['-khtml-opacity'] = '';}};/*** Gets the computed height of the given element, including padding and* borders.** @param   HTMLElement     el  The element* @return  Number              The computed height of the element* @private*//*---------->> helper method. Add it somewhere to the Shadowbox class.... */
var toInteger = function(value){
if(isNaN(value))
return 0;
return value;}
var getComputedHeight = function(el){
var h = Math.max(el.offsetHeight, el.clientHeight);
if(!h){
h = parseInt(SL.getStyle(el, 'height'), 10) || 0;
if(!client.isBorderBox){/*--------->>> this is original-comment it out
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);*//*----------->>> this is patched addition */
h+= toInteger(parseInt(SL.getStyle(el, 'padding-top'), 10))+toInteger(parseInt(SL.getStyle(el, 'padding-bottom'), 10))+toInteger(parseInt(SL.getStyle(el, 'border-top-width'), 10))+toInteger(parseInt(SL.getStyle(el, 'border-bottom-width'), 10));}}
return h;};/*** 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'.* If the file type is not supported but the correct player can be* determined, the return value will be 'unsupported-*' where * will be the* player abbreviation (e.g. 'qt' = QuickTime).** @param   String          url     The url of the file* @return  String                  The name of the player to use* @private*/
var getPlayer = function(url){
var m = url.match(RE.domain);
var d = m && document.domain == m[1]; // same domain
if(url.indexOf('#') >-1 && d) return 'inline';
var q = url.indexOf('?');
if(q >-1) url = url.substring(0, q); // strip query string for player detection purposes
if(RE.img.test(url)) return 'img';
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';
if(plugins.f4m) return 'qt';
if(client.isMac) return plugins.qt ? 'unsupported-f4m' : 'unsupported-qtf4m';
return 'unsupported-wmp';}else if(RE.qtwmp.test(url)){
if(plugins.qt) return 'qt';
if(plugins.wmp) return 'wmp';
return client.isMac ? 'unsupported-qt' : 'unsupported-qtwmp';}else if(!d || RE.iframe.test(url)){
return 'iframe';}
return 'unsupported'; // same domain, not supported};/*** Handles all clicks on links that have been set up to work with Shadowbox* and cancels the default event behavior when appropriate.** @param   {Event}         ev          The click event object* @return  void* @private*/
var handleClick = function(ev){
var link;
if(isLink(this)){
link = this; // jQuery, Prototype, YUI}else{
link = SL.getTarget(ev); // Ext, standalone
while(!isLink(link) && link.parentNode){
link = link.parentNode;}}
if(link){
SB.open(link);
if(gallery.length) SL.preventDefault(ev); // stop event}};/*** Toggles the display of the nav control with the given id on and off.** @param   String      id      The id of the navigation control* @param   Boolean     on      True to toggle on, false to toggle off* @return  void* @private*/
var toggleNav = function(id, on){
var el = SL.get('shadowbox_nav_'+id);
if(el) el.style.display = on ? '' : 'none';};/*** Builds the content for the title and information bars.** @param   Function    cb      A callback function to execute after the*                              bars are built* @return  void* @private*/
var buildBars = function(cb){
var obj = gallery[current];
var title_i = SL.get('shadowbox_title_inner');
title_i.innerHTML = obj.title || '';
var nav = SL.get('shadowbox_nav');
if(nav){
var c, n, pl, pa, p;
if(options.displayNav){
c = true;
var len = gallery.length;
if(len > 1){
if(options.continuous){
n = p = true; // show both}else{
n = (len-1) > current; // not last in gallery, show next
p = current > 0; // not first in gallery, show previous}}
if(options.slideshowDelay > 0 && hasNext()){
pa = slide_timer != 'paused';
pl = !pa;}}else{
c = n = pl = pa = p = false;}
toggleNav('close', c);
toggleNav('next', n);
toggleNav('play', pl);
toggleNav('pause', pa);
toggleNav('previous', p);}
var counter = SL.get('shadowbox_counter');
if(counter){
var co = '';
if(options.displayCounter && gallery.length > 1){
if(options.counterType == 'skip'){
var i = 0, len = gallery.length, end = len;
var limit = parseInt(options.counterLimit);
if(limit < len){ // support large galleries
var h = Math.round(limit / 2);
i = current-h;
if(i < 0) i+= len;
end = current+(limit-h);
if(end > len) end-= len;}
while(i != end){
if(i == len) i = 0;
co+= '<a onclick="Shadowbox.change('+i+');"';
if(i == current) co+= ' class="shadowbox_counter_current"';
co+= '>'+(++i)+'</a>';}}else{ // default
co = (current+1)+' '+SB.LANG.of+' '+len;}}
counter.innerHTML = co;}
cb();};/*** Hides the title and info bars.** @param   Boolean     anim    True to animate the transition* @param   Function    cb      A callback function to execute after the*                              animation completes* @return  void* @private*/
var hideBars = function(anim, cb){
var obj = gallery[current];
var title = SL.get('shadowbox_title');
var info = SL.get('shadowbox_info');
var title_i = SL.get('shadowbox_title_inner');
var info_i = SL.get('shadowbox_info_inner');
var fn = function(){
buildBars(cb);};
var title_h = getComputedHeight(title);
var info_h = getComputedHeight(info) *-1;
if(anim){
animate(title_i, 'margin-top', title_h, 0.35);
animate(info_i, 'margin-top', info_h, 0.35, fn);}else{
SL.setStyle(title_i, 'margin-top', title_h+'px');
SL.setStyle(info_i, 'margin-top', info_h+'px');
fn();}};/*** Shows the title and info bars.** @param   Function    cb      A callback function to execute after the*                              animation completes* @return  void* @private*/
var showBars = function(cb){
var title_i = SL.get('shadowbox_title_inner');
var info_i = SL.get('shadowbox_info_inner');
var t = title_i.innerHTML != ''; // is there a title to display?
if(t) animate(title_i, 'margin-top', 0, 0.35);
animate(info_i, 'margin-top', 0, 0.35, cb);};/*** Loads the Shadowbox with the current piece.** @return  void* @private*/
var loadContent = function(){
var obj = gallery[current];
if(!obj) return; // invalid
var changing = false;
if(content){
content.remove(); // remove old content first
changing = true; // changing from some previous content}
var p = obj.player == 'inline' ? 'html' : obj.player;
if(typeof SB[p] != 'function'){
SB.raise('Unknown player '+obj.player);}
content = new SB[p](content_id, obj); // instantiate new content object
listenKeys(false); // disable the keyboard temporarily
toggleLoading(true);
hideBars(changing, function(){ // if changing, animate the bars transition
if(!content) return;
if(!changing){
SL.get('shadowbox').style.display = '';}
var fn = function(){
resizeContent(function(){
if(!content) return;
showBars(function(){
if(!content) return;
SL.get('shadowbox_body_inner').innerHTML = SL.createHTML(content.markup(dims));
toggleLoading(false, function(){
if(!content) return;
if(typeof content.onLoad == 'function'){
content.onLoad(); // call onLoad callback if present}
if(options.onFinish && typeof options.onFinish == 'function'){
options.onFinish(gallery[current]); // fire onFinish handler}
if(slide_timer != 'paused'){
SB.play(); // kick off next slide}
listenKeys(true); // re-enable the keyboard});});});};
if(typeof content.ready != 'undefined'){ // does the object have a ready property?
var id = setInterval(function(){ // if so, wait for the object to be ready
if(content){
if(content.ready){
clearInterval(id); // clean up
id = null;
fn();}}else{ // content has been removed
clearInterval(id);
id = null;}}, 100);}else{
fn();}});
if(gallery.length > 1){
var next = gallery[current+1] || gallery[0];
if(next.player == 'img'){
var a = new Image();
a.src = next.content;}
var prev = gallery[current-1] || gallery[gallery.length-1];
if(prev.player == 'img'){
var b = new Image();
b.src = prev.content;}}};/*** Calculates the dimensions for Shadowbox, taking into account the borders* and surrounding elements of the shadowbox_body. If the height/width* combination is too large for Shadowbox and handleOversize option is set* to 'resize', the resized dimensions will be returned (preserving the* original aspect ratio). Otherwise, the originally calculated dimensions* will be used. Stores all dimensions in the private dims variable.** @param   Number      height      The content player height* @param   Number      width       The content player width* @param   Boolean     resizable   True if the content is able to be*                                  resized. Defaults to false.* @return  void* @private*/
var setDimensions = function(height, width, resizable){
resizable = resizable || false;
var sb = SL.get('shadowbox_body');
var h = height = parseInt(height);
var w = width = parseInt(width);
var view_h = SL.getViewportHeight();
var view_w = SL.getViewportWidth();
var border_w = parseInt(SL.getStyle(sb, 'border-left-width'), 10)+parseInt(SL.getStyle(sb, 'border-right-width'), 10);
var extra_w = border_w+2 * options.viewportPadding;
if(w+extra_w >= view_w){
w = view_w-extra_w;}
var border_h = parseInt(SL.getStyle(sb, 'border-top-width'), 10)+parseInt(SL.getStyle(sb, 'border-bottom-width'), 10);
var bar_h = getComputedHeight(SL.get('shadowbox_title'))+getComputedHeight(SL.get('shadowbox_info'));
var extra_h = border_h+2 * options.viewportPadding+bar_h;
if(h+extra_h >= view_h){
h = view_h-extra_h;}
var drag = false;
var resize_h = height;
var resize_w = width;
var handle = options.handleOversize;
if(resizable && (handle == 'resize' || handle == 'drag')){
var change_h = (height-h) / height;
var change_w = (width-w) / width;
if(handle == 'resize'){
if(change_h > change_w){
w = Math.round((width / height) * h);}else if(change_w > change_h){
h = Math.round((height / width) * w);}
resize_w = w;
resize_h = h;}else{
var link = gallery[current];
if(link) drag = link.player == 'img' && (change_h > 0 || change_w > 0);}}
dims = {
height:     h+border_h+bar_h,
width:      w+border_w,
inner_h:    h,
inner_w:    w,
top:        (view_h-(h+extra_h)) / 2+options.viewportPadding,
resize_h:   resize_h,
resize_w:   resize_w,
drag:       drag};};/*** Resizes Shadowbox to the given height and width. If the callback* parameter is given, the transition will be animated and the callback* function will be called when the animation completes. Note: The private* content variable must be updated before calling this function.** @param   Function    cb      A callback function to execute after the*                              content has been resized* @return  void* @private*/
var resizeContent = function(cb){
if(!content) return; // no content
setDimensions(content.height, content.width, content.resizable);
if(cb){
switch(options.animSequence){
case 'hw':
adjustHeight(dims.inner_h, dims.top, true, function(){
adjustWidth(dims.width, true, cb);});
break;
case 'wh':
adjustWidth(dims.width, true, function(){
adjustHeight(dims.inner_h, dims.top, true, cb);});
break;
case 'sync':
default:
adjustWidth(dims.width, true);
adjustHeight(dims.inner_h, dims.top, true, cb);}}else{ // window resize
adjustWidth(dims.width, false);
adjustHeight(dims.inner_h, dims.top, false);
var c = SL.get(content_id);
if(c){
if(content.resizable && options.handleOversize == 'resize'){
c.height = dims.resize_h;
c.width = dims.resize_w;}
if(gallery[current].player == 'img' && options.handleOversize == 'drag'){
var top = parseInt(SL.getStyle(c, 'top'));
if(top+content.height < dims.inner_h){
SL.setStyle(c, 'top', dims.inner_h-content.height+'px');}
var left = parseInt(SL.getStyle(c, 'left'));
if(left+content.width < dims.inner_w){
SL.setStyle(c, 'left', dims.inner_w-content.width+'px');}}}}};/*** Adjusts the height of #shadowbox_body and centers #shadowbox vertically* in the viewport.** @param   Number      height      The height to use for #shadowbox_body* @param   Number      top         The top to use for #shadowbox* @param   Boolean     anim        True to animate the transition* @param   Function    cb          A callback to use when the animation*                                  completes* @return  void* @private*/
var adjustHeight = function(height, top, anim, cb){
height = parseInt(height);
var sb = SL.get('shadowbox_body');
if(anim){
animate(sb, 'height', height, options.resizeDuration);}else{
SL.setStyle(sb, 'height', height+'px');}
var s = SL.get('shadowbox');
if(anim){
animate(s, 'top', top, options.resizeDuration, cb);}else{
SL.setStyle(s, 'top', top+'px');
if(typeof cb == 'function') cb();}};/*** Adjusts the width of #shadowbox.** @param   Number      width       The width to use for #shadowbox* @param   Boolean     anim        True to animate the transition* @param   Function    cb          A callback to use when the animation*                                  completes* @return  void* @private*/
var adjustWidth = function(width, anim, cb){
width = parseInt(width);
var s = SL.get('shadowbox');
if(anim){
animate(s, 'width', width, options.resizeDuration, cb);}else{
SL.setStyle(s, 'width', width+'px');
if(typeof cb == 'function') cb();}};/*** Sets up a listener on the document for keystrokes.** @param   Boolean     on      True to enable the listener, false to turn*                              it off* @return  void* @private*/
var listenKeys = function(on){
if(!options.enableKeys) return;
SL[(on ? 'add' : 'remove')+'Event'](document, 'keydown', handleKey);};/*** A listener function that is fired when a key is pressed.** @param   mixed       e       The event object* @return  void* @private*/
var handleKey = function(e){
var code = SL.keyCode(e);
SL.preventDefault(e);
if(code == 81 || code == 88 || code == 27){ // q, x, or esc
SB.close();}else if(code == 37){ // left arrow
SB.previous();}else if(code == 39){ // right arrow
SB.next();}else if(code == 32){ // space bar
SB[(typeof slide_timer == 'number' ? 'pause' : 'play')]();}};/*** Toggles the visibility of the "loading" layer.** @param   Boolean     on      True to toggle on, false to toggle off* @param   Function    cb      The callback function to call when toggling*                              completes* @return  void* @private*/
var toggleLoading = function(on, cb){
var loading = SL.get('shadowbox_loading');
if(on){
loading.style.display = '';
if(typeof cb == 'function') cb();}else{
var p = gallery[current].player;
var anim = (p == 'img' || p == 'html'); // fade on images & html
var fn = function(){
loading.style.display = 'none';
clearOpacity(loading);
if(typeof cb == 'function') cb();};
if(anim){
animate(loading, 'opacity', 0, options.fadeDuration, fn);}else{
fn();}}};/*** Sets the top of the container element. This is only necessary in IE6* where the container uses absolute positioning instead of fixed.** @return  void* @private*/
var fixTop = function(){
SL.get('shadowbox_container').style.top = document.documentElement.scrollTop+'px';};/*** Sets the height of the overlay element to the full viewport height. This* is only necessary in IE6 where the container uses absolute positioning* instead of fixed, thus restricting the size of the overlay element.** @return  void* @private*/
var fixHeight = function(){
SL.get('shadowbox_overlay').style.height = SL.getViewportHeight()+'px';};/*** Determines if there is a next piece to display in the current gallery.** @return  bool            True if there is another piece, false otherwise* @private*/
var hasNext = function(){
return gallery.length > 1 && (current != gallery.length-1 || options.continuous);};/*** Toggles the visibility of #shadowbox_container and sets its size (if on* IE6). Also toggles the visibility of elements (<select>, <object>, and* <embed>) that are troublesome for semi-transparent modal overlays. IE has* problems with <select> elements, while Firefox has trouble with* <object>s.** @param   Function    cb      A callback to call after toggling on, absent*                              when toggling off* @return  void* @private*/
var toggleVisible = function(cb){
var els, v = (cb) ? 'hidden' : 'visible';
var hide = ['select', 'object', 'embed']; // tags to hide
for(var i = 0; i < hide.length;++i){
els = document.getElementsByTagName(hide[i]);
for(var j = 0, len = els.length; j < len;++j){
els[j].style.visibility = v;}}
var so = SL.get('shadowbox_overlay');
var sc = SL.get('shadowbox_container');
var sb = SL.get('shadowbox');
if(cb){
SL.setStyle(so, {
backgroundColor: options.overlayColor,
opacity: 0});
if(!options.modal) SL.addEvent(so, 'click', SB.close);
if(ltIE7){
fixTop();
fixHeight();
SL.addEvent(window, 'scroll', fixTop);}
sb.style.display = 'none'; // will be cleared in loadContent()
sc.style.visibility = 'visible';
animate(so, 'opacity', parseFloat(options.overlayOpacity), options.fadeDuration, cb);}else{
SL.removeEvent(so, 'click', SB.close);
if(ltIE7) SL.removeEvent(window, 'scroll', fixTop);
sb.style.display = 'none';
animate(so, 'opacity', 0, options.fadeDuration, function(){
sc.style.visibility = 'hidden';
sb.style.display = '';
clearOpacity(so);});}};/*** Initializes the Shadowbox environment. Loads the skin (if necessary),* compiles the player matching regular expressions, and sets up the* window resize listener.** @param   Object      opts    (optional) The default options to use* @return  void* @public* @static*/
Shadowbox.init = function(opts){
if(initialized) return;
if(typeof SB.LANG == 'undefined'){
SB.raise('No Shadowbox language loaded');
return;}
if(typeof SB.SKIN == 'undefined'){
SB.raise('No Shadowbox skin loaded');
return;}
apply(options, opts || {});
var markup = SB.SKIN.markup.replace(/\{(\w+)\}/g, function(m, p){
return SB.LANG[p];});
var bd = document.body || document.documentElement;
SL.append(bd, markup);
if(ltIE7){
SL.setStyle(SL.get('shadowbox_container'), 'position', 'absolute');
SL.get('shadowbox_body').style.zoom = 1;
var png = SB.SKIN.png_fix;
if(png && png.constructor == Array){
for(var i = 0; i < png.length;++i){
var el = SL.get(png[i]);
if(el){
var match = SL.getStyle(el, 'background-image').match(/url\("(.*\.png)"\)/);
if(match){
SL.setStyle(el, {
backgroundImage: 'none',
filter: 'progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true,src='+match[1]+',sizingMethod=scale);'});}}}}}
for(var e in options.ext){
RE[e] = new RegExp('\.('+options.ext[e].join('|')+')\s*$', 'i');}
var id;
SL.addEvent(window, 'resize', function(){
if(id){
clearTimeout(id);
id = null;}
id = setTimeout(function(){
if(ltIE7) fixHeight();
resizeContent();}, 50);});
if(!options.skipSetup) SB.setup();
initialized = true;};/*** Dynamically loads the specified skin for use with Shadowbox. If the skin* is included already in the page via the appropriate <script> and <link>* tags, this function does not need to be called. Otherwise, this function* must be called before window.onload.** @param   String      skin        The directory where the skin is located* @param   String      dir         The directory where the Shadowbox skin*                                  files are located* @return  void* @public* @static*/
Shadowbox.loadSkin = function(skin, dir){
if(!(/\/$/.test(dir))) dir+= '/';
skin = dir+skin+'/';
document.write('<link rel="stylesheet" type="text/css" href="'+skin+'skin.css">');
document.write('<scr'+'ipt type="text/javascript" src="'+skin+'skin.js"><\/script>');};/*** Dynamically loads the specified language file to be used with Shadowbox.* If the language file is included already in the page via the appropriate* <script> tag, this function does not need to be called. Otherwise, this* function must be called before window.onload.** @param   String      lang        The language abbreviation (e.g. en)* @param   String      dir         The directory where the Shadowbox*                                  language file(s) is located* @return  void* @public* @static*/
Shadowbox.loadLanguage = function(lang, dir){
if(!(/\/$/.test(dir))) dir+= '/';
document.write('<scr'+'ipt type="text/javascript" src="'+dir+'shadowbox-'+lang+'.js"><\/script>');};/*** Dynamically loads the specified player(s) to be used with Shadowbox. If* the needed player(s) is already included in the page via the appropriate* <script> tag(s), this function does not need to be called. Otherwise,* this function must be called before window.onload.** @param   Array       players     The player(s) to load* @param   String      dir         The director where the Shadowbox player*                                  file(s) is located* @return  void* @public* @static*/
Shadowbox.loadPlayer = function(players, dir){
if(typeof players == 'string') players = [players];
if(!(/\/$/.test(dir))) dir+= '/';
for(var i = 0, len = players.length; i < len;++i){
document.write('<scr'+'ipt type="text/javascript" src="'+dir+'shadowbox-'+players[i]+'.js"><\/script>');}};/*** Sets up listeners on the given links that will trigger Shadowbox. If no* links are given, this method will set up every anchor element on the page* with the appropriate rel attribute. Note: Because AREA elements do not* support the rel attribute, they must be explicitly passed to this method.** @param   Array       links       An array (or array-like) list of anchor*                                  and/or area elements to set up* @param   Object      opts        Some options to use for the given links* @return  void* @public* @static*/
Shadowbox.setup = function(links, opts){
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;
for(var i = 0, len = links.length; i < len;++i){
link = links[i];
if(typeof link.shadowboxCacheKey == 'undefined'){
link.shadowboxCacheKey = cache.length;
SL.addEvent(link, 'click', handleClick); // add listener}
cache[link.shadowboxCacheKey] = this.buildCacheObj(link, opts);}};/*** Builds an object from the original link element data to store in cache.* These objects contain (most of) the following keys:**-el: the link element*-title: the linked file title*-player: the player to use for the linked file*-content: the linked file's URL*-gallery: the gallery the file belongs to (optional)*-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 element to process* @return  Object                  An object representing the link* @public* @static*/
Shadowbox.buildCacheObj = function(link, opts){
var href = link.href; // don't use getAttribute() here
var o = {
el:         link,
title:      link.getAttribute('title'),
player:     getPlayer(href),
options:    apply({}, opts || {}), // break the reference
content:    href};
var opt, l_opts = ['player', 'title', '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];}}
var rel = link.getAttribute('rel');
if(rel){
var match = rel.match(RE.gallery);
if(match) o.gallery = escape(match[2]);
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('apply(o.options, '+match[2]+')');}else{
o[match[1]] = match[2];}}}}
return o;};/*** Applies the given set of options to those currently in use. Note: Options* will be reset on Shadowbox.open() so this function is only useful after* it has already been called (while Shadowbox is open).** @param   Object      opts        The options to apply* @return  void* @public* @static*/
Shadowbox.applyOptions = function(opts){
if(opts){
default_options = apply({}, options); // store default options
options = apply(options, opts); // apply options}};/*** Reverts Shadowbox' options to the last default set in use before* Shadowbox.applyOptions() was called.** @return  void* @public* @static*/
Shadowbox.revertOptions = function(){
if(default_options){
options = default_options; // revert to default options
default_options = null; // erase for next time}};/*** Opens the given object in Shadowbox. This object may be either an* anchor/area element, or an object similar to the one created by* Shadowbox.buildCacheObj().** @param   mixed       obj         The object or link element that defines*                                  what to display* @return  void* @public* @static*/
Shadowbox.open = function(obj, opts){
this.revertOptions();
if(isLink(obj)){
if(typeof obj.shadowboxCacheKey == 'undefined' || typeof cache[obj.shadowboxCacheKey] == 'undefined'){
obj = this.buildCacheObj(obj, opts);}else{
obj = cache[obj.shadowboxCacheKey];}}
if(obj.constructor == Array){
gallery = obj;
current = 0;}else{
var copy = apply({}, obj);
if(!obj.gallery){ // single item, no gallery
gallery = [copy];
current = 0;}else{
current = null; // reset current
gallery = []; // clear the current gallery
var ci;
for(var i = 0, len = cache.length; i < len;++i){
ci = cache[i];
if(ci.gallery){
if(ci.content == obj.content&& ci.gallery == obj.gallery&& ci.title == obj.title){ // compare content, gallery, & title
current = gallery.length; // key element found}
if(ci.gallery == obj.gallery){
gallery.push(apply({}, ci));}}}
if(current == null){
gallery.unshift(copy);
current = 0;}}}
obj = gallery[current];
if(obj.options || opts){
this.applyOptions(apply(apply({}, obj.options || {}), opts || {}));}
var match, r;
for(var i = 0, len = gallery.length; i < len;++i){
r = false; // remove the element?
if(gallery[i].player == 'unsupported'){ // don't support this at all
r = true;}else if(match = RE.unsupported.exec(gallery[i].player)){ // handle unsupported elements
if(options.handleUnsupported == 'link'){
gallery[i].player = 'html';
var s, a, oe = options.errors;
switch(match[1]){
case 'qtwmp':
s = 'either';
a = [oe.qt.url, oe.qt.name, oe.wmp.url, oe.wmp.name];
break;
case 'qtf4m':
s = 'shared';
a = [oe.qt.url, oe.qt.name, oe.f4m.url, oe.f4m.name];
break;
default:
s = 'single';
if(match[1] == 'swf' || match[1] == 'flv') match[1] = 'fla';
a = [oe[match[1]].url, oe[match[1]].name];}
var msg = SB.LANG.errors[s].replace(/\{(\d+)\}/g, function(m, i){
return a[i];});
gallery[i].content = '<div class="shadowbox_message">'+msg+'</div>';}else{
r = true;}}else if(gallery[i].player == 'inline'){ // handle inline elements
var match = RE.inline.exec(gallery[i].content);
if(match){
var el;
if(el = SL.get(match[1])){
gallery[i].content = el.innerHTML;}else{
SB.raise('Cannot find element with id '+match[1]);}}else{
SB.raise('Cannot find element id for inline content');}}
if(r){
gallery.splice(i, 1); // remove the element from the gallery
if(i < current){--current;}else if(i == current){
current = i > 0 ? current-1 : i;}--i; // decrement to account for splice
len = gallery.length; // gallery.length has changed!}}
if(gallery.length){
if(options.onOpen && typeof options.onOpen == 'function'){
options.onOpen(obj);}
if(!activated){
setDimensions(options.initialHeight, options.initialWidth);
adjustHeight(dims.inner_h, dims.top, false);
adjustWidth(dims.width, false);
toggleVisible(loadContent);} else {
loadContent();}
activated = true;}};/*** Jumps to the piece in the current gallery with index num.** @param   Number      num     The gallery index to view* @return  void* @public* @static*/
Shadowbox.change = function(num){
if(!gallery) return; // no current gallery
if(!gallery[num]){ // index does not exist
if(!options.continuous){
return;}else{
num = num < 0 ? (gallery.length-1) : 0; // loop}}
if(typeof slide_timer == 'number'){
clearTimeout(slide_timer);
slide_timer = null;
slide_delay = slide_start = 0; // reset slideshow variables}
current = num; // update current
if(options.onChange && typeof options.onChange == 'function'){
options.onChange(gallery[current]); // fire onChange handler}
loadContent();};/*** Jumps to the next piece in the gallery.** @return  void* @public* @static*/
Shadowbox.next = function(){
this.change(current+1);};/*** Jumps to the previous piece in the gallery.** @return  void* @public* @static*/
Shadowbox.previous = function(){
this.change(current-1);};/*** Sets the timer for the next image in the slideshow to be displayed.** @return  void* @public* @static*/
Shadowbox.play = function(){
if(!hasNext()) return;
if(!slide_delay) slide_delay = options.slideshowDelay * 1000;
if(slide_delay){
slide_start = new Date().getTime();
slide_timer = setTimeout(function(){
slide_delay = slide_start = 0; // reset slideshow
SB.next();}, slide_delay);
toggleNav('play', false);
toggleNav('pause', true);}};/*** Pauses the current slideshow.** @return  void* @public* @static*/
Shadowbox.pause = function(){
if(typeof slide_timer == 'number'){
var time = new Date().getTime();
slide_delay = Math.max(0, slide_delay-(time-slide_start));
if(slide_delay){
clearTimeout(slide_timer);
slide_timer = 'paused';}
toggleNav('pause', false);
toggleNav('play', true);}};/*** Deactivates Shadowbox.** @return  void* @public* @static*/
Shadowbox.close = function(){
if(!activated) return; // already closed
listenKeys(false);
toggleVisible(false);
if(content){
content.remove();
content = null;}
if(typeof slide_timer == 'number') clearTimeout(slide_timer);
slide_timer = null;
slide_delay = 0;
if(options.onClose && typeof options.onClose == 'function'){
options.onClose(gallery[current]);}
activated = false;};/*** Clears Shadowbox' cache and removes listeners and expandos from all* cached link elements. May be used to completely reset Shadowbox in case* links on a page change.** @return  void* @public* @static*/
Shadowbox.clearCache = function(){
for(var i = 0, len = cache.length; i < len;++i){
if(cache[i].el){
SL.removeEvent(cache[i].el, 'click', handleClick);
delete cache[i].el.shadowboxCacheKey; // remove expando}}
cache = [];};/*** Gets an object that lists which plugins are supported by the client. 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* @static*/
Shadowbox.getPlugins = function(){
return plugins;};/*** Gets the current options object in use.** @return  Object          The options object* @public* @static*/
Shadowbox.getOptions = function(){
return options;};/*** Gets the current gallery object.** @return  Object          The current gallery item* @public* @static*/
Shadowbox.getCurrent = function(){
return gallery[current];};/*** Gets the current version number of Shadowbox.** @return  String          The current version* @public* @static*/
Shadowbox.getVersion = function(){
return version;};/*** Returns an object containing information about the current client* configuration.** @return  Object          The object containing client data* @public* @static*/
Shadowbox.getClient = function(){
return client;};/*** Returns the current content object in use.** @return  Object          The current content object* @public* @static*/
Shadowbox.getContent = function(){
return content;};/*** Gets the current dimensions of Shadowbox as calculated by* setDimensions().** @return  Object          The current dimensions of Shadowbox* @public* @static*/
Shadowbox.getDimensions = function(){
return dims;};/*** Handles all Shadowbox exceptions (errors). Calls the exception* handler callback if one is present (see handleException option) or* throws a new exception.** @param   String      e       The error message* @return  void* @public* @static*/
Shadowbox.raise = function(e){
if(typeof options.handleException == 'function'){
options.handleException(e);}else{
throw e;}};})();
