PageSourceSearch

https://art-ov.ru/components/com_morfeoshow/src/shadowbox.js

js art-ov.ru collected 2026-10-05 00:10:10 UTC 71,504 bytes, 2,146 lines download raw bytes

1/**
2 * The Shadowbox class.
3 *
4 * This file is part of Shadowbox.
5 *
6 * Shadowbox is an online media viewer application that supports all of the
7 * web's most popular media publishing formats. Shadowbox is written entirely
8 * in JavaScript and CSS and is highly customizable. Using Shadowbox, website
9 * authors can showcase a wide assortment of media in all major browsers without
10 * navigating users away from the linking page.
11 *
12 * Shadowbox is released under version 3.0 of the Creative Commons Attribution-
13 * Noncommercial-Share Alike license. This means that it is absolutely free
14 * for personal, noncommercial use provided that you 1) make attribution to the
15 * author and 2) release any derivative work under the same or a similar
16 * license.
17 *
18 * If you wish to use Shadowbox for commercial purposes, licensing information
19 * can be found at http://mjijackson.com/shadowbox/.
20 *
21 * @author      Michael J. I. Jackson <[email protected]>
22 * @copyright   2007-2008 Michael J. I. Jackson
23 * @license     http://creativecommons.org/licenses/by-nc-sa/3.0/
24 * @version     SVN: $Id: shadowbox.js 108 2008-07-11 04:19:01Z mjijackson $
25 */
26
27if(typeof Shadowbox == 'undefined'){
28    throw 'Unable to load Shadowbox, no base library adapter found';
29}
30
31/**
32 * The Shadowbox class. Used to display different media on a web page using a
33 * Lightbox-like effect.
34 *
35 * Useful resources:
36 *
37 * - http://www.alistapart.com/articles/byebyeembed
38 * - http://www.w3.org/TR/html401/struct/objects.html
39 * - http://www.dyn-web.com/dhtml/iframes/
40 * - http://www.apple.com/quicktime/player/specs.html
41 * - http://www.apple.com/quicktime/tutorials/embed2.html
42 * - http://www.howtocreate.co.uk/wrongWithIE/?chapter=navigator.plugins
43 * - http://msdn.microsoft.com/en-us/library/ms532969.aspx
44 * - http://support.microsoft.com/kb/316992
45 *
46 * @class       Shadowbox
47 * @author      Michael J. I. Jackson <[email protected]>
48 * @singleton
49 */
50(function(){
51
52    /**
53     * The current version of Shadowbox.
54     *
55     * @var         String
56     * @private
57     */
58    var version = '2.0';
59
60    /**
61     * Contains the default options for Shadowbox.
62     *
63     * @var         Object
64     * @private
65     */
66    var options = {
67
68        /**
69         * Enable all animations besides fades.
70         *
71         * @var     Boolean
72         */
73        animate:            true,
74
75        /**
76         * Enable fade animations.
77         *
78         * @var     Boolean
79         */
80        animateFade:        true,
81
82        /**
83         * Specifies the sequence of the height and width animations. May be
84         * 'wh' (width then height), 'hw' (height then width), or 'sync' (both
85         * at the same time). Of course this will only work if animate is true.
86         *
87         * @var     String
88         */
89        animSequence:       'wh',
90
91        /**
92         * The path to flvplayer.swf.
93         *
94         * @var     String
95         */
96        flvPlayer:          'flvplayer.swf',
97
98        /**
99         * Listen to the overlay for clicks. If the user clicks the overlay,
100         * it will trigger Shadowbox.close().
101         *
102         * @var     Boolean
103         */
104        modal:              false,
105
106        /**
107         * The color to use for the modal overlay (in hex).
108         *
109         * @var     String
110         */
111        overlayColor:       '#000',
112
113        /**
114         * The opacity to use for the modal overlay.
115         *
116         * @var     Number
117         */
118        overlayOpacity:     0.8,
119
120        /**
121         * The default background color to use for Flash movies (in hex).
122         *
123         * @var     String
124         */
125        flashBgColor:       '#000000',
126
127        /**
128         * Automatically play movies.
129         *
130         * @var     Boolean
131         */
132        autoplayMovies:     true,
133
134        /**
135         * Enable movie controllers on movie players.
136         *
137         * @var     Boolean
138         */
139        showMovieControls:  true,
140
141        /**
142         * A delay (in seconds) to use for slideshows. If set to anything other
143         * than 0, this value determines an interval at which Shadowbox will
144         * automatically proceed to the next piece in the gallery.
145         *
146         * @var     Number
147         */
148        slideshowDelay:     0,
149
150        /**
151         * The duration of the resizing animations (in seconds).
152         *
153         * @var     Number
154         */
155        resizeDuration:     0.55,
156
157        /**
158         * The duration of the fading animations (in seconds).
159         *
160         * @var     Number
161         */
162        fadeDuration:       0.35,
163
164        /**
165         * Show the navigation controls.
166         *
167         * @var     Boolean
168         */
169        displayNav:         true,
170
171        /**
172         * Enable continuous galleries. When this is true, users will be able
173         * to skip to the first gallery image from the last using next and vice
174         * versa.
175         *
176         * @var     Boolean
177         */
178        continuous:         false,
179
180        /**
181         * Display the gallery counter.
182         *
183         * @var     Boolean
184         */
185        displayCounter:     true,
186
187        /**
188         * This option may be either 'default' or 'skip'. The default counter is
189         * a simple '1 of 5' message. The skip counter displays a link for each
190         * piece in the gallery that enables a user to skip directly to any
191         * piece.
192         *
193         * @var     String
194         */
195        counterType:        'default',
196
197        /**
198         * Limits the number of counter links that will be displayed in a "skip"
199         * style counter. If the actual number of gallery elements is greater
200         * than this value, the counter will be restrained to the elements
201         * immediately preceeding and following the current element.
202         *
203         * @var     Number
204         */
205        counterLimit:       10,
206
207        /**
208         * The amount of padding to maintain around the viewport edge (in
209         * pixels). This only applies when the image is very large and takes up
210         * the entire viewport.
211         *
212         * @var     Number
213         */
214        viewportPadding:    20,
215
216        /**
217         * How to handle content that is too large to display in its entirety
218         * (and is resizable). A value of 'resize' will resize the content while
219         * preserving aspect ratio and display it at the smaller resolution. If
220         * the content is an image, a value of 'drag' will display the image at
221         * its original resolution but it will be draggable within Shadowbox. A
222         * value of 'none' will display the content at its original resolution
223         * but it may be cropped.
224         *
225         * @var     String
226         */
227        handleOversize:     'resize',
228
229        /**
230         * An exception handling function that will be called whenever
231         * Shadowbox should throw an exception. Will be passed the error
232         * message as its first argument.
233         *
234         * @var     Function
235         */
236        handleException:    null,
237
238        /**
239         * The mode to use when handling unsupported media. May be either
240         * 'remove' or 'link'. If it is 'remove', the unsupported gallery item
241         * will merely be removed from the gallery. If it is the only item in
242         * the gallery, the link will simply be followed. If it is 'link', a
243         * link will be provided to the appropriate plugin page in place of the
244         * gallery element.
245         *
246         * @var     String
247         */
248        handleUnsupported:  'link',
249
250        /**
251         * The initial height of Shadowbox (in pixels).
252         *
253         * @var     Number
254         */
255        initialHeight:      160,
256
257        /**
258         * The initial width of Shadowbox (in pixels).
259         *
260         * @var     Number
261         */
262        initialWidth:       320,
263
264        /**
265         * Enable keyboard control.
266         *
267         * @var     Boolean
268         */
269        enableKeys:         true,
270
271        /**
272         * A hook function to be fired when Shadowbox opens. The single argument
273         * will be the current gallery element.
274         *
275         * @var     Function
276         */
277        onOpen:             null,
278
279        /**
280         * A hook function to be fired when Shadowbox finishes loading its
281         * content. The single argument will be the current gallery element on
282         * display.
283         *
284         * @var     Function
285         */
286        onFinish:           null,
287
288        /**
289         * A hook function to be fired when Shadowbox changes from one gallery
290         * element to the next. The single argument will be the current gallery
291         * element that is about to be displayed.
292         *
293         * @var     Function
294         */
295        onChange:           null,
296
297        /**
298         * A hook function that will be fired when Shadowbox closes. The single
299         * argument will be the gallery element most recently displayed.
300         *
301         * @var     Function
302         */
303        onClose:            null,
304
305        /**
306         * Skips calling Shadowbox.setup() in init(). This means that it must
307         * be called later manually.
308         *
309         * @var     Boolean
310         */
311        skipSetup:          false,
312
313        /**
314         * An object containing names of plugins and links to their respective
315         * download pages.
316         *
317         * @var     Object
318         */
319        errors:         {
320
321            fla:        {
322                name:   'Flash',
323                url:    'http://www.adobe.com/products/flashplayer/'
324            },
325
326            qt:         {
327                name:   'QuickTime',
328                url:    'http://www.apple.com/quicktime/download/'
329            },
330
331            wmp:        {
332                name:   'Windows Media Player',
333                url:    'http://www.microsoft.com/windows/windowsmedia/'
334            },
335
336            f4m:        {
337                name:   'Flip4Mac',
338                url:    'http://www.flip4mac.com/wmv_download.htm'
339            }
340
341        },
342
343        /**
344         * A map of players to the file extensions they support. Each member of
345         * this object is the name of a player (with one exception), whose value
346         * is an array of file extensions that player will "play". The one
347         * exception to this rule is the "qtwmp" member, which contains extensions
348         * that may be played using either QuickTime or Windows Media Player.
349         *
350         * - img: Image file extensions
351         * - swf: Flash SWF file extensions
352         * - flv: Flash video file extensions (will be played by JW FLV player)
353         * - qt: Movie file extensions supported by QuickTime
354         * - wmp: Movie file extensions supported by Windows Media Player
355         * - qtwmp: Movie file extensions supported by both QuickTime and Windows Media Player
356         * - iframe: File extensions that will be display in an iframe
357         *
358         * IMPORTANT: If this object is to be modified, it must be copied in its
359         * entirety and tweaked because it is not merged recursively with the
360         * default. Also, any modifications must be passed into Shadowbox.init
361         * for speed reasons.
362         *
363         * @var     Object      ext
364         */
365        ext:     {
366            img:        ['png', 'jpg', 'jpeg', 'gif', 'bmp'],
367            swf:        ['swf'],
368            flv:        ['flv'],
369            qt:         ['dv', 'mov', 'moov', 'movie', 'mp4'],
370            wmp:        ['asf', 'wm', 'wmv'],
371            qtwmp:      ['avi', 'mpg', 'mpeg'],
372            iframe:     ['asp', 'aspx', 'cgi', 'cfm', 'htm', 'html', 'pl', 'php',
373                        'php3', 'php4', 'php5', 'phtml', 'rb', 'rhtml', 'shtml',
374                        'txt', 'vbs']
375        }
376
377    };
378
379    // shorthand
380    var SB = Shadowbox;
381    var SL = SB.lib;
382
383    /**
384     * Stores the default set of options in case a custom set of options is used
385     * on a link-by-link basis so we can restore them later.
386     *
387     * @var         Object
388     * @private
389     */
390    var default_options;
391
392    /**
393     * An object containing some regular expressions we'll need later. Compiled
394     * up front for speed.
395     *
396     * @var         Object
397     * @private
398     */
399    var RE = {
400        domain:         /:\/\/(.*?)[:\/]/, // domain prefix
401        inline:         /#(.+)$/, // inline element id
402        rel:            /^(light|shadow)box/i, // rel attribute format
403        gallery:        /^(light|shadow)box\[(.*?)\]/i, // rel attribute format for gallery link
404        unsupported:    /^unsupported-(\w+)/, // unsupported media type
405        param:          /\s*([a-z_]*?)\s*=\s*(.+)\s*/, // rel string parameter
406        empty:          /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i // elements that don't have children
407    };
408
409    /**
410     * A cache of options for links that have been set up for use with
411     * Shadowbox.
412     *
413     * @var         Array
414     * @private
415     */
416    var cache = [];
417
418    /**
419     * An array containing the gallery objects currently being viewed. In the
420     * case of non-gallery items, this will only hold one object.
421     *
422     * @var         Array
423     * @private
424     */
425    var gallery;
426
427    /**
428     * The array index of the current gallery that is currently being viewed.
429     *
430     * @var         Number
431     * @private
432     */
433    var current;
434
435    /**
436     * The current content object.
437     *
438     * @var         Object
439     * @private
440     */
441    var content;
442
443    /**
444     * The id to use for content objects.
445     *
446     * @var         String
447     * @private
448     */
449    var content_id = 'shadowbox_content';
450
451    /**
452     * Holds the current dimensions of Shadowbox as calculated by
453     * setDimensions(). Contains the following properties:
454     *
455     * - height: The total height of #shadowbox
456     * - width: The total width of #shadowbox
457     * - inner_h: The height of #shadowbox_body
458     * - inner_w: The width of #shadowbox_body
459     * - top: The top to use for #shadowbox
460     * - resize_h: The height to use for resizable content
461     * - resize_w: The width to use for resizable content
462     * - drag: True if dragging should be enabled (oversized image)
463     *
464     * @var         Object
465     * @private
466     */
467    var dims;
468
469    /**
470     * Keeps track of whether or not Shadowbox has been initialized. We never
471     * want to initialize twice.
472     *
473     * @var         Boolean
474     * @private
475     */
476    var initialized = false;
477
478    /**
479     * Keeps track of whether or not Shadowbox is activated.
480     *
481     * @var         Boolean
482     * @private
483     */
484    var activated = false;
485
486    /**
487     * The timeout id for the slideshow transition function.
488     *
489     * @var         Number
490     * @private
491     */
492    var slide_timer;
493
494    /**
495     * Keeps track of the time at which the current slideshow frame was
496     * displayed.
497     *
498     * @var         Number
499     * @private
500     */
501    var slide_start;
502
503    /**
504     * The delay on which the next slide will display.
505     *
506     * @var         Number
507     * @private
508     */
509    var slide_delay = 0;
510
511    /**
512     * These parameters for simple browser detection. Adapted from Ext.js.
513     *
514     * @var         Object
515     * @private
516     */
517    var ua = navigator.userAgent.toLowerCase();
518    var client = {
519        isStrict:   document.compatMode == 'CSS1Compat',
520        isOpera:    ua.indexOf('opera') > -1,
521        isIE:       ua.indexOf('msie') > -1,
522        isIE7:      ua.indexOf('msie 7') > -1,
523        isSafari:   /webkit|khtml/.test(ua),
524        isWindows:  ua.indexOf('windows') != -1 || ua.indexOf('win32') != -1,
525        isMac:      ua.indexOf('macintosh') != -1 || ua.indexOf('mac os x') != -1,
526        isLinux:    ua.indexOf('linux') != -1
527    };
528    client.isBorderBox = client.isIE && !client.isStrict;
529    client.isSafari3 = client.isSafari && !!(document.evaluate);
530    client.isGecko = ua.indexOf('gecko') != -1 && !client.isSafari;
531
532    /**
533     * You're not sill using IE6 are you?
534     *
535     * @var         Boolean
536     * @private
537     */
538    var ltIE7 = client.isIE && !client.isIE7;
539
540    /**
541     * Contains plugin support information. Each property of this object is a
542     * boolean indicating whether that plugin is supported.
543     *
544     * - fla: Flash player
545     * - qt: QuickTime player
546     * - wmp: Windows Media player
547     * - f4m: Flip4Mac plugin
548     *
549     * @var         Object
550     * @private
551     */
552    var plugins;
553
554    // detect plugin support
555    if(navigator.plugins && navigator.plugins.length){
556        var detectPlugin = function(plugin_name){
557            var detected = false;
558            for (var i = 0, len = navigator.plugins.length; i < len; ++i){
559                if(navigator.plugins[i].name.indexOf(plugin_name) > -1){
560                    detected = true;
561                    break;
562                }
563            }
564            return detected;
565        };
566        var f4m = detectPlugin('Flip4Mac');
567        plugins = {
568            fla:    detectPlugin('Shockwave Flash'),
569            qt:     detectPlugin('QuickTime'),
570            wmp:    !f4m && detectPlugin('Windows Media'), // if it's Flip4Mac, it's not really WMP
571            f4m:    f4m
572        };
573    }else{
574        var detectPlugin = function(plugin_name){
575            var detected = false;
576            try{
577                var axo = new ActiveXObject(plugin_name);
578                if(axo) detected = true;
579            }catch(e){}
580            return detected;
581        };
582        plugins = {
583            fla:    detectPlugin('ShockwaveFlash.ShockwaveFlash'),
584            qt:     detectPlugin('QuickTime.QuickTime'),
585            wmp:    detectPlugin('wmplayer.ocx'),
586            f4m:    false
587        };
588    }
589
590    /**
591     * Applies all properties of e to o.
592     *
593     * @param   Object      o       The original object
594     * @param   Object      e       The extension object
595     * @return  Object              The original object with all properties
596     *                              of the extension object applied
597     * @private
598     */
599    var apply = function(o, e){
600        for(var p in e) o[p] = e[p];
601        return o;
602    };
603
604    /**
605     * Determines if the given object is an anchor/area element.
606     *
607     * @param   mixed       el      The object to check
608     * @return  Boolean             True if the object is a link element
609     * @private
610     */
611    var isLink = function(el){
612        return el && typeof el.tagName == 'string' && (el.tagName.toUpperCase() == 'A' || el.tagName.toUpperCase() == 'AREA');
613    };
614
615    /**
616     * Gets the height of the viewport in pixels. Note: This function includes
617     * scrollbars in Safari 3.
618     *
619     * @return  Number          The height of the viewport
620     * @public
621     * @static
622     */
623    SL.getViewportHeight = function(){
624        var h = window.innerHeight; // Safari
625        var mode = document.compatMode;
626        if((mode || client.isIE) && !client.isOpera){
627            h = client.isStrict ? document.documentElement.clientHeight : document.body.clientHeight;
628        }
629        return h;
630    };
631
632    /**
633     * Gets the width of the viewport in pixels. Note: This function includes
634     * scrollbars in Safari 3.
635     *
636     * @return  Number          The width of the viewport
637     * @public
638     * @static
639     */
640    SL.getViewportWidth = function(){
641        var w = window.innerWidth; // Safari
642        var mode = document.compatMode;
643        if(mode || client.isIE){
644            w = client.isStrict ? document.documentElement.clientWidth : document.body.clientWidth;
645        }
646        return w;
647    };
648
649    /**
650     * Creates an HTML string from an object representing HTML elements. Based
651     * on Ext.DomHelper's createHtml.
652     *
653     * @param   Object      obj     The HTML definition object
654     * @return  String              An HTML string
655     * @public
656     * @static
657     */
658    SL.createHTML = function(obj){
659        var html = '<' + obj.tag;
660        for(var attr in obj){
661            if(attr == 'tag' || attr == 'html' || attr == 'children') continue;
662            if(attr == 'cls'){
663                html += ' class="' + obj['cls'] + '"';
664            }else{
665                html += ' ' + attr + '="' + obj[attr] + '"';
666            }
667        }
668        if(RE.empty.test(obj.tag)){
669            html += '/>';
670        }else{
671            html += '>';
672            var cn = obj.children;
673            if(cn){
674                for(var i = 0, len = cn.length; i < len; ++i){
675                    html += this.createHTML(cn[i]);
676                }
677            }
678            if(obj.html) html += obj.html;
679            html += '</' + obj.tag + '>';
680        }
681        return html;
682    };
683
684    /**
685     * Easing function used for animations. Based on a cubic polynomial.
686     *
687     * @param   Number      x       The state of the animation (% complete)
688     * @return  Number              The adjusted easing value
689     * @private
690     * @static
691     */
692    var ease = function(x){
693        return 1 + Math.pow(x - 1, 3);
694    };
695
696    /**
697     * Animates any numeric (not color) style of the given element from its
698     * current state to the given value. Defaults to using pixel-based
699     * measurements.
700     *
701     * @param   HTMLElement     el      The DOM element to animate
702     * @param   String          p       The property to animate (in camelCase)
703     * @param   mixed           to      The value to animate to
704     * @param   Number          d       The duration of the animation (in
705     *                                  seconds)
706     * @param   Function        cb      A callback function to call when the
707     *                                  animation completes
708     * @return  void
709     * @private
710     * @static
711     */
712    var animate = function(el, p, to, d, cb){
713        var from = parseFloat(SL.getStyle(el, p));
714        if(isNaN(from)) from = 0;
715
716        if(from == to){
717            if(typeof cb == 'function') cb();
718            return; // nothing to animate
719        }
720
721        var delta = to - from;
722        var op = p == 'opacity';
723        var unit = op ? '' : 'px'; // default unit is px
724        var fn = function(ease){
725            SL.setStyle(el, p, from + ease * delta + unit);
726        };
727
728        // cancel the animation here if set in the options
729        if(!options.animate && !op || op && !options.animateFade){
730            fn(1);
731            if(typeof cb == 'function') cb();
732            return;
733        }
734
735        d *= 1000; // convert to milliseconds
736        var begin = new Date().getTime();
737        var end = begin + d;
738
739        var timer = setInterval(function(){
740            var time = new Date().getTime();
741            if(time >
741= end){ // end of animation
742                clearInterval(timer);
743                fn(1);
744                if(typeof cb == 'function') cb();
745            }else{
746                fn(ease((time - begin) / d));
747            }
748        }, 10); // 10 ms interval is minimum on WebKit
749    };
750
751    /**
752     * A utility function used by the fade functions to clear the opacity
753     * style setting of the given element. Required in some cases for IE.
754     *
755     * @param   HTMLElement     el      The DOM element
756     * @return  void
757     * @private
758     */
759    var clearOpacity = function(el){
760        var s = el.style;
761        if(client.isIE){
762            if(typeof s.filter == 'string' && (/alpha/i).test(s.filter)){
763                // careful not to overwrite other filters!
764                s.filter = s.filter.replace(/[\w\.]*alpha\(.*?\);?/i, '');
765            }
766        }else{
767            s.opacity = '';
768            s['-moz-opacity'] = '';
769            s['-khtml-opacity'] = '';
770        }
771    };
772
773    /**
774     * Gets the computed height of the given element, including padding and
775     * borders.
776     *
777     * @param   HTMLElement     el  The element
778     * @return  Number              The computed height of the element
779     * @private
780     */
781    var getComputedHeight = function(el){
782        var h = Math.max(el.offsetHeight, el.clientHeight);
783        if(!h){
784            h = parseInt(SL.getStyle(el, 'height'), 10) || 0;
785            if(!client.isBorderBox){
786                h += parseInt(SL.getStyle(el, 'padding-top'), 10)
787                    + parseInt(SL.getStyle(el, 'padding-bottom'), 10)
788                    + parseInt(SL.getStyle(el, 'border-top-width'), 10)
789                    + parseInt(SL.getStyle(el, 'border-bottom-width'), 10);
790            }
791        }
792        return h;
793    };
794
795    /**
796     * Determines the player needed to display the file at the given URL. If
797     * the file type is not supported, the return value will be 'unsupported'.
798     * If the file type is not supported but the correct player can be
799     * determined, the return value will be 'unsupported-*' where * will be the
800     * player abbreviation (e.g. 'qt' = QuickTime).
801     *
802     * @param   String          url     The url of the file
803     * @return  String                  The name of the player to use
804     * @private
805     */
806    var getPlayer = function(url){
807        var m = url.match(RE.domain);
808        var d = m && document.domain == m[1]; // same domain
809        if(url.indexOf('#') > -1 && d) return 'inline';
810        var q = url.indexOf('?');
811        if(q > -1) url = url.substring(0, q); // strip query string for player detection purposes
812        if(RE.img.test(url)) return 'img';
813        if(RE.swf.test(url)) return plugins.fla ? 'swf' : 'unsupported-swf';
814        if(RE.flv.test(url)) return plugins.fla ? 'flv' : 'unsupported-flv';
815        if(RE.qt.test(url)) return plugins.qt ? 'qt' : 'unsupported-qt';
816        if(RE.wmp.test(url)){
817            if(plugins.wmp) return 'wmp';
818            if(plugins.f4m) return 'qt';
819            if(client.isMac) return plugins.qt ? 'unsupported-f4m' : 'unsupported-qtf4m';
820            return 'unsupported-wmp';
821        }else if(RE.qtwmp.test(url)){
822            if(plugins.qt) return 'qt';
823            if(plugins.wmp) return 'wmp';
824            return client.isMac ? 'unsupported-qt' : 'unsupported-qtwmp';
825        }else if(!d || RE.iframe.test(url)){
826            return 'iframe';
827        }
828        return 'unsupported'; // same domain, not supported
829    };
830
831    /**
832     * Handles all clicks on links that have been set up to work with Shadowbox
833     * and cancels the default event behavior when appropriate.
834     *
835     * @param   {Event}         ev          The click event object
836     * @return  void
837     * @private
838     */
839    var handleClick = function(ev){
840        // get anchor/area element
841        var link;
842        if(isLink(this)){
843            link = this; // jQuery, Prototype, YUI
844        }else{
845            link = SL.getTarget(ev); // Ext, standalone
846            while(!isLink(link) && link.parentNode){
847                link = link.parentNode;
848            }
849        }
850
851        //SL.preventDefault(ev); // good for debugging
852
853        if(link){
854            SB.open(link);
855            if(gallery.length) SL.preventDefault(ev); // stop event
856        }
857    };
858
859    /**
860     * Toggles the display of the nav control with the given id on and off.
861     *
862     * @param   String      id      The id of the navigation control
863     * @param   Boolean     on      True to toggle on, false to toggle off
864     * @return  void
865     * @private
866     */
867    var toggleNav = function(id, on){
868        var el = SL.get('shadowbox_nav_' + id);
869        if(el) el.style.display = on ? '' : 'none';
870    };
871
872    /**
873     * Builds the content for the title and information bars.
874     *
875     * @param   Function    cb      A callback function to execute after the
876     *                              bars are built
877     * @return  void
878     * @private
879     */
880    var buildBars = function(cb){
881        var obj = gallery[current];
882        var title_i = SL.get('shadowbox_title_inner');
883
884        // build the title
885        title_i.innerHTML = obj.title || '';
886
887        // build the nav
888        var nav = SL.get('shadowbox_nav');
889        if(nav){
890            var c, n, pl, pa, p;
891
892            // need to build the nav?
893            if(options.displayNav){
894                c = true;
895                // next & previous links
896                var len = gallery.length;
897                if(len > 1){
898                    if(options.continuous){
899                        n = p = true; // show both
900                    }else{
901                        n = (len - 1) > current; // not last in gallery, show next
902                        p = current > 0; // not first in gallery, show previous
903                    }
904                }
905                // in a slideshow?
906                if(options.slideshowDelay > 0 && hasNext()){
907                    pa = slide_timer != 'paused';
908                    pl = !pa;
909                }
910            }else{
911                c = n = pl = pa = p = false;
912            }
913
914            toggleNav('close', c);
915            toggleNav('next', n);
916            toggleNav('play', pl);
917            toggleNav('pause', pa);
918            toggleNav('previous', p);
919        }
920
921        // build the counter
922        var counter = SL.get('shadowbox_counter');
923        if(counter){
924            var co = '';
925
926            // need to build the counter?
927            if(options.displayCounter && gallery.length > 1){
928                if(options.counterType == 'skip'){
929                    // limit the counter?
930                    var i = 0, len = gallery.length, end = len;
931                    var limit = parseInt(options.counterLimit);
932                    if(limit < len){ // support large galleries
933                        var h = Math.round(limit / 2);
934                        i = current - h;
935                        if(i < 0) i += len;
936                        end = current + (limit - h);
937                        if(end > len) end -= len;
938                    }
939                    while(i != end){
940                        if(i == len) i = 0;
941                        co += '<a onclick="Shadowbox.change(' + i + ');"';
942                        if(i == current) co += ' class="shadowbox_counter_current"';
943                        co += '>' + (++i) + '</a>';
944                    }
945                }else{ // default
946                    co = (current + 1) + ' ' + SB.LANG.of + ' ' + len;
947                }
948            }
949
950            counter.innerHTML = co;
951        }
952
953        cb();
954    };
955
956    /**
957     * Hides the title and info bars.
958     *
959     * @param   Boolean     anim    True to animate the transition
960     * @param   Function    cb      A callback function to execute after the
961     *                              animation completes
962     * @return  void
963     * @private
964     */
965    var hideBars = function(anim, cb){
966        var obj = gallery[current];
967        var title = SL.get('shadowbox_title');
968        var info = SL.get('shadowbox_info');
969        var title_i = SL.get('shadowbox_title_inner');
970        var info_i = SL.get('shadowbox_info_inner');
971
972        // build bars after they are hidden
973        var fn = function(){
974            buildBars(cb);
975        };
976
977        var title_h = getComputedHeight(title);
978        var info_h = getComputedHeight(info) * -1;
979        if(anim){
980            // animate the transition
981            animate(title_i, 'margin-top', title_h, 0.35);
982            animate(info_i, 'margin-top', info_h, 0.35, fn);
983        }else{
984            SL.setStyle(title_i, 'margin-top', title_h + 'px');
985            SL.setStyle(info_i, 'margin-top', info_h + 'px');
986            fn();
987        }
988    };
989
990    /**
991     * Shows the title and info bars.
992     *
993     * @param   Function    cb      A callback function to execute after the
994     *                              animation completes
995     * @return  void
996     * @private
997     */
998    var showBars = function(cb){
999        var title_i = SL.get('shadowbox_title_inner');
1000        var info_i = SL.get('shadowbox_info_inner');
1001        var t = title_i.innerHTML != ''; // is there a title to display?
1002
1003        if(t) animate(title_i, 'margin-top', 0, 0.35);
1004        animate(info_i, 'margin-top', 0, 0.35, cb);
1005    };
1006
1007    /**
1008     * Loads the Shadowbox with the current piece.
1009     *
1010     * @return  void
1011     * @private
1012     */
1013    var loadContent = function(){
1014        var obj = gallery[current];
1015        if(!obj) return; // invalid
1016
1017        var changing = false;
1018        if(content){
1019            content.remove(); // remove old content first
1020            changing = true; // changing from some previous content
1021        }
1022
1023        // determine player, inline is really just HTML
1024        var p = obj.player == 'inline' ? 'html' : obj.player;
1025
1026        // make sure player is loaded
1027        if(typeof SB[p] != 'function'){
1028            SB.raise('Unknown player ' + obj.player);
1029        }
1030        content = new SB[p](content_id, obj); // instantiate new content object
1031
1032        listenKeys(false); // disable the keyboard temporarily
1033        toggleLoading(true);
1034
1035        hideBars(changing, function(){ // if changing, animate the bars transition
1036            if(!content) return;
1037
1038            // if opening, clear #shadowbox display
1039            if(!changing){
1040                 SL.get('shadowbox').style.display = '';
1041            }
1042
1043            var fn = function(){
1044                resizeContent(function(){
1045                    if(!content) return;
1046
1047
1048                    showBars(function(){
1049                        if(!content) return;
1050
1051                        // append content just before hiding the loading layer
1052                        SL.get('shadowbox_body_inner').innerHTML = SL.createHTML(content.markup(dims));
1053
1054                        toggleLoading(false, function(){
1055                            if(!content) return;
1056
1057                            if(typeof content.onLoad == 'function'){
1058                                content.onLoad(); // call onLoad callback if present
1059                            }
1060                            if(options.onFinish && typeof options.onFinish == 'function'){
1061                                options.onFinish(gallery[current]); // fire onFinish handler
1062                            }
1063                            if(slide_timer != 'paused'){
1064                                SB.play(); // kick off next slide
1065                            }
1066                            listenKeys(true); // re-enable the keyboard
1067                        });
1068                    });
1069                });
1070            };
1071
1072            if(typeof content.ready != 'undefined'){ // does the object have a ready property?
1073                var id = setInterval(function(){ // if so, wait for the object to be ready
1074                    if(content){
1075                        if(content.ready){
1076                            clearInterval(id); // clean up
1077                            id = null;
1078                            fn();
1079                        }
1080                    }else{ // content has been removed
1081                        clearInterval(id);
1082                        id = null;
1083                    }
1084                }, 100);
1085            }else{
1086                fn();
1087            }
1088        });
1089
1090        // preload neighboring gallery images
1091        if(gallery.length > 1){
1092            var next = gallery[current + 1] || gallery[0];
1093            if(next.player == 'img'){
1094                var a = new Image();
1095                a.src = next.content;
1096            }
1097            var prev = gallery[current - 1] || gallery[gallery.length - 1];
1098            if(prev.player == 'img'){
1099                var b = new Image();
1100                b.src = prev.content;
1101            }
1102        }
1103    };
1104
1105    /**
1106     * Calculates the dimensions for Shadowbox, taking into account the borders
1107     * and surrounding elements of the shadowbox_body. If the height/width
1108     * combination is too large for Shadowbox and handleOversize option is set
1109     * to 'resize', the resized dimensions will be returned (preserving the
1110     * original aspect ratio). Otherwise, the originally calculated dimensions
1111     * will be used. Stores all dimensions in the private dims variable.
1112     *
1113     * @param   Number      height      The content player height
1114     * @param   Number      width       The content player width
1115     * @param   Boolean     resizable   True if the content is able to be
1116     *                                  resized. Defaults to false.
1117     * @return  void
1118     * @private
1119     */
1120    var setDimensions = function(height, width, resizable){
1121        resizable = resizable || false;
1122
1123        var sb = SL.get('shadowbox_body');
1124        var h = height = parseInt(height);
1125        var w = width = parseInt(width);
1126        var view_h = SL.getViewportHeight();
1127        var view_w = SL.getViewportWidth();
1128
1129        // calculate the max width
1130        var border_w = parseInt(SL.getStyle(sb, 'border-left-width'), 10)
1131            + parseInt(SL.getStyle(sb, 'border-right-width'), 10);
1132        var extra_w = border_w + 2 * options.viewportPadding;
1133        if(w + extra_w >= view_w){
1134            w = view_w - extra_w;
1135        }
1136
1137        // calculate the max height
1138        var border_h = parseInt(SL.getStyle(sb, 'border-top-width'), 10)
1139            + parseInt(SL.getStyle(sb, 'border-bottom-width'), 10);
1140        var bar_h = getComputedHeight(SL.get('shadowbox_title'))
1141            + getComputedHeight(SL.get('shadowbox_info'));
1142        var extra_h = border_h + 2 * options.viewportPadding + bar_h;
1143        if(h + extra_h >= view_h){
1144            h = view_h - extra_h;
1145        }
1146
1147        // handle oversized content
1148        var drag = false;
1149        var resize_h = height;
1150        var resize_w = width;
1151        var handle = options.handleOversize;
1152        if(resizable && (handle == 'resize' || handle == 'drag')){
1153            var change_h = (height - h) / height;
1154            var change_w = (width - w) / width;
1155            if(handle == 'resize'){
1156                if(change_h > change_w){
1157                    w = Math.round((width / height) * h);
1158                }else if(change_w > change_h){
1159                    h = Math.round((height / width) * w);
1160                }
1161                // adjust resized height or width accordingly
1162                resize_w = w;
1163                resize_h = h;
1164            }else{
1165                // drag on oversized images only
1166                var link = gallery[current];
1167                if(link) drag = link.player == 'img' && (change_h > 0 || change_w > 0);
1168            }
1169        }
1170
1171        // update dims
1172        dims = {
1173            height:     h + border_h + bar_h,
1174            width:      w + border_w,
1175            inner_h:    h,
1176            inner_w:    w,
1177            top:        (view_h - (h + extra_h)) / 2 + options.viewportPadding,
1178            resize_h:   resize_h,
1179            resize_w:   resize_w,
1180            drag:       drag
1181        };
1182    };
1183
1184    /**
1185     * Resizes Shadowbox to the given height and width. If the callback
1186     * parameter is given, the transition will be animated and the callback
1187     * function will be called when the animation completes. Note: The private
1188     * content variable must be updated before calling this function.
1189     *
1190     * @param   Function    cb      A callback function to execute after the
1191     *                              content has been resized
1192     * @return  void
1193     * @private
1194     */
1195    var resizeContent = function(cb){
1196        if(!content) return; // no content
1197
1198        // set new dimensions
1199        setDimensions(content.height, content.width, content.resizable);
1200
1201        if(cb){
1202            switch(options.animSequence){
1203                case 'hw':
1204                    adjustHeight(dims.inner_h, dims.top, true, function(){
1205                        adjustWidth(dims.width, true, cb);
1206                    });
1207                    break;
1208                case 'wh':
1209                    adjustWidth(dims.width, true, function(){
1210                        adjustHeight(dims.inner_h, dims.top, true, cb);
1211                    });
1212                    break;
1213                case 'sync':
1214                default:
1215                    adjustWidth(dims.width, true);
1216                    adjustHeight(dims.inner_h, dims.top, true, cb);
1217            }
1218        }else{ // window resize
1219            adjustWidth(dims.width, false);
1220            adjustHeight(dims.inner_h, dims.top, false);
1221            var c = SL.get(content_id);
1222            if(c){
1223                // resize resizable content when in resize mode
1224                if(content.resizable && options.handleOversize == 'resize'){
1225                    c.height = dims.resize_h;
1226                    c.width = dims.resize_w;
1227                }
1228                // fix draggable positioning if enlarging viewport
1229                if(gallery[current].player == 'img' && options.handleOversize == 'drag'){
1230                    var top = parseInt(SL.getStyle(c, 'top'));
1231                    if(top + content.height < dims.inner_h){
1232                        SL.setStyle(c, 'top', dims.inner_h - content.height + 'px');
1233                    }
1234                    var left = parseInt(SL.getStyle(c, 'left'));
1235                    if(left + content.width < dims.inner_w){
1236                        SL.setStyle(c, 'left', dims.inner_w - content.width + 'px');
1237                    }
1238                }
1239            }
1240        }
1241    };
1242
1243    /**
1244     * Adjusts the height of #shadowbox_body and centers #shadowbox vertically
1245     * in the viewport.
1246     *
1247     * @param   Number      height      The height to use for #shadowbox_body
1248     * @param   Number      top         The top to use for #shadowbox
1249     * @param   Boolean     anim        True to animate the transition
1250     * @param   Function    cb          A callback to use when the animation
1251     *                                  completes
1252     * @return  void
1253     * @private
1254     */
1255    var adjustHeight = function(height, top, anim, cb){
1256        height = parseInt(height);
1257
1258        // adjust the height
1259        var sb = SL.get('shadowbox_body');
1260        if(anim){
1261            animate(sb, 'height', height, options.resizeDuration);
1262        }else{
1263            SL.setStyle(sb, 'height', height + 'px');
1264        }
1265
1266        // adjust the top
1267        var s = SL.get('shadowbox');
1268        if(anim){
1269            animate(s, 'top', top, options.resizeDuration, cb);
1270        }else{
1271            SL.setStyle(s, 'top', top + 'px');
1272            if(typeof cb == 'function') cb();
1273        }
1274    };
1275
1276    /**
1277     * Adjusts the width of #shadowbox.
1278     *
1279     * @param   Number      width       The width to use for #shadowbox
1280     * @param   Boolean     anim        True to animate the transition
1281     * @param   Function    cb          A callback to use when the animation
1282     *                                  completes
1283     * @return  void
1284     * @private
1285     */
1286    var adjustWidth = function(width, anim, cb){
1287        width = parseInt(width);
1288
1289        // adjust the width
1290        var s = SL.get('shadowbox');
1291        if(anim){
1292            animate(s, 'width', width, options.resizeDuration, cb);
1293        }else{
1294            SL.setStyle(s, 'width', width + 'px');
1295            if(typeof cb == 'function') cb();
1296        }
1297    };
1298
1299    /**
1300     * Sets up a listener on the document for keystrokes.
1301     *
1302     * @param   Boolean     on      True to enable the listener, false to turn
1303     *                              it off
1304     * @return  void
1305     * @private
1306     */
1307    var listenKeys = function(on){
1308        if(!options.enableKeys) return;
1309        SL[(on ? 'add' : 'remove') + 'Event'](document, 'keydown', handleKey);
1310    };
1311
1312    /**
1313     * A listener function that is fired when a key is pressed.
1314     *
1315     * @param   mixed       e       The event object
1316     * @return  void
1317     * @private
1318     */
1319    var handleKey = function(e){
1320        var code = SL.keyCode(e);
1321
1322        // attempt to prevent default key action
1323        SL.preventDefault(e);
1324
1325        if(code == 81 || code == 88 || code == 27){ // q, x, or esc
1326            SB.close();
1327        }else if(code == 37){ // left arrow
1328            SB.previous();
1329        }else if(code == 39){ // right arrow
1330            SB.next();
1331        }else if(code == 32){ // space bar
1332            SB[(typeof slide_timer == 'number' ? 'pause' : 'play')]();
1333        }
1334    };
1335
1336    /**
1337     * Toggles the visibility of the "loading" layer.
1338     *
1339     * @param   Boolean     on      True to toggle on, false to toggle off
1340     * @param   Function    cb      The callback function to call when toggling
1341     *                              completes
1342     * @return  void
1343     * @private
1344     */
1345    var toggleLoading = function(on, cb){
1346        var loading = SL.get('shadowbox_loading');
1347        if(on){
1348            loading.style.display = '';
1349            if(typeof cb == 'function') cb();
1350        }else{
1351            var p = gallery[current].player;
1352            var anim = (p == 'img' || p == 'html'); // fade on images & html
1353            var fn = function(){
1354                loading.style.display = 'none';
1355                clearOpacity(loading);
1356                if(typeof cb == 'function') cb();
1357            };
1358            if(anim){
1359                animate(loading, 'opacity', 0, options.fadeDuration, fn);
1360            }else{
1361                fn();
1362            }
1363        }
1364    };
1365
1366    /**
1367     * Sets the top of the container element. This is only necessary in IE6
1368     * where the container uses absolute positioning instead of fixed.
1369     *
1370     * @return  void
1371     * @private
1372     */
1373    var fixTop = function(){
1374        SL.get('shadowbox_container').style.top = document.documentElement.scrollTop + 'px';
1375    };
1376
1377    /**
1378     * Sets the height of the overlay element to the full viewport height. This
1379     * is only necessary in IE6 where the container uses absolute positioning
1380     * instead of fixed, thus restricting the size of the overlay element.
1381     *
1382     * @return  void
1383     * @private
1384     */
1385    var fixHeight = function(){
1386        SL.get('shadowbox_overlay').style.height = SL.getViewportHeight() + 'px';
1387    };
1388
1389    /**
1390     * Determines if there is a next piece to display in the current gallery.
1391     *
1392     * @return  bool            True if there is another piece, false otherwise
1393     * @private
1394     */
1395    var hasNext = function(){
1396        return gallery.length > 1 && (current != gallery.length - 1 || options.continuous);
1397    };
1398
1399    /**
1400     * Toggles the visibility of #shadowbox_container and sets its size (if on
1401     * IE6). Also toggles the visibility of elements (<select>, <object>, and
1402     * <embed>) that are troublesome for semi-transparent modal overlays. IE has
1403     * problems with <select> elements, while Firefox has trouble with
1404     * <object>s.
1405     *
1406     * @param   Function    cb      A callback to call after toggling on, absent
1407     *                              when toggling off
1408     * @return  void
1409     * @private
1410     */
1411    var toggleVisible = function(cb){
1412        var els, v = (cb) ? 'hidden' : 'visible';
1413        var hide = ['select', 'object', 'embed']; // tags to hide
1414        for(var i = 0; i < hide.length; ++i){
1415            els = document.getElementsByTagName(hide[i]);
1416            for(var j = 0, len = els.length; j < len; ++j){
1417                els[j].style.visibility = v;
1418            }
1419        }
1420
1421        // resize & show container
1422        var so = SL.get('shadowbox_overlay');
1423        var sc = SL.get('shadowbox_container');
1424        var sb = SL.get('shadowbox');
1425        if(cb){
1426            // set overlay color/opacity
1427            SL.setStyle(so, {
1428                backgroundColor: options.overlayColor,
1429                opacity: 0
1430            });
1431            if(!options.modal) SL.addEvent(so, 'click', SB.close);
1432            if(ltIE7){
1433                // fix container top & overlay height before showing
1434                fixTop();
1435                fixHeight();
1436                SL.addEvent(window, 'scroll', fixTop);
1437            }
1438
1439            // fade in animation
1440            sb.style.display = 'none'; // will be cleared in loadContent()
1441            sc.style.visibility = 'visible';
1442            animate(so, 'opacity', parseFloat(options.overlayOpacity), options.fadeDuration, cb);
1443        }else{
1444            SL.removeEvent(so, 'click', SB.close);
1445            if(ltIE7) SL.removeEvent(window, 'scroll', fixTop);
1446
1447            // fade out effect
1448            sb.style.display = 'none';
1449            animate(so, 'opacity', 0, options.fadeDuration, function(){
1450                sc.style.visibility = 'hidden';
1451                sb.style.display = '';
1452                clearOpacity(so);
1453            });
1454        }
1455    };
1456
1457    /**
1458     * Initializes the Shadowbox environment. Loads the skin (if necessary),
1459     * compiles the player matching regular expressions, and sets up the
1460     * window resize listener.
1461     *
1462     * @param   Object      opts    (optional) The default options to use
1463     * @return  void
1464     * @public
1465     * @static
1466     */
1467    Shadowbox.init = function(opts){
1468        // don't initialize twice
1469        if(initialized) return;
1470
1471        // make sure language is loaded
1472        if(typeof SB.LANG == 'undefined'){
1473            SB.raise('No Shadowbox language loaded');
1474            return;
1475        }
1476        // make sure skin is loaded
1477        if(typeof SB.SKIN == 'undefined'){
1478            SB.raise('No Shadowbox skin loaded');
1479            return;
1480        }
1481
1482        // apply custom options
1483        apply(options, opts || {});
1484
1485        // add markup
1486        var markup = SB.SKIN.markup.replace(/\{(\w+)\}/g, function(m, p){
1487            return SB.LANG[p];
1488        });
1489        var bd = document.body || document.documentElement;
1490        SL.append(bd, markup);
1491
1492        // several fixes for IE6
1493        if(ltIE7){
1494            // give the container absolute positioning
1495            SL.setStyle(SL.get('shadowbox_container'), 'position', 'absolute');
1496            // give shadowbox_body "layout"...whatever that is
1497            SL.get('shadowbox_body').style.zoom = 1;
1498            // use AlphaImageLoader for transparent PNG support
1499            var png = SB.SKIN.png_fix;
1500            if(png && png.constructor == Array){
1501                for(var i = 0; i < png.length; ++i){
1502                    var el = SL.get(png[i]);
1503                    if(el){
1504                        var match = SL.getStyle(el, 'background-image').match(/url\("(.*\.png)"\)/);
1505                        if(match){
1506                            SL.setStyle(el, {
1507                                backgroundImage: 'none',
1508                                filter: 'progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true,src=' + match[1] + ',sizingMethod=scale);'
1509                            });
1510                        }
1511                    }
1512                }
1513            }
1514        }
1515
1516        // compile file type regular expressions here for speed
1517        for(var e in options.ext){
1518            RE[e] = new RegExp('\.(' + options.ext[e].join('|') + ')\s*$', 'i');
1519        }
1520
1521        // set up window resize event handler
1522        var id;
1523        SL.addEvent(window, 'resize', function(){
1524            // use 50 ms event buffering to prevent jerky window resizing
1525            if(id){
1526                clearTimeout(id);
1527                id = null;
1528            }
1529            id = setTimeout(function(){
1530                if(ltIE7) fixHeight();
1531                resizeContent();
1532            }, 50);
1533        });
1534
1535        if(!options.skipSetup) SB.setup();
1536        initialized = true;
1537    };
1538
1539    /**
1540     * Dynamically loads the specified skin for use with Shadowbox. If the skin
1541     * is included already in the page via the appropriate <script> and <link>
1542     * tags, this function does not need to be called. Otherwise, this function
1543     * must be called before window.onload.
1544     *
1545     * @param   String      skin        The directory where the skin is located
1546     * @param   String      dir         The directory where the Shadowbox skin
1547     *                                  files are located
1548     * @return  void
1549     * @public
1550     * @static
1551     */
1552    Shadowbox.loadSkin = function(skin, dir){
1553        if(!(/\/$/.test(dir))) dir += '/';
1554        skin = dir + skin + '/';
1555
1556        // Safari 2.0 fails using DOM, use document.write instead
1557        document.write('<link rel="stylesheet" type="text/css" href="' + skin + 'skin.css">');
1558        document.write('<scr' + 'ipt type="text/javascript" src="' + skin + 'skin.js"><\/script>');
1559    };
1560
1561    /**
1562     * Dynamically loads the specified language file to be used with Shadowbox.
1563     * If the language file is included already in the page via the appropriate
1564     * <script> tag, this function does not need to be called. Otherwise, this
1565     * function must be called before window.onload.
1566     *
1567     * @param   String      lang        The language abbreviation (e.g. en)
1568     * @param   String      dir         The directory where the Shadowbox
1569     *                                  language file(s) is located
1570     * @return  void
1571     * @public
1572     * @static
1573     */
1574    Shadowbox.loadLanguage = function(lang, dir){
1575        if(!(/\/$/.test(dir))) dir += '/';
1576
1577        // Safari 2.0 fails using DOM, use document.write instead
1578        document.write('<scr' + 'ipt type="text/javascript" src="' + dir + 'shadowbox-' + lang + '.js"><\/script>');
1579    };
1580
1581    /**
1582     * Dynamically loads the specified player(s) to be used with Shadowbox. If
1583     * the needed player(s) is already included in the page via the appropriate
1584     * <script> tag(s), this function does not need to be called. Otherwise,
1585     * this function must be called before window.onload.
1586     *
1587     * @param   Array       players     The player(s) to load
1588     * @param   String      dir         The director where the Shadowbox player
1589     *                                  file(s) is located
1590     * @return  void
1591     * @public
1592     * @static
1593     */
1594    Shadowbox.loadPlayer = function(players, dir){
1595        if(typeof players == 'string') players = [players];
1596        if(!(/\/$/.test(dir))) dir += '/';
1597
1598        for(var i = 0, len = players.length; i < len; ++i){
1599            // Safari 2.0 fails using DOM, use document.write instead
1600            document.write('<scr' + 'ipt type="text/javascript" src="' + dir + 'shadowbox-' + players[i] + '.js"><\/script>');
1601        }
1602    };
1603
1604    /**
1605     * Sets up listeners on the given links that will trigger Shadowbox. If no
1606     * links are given, this method will set up every anchor element on the page
1607     * with the appropriate rel attribute. Note: Because AREA elements do not
1608     * support the rel attribute, they must be explicitly passed to this method.
1609     *
1610     * @param   Array       links       An array (or array-like) list of anchor
1611     *                                  and/or area elements to set up
1612     * @param   Object      opts        Some options to use for the given links
1613     * @return  void
1614     * @public
1615     * @static
1616     */
1617    Shadowbox.setup = function(links, opts){
1618        // get links if none specified
1619        if(!links){
1620            var links = [];
1621            var a = document.getElementsByTagName('a'), rel;
1622            for(var i = 0, len = a.length; i < len; ++i){
1623                rel = a[i].getAttribute('rel');
1624                if(rel && RE.rel.test(rel)) links[links.length] = a[i];
1625            }
1626        }else if(!links.length){
1627            links = [links]; // one link
1628        }
1629
1630        var link;
1631        for(var i = 0, len = links.length; i < len; ++i){
1632            link = links[i];
1633            if(typeof link.shadowboxCacheKey == 'undefined'){
1634                // assign cache key expando
1635                // use integer primitive to avoid memory leak in IE
1636                link.shadowboxCacheKey = cache.length;
1637                SL.addEvent(link, 'click', handleClick); // add listener
1638            }
1639            cache[link.shadowboxCacheKey] = this.buildCacheObj(link, opts);
1640        }
1641    };
1642
1643    /**
1644     * Builds an object from the original link element data to store in cache.
1645     * These objects contain (most of) the following keys:
1646     *
1647     * - el: the link element
1648     * - title: the linked file title
1649     * - player: the player to use for the linked file
1650     * - content: the linked file's URL
1651     * - gallery: the gallery the file belongs to (optional)
1652     * - height: the height of the linked file (only necessary for movies)
1653     * - width: the width of the linked file (only necessary for movies)
1654     * - options: custom options to use (optional)
1655     *
1656     * @param   HTMLElement     link    The link element to process
1657     * @return  Object                  An object representing the link
1658     * @public
1659     * @static
1660     */
1661    Shadowbox.buildCacheObj = function(link, opts){
1662        var href = link.href; // don't use getAttribute() here
1663        var o = {
1664            el:         link,
1665            title:      link.getAttribute('title'),
1666            player:     getPlayer(href),
1667            options:    apply({}, opts || {}), // break the reference
1668            content:    href
1669        };
1670
1671        // remove link-level options from top-level options
1672        var opt, l_opts = ['player', 'title', 'height', 'width', 'gallery'];
1673        for(var i = 0, len = l_opts.length; i < len; ++i){
1674            opt = l_opts[i];
1675            if(typeof o.options[opt] != 'undefined'){
1676                o[opt] = o.options[opt];
1677                delete o.options[opt];
1678            }
1679        }
1680
1681        // HTML options always trump JavaScript options, so do these last
1682        var rel = link.getAttribute('rel');
1683        if(rel){
1684            // extract gallery name from shadowbox[name] format
1685            var match = rel.match(RE.gallery);
1686            if(match) o.gallery = escape(match[2]);
1687
1688            // other parameters
1689            var params = rel.split(';');
1690            for(var i = 0, len = params.length; i < len; ++i){
1691                match = params[i].match(RE.param);
1692                if(match){
1693                    if(match[1] == 'options'){
1694                        eval('apply(o.options, ' + match[2] + ')');
1695                    }else{
1696                        o[match[1]] = match[2];
1697                    }
1698                }
1699            }
1700        }
1701
1702        return o;
1703    };
1704
1705    /**
1706     * Applies the given set of options to those currently in use. Note: Options
1707     * will be reset on Shadowbox.open() so this function is only useful after
1708     * it has already been called (while Shadowbox is open).
1709     *
1710     * @param   Object      opts        The options to apply
1711     * @return  void
1712     * @public
1713     * @static
1714     */
1715    Shadowbox.applyOptions = function(opts){
1716        if(opts){
1717            // use apply here to break references
1718            default_options = apply({}, options); // store default options
1719            options = apply(options, opts); // apply options
1720        }
1721    };
1722
1723    /**
1724     * Reverts Shadowbox' options to the last default set in use before
1725     * Shadowbox.applyOptions() was called.
1726     *
1727     * @return  void
1728     * @public
1729     * @static
1730     */
1731    Shadowbox.revertOptions = function(){
1732        if(default_options){
1733            options = default_options; // revert to default options
1734            default_options = null; // erase for next time
1735        }
1736    };
1737
1738    /**
1739     * Opens the given object in Shadowbox. This object may be either an
1740     * anchor/area element, or an object similar to the one created by
1741     * Shadowbox.buildCacheObj().
1742     *
1743     * @param   mixed       obj         The object or link element that defines
1744     *                                  what to display
1745     * @return  void
1746     * @public
1747     * @static
1748     */
1749    Shadowbox.open = function(obj, opts){
1750        // revert options
1751        this.revertOptions();
1752
1753        // is it a link?
1754        if(isLink(obj)){
1755            if(typeof obj.shadowboxCacheKey == 'undefined' || typeof cache[obj.shadowboxCacheKey] == 'undefined'){
1756                // link element that hasn't been set up before
1757                // create on-the-fly object
1758                obj = this.buildCacheObj(obj, opts);
1759            }else{
1760                // link element that has been set up before, get from cache
1761                obj = cache[obj.shadowboxCacheKey];
1762            }
1763        }
1764
1765        // is it already a gallery?
1766        if(obj.constructor == Array){
1767            gallery = obj;
1768            current = 0;
1769        }else{
1770            // create a copy so it doesn't get modified later
1771            var copy = apply({}, obj);
1772
1773            // is it part of a gallery?
1774            if(!obj.gallery){ // single item, no gallery
1775                gallery = [copy];
1776                current = 0;
1777            }else{
1778                current = null; // reset current
1779                gallery = []; // clear the current gallery
1780                var ci;
1781                for(var i = 0, len = cache.length; i < len; ++i){
1782                    ci = cache[i];
1783                    if(ci.gallery){
1784                        if(ci.content == obj.content
1785                            && ci.gallery == obj.gallery
1786                            && ci.title == obj.title){ // compare content, gallery, & title
1787                                current = gallery.length; // key element found
1788                        }
1789                        if(ci.gallery == obj.gallery){
1790                            gallery.push(apply({}, ci));
1791                        }
1792                    }
1793                }
1794                // if not found in cache, prepend to front of gallery
1795                if(current == null){
1796                    gallery.unshift(copy);
1797                    current = 0;
1798                }
1799            }
1800        }
1801
1802        obj = gallery[current];
1803
1804        // apply custom options
1805        if(obj.options || opts){
1806            // use apply here to break references
1807            this.applyOptions(apply(apply({}, obj.options || {}), opts || {}));
1808        }
1809
1810        // filter gallery for unsupported elements
1811        var match, r;
1812        for(var i = 0, len = gallery.length; i < len; ++i){
1813            r = false; // remove the element?
1814            if(gallery[i].player == 'unsupported'){ // don't support this at all
1815                r = true;
1816            }else if(match = RE.unsupported.exec(gallery[i].player)){ // handle unsupported elements
1817                if(options.handleUnsupported == 'link'){
1818                    gallery[i].player = 'html';
1819                    // generate a link to the appropriate plugin download page(s)
1820                    var s, a, oe = options.errors;
1821                    switch(match[1]){
1822                        case 'qtwmp':
1823                            s = 'either';
1824                            a = [oe.qt.url, oe.qt.name, oe.wmp.url, oe.wmp.name];
1825                        break;
1826                        case 'qtf4m':
1827                            s = 'shared';
1828                            a = [oe.qt.url, oe.qt.name, oe.f4m.url, oe.f4m.name];
1829                        break;
1830                        default:
1831                            s = 'single';
1832                            if(match[1] == 'swf' || match[1] == 'flv') match[1] = 'fla';
1833                            a = [oe[match[1]].url, oe[match[1]].name];
1834                    }
1835                    var msg = SB.LANG.errors[s].replace(/\{(\d+)\}/g, function(m, i){
1836                        return a[i];
1837                    });
1838                    gallery[i].content = '<div class="shadowbox_message">' + msg + '</div>';
1839                }else{
1840                    r = true;
1841                }
1842            }else if(gallery[i].player == 'inline'){ // handle inline elements
1843                // retrieve the innerHTML of the inline element
1844                var match = RE.inline.exec(gallery[i].content);
1845                if(match){
1846                    var el;
1847                    if(el = SL.get(match[1])){
1848                        gallery[i].content = el.innerHTML;
1849                    }else{
1850                        SB.raise('Cannot find element with id ' + match[1]);
1851                    }
1852                }else{
1853                    SB.raise('Cannot find element id for inline content');
1854                }
1855            }
1856            if(r){
1857                gallery.splice(i, 1); // remove the element from the gallery
1858                if(i < current){
1859                    --current;
1860                }else if(i == current){
1861                    // if current is unsupported, look for supported neighbor
1862                    current = i > 0 ? current - 1 : i;
1863                }
1864                --i; // decrement to account for splice
1865                len = gallery.length; // gallery.length has changed!
1866            }
1867        }
1868
1869        // anything left?
1870        if(gallery.length){
1871            // fire onOpen hook
1872            if(options.onOpen && typeof options.onOpen == 'function'){
1873                options.onOpen(obj);
1874            }
1875
1876            if(!activated){
1877                // set initial dimensions & load
1878                setDimensions(options.initialHeight, options.initialWidth);
1879                adjustHeight(dims.inner_h, dims.top, false);
1880                adjustWidth(dims.width, false);
1881                toggleVisible(loadContent);
1882            } else {
1883                loadContent();
1884            }
1885
1886            activated = true;
1887        }
1888    };
1889
1890    /**
1891     * Jumps to the piece in the current gallery with index num.
1892     *
1893     * @param   Number      num     The gallery index to view
1894     * @return  void
1895     * @public
1896     * @static
1897     */
1898    Shadowbox.change = function(num){
1899        if(!gallery) return; // no current gallery
1900        if(!gallery[num]){ // index does not exist
1901            if(!options.continuous){
1902                return;
1903            }else{
1904                num = num < 0 ? (gallery.length - 1) : 0; // loop
1905            }
1906        }
1907
1908        if(typeof slide_timer == 'number'){
1909            clearTimeout(slide_timer);
1910            slide_timer = null;
1911            slide_delay = slide_start = 0; // reset slideshow variables
1912        }
1913        current = num; // update current
1914
1915        if(options.onChange && typeof options.onChange == 'function'){
1916            options.onChange(gallery[current]); // fire onChange handler
1917        }
1918
1919        loadContent();
1920    };
1921
1922    /**
1923     * Jumps to the next piece in the gallery.
1924     *
1925     * @return  void
1926     * @public
1927     * @static
1928     */
1929    Shadowbox.next = function(){
1930        this.change(current + 1);
1931    };
1932
1933    /**
1934     * Jumps to the previous piece in the gallery.
1935     *
1936     * @return  void
1937     * @public
1938     * @static
1939     */
1940    Shadowbox.previous = function(){
1941        this.change(current - 1);
1942    };
1943
1944    /**
1945     * Sets the timer for the next image in the slideshow to be displayed.
1946     *
1947     * @return  void
1948     * @public
1949     * @static
1950     */
1951    Shadowbox.play = function(){
1952        if(!hasNext()) return;
1953        if(!slide_delay) slide_delay = options.slideshowDelay * 1000;
1954        if(slide_delay){
1955            slide_start = new Date().getTime();
1956            slide_timer = setTimeout(function(){
1957                slide_delay = slide_start = 0; // reset slideshow
1958                SB.next();
1959            }, slide_delay);
1960
1961            // change play nav to pause
1962            toggleNav('play', false);
1963            toggleNav('pause', true);
1964        }
1965    };
1966
1967    /**
1968     * Pauses the current slideshow.
1969     *
1970     * @return  void
1971     * @public
1972     * @static
1973     */
1974    Shadowbox.pause = function(){
1975        if(typeof slide_timer == 'number'){
1976            var time = new Date().getTime();
1977            slide_delay = Math.max(0, slide_delay - (time - slide_start));
1978
1979            // any delay left on current slide? if so, stop the timer
1980            if(slide_delay){
1981                clearTimeout(slide_timer);
1982                slide_timer = 'paused';
1983            }
1984
1985            // change pause nav to play
1986            toggleNav('pause', false);
1987            toggleNav('play', true);
1988        }
1989    };
1990
1991    /**
1992     * Deactivates Shadowbox.
1993     *
1994     * @return  void
1995     * @public
1996     * @static
1997     */
1998    Shadowbox.close = function(){
1999        if(!activated) return; // already closed
2000
2001        // stop listening for keys
2002        listenKeys(false);
2003        // hide
2004        toggleVisible(false);
2005        // remove the content
2006        if(content){
2007            content.remove();
2008            content = null;
2009        }
2010
2011        // clear slideshow variables
2012        if(typeof slide_timer == 'number') clearTimeout(slide_timer);
2013        slide_timer = null;
2014        slide_delay = 0;
2015
2016        // fire onClose handler
2017        if(options.onClose && typeof options.onClose == 'function'){
2018            options.onClose(gallery[current]);
2019        }
2020
2021        activated = false;
2022    };
2023
2024    /**
2025     * Clears Shadowbox' cache and removes listeners and expandos from all
2026     * cached link elements. May be used to completely reset Shadowbox in case
2027     * links on a page change.
2028     *
2029     * @return  void
2030     * @public
2031     * @static
2032     */
2033    Shadowbox.clearCache = function(){
2034        for(var i = 0, len = cache.length; i < len; ++i){
2035            if(cache[i].el){
2036                SL.removeEvent(cache[i].el, 'click', handleClick);
2037                delete cache[i].el.shadowboxCacheKey; // remove expando
2038            }
2039        }
2040        cache = [];
2041    };
2042
2043    /**
2044     * Gets an object that lists which plugins are supported by the client. The
2045     * keys of this object will be:
2046     *
2047     * - fla: Adobe Flash Player
2048     * - qt: QuickTime Player
2049     * - wmp: Windows Media Player
2050     * - f4m: Flip4Mac QuickTime Player
2051     *
2052     * @return  Object          The plugins object
2053     * @public
2054     * @static
2055     */
2056    Shadowbox.getPlugins = function(){
2057        return plugins;
2058    };
2059
2060    /**
2061     * Gets the current options object in use.
2062     *
2063     * @return  Object          The options object
2064     * @public
2065     * @static
2066     */
2067    Shadowbox.getOptions = function(){
2068        return options;
2069    };
2070
2071    /**
2072     * Gets the current gallery object.
2073     *
2074     * @return  Object          The current gallery item
2075     * @public
2076     * @static
2077     */
2078    Shadowbox.getCurrent = function(){
2079        return gallery[current];
2080    };
2081
2082    /**
2083     * Gets the current version number of Shadowbox.
2084     *
2085     * @return  String          The current version
2086     * @public
2087     * @static
2088     */
2089    Shadowbox.getVersion = function(){
2090        return version;
2091    };
2092
2093    /**
2094     * Returns an object containing information about the current client
2095     * configuration.
2096     *
2097     * @return  Object          The object containing client data
2098     * @public
2099     * @static
2100     */
2101    Shadowbox.getClient = function(){
2102        return client;
2103    };
2104
2105    /**
2106     * Returns the current content object in use.
2107     *
2108     * @return  Object          The current content object
2109     * @public
2110     * @static
2111     */
2112    Shadowbox.getContent = function(){
2113        return content;
2114    };
2115
2116    /**
2117     * Gets the current dimensions of Shadowbox as calculated by
2118     * setDimensions().
2119     *
2120     * @return  Object          The current dimensions of Shadowbox
2121     * @public
2122     * @static
2123     */
2124    Shadowbox.getDimensions = function(){
2125        return dims;
2126    };
2127
2128    /**
2129     * Handles all Shadowbox exceptions (errors). Calls the exception
2130     * handler callback if one is present (see handleException option) or
2131     * throws a new exception.
2132     *
2133     * @param   String      e       The error message
2134     * @return  void
2135     * @public
2136     * @static
2137     */
2138    Shadowbox.raise = function(e){
2139        if(typeof options.handleException == 'function'){
2140            options.handleException(e);
2141        }else{
2142            throw e;
2143        }
2144    };
2145
2146})();

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.