PageSourceSearch

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

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

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.