diff --git a/components/com_morfeoshow/src/shadowbox.js b/components/com_morfeoshow/src/shadowbox.js new file mode 100644 --- /dev/null +++ b/components/com_morfeoshow/src/shadowbox.js @@ -0,0 +1,2146 @@ +/** + * 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 + * @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 + * @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'] + } + + }; + + // shorthand + 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; + + // 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'); + 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 += ''; + } + 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); + }; + + // cancel the animation here if set in the options + 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)){ + // careful not to overwrite other filters! + 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 + */ + 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){ + 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; + }; + + /** + * 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){ + // get anchor/area element + 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; + } + } + + //SL.preventDefault(ev); // good for debugging + + 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'); + + // build the title + title_i.innerHTML = obj.title || ''; + + // build the nav + var nav = SL.get('shadowbox_nav'); + if(nav){ + var c, n, pl, pa, p; + + // need to build the nav? + if(options.displayNav){ + c = true; + // next & previous links + 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 + } + } + // in a slideshow? + 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); + } + + // build the counter + var counter = SL.get('shadowbox_counter'); + if(counter){ + var co = ''; + + // need to build the counter? + if(options.displayCounter && gallery.length > 1){ + if(options.counterType == 'skip'){ + // limit the counter? + 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 += ''; + } + }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'); + + // build bars after they are hidden + var fn = function(){ + buildBars(cb); + }; + + var title_h = getComputedHeight(title); + var info_h = getComputedHeight(info) * -1; + if(anim){ + // animate the transition + 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 + } + + // determine player, inline is really just HTML + var p = obj.player == 'inline' ? 'html' : obj.player; + + // make sure player is loaded + 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 opening, clear #shadowbox display + if(!changing){ + SL.get('shadowbox').style.display = ''; + } + + var fn = function(){ + resizeContent(function(){ + if(!content) return; + + + showBars(function(){ + if(!content) return; + + // append content just before hiding the loading layer + 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(); + } + }); + + // preload neighboring gallery images + 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(); + + // calculate the max width + 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; + } + + // calculate the max height + 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; + } + + // handle oversized content + 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); + } + // adjust resized height or width accordingly + resize_w = w; + resize_h = h; + }else{ + // drag on oversized images only + var link = gallery[current]; + if(link) drag = link.player == 'img' && (change_h > 0 || change_w > 0); + } + } + + // update dims + 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 + + // set new dimensions + 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){ + // resize resizable content when in resize mode + if(content.resizable && options.handleOversize == 'resize'){ + c.height = dims.resize_h; + c.width = dims.resize_w; + } + // fix draggable positioning if enlarging viewport + 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); + + // adjust the height + var sb = SL.get('shadowbox_body'); + if(anim){ + animate(sb, 'height', height, options.resizeDuration); + }else{ + SL.setStyle(sb, 'height', height + 'px'); + } + + // adjust the top + 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); + + // adjust the 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); + + // attempt to prevent default key action + 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 ( elements, while Firefox has trouble with + * 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; + } + } + + // resize & show container + var so = SL.get('shadowbox_overlay'); + var sc = SL.get('shadowbox_container'); + var sb = SL.get('shadowbox'); + if(cb){ + // set overlay color/opacity + SL.setStyle(so, { + backgroundColor: options.overlayColor, + opacity: 0 + }); + if(!options.modal) SL.addEvent(so, 'click', SB.close); + if(ltIE7){ + // fix container top & overlay height before showing + fixTop(); + fixHeight(); + SL.addEvent(window, 'scroll', fixTop); + } + + // fade in animation + 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); + + // fade out effect + 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){ + // don't initialize twice + if(initialized) return; + + // make sure language is loaded + if(typeof SB.LANG == 'undefined'){ + SB.raise('No Shadowbox language loaded'); + return; + } + // make sure skin is loaded + if(typeof SB.SKIN == 'undefined'){ + SB.raise('No Shadowbox skin loaded'); + return; + } + + // apply custom options + apply(options, opts || {}); + + // add markup + 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); + + // several fixes for IE6 + if(ltIE7){ + // give the container absolute positioning + SL.setStyle(SL.get('shadowbox_container'), 'position', 'absolute'); + // give shadowbox_body "layout"...whatever that is + SL.get('shadowbox_body').style.zoom = 1; + // use AlphaImageLoader for transparent PNG support + 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);' + }); + } + } + } + } + } + + // compile file type regular expressions here for speed + for(var e in options.ext){ + RE[e] = new RegExp('\.(' + options.ext[e].join('|') + ')\s*$', 'i'); + } + + // set up window resize event handler + var id; + SL.addEvent(window, 'resize', function(){ + // use 50 ms event buffering to prevent jerky window resizing + 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