/** * 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 . * * Credits: * - Lokesh Dhakar * - Kevin Miller * * @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 50 2008-01-27 07:33:39Z mjijackson $ * Modified by Osamwal 4 PictureFlow : 2008-02-27 */ 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 Look into alternatives for image.onload * @todo Find a way to tell when movies and iframes are loaded */ (function(){ /** * Contains the default options for Shadowbox. This object is almost * entirely customizable. * * @var options * @type Object * @private */ var options = { // The image to display while loading loadingImage: '/images/loading.gif', // Enable animations 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: 'sync', // The path to flvplayer.swf 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 overlayColor: '#fff', overlayOpacity: 0.85, //overlayBgImage: 'images/overlay-85.png', // Automatically play movies autoplayMovies: true, // Enable movie controllers on QuickTime and Windows Media players showMovieControls: true, // The duration of the resizing animations (in seconds) resizeDuration: 0.35, // The duration of the overlay fade animation (in seconds) fadeDuration: 0.35, // Show the navigation controls 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. continuous: false, // Display the gallery counter 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. 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. 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. handleLgImages: 'resize', // The initial dimensions of the shadowbox (in pixels) initialHeight: 160, 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. enableKeys: true, // The keys used to control Shadowbox. Note: In order to use these, // enableKeys must be true. Also, keys must be separated by a pipe // character. Key values or key codes may be used. keysClose: ['f', 27], // f, or esc keysNext: ['s', 39], // s or right arrow keysPrev: ['p', 37], // p or left arrow // Insert hook functions here 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. onOpen: 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. handleUnsupported: 'link', // An object containing error message templates and links to browser // plugin download pages errors: { templates: { 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.' }, 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 to use for Shadowbox. Note: The script depends on most of // these elements being present, so don't modify it unless you really // know what you're doing. skin: { main: '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + 'loading' + 'Cancelar' + '
' + '
' + '
' + '
' + '
' + '
' + '
', counter: '
', nav: { close: '
' + 'Cerrar' + '
', next: '
' + 'Siguiente' + '
', prev: '
' + 'Anterior' + '
' } } }; /** * Shorthand for Shadowbox.lib. * * @var SL * @type Object * @private */ var SL = Shadowbox.lib; /** * An array to hold all of our galleries. * * @var galleries * @type Array * @private */ var galleries = []; /** * An array of pieces currently being viewed. In the case of non-gallery * pieces, this will only hold one object. * * @var current_gallery * @type Array * @private */ var current_gallery = null; /** * The array index of the current_gallery that is currently being viewed. * * @var current * @type Number * @private */ var current = null; /** * 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() * @var optimal_height * @type Number * @private */ var optimal_height = options.initialHeight; /** * Keeps track of the current optimal width of the box. See optimal_height * explanation (above). * * @var optimal_width * @type Number * @private */ var optimal_width = options.initialWidth; /** * 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. * * @var preloader * @type Image * @private */ var preloader = null; /** * Keeps track of whether or not Shadowbox is activated. * * @var activated * @type Boolean * @private */ var activated = false; /** * 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); /** * Gets the height of the viewport in pixels. Note: This function includes * scrollbars in Safari 3. * * @return {Number} The height of the viewport * @private */ var 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 * @private */ var 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 * @private */ var getDocumentHeight = function(){ var scrollHeight = isStrict ? document.documentElement.scrollHeight : document.body.scrollHeight; return Math.max(scrollHeight, getViewportHeight()); }; /** * Gets the width of the document (body and its margins) in pixels. * * @return {Number} The width of the document * @private */ var getDocumentWidth = function(){ var scrollWidth = isStrict ? document.documentElement.scrollWidth : document.body.scrollWidth; return Math.max(scrollWidth, 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(); } }; /** * 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 plugins * @type Object * @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 }; } /** * 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... * * @var absolute_pos * @type Boolean * @private */ var absolute_pos = isIE && !isIE7; /** * 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; }; /** * An object containing arrays of all supported file extensions. Each * property of this object contains an array. * * - 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 * - external: File extensions that will be display in an iframe * * @var file_types * @type Object * @private */ var file_types = { img: ['png', 'jpg', 'jpeg', 'gif', 'bmp'], qt: ['dv', 'mov', 'moov', 'movie', 'mp4'], wmp: ['asf', 'wm', 'wmv'], qtwmp: ['avi', 'mpg', 'mpeg'], external: ['asp', 'aspx', 'cgi', 'cfm', 'htm', 'html', 'pl', 'php', 'php3', 'php4', 'php5', 'phtml', 'rb', 'rhtml', 'shtml', 'txt', 'vbs'] }; // regular expressions compiled here for speed var img_re = new RegExp('\.(' + file_types.img.join('|') + ')\s*$', 'i'); var qt_re = new RegExp('\.(' + file_types.qt.join('|') + ')\s*$', 'i'); var wmp_re = new RegExp('\.(' + file_types.wmp.join('|') + ')\s*$', 'i'); var qtwmp_re = new RegExp('\.(' + file_types.qtwmp.join('|') + ')\s*$', 'i'); var external_re = new RegExp('\.(' + file_types.external.join('|') + ')\s*$', 'i'); var swf_re = /\.swf\s*$/i; var flv_re = /\.flv\s*$/i; var domain_re = /:\/\/(.*?)[:\/]/; /** * 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(img_re.test(url)){ return 'img'; } var this_domain = (domain_match = url.match(domain_re)) ? (document.domain == domain_match[1]) : false; var q_index = url.indexOf('?'); if(q_index > -1){ url = url.substring(0, q_index); } if(swf_re.test(url)){ return (plugins.fla) ? 'swf' : 'unsupported-swf'; }else if(flv_re.test(url)){ return (plugins.fla) ? 'flv' : 'unsupported-flv'; }else if(qt_re.test(url)){ return (plugins.qt) ? 'qt' : 'unsupported-qt'; }else if(wmp_re.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(qtwmp_re.test(url)){ if(plugins.qt){ return 'qt'; }else if(plugins.wmp){ return 'wmp'; }else{ return (isMac) ? 'unsupported-qt' : 'unsupported-qtwmp'; } }else if(!this_domain || external_re.test(url)){ return 'external'; } return 'unsupported'; }; var gallery_re = /^shadowbox\[(.*?)\]/i; /** * Extracts a gallery name from a link's rel string. * * @param {HTMLElement} link The link to get the gallery for * @return {mixed} String on success, null on failure * @private */ var getGalleryName = function(link){ if(!(rel = link.getAttribute('rel'))) return null; var match = rel.match(gallery_re); return (match ? escape(match[1]) : null); }; /** * Sets up a link for use with Shadowbox. * * @param {HTMLElement} link The link to set up * @return void * @private */ var setupLink = function(link){ // build galleries var name; if(name = getGalleryName(link)){ if(!galleries[name]) galleries[name] = []; galleries[name].push(Shadowbox.buildLinkObj(link)); } if(Shadowbox.isSetup){ SL.removeEvent(link, 'click', handleClick); // clear old listener } SL.addEvent(link, 'click', handleClick); }; var unsupported_re = /^unsupported-(\w+)/; /** * 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){ 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; } } var name = getGalleryName(link); if(name){ current_gallery = galleries[name]; var href = link.href; // don't use getAttribute() here // which piece in the gallery was clicked? for(var i = 0, len = current_gallery.length; i < len; ++i){ if(current_gallery[i].href == href){ current = i; break; } } }else{ // not part of a gallery, create a gallery with just one piece current_gallery = [Shadowbox.buildLinkObj(link)]; current = 0; } // are any media in the current gallery supported? var match; for(var i = 0; i < current_gallery.length; ++i){ if(match = unsupported_re.exec(current_gallery[i].type)){ if(options.handleUnsupported == 'link'){ // handle unsupported elements // generate a link to the appropriate plugin download page(s) current_gallery[i].type = 'msg'; switch(match[1]){ case 'qtwmp': current_gallery[i].message = String.format( options.errors.templates['either'], options.errors['qt'].url, options.errors['qt'].name, options.errors['wmp'].url, options.errors['wmp'].name); break; case 'qtf4m': current_gallery[i].message = String.format( options.errors.templates['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'; } current_gallery[i].message = String.format( options.errors.templates['single'], options.errors[match[1]].url, options.errors[match[1]].name); } }else{ // remove the element from the gallery var removed = current_gallery.splice(i, 1); if(i < current) --current; --i; } } } // if so, don't follow the link and open Shadowbox if(current_gallery.length){ SL.preventDefault(ev); Shadowbox.open(link); } }; /** * Direct trigger to launch Shadowbox. * Determines if the type of medium is supported. If so, stops the browser * from navigating away and opens Shadowbox. * * @param {Object} directLink The link object * @return void * @public */ Shadowbox.trigger = function(directLink){ var link; link = directLink; // var href = link.href; // don't use getAttribute() here var name = link.rel.substring(10,link.rel.indexOf("]",10)); if(name){ current_gallery = galleries[name]; current = link.id; }else{ // not part of a gallery, create a gallery with just one piece current_gallery = [Shadowbox.buildLinkObj(link)]; current = 0; } // are any media in the current gallery supported? var match; for(var i = 0; i < current_gallery.length; ++i){ if(match = unsupported_re.exec(current_gallery[i].type)){ if(options.handleUnsupported == 'link'){ // handle unsupported elements // generate a link to the appropriate plugin download page(s) current_gallery[i].type = 'msg'; switch(match[1]){ case 'qtwmp': current_gallery[i].message = String.format( options.errors.templates['either'], options.errors['qt'].url, options.errors['qt'].name, options.errors['wmp'].url, options.errors['wmp'].name); break; case 'qtf4m': current_gallery[i].message = String.format( options.errors.templates['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'; } current_gallery[i].message = String.format( options.errors.templates['single'], options.errors[match[1]].url, options.errors[match[1]].name); } }else{ // remove the element from the gallery var removed = current_gallery.splice(i, 1); if(i < current) --current; --i; } } } // if so, don't follow the link and open Shadowbox if(current_gallery.length){ Shadowbox.open(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 var title_i = SL.get('shadowbox_title_inner'); var tool_i = SL.get('shadowbox_toolbar_inner'); // build the title title_i.innerHTML = (link.title) ? link.title : ''; title_i.innerHTML = ''; // empty the toolbar tool_i.innerHTML = ''; // build the nav if(options.displayNav){ tool_i.innerHTML = options.skin.nav.close; if(current_gallery.length > 1){ if(options.continuous){ // show both appendHTML(tool_i, options.skin.nav.next); appendHTML(tool_i, options.skin.nav.prev); }else{ // not last in the gallery, show the next link if((current_gallery.length - 1) > current){ appendHTML(tool_i, options.skin.nav.next); } // not first in the gallery, show the previous link if(current > 0){ appendHTML(tool_i, options.skin.nav.prev); } } } } // build the counter /*if(current_gallery.length > 1 && options.displayCounter){ // append the counter div appendHTML(tool_i, options.skin.counter); var counter = ''; if(options.counterType == 'skip'){ for(var i = 0, len = current_gallery.length; i < len; ++i){ counter += ''; } }else{ counter = (current + 1) + ' of ' + current_gallery.length; } overwriteHTML('shadowbox_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 (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(); } }; /** * 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; }; /** * Keeps track of 4 floating values that are used in the drag calculations. * * @property {Object} * @private */ var drag = null; /** * Holds the draggable element so we don't have to fetch it every time * the mouse moves. * * @property {HTMLElement} * @private */ var draggable = null; /** * 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(); 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'); }; /** * 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(){ 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'){ // add drag layer to prevent browser dragging of actual image var styles = [ 'position:absolute', 'width:' + dims.i_width + 'px', 'height:' + dims.i_height + 'px', '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', '
'); // listen for drag toggleDrag(true); } 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 'external': // iframes default to full viewport width and height var height = link.height ? parseInt(link.height, 10) : getViewportHeight(); var width = link.width ? parseInt(link.width, 10) : getViewportWidth(); var content = { tag: 'iframe', name: 'shadowbox_content', height: '100%', width: '100%', frameborder: '0', marginwidth: '0', marginheight: '0', scrolling: 'auto' }; resizeContent(height, width, function(){ showBars(function(){ setContent(content); var win = (isIE) ? SL.get('shadowbox_content').contentWindow : window.frames['shadowbox_content']; win.location = link.href; finishContent(); }); }); break; case 'msg': // set height and width to what they were initially var height = options.initialHeight; var width = options.initialWidth; var content = { tag: 'div', cls: 'shadowbox_message', html: link.message }; resizeContent(height, width, 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 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(){ if(options.onClose && typeof options.onClose == 'function'){ options.onClose(SL.get(link.id)); } listenKeyboard(true); }); }; /** * 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 dims = getDimensions(optimal_height, optimal_width); 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 as well in 'resize' mode var content = SL.get('shadowbox_content'); if(content && content.tagName.toUpperCase() == 'IMG' && options.handleLgImages == 'resize'){ content.height = dims.height; content.width = dims.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 images * - i_width: The width to use for images * - 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 * @return {Object} The resize dimensions (see above) * @private */ var getDimensions = function(o_height, o_width){ 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 = 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 = 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 var enableDrag = false; var i_height = o_height; var i_width = o_width; var h = options.handleLgImages == 'resize' || options.handleLgImages == 'drag'; if(h){ var change_h = (o_height - height) / o_height; var change_w = (o_width - width) / o_width; if(options.handleLgImages == '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{ enableDrag = 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((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 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'); 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 content = current_gallery[current]; var anim = (content.type == 'img'); // fade on images 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, { // Safari3 includes vertical scrollbar in getDocumentWidth()! // width: getDocumentWidth() + 'px', height: getDocumentHeight() + '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 * @private */ var checkOverlayImgNeeded = function(){ if(!(isGecko && isMac)){ return false; } var type; for(var i = 0, len = current_gallery.length; i < len; ++i){ type = current_gallery[i].type; if(type != 'img' && type != 'msg'){ return true; } } return false; }; /** * 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. * * @var overlay_img_needed * @type Boolean * @private */ var overlay_img_needed = null; /** * 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; } }; /** * Applies all properties of ext to orig. * * @param {Object} orig The original object * @param {Object} ext The extension object * @return {Object} The original object with all properties * of the extension object applied * @private */ var applyProps = function(orig, ext){ for(var p in ext) orig[p] = ext[p]; return orig; }; /** * Keeps track of whether or not Shadowbox has been initialized. We never * want to initialize twice. * * @var isInitialized * @type Boolean * @private */ var isInitialized = false; /** * Initializes the Shadowbox environment. * * @param {Object} default_options The default options to use * @return void * @public */ Shadowbox.init = function(default_options){ if(isInitialized){ return; // don't initialize twice } options = applyProps(options, default_options || {}); Shadowbox.setup(); // add markup options.skin.main = options.skin.main.replace(/\{loading_img_replace\}/, options.loadingImage); appendHTML(document.body, options.skin.main); var id = null; var resize = function(){ clearInterval(id); id = null; resizeOverlay(); resizeContent(optimal_height, optimal_width); }; // handle window resize events SL.addEvent(window, 'resize', function(){ if(activated){ // use event buffering to prevent jerky window resizing if(id){ clearInterval(id); id = null; } id = setInterval(resize, 500) } }); // 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(); } }); } isInitialized = true; }; /** * Keeps track of whether or not Shadowbox has been "set up". During setup, * Shadowbox grabs all relevant anchor elements on the page and adds an * onclick listener to them. Thus, if setup() is called again later (because * some of the links on the page have changed presumably) we know we need * to remove the old listeners before we can add new ones. * * @var isSetup * @type Boolean * @public */ Shadowbox.isSetup = false; /** * 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. * * @return void * @public */ Shadowbox.setup = function(){ galleries = []; // clear galleries var links = document.getElementsByTagName('a'); var rel_re = /^shadowbox/i, rel; for(var i = 0, len = links.length; i < len; ++i){ rel = links[i].getAttribute('rel'); if(rel && rel_re.test(rel)) setupLink(links[i]); } this.isSetup = true; }; /** * 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 default_options * @type Object * @private */ var default_options = null; /** * Activates the Shadowbox. * * @param {HTMLElement} link The link that was clicked * @return void * @public */ Shadowbox.open = function(link){ if(activated) return; // already open activated = true; if(default_options){ // revert to default options options = default_options; default_options = null; } if(current_gallery[current].options){ // custom options default_options = applyProps({}, options); // store default options options = applyProps(options, current_gallery[current].options); } // fire onOpen hook if(options.onOpen && typeof options.onOpen == 'function'){ options.onOpen(link); } // display: block 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(); }); }; /** * 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 } } // stop listening for drag toggleDrag(false); // empty the content setContent(null); // turn this back on when done listenKeyboard(false); current = num; showLoading(); hideBars(loadContent); }; /** * Attempts to forward the gallery to the next image. * * @return void * @public */ Shadowbox.next = function(){ this.change(current + 1); }; /** * Attempts to rewind the gallery to the previous image. * * @return void * @public */ Shadowbox.previous = function(){ this.change(current - 1); }; /** * Deactivates the Shadowbox. * * @return void * @public */ Shadowbox.close = function(){ if(!activated) return; // already closed listenKeyboard(false); SL.setStyle(SL.get('shadowbox'), { display: 'none', visibility: 'hidden' }); toggleTroubleElements(true); // 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); activated = false; }; var param_re = /\s*([a-z_]*?)\s*=\s*(.+)\s*/; var extension_re = /\.([a-z]+)(\?|#|$)/; /** * Builds a link object from the original element data. This saves us having * to call these same methods over again in different places and ensures * consistency. These link objects will be stored in the galleries. Link * 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 * - extension: the linked file extension * - 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 (not necessary) * * @param {HTMLElement} link The link to process * @return {Object} An object representing the link * @public */ Shadowbox.buildLinkObj = function(link){ var href = link.href; // don't use getAttribute() here var match = href.match(extension_re); var ext = (match ? match[1].toLowerCase() : ''); var obj = { id: link.id, title: link.getAttribute('title'), href: href, type: getPlayerType(href), extension: ext }; var params = link.getAttribute('rel').split(';'); if(params.length > 1){ params.shift(); // get rid of gallery name var match; for(var i = 0, len = params.length; i < len; ++i){ match = params[i].match(param_re); if(match){ if(match[1] == 'options'){ eval('obj.options = ' + match[2]); }else{ obj[match[1]] = match[2]; } } } } return obj; }; /** * 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 height = link.height ? parseInt(link.height) : 300; var width = link.width ? parseInt(link.width) : 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': 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 displayheight = height; var showicons = 'false'; if(controls){ showicons = 'true'; height += 20; // height of JW FLV player controller } var flashvars = [ 'file=' + link.href, 'height=' + height, 'width=' + width, 'autostart=' + autoplay, 'displayheight=' + displayheight, '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'; height += 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(navigator.plugins && navigator.plugins.length){ markup.type = 'video/quicktime'; markup.data = link.href; }else{ // IE markup.classid = 'clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B'; markup.codebase = 'http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0'; } break; case 'wmp': autoplay = autoplay ? 1 : 0; markup.children = [ { tag: 'param', name: 'autostart', value: autoplay } ]; if(navigator.plugins && navigator.plugins.length){ if(controls){ controls = 1; height += 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 }; }else{ // IE if(controls){ controls = 'full'; height += 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 }; } break; } markup.height = height; // new height includes controller markup.width = width; return markup; }; var empty_re = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i; /** * 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 == 'children') continue; if(attr == 'cls'){ html += ' class="' + obj['cls'] + '"'; }else{ html += ' ' + attr + '="' + obj[attr] + '"'; } } if(empty_re.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]); } } html += '\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; }; })(); 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; }; 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]; }); };