root/branches/5.2/www/default/js/shadowbox.js

Revision 278, 90.6 KB (checked in by mattlevine, 15 months ago)

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 browser.