source: branches/5.2/www/default/js/shadowbox.js @ 278

Revision 278, 90.6 KB checked in by mattlevine, 3 years ago (diff)

Changed the default shadowbox.js overlay settings to false. This makes it so that user must click the close button in order to close it.

Line 
1/**
2 * A media-viewer script for web pages that allows content to be viewed without
3 * navigating away from the original linking page.
4 *
5 * This file is part of Shadowbox.
6 *
7 * Shadowbox is free software: you can redistribute it and/or modify it under
8 * the terms of the GNU Lesser General Public License as published by the Free
9 * Software Foundation, either version 3 of the License, or (at your option)
10 * any later version.
11 *
12 * Shadowbox is distributed in the hope that it will be useful, but WITHOUT ANY
13 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with Shadowbox. If not, see <http://www.gnu.org/licenses/>.
19 *
20 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
21 * @copyright   2007 Michael J. I. Jackson
22 * @license     http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL 3.0
23 * @version     SVN: $Id: shadowbox.js 75 2008-02-21 16:51:29Z mjijackson $
24 */
25
26if(typeof Shadowbox == 'undefined'){
27    throw 'Unable to load Shadowbox, no base library adapter found.';
28}
29
30/**
31 * The Shadowbox class. Used to display different media on a web page using a
32 * Lightbox-like effect.
33 *
34 * Useful resources:
35 * - http://www.alistapart.com/articles/byebyeembed
36 * - http://www.w3.org/TR/html401/struct/objects.html
37 * - http://www.dyn-web.com/dhtml/iframes/
38 * - http://support.microsoft.com/kb/316992
39 * - http://www.apple.com/quicktime/player/specs.html
40 * - http://www.howtocreate.co.uk/wrongWithIE/?chapter=navigator.plugins
41 *
42 * @class       Shadowbox
43 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
44 * @singleton
45 */
46
47
48(function(){
49
50    /**
51     * The current version of Shadowbox.
52     *
53     * @property    {String}    version
54     * @private
55     */
56    var version = '1.0';
57
58    /**
59     * Contains the default options for Shadowbox. This object is almost
60     * entirely customizable.
61     *
62     * @property    {Object}    options
63     * @private
64     */
65    var options = {
66
67        /**
68         * A base URL that will be prepended to the loadingImage, flvPlayer, and
69         * overlayBgImage options to save on typing.
70         *
71         * @var     {String}    assetURL
72         */
73        assetURL:        context +  '/default/images/shadowbox/',
74
75        /**
76         * The path to the image to display while loading.
77         *
78         * @var     {String}    loadingImage
79         */
80        loadingImage:       'loading.gif',
81
82        /**
83         * Enable animations.
84         *
85         * @var     {Boolean}   animate
86         */
87        animate:            true,
88
89        /**
90         * Specifies the sequence of the height and width animations. May be
91         * 'wh' (width then height), 'hw' (height then width), or 'sync' (both
92         * at the same time). Of course this will only work if animate is true.
93         *
94         * @var     {String}    animSequence
95         */
96        animSequence:       'wh',
97
98        /**
99         * The path to flvplayer.swf.
100         *
101         * @var     {String}    flvPlayer
102         */
103        flvPlayer:          'flvplayer.swf',
104
105        /**
106         * The background color and opacity of the overlay. Note: When viewing
107         * movie files on FF Mac, the default background image will be used
108         * because that browser has problems displaying movies above layers
109         * that aren't 100% opaque.
110         *
111         * @var     {String}    overlayColor
112         */
113        overlayColor:       '#000',
114
115        /**
116         * The background opacity to use for the overlay.
117         *
118         * @var     {Number}    overlayOpacity
119         */
120        overlayOpacity:     0.85,
121
122        /**
123         * A background image to use for browsers such as FF Mac that don't
124         * support displaying movie content over backgrounds that aren't 100%
125         * opaque.
126         *
127         * @var     {String}    overlayBgImage
128         */
129        overlayBgImage:     'overlay-85.png',
130
131        /**
132         * Listen to the overlay for clicks. If the user clicks the overlay,
133         * it will trigger Shadowbox.close().
134         *
135         * @var     {Boolean}   listenOverlay
136         */
137        listenOverlay:      false,
138
139        /**
140         * Automatically play movies.
141         *
142         * @var     {Boolean}   autoplayMovies
143         */
144        autoplayMovies:     true,
145
146        /**
147         * Enable movie controllers on movie players.
148         *
149         * @var     {Boolean}   showMovieControls
150         */
151        showMovieControls:  true,
152
153        /**
154         * The duration of the resizing animations (in seconds).
155         *
156         * @var     {Number}    resizeDuration
157         */
158        resizeDuration:     0.35,
159
160        /**
161         * The duration of the overlay fade animation (in seconds).
162         *
163         * @var     {Number}    fadeDuration
164         */
165        fadeDuration:       0.35,
166
167        /**
168         * Show the navigation controls.
169         *
170         * @var     {Boolean}   displayNav
171         */
172        displayNav:         true,
173
174        /**
175         * Enable continuous galleries. When this is true, users will be able
176         * to skip to the first gallery image from the last using next and vice
177         * versa.
178         *
179         * @var     {Boolean}   continuous
180         */
181        continuous:         false,
182
183        /**
184         * Display the gallery counter.
185         *
186         * @var     {Boolean}   displayCounter
187         */
188        displayCounter:     true,
189
190        /**
191         * This option may be either 'default' or 'skip'. The default counter is
192         * a simple '1 of 5' message. The skip counter displays a link for each
193         * piece in the gallery that enables a user to skip directly to any
194         * piece.
195         *
196         * @var     {String}    counterType
197         */
198        counterType:        'default',
199
200        /**
201         * The amount of padding to maintain around the viewport edge (in
202         * pixels). This only applies when the image is very large and takes up
203         * the entire viewport.
204         *
205         * @var     {Number}    viewportPadding
206         */
207        viewportPadding:    40,
208
209        /**
210         * How to handle images that are too large for the viewport. 'resize'
211         * will resize the image while preserving aspect ratio and display it at
212         * the smaller resolution. 'drag' will display the image at its native
213         * resolution but it will be draggable within the Shadowbox. 'none' will
214         * display the image at its native resolution but it may be cropped.
215         *
216         * @var     {String}    handleLgImages
217         */
218        handleLgImages:     'resize',
219
220        /**
221         * The initial height of Shadowbox (in pixels).
222         *
223         * @var     {Number}    initialHeight
224         */
225        initialHeight:      160,
226
227        /**
228         * The initial width of Shadowbox (in pixels).
229         *
230         * @var     {Number}    initialWidth
231         */
232        initialWidth:       320,
233
234        /**
235         * Enable keyboard control. Note: If you disable the keys, you may want
236         * to change the visual styles for the navigation elements that suggest
237         * keyboard shortcuts.
238         *
239         * @var     {Boolean}   enableKeys
240         */
241        enableKeys:         true,
242
243        /**
244         * The keys used to control Shadowbox. Note: In order to use these,
245         * enableKeys must be true. Key values or key codes may be used.
246         *
247         * @var     {Array}
248         */
249        keysClose:          ['c', 'q', 27], // c, q, or esc
250        keysNext:           ['n', 39],      // n or right arrow
251        keysPrev:           ['p', 37],      // p or left arrow
252
253        /**
254         * A hook function to be fired when Shadowbox opens. The single argument
255         * will be the current gallery element.
256         *
257         * @var     {Function}
258         */
259        onOpen:             null,
260
261        /**
262         * A hook function to be fired when Shadowbox finishes loading its
263         * content. The single argument will be the current gallery element on
264         * display.
265         *
266         * @var     {Function}
267         */
268        onFinish:           null,
269
270        /**
271         * A hook function to be fired when Shadowbox changes from one gallery
272         * element to the next. The single argument will be the current gallery
273         * element that is about to be displayed.
274         *
275         * @var     {Function}
276         */
277        onChange:           null,
278
279        /**
280         * A hook function that will be fired when Shadowbox closes. The single
281         * argument will be the gallery element most recently displayed.
282         *
283         * @var     {Function}
284         */
285        onClose:            null,
286
287        /**
288         * The mode to use when handling unsupported media. May be either
289         * 'remove' or 'link'. If it is 'remove', the unsupported gallery item
290         * will merely be removed from the gallery. If it is the only item in
291         * the gallery, the link will simply be followed. If it is 'link', a
292         * link will be provided to the appropriate plugin page in place of the
293         * gallery element.
294         *
295         * @var     {String}    handleUnsupported
296         */
297        handleUnsupported:  'link',
298
299        /**
300         * Skips calling Shadowbox.setup() in init(). This means that it must
301         * be called later manually.
302         *
303         * @var     {Boolean}   skipSetup
304         */
305        skipSetup:          false,
306
307        /**
308         * Text messages to use for Shadowbox. These are provided so they may be
309         * translated into different languages.
310         *
311         * @var     {Object}    text
312         */
313        text:           {
314
315            cancel:     'Cancel',
316
317            loading:    'Loading',
318
319            close:      '<span class="shortcut">C</span>lose',
320
321            next:       '<span class="shortcut">N</span>ext',
322
323            prev:       '<span class="shortcut">P</span>revious',
324
325            errors:     {
326                single: 'You must install the <a href="{0}">{1}</a> browser plugin to view this content.',
327                shared: 'You must install both the <a href="{0}">{1}</a> and <a href="{2}">{3}</a> browser plugins to view this content.',
328                either: 'You must install either the <a href="{0}">{1}</a> or the <a href="{2}">{3}</a> browser plugin to view this content.'
329            }
330
331        },
332
333        /**
334         * An object containing names of plugins and links to their respective
335         * download pages.
336         *
337         * @var     {Object}    errors
338         */
339        errors:         {
340
341            fla:        {
342                name:   'Flash',
343                url:    'http://www.adobe.com/products/flashplayer/'
344            },
345
346            qt:         {
347                name:   'QuickTime',
348                url:    'http://www.apple.com/quicktime/download/'
349            },
350
351            wmp:        {
352                name:   'Windows Media Player',
353                url:    'http://www.microsoft.com/windows/windowsmedia/'
354            },
355
356            f4m:        {
357                name:   'Flip4Mac',
358                url:    'http://www.flip4mac.com/wmv_download.htm'
359            }
360
361        },
362
363        /**
364         * The HTML markup to use for Shadowbox. Note: The script depends on
365         * most of these elements being present, so don't modify this variable
366         * unless you know what you're doing.
367         *
368         * @var     {Object}    skin
369         */
370        skin:           {
371
372            main:       '<div id="shadowbox_overlay"></div>' +
373                        '<div id="shadowbox_container">' +
374                            '<div id="shadowbox">' +
375                                '<div id="shadowbox_title">' +
376                                    '<div id="shadowbox_title_inner"></div>' +
377                                '</div>' +
378                                '<div id="shadowbox_body">' +
379                                    '<div id="shadowbox_body_inner"></div>' +
380                                    '<div id="shadowbox_loading"></div>' +
381                                '</div>' +
382                                '<div id="shadowbox_toolbar">' +
383                                    '<div id="shadowbox_toolbar_inner"></div>' +
384                                '</div>' +
385                            '</div>' +
386                        '</div>',
387
388            loading:    '<img src="{0}" alt="{1}" />' +
389                        '<span><a href="javascript:Shadowbox.close();">{2}</a></span>',
390
391            counter:    '<div id="shadowbox_counter">{0}</div>',
392
393            close:      '<div id="shadowbox_nav_close">' +
394                            '<a href="javascript:Shadowbox.close();">{0}</a>' +
395                        '</div>',
396
397            next:       '<div id="shadowbox_nav_next">' +
398                            '<a href="javascript:Shadowbox.next();">{0}</a>' +
399                        '</div>',
400
401            prev:       '<div id="shadowbox_nav_previous">' +
402                            '<a href="javascript:Shadowbox.previous();">{0}</a>' +
403                        '</div>'
404
405        },
406
407        /**
408         * An object containing arrays of all supported file extensions. Each
409         * property of this object contains an array. If this object is to be
410         * modified, it must be done before calling init().
411         *
412         * - img: Supported image file extensions
413         * - qt: Movie file extensions supported by QuickTime
414         * - wmp: Movie file extensions supported by Windows Media Player
415         * - qtwmp: Movie file extensions supported by both QuickTime and Windows Media Player
416         * - iframe: File extensions that will be display in an iframe
417         *
418         * @var     {Object}    ext
419         */
420        ext:     {
421            img:        ['png', 'jpg', 'jpeg', 'gif', 'bmp'],
422            qt:         ['dv', 'mov', 'moov', 'movie', 'mp4'],
423            wmp:        ['asf', 'wm', 'wmv'],
424            qtwmp:      ['avi', 'mpg', 'mpeg'],
425            iframe:     ['asp', 'aspx', 'cgi', 'cfm', 'htm', 'html', 'pl', 'php',
426                        'php3', 'php4', 'php5', 'phtml', 'rb', 'rhtml', 'shtml',
427                        'txt', 'vbs']
428        }
429
430    };
431
432    /**
433     * Stores the default set of options in case a custom set of options is used
434     * on a link-by-link basis so we can restore them later.
435     *
436     * @property    {Object}    default_options
437     * @private
438     */
439    var default_options = null;
440
441    /**
442     * Shorthand for Shadowbox.lib.
443     *
444     * @property    {Object}        SL
445     * @private
446     */
447    var SL = Shadowbox.lib;
448
449    /**
450     * An object containing some regular expressions we'll need later. Compiled
451     * up front for speed.
452     *
453     * @property    {Object}        RE
454     * @private
455     */
456    var RE = {
457        resize:         /(img|swf|flv)/, // file types to resize
458        overlay:        /(img|iframe|html|inline)/, // content types to not use an overlay image for on FF Mac
459        swf:            /\.swf\s*$/i, // swf file extension
460        flv:            /\.flv\s*$/i, // flv file extension
461        domain:         /:\/\/(.*?)[:\/]/, // domain prefix
462        inline:         /#(.+)$/, // inline element id
463        rel:            /^(light|shadow)box/i, // rel attribute format
464        gallery:        /^(light|shadow)box\[(.*?)\]/i, // rel attribute format for gallery link
465        unsupported:    /^unsupported-(\w+)/, // unsupported media type
466        param:          /\s*([a-z_]*?)\s*=\s*(.+)\s*/, // rel string parameter
467        empty:          /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i // elements that don't have children
468    };
469
470    /**
471     * A cache of options for links that have been set up for use with
472     * Shadowbox.
473     *
474     * @property    {Array}         cache
475     * @private
476     */
477    var cache = [];
478
479    /**
480     * An array of pieces currently being viewed. In the case of non-gallery
481     * pieces, this will only hold one object.
482     *
483     * @property    {Array}         current_gallery
484     * @private
485     */
486    var current_gallery;
487
488    /**
489     * The array index of the current_gallery that is currently being viewed.
490     *
491     * @property    {Number}        current
492     * @private
493     */
494    var current;
495
496    /**
497     * Keeps track of the current optimal height of the box. We use this so that
498     * if the user resizes the browser window to get a better view, and we're
499     * currently at a size smaller than the optimal, we can resize easily.
500     *
501     * @see         resizeContent()
502     * @property    {Number}        optimal_height
503     * @private
504     */
505    var optimal_height = options.initialHeight;
506
507    /**
508     * Keeps track of the current optimal width of the box. See optimal_height
509     * explanation (above).
510     *
511     * @property    {Number}        optimal_width
512     * @private
513     */
514    var optimal_width = options.initialWidth;
515
516    /**
517     * Keeps track of the current height of the box. This is useful in drag
518     * calculations.
519     *
520     * @property    {Number}        current_height
521     * @private
522     */
523    var current_height = 0;
524
525    /**
526     * Keeps track of the current width of the box. Useful in drag calculations.
527     *
528     * @property    {Number}        current_width
529     * @private
530     */
531    var current_width = 0;
532
533    /**
534     * Resource used to preload images. It's class-level so that when a new
535     * image is requested, the same resource can be reassigned, cancelling
536     * the original's callback.
537     *
538     * @property    {HTMLElement}   preloader
539     * @private
540     */
541    var preloader;
542
543    /**
544     * Keeps track of whether or not Shadowbox has been initialized. We never
545     * want to initialize twice.
546     *
547     * @property    {Boolean}       initialized
548     * @private
549     */
550    var initialized = false;
551
552    /**
553     * Keeps track of whether or not Shadowbox is activated.
554     *
555     * @property    {Boolean}       activated
556     * @private
557     */
558    var activated = false;
559
560    /**
561     * Keeps track of 4 floating values (x, y, start_x, & start_y) that are used
562     * in the drag calculations.
563     *
564     * @property    {Object}        drag
565     * @private
566     */
567    var drag;
568
569    /**
570     * Holds the draggable element so we don't have to fetch it every time
571     * the mouse moves.
572     *
573     * @property    {HTMLElement}   draggable
574     * @private
575     */
576    var draggable;
577
578    /**
579     * Keeps track of whether or not we're currently using the overlay
580     * background image to display the current gallery. We do this because we
581     * use different methods for fading the overlay in and out. The color fill
582     * overlay fades in and out nicely, but the image overlay stutters. By
583     * keeping track of the type of overlay in use, we don't have to check again
584     * what type of overlay we're using when it's time to get rid of it later.
585     *
586     * @property    {Boolean}       overlay_img_needed
587     * @private
588     */
589    var overlay_img_needed;
590
591    /**
592     * These parameters for simple browser detection. Used in Ext.js.
593     *
594     * @ignore
595     */
596    var ua = navigator.userAgent.toLowerCase();
597    var isStrict = document.compatMode == 'CSS1Compat',
598        isOpera = ua.indexOf("opera") > -1,
599        isIE = ua.indexOf('msie') > -1,
600        isIE7 = ua.indexOf('msie 7') > -1,
601        isBorderBox = isIE && !isStrict,
602        isSafari = (/webkit|khtml/).test(ua),
603        isSafari3 = isSafari && !!(document.evaluate),
604        isGecko = !isSafari && ua.indexOf('gecko') > -1,
605        isWindows = (ua.indexOf('windows') != -1 || ua.indexOf('win32') != -1),
606        isMac = (ua.indexOf('macintosh') != -1 || ua.indexOf('mac os x') != -1),
607        isLinux = (ua.indexOf('linux') != -1);
608
609    /**
610     * Do we need to hack the position to make Shadowbox appear fixed? We could
611     * hack this using CSS, but let's just get over all the hacks and let IE6
612     * users get what they deserve! Down with hacks! Hmm...now that I think
613     * about it, I should just flash all kinds of alerts and annoying popups on
614     * their screens, and then redirect them to some foreign spyware site that
615     * will upload a nasty virus...
616     *
617     * @property    {Boolean}   absolute_pos
618     * @private
619     */
620    var absolute_pos = isIE && !isIE7;
621
622    /**
623     * Contains plugin support information. Each property of this object is a
624     * boolean indicating whether that plugin is supported.
625     *
626     * - fla: Flash player
627     * - qt: QuickTime player
628     * - wmp: Windows Media player
629     * - f4m: Flip4Mac plugin
630     *
631     * @property    {Object}    plugins
632     * @private
633     */
634    var plugins = null;
635
636    // detect plugin support
637    if(navigator.plugins && navigator.plugins.length){
638        var detectPlugin = function(plugin_name){
639            var detected = false;
640            for (var i = 0, len = navigator.plugins.length; i < len; ++i){
641                if(navigator.plugins[i].name.indexOf(plugin_name) > -1){
642                    detected = true;
643                    break;
644                }
645            }
646            return detected;
647        };
648        var f4m = detectPlugin('Flip4Mac');
649        var plugins = {
650            fla:    detectPlugin('Shockwave Flash'),
651            qt:     detectPlugin('QuickTime'),
652            wmp:    !f4m && detectPlugin('Windows Media'), // if it's Flip4Mac, it's not really WMP
653            f4m:    f4m
654        };
655    }else{
656        var detectPlugin = function(plugin_name){
657            var detected = false;
658            try {
659                var axo = new ActiveXObject(plugin_name);
660                if(axo){
661                    detected = true;
662                }
663            } catch (e) {}
664            return detected;
665        };
666        var plugins = {
667            fla:    detectPlugin('ShockwaveFlash.ShockwaveFlash'),
668            qt:     detectPlugin('QuickTime.QuickTime'),
669            wmp:    detectPlugin('wmplayer.ocx'),
670            f4m:    false
671        };
672    }
673
674    /**
675     * Applies all properties of e to o. This function is recursive so that if
676     * any properties of e are themselves objects, those objects will be applied
677     * to objects with the same key that may exist in o.
678     *
679     * @param   {Object}    o       The original object
680     * @param   {Object}    e       The extension object
681     * @return  {Object}            The original object with all properties
682     *                              of the extension object applied (deep)
683     * @private
684     */
685    var apply = function(o, e){
686        for(var p in e) o[p] = e[p];
687        return o;
688    };
689
690    /**
691     * Determines if the given object is an anchor/area element.
692     *
693     * @param   {mixed}     el      The object to check
694     * @return  {Boolean}           True if the object is a link element
695     * @private
696     */
697    var isLink = function(el){
698        return typeof el.tagName == 'string' && (el.tagName.toUpperCase() == 'A' || el.tagName.toUpperCase() == 'AREA');
699    };
700
701    /**
702     * Gets the height of the viewport in pixels. Note: This function includes
703     * scrollbars in Safari 3.
704     *
705     * @return  {Number}        The height of the viewport
706     * @public
707     * @static
708     */
709    SL.getViewportHeight = function(){
710        var height = window.innerHeight; // Safari
711        var mode = document.compatMode;
712        if((mode || isIE) && !isOpera){
713            height = isStrict ? document.documentElement.clientHeight : document.body.clientHeight;
714        }
715        return height;
716    };
717
718    /**
719     * Gets the width of the viewport in pixels. Note: This function includes
720     * scrollbars in Safari 3.
721     *
722     * @return  {Number}        The width of the viewport
723     * @public
724     * @static
725     */
726    SL.getViewportWidth = function(){
727        var width = window.innerWidth; // Safari
728        var mode = document.compatMode;
729        if(mode || isIE){
730            width = isStrict ? document.documentElement.clientWidth : document.body.clientWidth;
731        }
732        return width;
733    };
734
735    /**
736     * Gets the height of the document (body and its margins) in pixels.
737     *
738     * @return  {Number}        The height of the document
739     * @public
740     * @static
741     */
742    SL.getDocumentHeight = function(){
743        var scrollHeight = isStrict ? document.documentElement.scrollHeight : document.body.scrollHeight;
744        return Math.max(scrollHeight, SL.getViewportHeight());
745    };
746
747    /**
748     * Gets the width of the document (body and its margins) in pixels.
749     *
750     * @return  {Number}        The width of the document
751     * @public
752     * @static
753     */
754    SL.getDocumentWidth = function(){
755        var scrollWidth = isStrict ? document.documentElement.scrollWidth : document.body.scrollWidth;
756        return Math.max(scrollWidth, SL.getViewportWidth());
757    };
758
759    /**
760     * A utility function used by the fade functions to clear the opacity
761     * style setting of the given element. Required in some cases for IE.
762     * Based on Ext.Element's clearOpacity.
763     *
764     * @param   {HTMLElement}   el      The DOM element
765     * @return  void
766     * @private
767     */
768    var clearOpacity = function(el){
769        if(isIE){
770            if(typeof el.style.filter == 'string' && (/alpha/i).test(el.style.filter)){
771                el.style.filter = '';
772            }
773        }else{
774            el.style.opacity = '';
775            el.style['-moz-opacity'] = '';
776            el.style['-khtml-opacity'] = '';
777        }
778    };
779
780    /**
781     * Fades the given element from 0 to the specified opacity.
782     *
783     * @param   {HTMLElement}   el              The DOM element to fade
784     * @param   {Number}        endingOpacity   The final opacity to animate to
785     * @param   {Number}        duration        The duration of the animation
786     *                                          (in seconds)
787     * @param   {Function}      callback        A callback function to call
788     *                                          when the animation completes
789     * @return  void
790     * @private
791     */
792    var fadeIn = function(el, endingOpacity, duration, callback){
793        if(options.animate){
794            SL.setStyle(el, 'opacity', 0);
795            el.style.visibility = 'visible';
796            SL.animate(el, {
797                opacity: { to: endingOpacity }
798            }, duration, function(){
799                if(endingOpacity == 1) clearOpacity(el);
800                if(typeof callback == 'function') callback();
801            });
802        }else{
803            if(endingOpacity == 1){
804                clearOpacity(el);
805            }else{
806                SL.setStyle(el, 'opacity', endingOpacity);
807            }
808            el.style.visibility = 'visible';
809            if(typeof callback == 'function') callback();
810        }
811    };
812
813    /**
814     * Fades the given element from its current opacity to 0.
815     *
816     * @param   {HTMLElement}   el          The DOM element to fade
817     * @param   {Number}        duration    The duration of the fade animation
818     * @param   {Function}      callback    A callback function to call when
819     *                                      the animation completes
820     * @return  void
821     * @private
822     */
823    var fadeOut = function(el, duration, callback){
824        var cb = function(){
825            el.style.visibility = 'hidden';
826            clearOpacity(el);
827            if(typeof callback == 'function') callback();
828        };
829        if(options.animate){
830            SL.animate(el, {
831                opacity: { to: 0 }
832            }, duration, cb);
833        }else{
834            cb();
835        }
836    };
837
838    /**
839     * Appends an HTML fragment to the given element.
840     *
841     * @param   {String/HTMLElement}    el      The element to append to
842     * @param   {String}                html    The HTML fragment to use
843     * @return  {HTMLElement}                   The newly appended element
844     * @private
845     */
846    var appendHTML = function(el, html){
847        el = SL.get(el);
848        if(el.insertAdjacentHTML){
849            el.insertAdjacentHTML('BeforeEnd', html);
850            return el.lastChild;
851        }
852        if(el.lastChild){
853            var range = el.ownerDocument.createRange();
854            range.setStartAfter(el.lastChild);
855            var frag = range.createContextualFragment(html);
856            el.appendChild(frag);
857            return el.lastChild;
858        }else{
859            el.innerHTML = html;
860            return el.lastChild;
861        }
862    };
863
864    /**
865     * Overwrites the HTML of the given element.
866     *
867     * @param   {String/HTMLElement}    el      The element to overwrite
868     * @param   {String}                html    The new HTML to use
869     * @return  {HTMLElement}                   The new firstChild element
870     * @private
871     */
872    var overwriteHTML = function(el, html){
873        el = SL.get(el);
874        el.innerHTML = html;
875        return el.firstChild;
876    };
877
878    /**
879     * Gets either the offsetHeight or the height of the given element plus
880     * padding and borders (when offsetHeight is not available). Based on
881     * Ext.Element's getComputedHeight.
882     *
883     * @return  {Number}            The computed height of the element
884     * @private
885     */
886    var getComputedHeight = function(el){
887        var h = Math.max(el.offsetHeight, el.clientHeight);
888        if(!h){
889            h = parseInt(SL.getStyle(el, 'height'), 10) || 0;
890            if(!isBorderBox){
891                h += parseInt(SL.getStyle(el, 'padding-top'), 10)
892                    + parseInt(SL.getStyle(el, 'padding-bottom'), 10)
893                    + parseInt(SL.getStyle(el, 'border-top-width'), 10)
894                    + parseInt(SL.getStyle(el, 'border-bottom-width'), 10);
895            }
896        }
897        return h;
898    };
899
900    /**
901     * Gets either the offsetWidth or the width of the given element plus
902     * padding and borders (when offsetWidth is not available). Based on
903     * Ext.Element's getComputedWidth.
904     *
905     * @return  {Number}            The computed width of the element
906     * @private
907     */
908    var getComputedWidth = function(el){
909        var w = Math.max(el.offsetWidth, el.clientWidth);
910        if(!w){
911            w = parseInt(SL.getStyle(el, 'width'), 10) || 0;
912            if(!isBorderBox){
913                w += parseInt(SL.getStyle(el, 'padding-left'), 10)
914                    + parseInt(SL.getStyle(el, 'padding-right'), 10)
915                    + parseInt(SL.getStyle(el, 'border-left-width'), 10)
916                    + parseInt(SL.getStyle(el, 'border-right-width'), 10);
917            }
918        }
919        return w;
920    };
921
922    /**
923     * Determines the player needed to display the file at the given URL. If
924     * the file type is not supported, the return value will be 'unsupported'.
925     * If the file type is not supported but the correct player can be
926     * determined, the return value will be 'unsupported-*' where * will be the
927     * player abbreviation (e.g. 'qt' = QuickTime).
928     *
929     * @param   {String}        url     The url of the file
930     * @return  {String}                The name of the player to use
931     * @private
932     */
933    var getPlayerType = function(url){
934        if(RE.img.test(url)) return 'img';
935        var match = url.match(RE.domain);
936        var this_domain = match ? document.domain == match[1] : false;
937        if(url.indexOf('#') > -1 && this_domain) return 'inline';
938        var q_index = url.indexOf('?');
939        if(q_index > -1) url = url.substring(0, q_index); // strip query string for player detection purposes
940        if(RE.swf.test(url)) return plugins.fla ? 'swf' : 'unsupported-swf';
941        if(RE.flv.test(url)) return plugins.fla ? 'flv' : 'unsupported-flv';
942        if(RE.qt.test(url)) return plugins.qt ? 'qt' : 'unsupported-qt';
943        if(RE.wmp.test(url)){
944            if(plugins.wmp){
945                return 'wmp';
946            }else if(plugins.f4m){
947                return 'qt';
948            }else{
949                return isMac ? (plugins.qt ? 'unsupported-f4m' : 'unsupported-qtf4m') : 'unsupported-wmp';
950            }
951        }else if(RE.qtwmp.test(url)){
952            if(plugins.qt){
953                return 'qt';
954            }else if(plugins.wmp){
955                return 'wmp';
956            }else{
957                return isMac ? 'unsupported-qt' : 'unsupported-qtwmp';
958            }
959        }else if(!this_domain || RE.iframe.test(url)){
960            return 'iframe';
961        }
962        return 'unsupported';
963    };
964
965    /**
966     * Handles all clicks on links that have been set up to work with Shadowbox
967     * and cancels the default event behavior when appropriate.
968     *
969     * @param   {Event}         ev          The click event object
970     * @return  void
971     * @private
972     */
973    var handleClick = function(ev){
974        // get anchor/area element
975        var link;
976        if(isLink(this)){
977            link = this; // jQuery, Prototype, YUI
978        }else{
979            link = SL.getTarget(ev); // Ext
980            while(!isLink(link) && link.parentNode){
981                link = link.parentNode;
982            }
983        }
984
985        Shadowbox.open(link);
986        if(current_gallery.length) SL.preventDefault(ev);
987    };
988
989    /**
990     * Sets up the current gallery for the given object. Modifies the current
991     * and current_gallery variables to contain the appropriate information.
992     * Also, checks to see if there are any gallery pieces that are not
993     * supported by the client's browser/plugins. If there are, they will be
994     * handled according to the handleUnsupported option.
995     *
996     * @param   {Object}    obj         The content to get the gallery for
997     * @return  void
998     * @private
999     */
1000    var setupGallery = function(obj){
1001        // create a copy so it doesn't get modified later
1002        var copy = apply({}, obj);
1003
1004        // is it part of a gallery?
1005        if(!obj.gallery){ // single item, no gallery
1006            current_gallery = [copy];
1007            current = 0;
1008        }else{
1009            current_gallery = []; // clear the current gallery
1010            var index, ci;
1011            for(var i = 0, len = cache.length; i < len; ++i){
1012                ci = cache[i];
1013                if(ci.gallery){
1014                    if(ci.content == obj.content
1015                        && ci.gallery == obj.gallery
1016                        && ci.title == obj.title){ // compare content, gallery, & title
1017                            index = current_gallery.length; // key element found
1018                    }
1019                    if(ci.gallery == obj.gallery){
1020                        current_gallery.push(apply({}, ci));
1021                    }
1022                }
1023            }
1024            // if not found in cache, prepend to front of gallery
1025            if(index == null){
1026                current_gallery.unshift(copy);
1027                index = 0;
1028            }
1029            current = index;
1030        }
1031
1032        // are any media in the current gallery supported?
1033        var match, r;
1034        for(var i = 0, len = current_gallery.length; i < len; ++i){
1035            r = false;
1036            if(current_gallery[i].type == 'unsupported'){ // don't support this at all
1037                r = true;
1038            }else if(match = RE.unsupported.exec(current_gallery[i].type)){ // handle unsupported elements
1039                if(options.handleUnsupported == 'link'){
1040                    current_gallery[i].type = 'html';
1041                    // generate a link to the appropriate plugin download page(s)
1042                    var m;
1043                    switch(match[1]){
1044                        case 'qtwmp':
1045                            m = String.format(options.text.errors.either,
1046                                options.errors.qt.url, options.errors.qt.name,
1047                                options.errors.wmp.url, options.errors.wmp.name);
1048                        break;
1049                        case 'qtf4m':
1050                            m = String.format(options.text.errors.shared,
1051                                options.errors.qt.url, options.errors.qt.name,
1052                                options.errors.f4m.url, options.errors.f4m.name);
1053                        break;
1054                        default:
1055                            if(match[1] == 'swf' || match[1] == 'flv') match[1] = 'fla';
1056                            m = String.format(options.text.errors.single,
1057                                options.errors[match[1]].url, options.errors[match[1]].name);
1058                    }
1059                    current_gallery[i] = apply(current_gallery[i], {
1060                        height:     160, // error messages are short so they
1061                        width:      320, // only need a small box to display properly
1062                        content:    '<div class="shadowbox_message">' + m + '</div>'
1063                    });
1064                }else{
1065                    r = true;
1066                }
1067            }else if(current_gallery[i].type == 'inline'){ // handle inline elements
1068                // retrieve the innerHTML of the inline element
1069                var match = RE.inline.exec(current_gallery[i].content);
1070                if(match){
1071                    var el;
1072                    if(el = SL.get(match[1])){
1073                        current_gallery[i].content = el.innerHTML;
1074                    }else{
1075                        throw 'No element found with id ' + match[1];
1076                    }
1077                }else{
1078                    throw 'No element id found for inline content';
1079                }
1080            }
1081            if(r){
1082                // remove the element from the gallery
1083                current_gallery.splice(i, 1);
1084                if(i < current) --current;
1085                --i;
1086            }
1087        }
1088    };
1089
1090    /**
1091     * Hides the title bar and toolbar and populates them with the proper
1092     * content.
1093     *
1094     * @return  void
1095     * @private
1096     */
1097    var buildBars = function(){
1098        var link = current_gallery[current];
1099        if(!link) return; // nothing to build
1100
1101        // build the title
1102        var title_i = SL.get('shadowbox_title_inner');
1103        title_i.innerHTML = (link.title) ? link.title : '';
1104        // empty the toolbar
1105        var tool_i = SL.get('shadowbox_toolbar_inner');
1106        tool_i.innerHTML = '';
1107
1108        // build the nav
1109        if(options.displayNav){
1110            tool_i.innerHTML = String.format(options.skin.close, options.text.close);
1111            if(current_gallery.length > 1){
1112                if(options.continuous){
1113                    // show both
1114                    appendHTML(tool_i, String.format(options.skin.next, options.text.next));
1115                    appendHTML(tool_i, String.format(options.skin.prev, options.text.prev));
1116                }else{
1117                    // not last in the gallery, show the next link
1118                    if((current_gallery.length - 1) > current){
1119                        appendHTML(tool_i, String.format(options.skin.next, options.text.next));
1120                    }
1121                    // not first in the gallery, show the previous link
1122                    if(current > 0){
1123                        appendHTML(tool_i, String.format(options.skin.prev, options.text.prev));
1124                    }
1125                }
1126            }
1127        }
1128
1129        // build the counter
1130        if(current_gallery.length > 1 && options.displayCounter){
1131            // append the counter div
1132            var counter = '';
1133            if(options.counterType == 'skip'){
1134                for(var i = 0, len = current_gallery.length; i < len; ++i){
1135                    counter += '<a href="javascript:Shadowbox.change(' + i + ');"';
1136                    if(i == current){
1137                        counter += ' class="shadowbox_counter_current"';
1138                    }
1139                    counter += '>' + (i + 1) + '</a>';
1140                }
1141            }else{
1142                counter = (current + 1) + ' of ' + current_gallery.length;
1143            }
1144            appendHTML(tool_i, String.format(options.skin.counter, counter));
1145        }
1146    };
1147
1148    /**
1149     * Hides the title and tool bars.
1150     *
1151     * @param   {Function}  callback        A function to call on finish
1152     * @return  void
1153     * @private
1154     */
1155    var hideBars = function(callback){
1156        var title_m = getComputedHeight(SL.get('shadowbox_title'));
1157        var tool_m = 0 - getComputedHeight(SL.get('shadowbox_toolbar'));
1158        var title_i = SL.get('shadowbox_title_inner');
1159        var tool_i = SL.get('shadowbox_toolbar_inner');
1160
1161        if(options.animate && callback){
1162            // animate the transition
1163            SL.animate(title_i, {
1164                marginTop: { to: title_m }
1165            }, 0.2);
1166            SL.animate(tool_i, {
1167                marginTop: { to: tool_m }
1168            }, 0.2, callback);
1169        }else{
1170            SL.setStyle(title_i, 'marginTop', title_m + 'px');
1171            SL.setStyle(tool_i, 'marginTop', tool_m + 'px');
1172        }
1173    };
1174
1175    /**
1176     * Shows the title and tool bars.
1177     *
1178     * @param   {Function}  callback        A callback function to execute after
1179     *                                      the animation completes
1180     * @return  void
1181     * @private
1182     */
1183    var showBars = function(callback){
1184        var title_i = SL.get('shadowbox_title_inner');
1185        if(options.animate){
1186            if(title_i.innerHTML != ''){
1187                SL.animate(title_i, { marginTop: { to: 0 } }, 0.35);
1188            }
1189            SL.animate(SL.get('shadowbox_toolbar_inner'), {
1190                marginTop: { to: 0 }
1191            }, 0.35, callback);
1192        }else{
1193            if(title_i.innerHTML != ''){
1194                SL.setStyle(title_i, 'margin-top', '0px');
1195            }
1196            SL.setStyle(SL.get('shadowbox_toolbar_inner'), 'margin-top', '0px');
1197            callback();
1198        }
1199    };
1200
1201    /**
1202     * Resets the class drag variable.
1203     *
1204     * @return  void
1205     * @private
1206     */
1207    var resetDrag = function(){
1208        drag = {
1209            x:          0,
1210            y:          0,
1211            start_x:    null,
1212            start_y:    null
1213        };
1214    };
1215
1216    /**
1217     * Toggles the drag function on and off.
1218     *
1219     * @param   {Boolean}   on      True to toggle on, false to toggle off
1220     * @return  void
1221     * @private
1222     */
1223    var toggleDrag = function(on){
1224        if(on){
1225            resetDrag();
1226            // add drag layer to prevent browser dragging of actual image
1227            var styles = [
1228                'position:absolute',
1229                'cursor:' + (isGecko ? '-moz-grab' : 'move')
1230            ];
1231            // make drag layer transparent
1232            styles.push(isIE ? 'background-color:#fff;filter:alpha(opacity=0)' : 'background-color:transparent');
1233            appendHTML('shadowbox_body_inner', '<div id="shadowbox_drag_layer" style="' + styles.join(';') + '"></div>');
1234            SL.addEvent(SL.get('shadowbox_drag_layer'), 'mousedown', listenDrag);
1235        }else{
1236            var d = SL.get('shadowbox_drag_layer');
1237            if(d){
1238                SL.removeEvent(d, 'mousedown', listenDrag);
1239                SL.remove(d);
1240            }
1241        }
1242    };
1243
1244    /**
1245     * Sets up a drag listener on the document. Called when the mouse button is
1246     * pressed (mousedown).
1247     *
1248     * @param   {mixed}     ev      The mousedown event
1249     * @return  void
1250     * @private
1251     */
1252    var listenDrag = function(ev){
1253        drag.start_x = ev.clientX;
1254        drag.start_y = ev.clientY;
1255        draggable = SL.get('shadowbox_content');
1256        SL.addEvent(document, 'mousemove', positionDrag);
1257        SL.addEvent(document, 'mouseup', unlistenDrag);
1258        if(isGecko) SL.setStyle(SL.get('shadowbox_drag_layer'), 'cursor', '-moz-grabbing');
1259    };
1260
1261    /**
1262     * Removes the drag listener. Called when the mouse button is released
1263     * (mouseup).
1264     *
1265     * @return  void
1266     * @private
1267     */
1268    var unlistenDrag = function(){
1269        SL.removeEvent(document, 'mousemove', positionDrag);
1270        SL.removeEvent(document, 'mouseup', unlistenDrag); // clean up
1271        if(isGecko) SL.setStyle(SL.get('shadowbox_drag_layer'), 'cursor', '-moz-grab');
1272    };
1273
1274    /**
1275     * Positions an oversized image on drag.
1276     *
1277     * @param   {mixed}     ev      The drag event
1278     * @return  void
1279     * @private
1280     */
1281    var positionDrag = function(ev){
1282        var move_y = ev.clientY - drag.start_y;
1283        drag.start_y = drag.start_y + move_y;
1284        drag.y = Math.max(Math.min(0, drag.y + move_y), current_height - optimal_height); // y boundaries
1285        SL.setStyle(draggable, 'top', drag.y + 'px');
1286        var move_x = ev.clientX - drag.start_x;
1287        drag.start_x = drag.start_x + move_x;
1288        drag.x = Math.max(Math.min(0, drag.x + move_x), current_width - optimal_width); // x boundaries
1289        SL.setStyle(draggable, 'left', drag.x + 'px');
1290    };
1291
1292    /**
1293     * Loads the Shadowbox with the current piece.
1294     *
1295     * @return  void
1296     * @private
1297     */
1298    var loadContent = function(){
1299        var obj = current_gallery[current];
1300        if(!obj) return; // invalid
1301
1302        buildBars();
1303
1304        switch(obj.type){
1305            case 'img':
1306                // preload the image
1307                preloader = new Image();
1308                preloader.onload = function(){
1309                    // images default to image height and width
1310                    var h = obj.height ? parseInt(obj.height, 10) : preloader.height;
1311                    var w = obj.width ? parseInt(obj.width, 10) : preloader.width;
1312                    resizeContent(h, w, function(dims){
1313                        showBars(function(){
1314                            setContent({
1315                                tag:    'img',
1316                                height: dims.i_height,
1317                                width:  dims.i_width,
1318                                src:    obj.content,
1319                                style:  'position:absolute'
1320                            });
1321                            if(dims.enableDrag && options.handleLgImages == 'drag'){
1322                                // listen for drag
1323                                toggleDrag(true);
1324                                SL.setStyle(SL.get('shadowbox_drag_layer'), {
1325                                    height:     dims.i_height + 'px',
1326                                    width:      dims.i_width + 'px'
1327                                });
1328                            }
1329                            finishContent();
1330                        });
1331                    });
1332
1333                    preloader.onload = function(){}; // clear onload for IE
1334                };
1335                preloader.src = obj.content;
1336            break;
1337
1338            case 'swf':
1339            case 'flv':
1340            case 'qt':
1341            case 'wmp':
1342                var markup = Shadowbox.movieMarkup(obj);
1343                resizeContent(markup.height, markup.width, function(){
1344                    showBars(function(){
1345                        setContent(markup);
1346                        finishContent();
1347                    });
1348                });
1349            break;
1350
1351            case 'iframe':
1352                // iframes default to full viewport height and width
1353                var h = obj.height ? parseInt(obj.height, 10) : SL.getViewportHeight();
1354                var w = obj.width ? parseInt(obj.width, 10) : SL.getViewportWidth();
1355                var content = {
1356                    tag:            'iframe',
1357                    name:           'shadowbox_content',
1358                    height:         '100%',
1359                    width:          '100%',
1360                    frameborder:    '0',
1361                    marginwidth:    '0',
1362                    marginheight:   '0',
1363                    scrolling:      'auto'
1364                };
1365
1366                resizeContent(h, w, function(dims){
1367                    showBars(function(){
1368                        setContent(content);
1369                        var win = (isIE)
1370                            ? SL.get('shadowbox_content').contentWindow
1371                            : window.frames['shadowbox_content'];
1372                        win.location = obj.content;
1373                        finishContent();
1374                    });
1375                });
1376            break;
1377
1378            case 'html':
1379            case 'inline':
1380                // HTML content defaults to full viewport height and width
1381                var h = obj.height ? parseInt(obj.height, 10) : SL.getViewportHeight();
1382                var w = obj.width ? parseInt(obj.width, 10) : SL.getViewportWidth();
1383                var content = {
1384                    tag:    'div',
1385                    cls:    'html', /* give special class to make scrollable */
1386                    html:   obj.content
1387                };
1388                resizeContent(h, w, function(){
1389                    showBars(function(){
1390                        setContent(content);
1391                        finishContent();
1392                    });
1393                });
1394            break;
1395
1396            default:
1397                // should never happen
1398                throw 'Shadowbox cannot open content of type ' + obj.type;
1399        }
1400
1401        // preload neighboring images
1402        if(current_gallery.length > 0){
1403            var next = current_gallery[current + 1];
1404            if(!next){
1405                next = current_gallery[0];
1406            }
1407            if(next.type == 'img'){
1408                var preload_next = new Image();
1409                preload_next.src = next.href;
1410            }
1411
1412            var prev = current_gallery[current - 1];
1413            if(!prev){
1414                prev = current_gallery[current_gallery.length - 1];
1415            }
1416            if(prev.type == 'img'){
1417                var preload_prev = new Image();
1418                preload_prev.src = prev.href;
1419            }
1420        }
1421    };
1422
1423    /**
1424     * Removes old content and sets the new content of the Shadowbox.
1425     *
1426     * @param   {Object}        obj     The content to set (appropriate to pass
1427     *                                  directly to Shadowbox.createHTML())
1428     * @return  {HTMLElement}           The newly appended element (or null if
1429     *                                  none is provided)
1430     * @private
1431     */
1432    var setContent = function(obj){
1433        var id = 'shadowbox_content';
1434        var content = SL.get(id);
1435        if(content){
1436            // remove old content first
1437            switch(content.tagName.toUpperCase()){
1438                case 'OBJECT':
1439                    // if we're in a gallery (i.e. changing and there's a new
1440                    // object) we want the LAST link object
1441                    var link = current_gallery[(obj ? current - 1 : current)];
1442                    if(link.type == 'wmp' && isIE){
1443                        try{
1444                            shadowbox_content.controls.stop(); // stop the movie
1445                            shadowbox_content.URL = 'non-existent.wmv'; // force player refresh
1446                            window.shadowbox_content = function(){}; // remove from window
1447                        }catch(e){}
1448                    }else if(link.type == 'qt' && isSafari){
1449                        try{
1450                            document.shadowbox_content.Stop(); // stop QT movie
1451                        }catch(e){}
1452                        // stop QT audio stream for movies that have not yet loaded
1453                        content.innerHTML = '';
1454                        // console.log(document.shadowbox_content);
1455                    }
1456                    setTimeout(function(){ // using setTimeout prevents browser crashes with WMP
1457                        SL.remove(content);
1458                    }, 10);
1459                break;
1460                case 'IFRAME':
1461                    SL.remove(content);
1462                    if(isGecko) delete window.frames[id]; // needed for Firefox
1463                break;
1464                default:
1465                    SL.remove(content);
1466            }
1467        }
1468        if(obj){
1469            if(!obj.id) obj.id = id;
1470            return appendHTML('shadowbox_body_inner', Shadowbox.createHTML(obj));
1471        }
1472        return null;
1473    };
1474
1475    /**
1476     * This function is used as the callback after the Shadowbox has been
1477     * positioned, resized, and loaded with content.
1478     *
1479     * @return  void
1480     * @private
1481     */
1482    var finishContent = function(){
1483        var obj = current_gallery[current];
1484        if(!obj) return; // invalid
1485        hideLoading(function(){
1486            listenKeyboard(true);
1487            // fire onFinish handler
1488            if(options.onFinish && typeof options.onFinish == 'function'){
1489                options.onFinish(obj);
1490            }
1491        });
1492    };
1493
1494    /**
1495     * Resizes and positions the content box using the given height and width.
1496     * If the callback parameter is missing, the transition will not be
1497     * animated. If the callback parameter is present, it will be passed the
1498     * new calculated dimensions object as its first parameter. Note: the height
1499     * and width here should represent the optimal height and width of the box.
1500     *
1501     * @param   {Function}  callback    A callback function to use when the
1502     *                                  resize completes
1503     * @return  void
1504     * @private
1505     */
1506    var resizeContent = function(height, width, callback){
1507        // update optimal height and width
1508        optimal_height = height;
1509        optimal_width = width;
1510        var resizable = RE.resize.test(current_gallery[current].type);
1511        var dims = getDimensions(optimal_height, optimal_width, resizable);
1512        if(callback){
1513            var cb = function(){ callback(dims); };
1514            switch(options.animSequence){
1515                case 'hw':
1516                    adjustHeight(dims.height, dims.top, true, function(){
1517                        adjustWidth(dims.width, true, cb);
1518                    });
1519                break;
1520                case 'wh':
1521                    adjustWidth(dims.width, true, function(){
1522                        adjustHeight(dims.height, dims.top, true, cb);
1523                    });
1524                break;
1525                default: // sync
1526                    adjustWidth(dims.width, true);
1527                    adjustHeight(dims.height, dims.top, true, cb);
1528            }
1529        }else{ // window resize
1530            adjustWidth(dims.width, false);
1531            adjustHeight(dims.height, dims.top, false);
1532            // resize content images & flash in 'resize' mode
1533            if(options.handleLgImages == 'resize' && resizable){
1534                var content = SL.get('shadowbox_content');
1535                if(content){ // may be animating, not present
1536                    content.height = dims.i_height;
1537                    content.width = dims.i_width;
1538                }
1539            }
1540        }
1541    };
1542
1543    /**
1544     * Calculates the dimensions for Shadowbox, taking into account the borders,
1545     * margins, and surrounding elements of the shadowbox_body. If the image
1546     * is still to large for Shadowbox, and options.handleLgImages is 'resize',
1547     * the resized dimensions will be returned (preserving the original aspect
1548     * ratio). Otherwise, the originally calculated dimensions will be returned.
1549     * The returned object will have the following properties:
1550     *
1551     * - height: The height to use for shadowbox_body_inner
1552     * - width: The width to use for shadowbox
1553     * - i_height: The height to use for resizable content
1554     * - i_width: The width to use for resizable content
1555     * - top: The top to use for shadowbox
1556     * - enableDrag: True if dragging should be enabled (image is oversized)
1557     *
1558     * @param   {Number}    o_height    The optimal height
1559     * @param   {Number}    o_width     The optimal width
1560     * @param   {Boolean}   resizable   True if the content is able to be
1561     *                                  resized. Defaults to false.
1562     * @return  {Object}                The resize dimensions (see above)
1563     * @private
1564     */
1565    var getDimensions = function(o_height, o_width, resizable){
1566        if(typeof resizable == 'undefined') resizable = false;
1567
1568        var height = o_height = parseInt(o_height);
1569        var width = o_width = parseInt(o_width);
1570        var shadowbox_b = SL.get('shadowbox_body');
1571
1572        // calculate the max height
1573        var view_height = SL.getViewportHeight();
1574        var extra_height = parseInt(SL.getStyle(shadowbox_b, 'border-top-width'), 10)
1575            + parseInt(SL.getStyle(shadowbox_b, 'border-bottom-width'), 10)
1576            + parseInt(SL.getStyle(shadowbox_b, 'margin-top'), 10)
1577            + parseInt(SL.getStyle(shadowbox_b, 'margin-bottom'), 10)
1578            + getComputedHeight(SL.get('shadowbox_title'))
1579            + getComputedHeight(SL.get('shadowbox_toolbar'))
1580            + (2 * options.viewportPadding);
1581        if((height + extra_height) >= view_height){
1582            height = view_height - extra_height;
1583        }
1584
1585        // calculate the max width
1586        var view_width = SL.getViewportWidth();
1587        var extra_body_width = parseInt(SL.getStyle(shadowbox_b, 'border-left-width'), 10)
1588            + parseInt(SL.getStyle(shadowbox_b, 'border-right-width'), 10)
1589            + parseInt(SL.getStyle(shadowbox_b, 'margin-left'), 10)
1590            + parseInt(SL.getStyle(shadowbox_b, 'margin-right'), 10);
1591        var extra_width = extra_body_width + (2 * options.viewportPadding);
1592        if((width + extra_width) >= view_width){
1593            width = view_width - extra_width;
1594        }
1595
1596        // handle oversized images & flash
1597        var enableDrag = false;
1598        var i_height = o_height;
1599        var i_width = o_width;
1600        var handle = options.handleLgImages;
1601        if(resizable && (handle == 'resize' || handle == 'drag')){
1602            var change_h = (o_height - height) / o_height;
1603            var change_w = (o_width - width) / o_width;
1604            if(handle == 'resize'){
1605                if(change_h > change_w){
1606                    width = Math.round((o_width / o_height) * height);
1607                }else if(change_w > change_h){
1608                    height = Math.round((o_height / o_width) * width);
1609                }
1610                // adjust image height or width accordingly
1611                i_width = width;
1612                i_height = height;
1613            }else{
1614                // drag on oversized images only
1615                var link = current_gallery[current];
1616                if(link) enableDrag = link.type == 'img' && (change_h > 0 || change_w > 0);
1617            }
1618        }
1619
1620        return {
1621            height: height,
1622            width: width + extra_body_width,
1623            i_height: i_height,
1624            i_width: i_width,
1625            top: ((view_height - (height + extra_height)) / 2) + options.viewportPadding,
1626            enableDrag: enableDrag
1627        };
1628    };
1629
1630    /**
1631     * Centers Shadowbox vertically in the viewport. Needs to be called on
1632     * scroll in IE6 because it does not support fixed positioning.
1633     *
1634     * @return  void
1635     * @private
1636     */
1637    var centerVertically = function(){
1638        var shadowbox = SL.get('shadowbox');
1639        var scroll = document.documentElement.scrollTop;
1640        var s_top = scroll + Math.round((SL.getViewportHeight() - (shadowbox.offsetHeight || 0)) / 2);
1641        SL.setStyle(shadowbox, 'top', s_top + 'px');
1642    };
1643
1644    /**
1645     * Adjusts the height of shadowbox_body_inner and centers Shadowbox
1646     * vertically in the viewport.
1647     *
1648     * @param   {Number}    height      The height of shadowbox_body_inner
1649     * @param   {Number}    top         The top of the Shadowbox
1650     * @param   {Boolean}   animate     True to animate the transition
1651     * @param   {Function}  callback    A callback to use when the animation completes
1652     * @return  void
1653     * @private
1654     */
1655    var adjustHeight = function(height, top, animate, callback){
1656        height = parseInt(height);
1657
1658        // update current_height
1659        current_height = height;
1660
1661        // adjust the height
1662        var sbi = SL.get('shadowbox_body_inner');
1663        if(animate && options.animate){
1664            SL.animate(sbi, {
1665                height: { to: height }
1666            }, options.resizeDuration, callback);
1667        }else{
1668            SL.setStyle(sbi, 'height', height + 'px');
1669            if(typeof callback == 'function') callback();
1670        }
1671
1672        // manually adjust the top because we're using fixed positioning in IE6
1673        if(absolute_pos){
1674            // listen for scroll so we can adjust
1675            centerVertically();
1676            SL.addEvent(window, 'scroll', centerVertically);
1677
1678            // add scroll to top
1679            top += document.documentElement.scrollTop;
1680        }
1681
1682        // adjust the top
1683        var shadowbox = SL.get('shadowbox');
1684        if(animate && options.animate){
1685            SL.animate(shadowbox, {
1686                top: { to: top }
1687            }, options.resizeDuration);
1688        }else{
1689            SL.setStyle(shadowbox, 'top', top + 'px');
1690        }
1691    };
1692
1693    /**
1694     * Adjusts the width of shadowbox.
1695     *
1696     * @param   {Number}    width       The width to use
1697     * @param   {Boolean}   animate     True to animate the transition
1698     * @param   {Function}  callback    A callback to use when the animation completes
1699     * @return  void
1700     * @private
1701     */
1702    var adjustWidth = function(width, animate, callback){
1703        width = parseInt(width);
1704
1705        // update current_width
1706        current_width = width;
1707
1708        var shadowbox = SL.get('shadowbox');
1709        if(animate && options.animate){
1710            SL.animate(shadowbox, {
1711                width: { to: width }
1712            }, options.resizeDuration, callback);
1713        }else{
1714            SL.setStyle(shadowbox, 'width', width + 'px');
1715            if(typeof callback == 'function') callback();
1716        }
1717    };
1718
1719    /**
1720     * Sets up a listener on the document for keystrokes.
1721     *
1722     * @param   {Boolean}   on      True to enable the listner, false to turn
1723     *                              it off
1724     * @return  void
1725     * @private
1726     */
1727    var listenKeyboard = function(on){
1728        if(!options.enableKeys) return;
1729        if(on){
1730            document.onkeydown = handleKey;
1731        }else{
1732            document.onkeydown = '';
1733        }
1734    };
1735
1736    /**
1737     * Asserts the given key or code is present in the array of valid keys.
1738     *
1739     * @param   {Array}     valid       An array of valid keys and codes
1740     * @param   {String}    key         The character that was pressed
1741     * @param   {Number}    code        The key code that was pressed
1742     * @return  {Boolean}               True if the key is valid
1743     * @private
1744     */
1745    var assertKey = function(valid, key, code){
1746        return (valid.indexOf(key) != -1 || valid.indexOf(code) != -1);
1747    };
1748
1749    /**
1750     * A listener function that will act on a key pressed.
1751     *
1752     * @param   {Event}     e       The event object
1753     * @return  void
1754     * @private
1755     */
1756    var handleKey = function(e){
1757        var code = e ? e.which : event.keyCode;
1758        var key = String.fromCharCode(code).toLowerCase();
1759        if(assertKey(options.keysClose, key, code)){
1760            Shadowbox.close();
1761        }else if(assertKey(options.keysPrev, key, code)){
1762            Shadowbox.previous();
1763        }else if(assertKey(options.keysNext, key, code)){
1764            Shadowbox.next();
1765        }
1766    };
1767
1768    /**
1769     * Shows and hides elements that are troublesome for modal overlays.
1770     *
1771     * @param   {Boolean}   on      True to show the elements, false otherwise
1772     * @return  void
1773     * @private
1774     */
1775    var toggleTroubleElements = function(on){
1776        var vis = (on ? 'visible' : 'hidden');
1777        var selects = document.getElementsByTagName('select');
1778        for(i = 0, len = selects.length; i < len; ++i){
1779            selects[i].style.visibility = vis;
1780        }
1781        var objects = document.getElementsByTagName('object');
1782        for(i = 0, len = objects.length; i < len; ++i){
1783            objects[i].style.visibility = vis;
1784        }
1785        var embeds = document.getElementsByTagName('embed');
1786        for(i = 0, len = embeds.length; i < len; ++i){
1787            embeds[i].style.visibility = vis;
1788        }
1789    };
1790
1791    /**
1792     * Fills the Shadowbox with the loading skin.
1793     *
1794     * @return  void
1795     * @private
1796     */
1797    var showLoading = function(){
1798        var loading = SL.get('shadowbox_loading');
1799        overwriteHTML(loading, String.format(options.skin.loading,
1800            options.assetURL + options.loadingImage,
1801            options.text.loading,
1802            options.text.cancel));
1803        loading.style.visibility = 'visible';
1804    };
1805
1806    /**
1807     * Hides the Shadowbox loading skin.
1808     *
1809     * @param   {Function}  callback        The callback function to call after
1810     *                                      hiding the loading skin
1811     * @return  void
1812     * @private
1813     */
1814    var hideLoading = function(callback){
1815        var t = current_gallery[current].type;
1816        var anim = (t == 'img' || t == 'html'); // fade on images & html
1817        var loading = SL.get('shadowbox_loading');
1818        if(anim){
1819            fadeOut(loading, 0.35, callback);
1820        }else{
1821            loading.style.visibility = 'hidden';
1822            callback();
1823        }
1824    };
1825
1826    /**
1827     * Sets the size of the overlay to the size of the document.
1828     *
1829     * @return  void
1830     * @private
1831     */
1832    var resizeOverlay = function(){
1833        var overlay = SL.get('shadowbox_overlay');
1834        SL.setStyle(overlay, {
1835            height: '100%',
1836            width: '100%'
1837        });
1838        SL.setStyle(overlay, 'height', SL.getDocumentHeight() + 'px');
1839        if(!isSafari3){
1840            // Safari3 includes vertical scrollbar in SL.getDocumentWidth()!
1841            // Leave overlay width at 100% for now...
1842            SL.setStyle(overlay, 'width', SL.getDocumentWidth() + 'px');
1843        }
1844    };
1845
1846    /**
1847     * Used to determine if the pre-made overlay background image is needed
1848     * instead of using the trasparent background overlay. A pre-made background
1849     * image is used for all but image pieces in FF Mac because it has problems
1850     * displaying correctly if the background layer is not 100% opaque. When
1851     * displaying a gallery, if any piece in the gallery meets these criteria,
1852     * the pre-made background image will be used.
1853     *
1854     * @return  {Boolean}       Whether or not an overlay image is needed
1855     * @private
1856     */
1857    var checkOverlayImgNeeded = function(){
1858        if(!(isGecko && isMac)) return false;
1859        for(var i = 0, len = current_gallery.length; i < len; ++i){
1860            if(!RE.overlay.exec(current_gallery[i].type)) return true;
1861        }
1862        return false;
1863    };
1864
1865    /**
1866     * Activates (or deactivates) the Shadowbox overlay. If a callback function
1867     * is provided, we know we're activating. Otherwise, deactivate the overlay.
1868     *
1869     * @param   {Function}  callback    A callback to call after activation
1870     * @return  void
1871     * @private
1872     */
1873    var toggleOverlay = function(callback){
1874        var overlay = SL.get('shadowbox_overlay');
1875        if(overlay_img_needed == null){
1876            overlay_img_needed = checkOverlayImgNeeded();
1877        }
1878
1879        if(callback){
1880            resizeOverlay(); // size the overlay before showing
1881            if(overlay_img_needed){
1882                SL.setStyle(overlay, {
1883                    visibility:         'visible',
1884                    backgroundColor:    'transparent',
1885                    backgroundImage:    'url(' + options.assetURL + options.overlayBgImage + ')',
1886                    backgroundRepeat:   'repeat',
1887                    opacity:            1
1888                });
1889                callback();
1890            }else{
1891                SL.setStyle(overlay, {
1892                    visibility:         'visible',
1893                    backgroundColor:    options.overlayColor,
1894                    backgroundImage:    'none'
1895                });
1896                fadeIn(overlay, options.overlayOpacity, options.fadeDuration,
1897                    callback);
1898            }
1899        }else{
1900            if(overlay_img_needed){
1901                SL.setStyle(overlay, 'visibility', 'hidden');
1902            }else{
1903                fadeOut(overlay, options.fadeDuration);
1904            }
1905
1906            // reset for next time
1907            overlay_img_needed = null;
1908        }
1909    };
1910
1911    /**
1912     * Initializes the Shadowbox environment. Appends Shadowbox' HTML to the
1913     * document and sets up listeners on the window and overlay element.
1914     *
1915     * @param   {Object}    opts    The default options to use
1916     * @return  void
1917     * @public
1918     * @static
1919     */
1920    Shadowbox.init = function(opts){
1921        if(initialized) return; // don't initialize twice
1922        options = apply(options, opts || {});
1923
1924        // add markup
1925        appendHTML(document.body, options.skin.main);
1926
1927        // compile file type regular expressions here for speed
1928        RE.img = new RegExp('\.(' + options.ext.img.join('|') + ')\s*$', 'i');
1929        RE.qt = new RegExp('\.(' + options.ext.qt.join('|') + ')\s*$', 'i');
1930        RE.wmp = new RegExp('\.(' + options.ext.wmp.join('|') + ')\s*$', 'i');
1931        RE.qtwmp = new RegExp('\.(' + options.ext.qtwmp.join('|') + ')\s*$', 'i');
1932        RE.iframe = new RegExp('\.(' + options.ext.iframe.join('|') + ')\s*$', 'i');
1933
1934        // handle window resize events
1935        var id = null;
1936        var resize = function(){
1937            clearInterval(id);
1938            id = null;
1939            resizeOverlay();
1940            resizeContent(optimal_height, optimal_width);
1941        };
1942        SL.addEvent(window, 'resize', function(){
1943            if(activated){
1944                // use event buffering to prevent jerky window resizing
1945                if(id){
1946                    clearInterval(id);
1947                    id = null;
1948                }
1949                if(!id) id = setInterval(resize, 50);
1950            }
1951        });
1952
1953        if(options.listenOverlay){
1954            // add a listener to the overlay
1955            SL.addEvent(SL.get('shadowbox_overlay'), 'click', Shadowbox.close);
1956        }
1957
1958        // adjust some positioning if needed
1959        if(absolute_pos){
1960            // give the container absolute positioning
1961            SL.setStyle(SL.get('shadowbox_container'), 'position', 'absolute');
1962            // give shadowbox_body "layout"...whatever that is
1963            SL.setStyle('shadowbox_body', 'zoom', 1);
1964            // need to listen to the container element because it covers the top
1965            // half of the page
1966            SL.addEvent(SL.get('shadowbox_container'), 'click', function(e){
1967                var target = SL.getTarget(e);
1968                if(target.id && target.id == 'shadowbox_container') Shadowbox.close();
1969            });
1970        }
1971
1972        // skip setup, will need to be done manually later
1973        if(!options.skipSetup) Shadowbox.setup();
1974        initialized = true;
1975    };
1976
1977    /**
1978     * Sets up listeners on the given links that will trigger Shadowbox. If no
1979     * links are given, this method will set up every anchor element on the page
1980     * with the appropriate rel attribute. Note: Because AREA elements do not
1981     * support the rel attribute, they must be explicitly passed to this method.
1982     *
1983     * @param   {Array}     links       An array (or array-like) list of anchor
1984     *                                  and/or area elements to set up
1985     * @param   {Object}    opts        Some options to use for the given links
1986     * @return  void
1987     * @public
1988     * @static
1989     */
1990    Shadowbox.setup = function(links, opts){
1991        // get links if none specified
1992        if(!links){
1993            var links = [];
1994            var a = document.getElementsByTagName('a'), rel;
1995            for(var i = 0, len = a.length; i < len; ++i){
1996                rel = a[i].getAttribute('rel');
1997                if(rel && RE.rel.test(rel)) links[links.length] = a[i];
1998            }
1999        }else if(!links.length){
2000            links = [links]; // one link
2001        }
2002
2003        var link;
2004        for(var i = 0, len = links.length; i < len; ++i){
2005            link = links[i];
2006            if(typeof link.shadowboxCacheKey == 'undefined'){
2007                // assign cache key expando
2008                // use integer primitive to avoid memory leak in IE
2009                link.shadowboxCacheKey = cache.length;
2010                SL.addEvent(link, 'click', handleClick); // add listener
2011            }
2012            cache[link.shadowboxCacheKey] = this.buildCacheObj(link, opts);
2013        }
2014    };
2015
2016    /**
2017     * Builds an object from the original link element data to store in cache.
2018     * These objects contain (most of) the following keys:
2019     *
2020     * - el: the link element
2021     * - title: the linked file title
2022     * - type: the linked file type
2023     * - content: the linked file's URL
2024     * - gallery: the gallery the file belongs to (optional)
2025     * - height: the height of the linked file (only necessary for movies)
2026     * - width: the width of the linked file (only necessary for movies)
2027     * - options: custom options to use (optional)
2028     *
2029     * @param   {HTMLElement}   link    The link element to process
2030     * @return  {Object}                An object representing the link
2031     * @public
2032     * @static
2033     */
2034    Shadowbox.buildCacheObj = function(link, opts){
2035        var href = link.href; // don't use getAttribute() here
2036        var o = {
2037            el:         link,
2038            title:      link.getAttribute('title'),
2039            type:       getPlayerType(href),
2040            options:    apply({}, opts || {}), // break the reference
2041            content:    href
2042        };
2043
2044        // remove link-level options from top-level options
2045        var opt, l_opts = ['title', 'type', 'height', 'width', 'gallery'];
2046        for(var i = 0, len = l_opts.length; i < len; ++i){
2047            opt = l_opts[i];
2048            if(typeof o.options[opt] != 'undefined'){
2049                o[opt] = o.options[opt];
2050                delete o.options[opt];
2051            }
2052        }
2053
2054        // HTML options always trump JavaScript options, so do these last
2055        var rel = link.getAttribute('rel');
2056        if(rel){
2057            // extract gallery name from shadowbox[name] format
2058            var match = rel.match(RE.gallery);
2059            if(match) o.gallery = escape(match[2]);
2060
2061            // other parameters
2062            var params = rel.split(';');
2063            for(var i = 0, len = params.length; i < len; ++i){
2064                match = params[i].match(RE.param);
2065                if(match){
2066                    if(match[1] == 'options'){
2067                        eval('o.options = apply(o.options, ' + match[2] + ')');
2068                    }else{
2069                        o[match[1]] = match[2];
2070                    }
2071                }
2072            }
2073        }
2074
2075        return o;
2076    };
2077
2078    /**
2079     * Applies the given set of options to those currently in use. Note: Options
2080     * will be reset on Shadowbox.open() so this function is only useful after
2081     * it has already been called (while Shadowbox is open).
2082     *
2083     * @param   {Object}    opts        The options to apply
2084     * @return  void
2085     * @public
2086     * @static
2087     */
2088    Shadowbox.applyOptions = function(opts){
2089        if(opts){
2090            // use apply here to break references
2091            default_options = apply({}, options); // store default options
2092            options = apply(options, opts); // apply options
2093        }
2094    };
2095
2096    /**
2097     * Reverts Shadowbox' options to the last default set in use before
2098     * Shadowbox.applyOptions() was called.
2099     *
2100     * @return  void
2101     * @public
2102     * @static
2103     */
2104    Shadowbox.revertOptions = function(){
2105        if(default_options){
2106            options = default_options; // revert to default options
2107            default_options = null; // erase for next time
2108        }
2109    };
2110
2111    /**
2112     * Opens the given object in Shadowbox. This object may be either an
2113     * anchor/area element, or an object similar to the one created by
2114     * Shadowbox.buildCacheObj().
2115     *
2116     * @param   {mixed}     obj         The object or link element that defines
2117     *                                  what to display
2118     * @return  void
2119     * @public
2120     * @static
2121     */
2122    Shadowbox.open = function(obj, opts){
2123        if(activated) return; // already open
2124        activated = true;
2125
2126        // is it a link?
2127        if(isLink(obj)){
2128            if(typeof obj.shadowboxCacheKey == 'undefined' || typeof cache[obj.shadowboxCacheKey] == 'undefined'){
2129                // link element that hasn't been set up before
2130                // create an object on-the-fly
2131                obj = this.buildCacheObj(obj, opts);
2132            }else{
2133                // link element that has been set up before, get from cache
2134                obj = cache[obj.shadowboxCacheKey];
2135            }
2136        }
2137
2138        this.revertOptions();
2139        if(obj.options || opts){
2140            // use apply here to break references
2141            this.applyOptions(apply(apply({}, obj.options || {}), opts || {}));
2142        }
2143
2144        // update current & current_gallery
2145        setupGallery(obj);
2146
2147        // anything to display?
2148        if(current_gallery.length){
2149            // fire onOpen hook
2150            if(options.onOpen && typeof options.onOpen == 'function'){
2151                options.onOpen(obj);
2152            }
2153
2154            // display:block here helps with correct dimension calculations
2155            SL.setStyle(SL.get('shadowbox'), 'display', 'block');
2156
2157            toggleTroubleElements(false);
2158            var dims = getDimensions(options.initialHeight, options.initialWidth);
2159            adjustHeight(dims.height, dims.top);
2160            adjustWidth(dims.width);
2161            hideBars(false);
2162
2163            // show the overlay and load the content
2164            toggleOverlay(function(){
2165                SL.setStyle(SL.get('shadowbox'), 'visibility', 'visible');
2166                showLoading();
2167                loadContent();
2168            });
2169        }
2170    };
2171
2172    /**
2173     * Jumps to the piece in the current gallery with index num.
2174     *
2175     * @param   {Number}    num     The gallery index to view
2176     * @return  void
2177     * @public
2178     * @static
2179     */
2180    Shadowbox.change = function(num){
2181        if(!current_gallery) return; // no current gallery
2182        if(!current_gallery[num]){ // index does not exist
2183            if(!options.continuous){
2184                return;
2185            }else{
2186                num = (num < 0) ? (current_gallery.length - 1) : 0; // loop
2187            }
2188        }
2189
2190        // update current
2191        current = num;
2192
2193        // stop listening for drag
2194        toggleDrag(false);
2195        // empty the content
2196        setContent(null);
2197        // turn this back on when done
2198        listenKeyboard(false);
2199
2200        // fire onChange handler
2201        if(options.onChange && typeof options.onChange == 'function'){
2202            options.onChange(current_gallery[current]);
2203        }
2204
2205        showLoading();
2206        hideBars(loadContent);
2207    };
2208
2209    /**
2210     * Jumps to the next piece in the gallery.
2211     *
2212     * @return  {Boolean}       True if the gallery changed to next item, false
2213     *                          otherwise
2214     * @public
2215     * @static
2216     */
2217    Shadowbox.next = function(){
2218        return this.change(current + 1);
2219    };
2220
2221    /**
2222     * Jumps to the previous piece in the gallery.
2223     *
2224     * @return  {Boolean}       True if the gallery changed to previous item,
2225     *                          false otherwise
2226     * @public
2227     * @static
2228     */
2229    Shadowbox.previous = function(){
2230        return this.change(current - 1);
2231    };
2232
2233    /**
2234     * Deactivates Shadowbox.
2235     *
2236     * @return  void
2237     * @public
2238     * @static
2239     */
2240    Shadowbox.close = function(){
2241        if(!activated) return; // already closed
2242
2243        // stop listening for keys
2244        listenKeyboard(false);
2245        // hide
2246        SL.setStyle(SL.get('shadowbox'), {
2247            display: 'none',
2248            visibility: 'hidden'
2249        });
2250        // stop listening for scroll on IE
2251        if(absolute_pos) SL.removeEvent(window, 'scroll', centerVertically);
2252        // stop listening for drag
2253        toggleDrag(false);
2254        // empty the content
2255        setContent(null);
2256        // prevent old image requests from loading
2257        if(preloader){
2258            preloader.onload = function(){};
2259            preloader = null;
2260        }
2261        // hide the overlay
2262        toggleOverlay(false);
2263        // turn on trouble elements
2264        toggleTroubleElements(true);
2265
2266        // fire onClose handler
2267        if(options.onClose && typeof options.onClose == 'function'){
2268            options.onClose(current_gallery[current]);
2269        }
2270
2271        activated = false;
2272    };
2273
2274    /**
2275     * Clears Shadowbox' cache and removes listeners and expandos from all
2276     * cached link elements. May be used to completely reset Shadowbox in case
2277     * links on a page change.
2278     *
2279     * @return  void
2280     * @public
2281     * @static
2282     */
2283    Shadowbox.clearCache = function(){
2284        for(var i = 0, len = cache.length; i < len; ++i){
2285            if(cache[i].el){
2286                SL.removeEvent(cache[i].el, 'click', handleClick);
2287                delete cache[i].shadowboxCacheKey;
2288            }
2289        }
2290        cache = [];
2291    };
2292
2293    /**
2294     * Generates the markup necessary to embed the movie file with the given
2295     * link element. This markup will be browser-specific. Useful for generating
2296     * the media test suite.
2297     *
2298     * @param   {HTMLElement}   link        The link to the media file
2299     * @return  {Object}                    The proper markup to use (see above)
2300     * @public
2301     * @static
2302     */
2303    Shadowbox.movieMarkup = function(obj){
2304        // movies default to 300x300 pixels
2305        var h = obj.height ? parseInt(obj.height, 10) : 300;
2306        var w = obj.width ? parseInt(obj.width, 10) : 300;
2307
2308        var autoplay = options.autoplayMovies;
2309        var controls = options.showMovieControls;
2310        if(obj.options){
2311            if(obj.options.autoplayMovies != null){
2312                autoplay = obj.options.autoplayMovies;
2313            }
2314            if(obj.options.showMovieControls != null){
2315                controls = obj.options.showMovieControls;
2316            }
2317        }
2318
2319        var markup = {
2320            tag:    'object',
2321            name:   'shadowbox_content'
2322        };
2323
2324        switch(obj.type){
2325            case 'swf':
2326                var dims = getDimensions(h, w, true);
2327                h = dims.height;
2328                w = dims.width;
2329                markup.type = 'application/x-shockwave-flash';
2330                markup.data = obj.content;
2331                markup.children = [
2332                    { tag: 'param', name: 'movie', value: obj.content }
2333                ];
2334            break;
2335            case 'flv':
2336                autoplay = autoplay ? 'true' : 'false';
2337                var showicons = 'false';
2338                var a = h/w; // aspect ratio
2339                if(controls){
2340                    showicons = 'true';
2341                    h += 20; // height of JW FLV player controller
2342                }
2343                var dims = getDimensions(h, h/a, true); // resize
2344                h = dims.height;
2345                w = (h-(controls?20:0))/a; // maintain aspect ratio
2346                var flashvars = [
2347                    'file=' + obj.content,
2348                    'height=' + h,
2349                    'width=' + w,
2350                    'autostart=' + autoplay,
2351                    'displayheight=' + (h - (controls?20:0)),
2352                    'showicons=' + showicons,
2353                    'backcolor=0x000000&amp;frontcolor=0xCCCCCC&amp;lightcolor=0x557722'
2354                ];
2355                markup.type = 'application/x-shockwave-flash';
2356                markup.data = options.assetURL + options.flvPlayer;
2357                markup.children = [
2358                    { tag: 'param', name: 'movie', value: options.assetURL + options.flvPlayer },
2359                    { tag: 'param', name: 'flashvars', value: flashvars.join('&amp;') },
2360                    { tag: 'param', name: 'allowfullscreen', value: 'true' }
2361                ];
2362            break;
2363            case 'qt':
2364                autoplay = autoplay ? 'true' : 'false';
2365                if(controls){
2366                    controls = 'true';
2367                    h += 16; // height of QuickTime controller
2368                }else{
2369                    controls = 'false';
2370                }
2371                markup.children = [
2372                    { tag: 'param', name: 'src', value: obj.content },
2373                    { tag: 'param', name: 'scale', value: 'aspect' },
2374                    { tag: 'param', name: 'controller', value: controls },
2375                    { tag: 'param', name: 'autoplay', value: autoplay }
2376                ];
2377                if(isIE){
2378                    markup.classid = 'clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B';
2379                    markup.codebase = 'http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0';
2380                }else{
2381                    markup.type = 'video/quicktime';
2382                    markup.data = obj.content;
2383                }
2384            break;
2385            case 'wmp':
2386                autoplay = autoplay ? 1 : 0;
2387                markup.children = [
2388                    { tag: 'param', name: 'autostart', value: autoplay }
2389                ];
2390                if(isIE){
2391                    if(controls){
2392                        controls = 'full';
2393                        h += 70; // height of WMP controller in IE
2394                    }else{
2395                        controls = 'none';
2396                    }
2397                    // markup.type = 'application/x-oleobject';
2398                    markup.classid = 'clsid:6BF52A52-394A-11d3-B153-00C04F79FAA6';
2399                    markup.children[markup.children.length] = { tag: 'param', name: 'url', value: obj.content };
2400                    markup.children[markup.children.length] = { tag: 'param', name: 'uimode', value: controls };
2401                }else{
2402                    if(controls){
2403                        controls = 1;
2404                        h += 45; // height of WMP controller in non-IE
2405                    }else{
2406                        controls = 0;
2407                    }
2408                    markup.type = 'video/x-ms-wmv';
2409                    markup.data = obj.content;
2410                    markup.children[markup.children.length] = { tag: 'param', name: 'showcontrols', value: controls };
2411                }
2412            break;
2413        }
2414
2415        markup.height = h; // new height includes controller
2416        markup.width = w;
2417
2418        return markup;
2419    };
2420
2421    /**
2422     * Creates an HTML string from an object representing HTML elements. Based
2423     * on Ext.DomHelper's createHtml.
2424     *
2425     * @param   {Object}    obj     The HTML definition object
2426     * @return  {String}            An HTML string
2427     * @public
2428     * @static
2429     */
2430    Shadowbox.createHTML = function(obj){
2431        var html = '<' + obj.tag;
2432        for(var attr in obj){
2433            if(attr == 'tag' || attr == 'html' || attr == 'children') continue;
2434            if(attr == 'cls'){
2435                html += ' class="' + obj['cls'] + '"';
2436            }else{
2437                html += ' ' + attr + '="' + obj[attr] + '"';
2438            }
2439        }
2440        if(RE.empty.test(obj.tag)){
2441            html += '/>\n';
2442        }else{
2443            html += '>\n';
2444            var cn = obj.children;
2445            if(cn){
2446                for(var i = 0, len = cn.length; i < len; ++i){
2447                    html += this.createHTML(cn[i]);
2448                }
2449            }
2450            if(obj.html) html += obj.html;
2451            html += '</' + obj.tag + '>\n';
2452        }
2453        return html;
2454    };
2455
2456    /**
2457     * Gets an object that lists which plugins are supported by the client. The
2458     * keys of this object will be:
2459     *
2460     * - fla: Adobe Flash Player
2461     * - qt: QuickTime Player
2462     * - wmp: Windows Media Player
2463     * - f4m: Flip4Mac QuickTime Player
2464     *
2465     * @return  {Object}        The plugins object
2466     * @public
2467     * @static
2468     */
2469    Shadowbox.getPlugins = function(){
2470        return plugins;
2471    };
2472
2473    /**
2474     * Gets the current options object in use.
2475     *
2476     * @return  {Object}        The options object
2477     * @public
2478     * @static
2479     */
2480    Shadowbox.getOptions = function(){
2481        return options;
2482    };
2483
2484    /**
2485     * Gets the current gallery object.
2486     *
2487     * @return  {Object}        The current gallery item
2488     * @public
2489     * @static
2490     */
2491    Shadowbox.getCurrent = function(){
2492        return current_gallery[current];
2493    };
2494
2495    /**
2496     * Gets the current version number of Shadowbox.
2497     *
2498     * @return  {String}        The current version
2499     * @public
2500     * @static
2501     */
2502    Shadowbox.getVersion = function(){
2503        return version;
2504    };
2505
2506})();
2507
2508/**
2509 * Finds the index of the given object in this array.
2510 *
2511 * @param   {mixed}     o   The object to search for
2512 * @return  {Number}        The index of the given object
2513 * @public
2514 */
2515Array.prototype.indexOf = Array.prototype.indexOf || function(o){
2516    for(var i = 0, len = this.length; i < len; ++i){
2517        if(this[i] == o) return i;
2518    }
2519    return -1;
2520};
2521
2522/**
2523 * Formats a string with the given parameters. The string for format must have
2524 * placeholders that correspond to the numerical index of the arguments passed
2525 * in surrounded by curly braces (e.g. 'Some {0} string {1}').
2526 *
2527 * @param   {String}    format      The string to format
2528 * @param   ...                     The parameters to put inside the string
2529 * @return  {String}                The string with the specified parameters
2530 *                                  replaced
2531 * @public
2532 * @static
2533 */
2534String.format = String.format || function(format){
2535    var args = Array.prototype.slice.call(arguments, 1);
2536    return format.replace(/\{(\d+)\}/g, function(m, i){
2537        return args[i];
2538    });
2539};
Note: See TracBrowser for help on using the repository browser.