PageSourceSearch

https://infratil.com/core/modules/v1/core_assets/js/shadowbox.js

js infratil.com collected 2026-10-02 11:51:36 UTC 95,345 bytes, 2,693 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.