4
* This file is part of Shadowbox.
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.
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
18
* If you wish to use Shadowbox for commercial purposes, licensing information
19
* can be found at http://mjijackson.com/shadowbox/.
21
* @author Michael J. I. Jackson <mjijackson@gmail.com>
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 $
27
if(typeof Shadowbox == 'undefined'){
28
throw 'Unable to load Shadowbox, no base library adapter found';
32
* The Shadowbox class. Used to display different media on a web page using a
33
* Lightbox-like effect.
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
47
* @author Michael J. I. Jackson <mjijackson@gmail.com>
53
* The current version of Shadowbox.
61
* Contains the default options for Shadowbox.
69
* Enable all animations besides fades.
76
* Enable fade animations.
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.
92
* The path to flvplayer.swf.
96
flvPlayer: 'flvplayer.swf',
99
* Listen to the overlay for clicks. If the user clicks the overlay,
100
* it will trigger Shadowbox.close().
107
* The color to use for the modal overlay (in hex).
111
overlayColor: '#000',
114
* The opacity to use for the modal overlay.
121
* The default background color to use for Flash movies (in hex).
125
flashBgColor: '#000000',
128
* Automatically play movies.
132
autoplayMovies: true,
135
* Enable movie controllers on movie players.
139
showMovieControls: true,
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.
151
* The duration of the resizing animations (in seconds).
155
resizeDuration: 0.55,
158
* The duration of the fading animations (in seconds).
165
* Show the navigation controls.
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
181
* Display the gallery counter.
185
displayCounter: true,
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
195
counterType: 'default',
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.
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.
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.
227
handleOversize: 'resize',
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.
236
handleException: null,
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
248
handleUnsupported: 'link',
251
* The initial height of Shadowbox (in pixels).
258
* The initial width of Shadowbox (in pixels).
265
* Enable keyboard control.
272
* A hook function to be fired when Shadowbox opens. The single argument
273
* will be the current gallery element.
280
* A hook function to be fired when Shadowbox finishes loading its
281
* content. The single argument will be the current gallery element on
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.
298
* A hook function that will be fired when Shadowbox closes. The single
299
* argument will be the gallery element most recently displayed.
306
* Skips calling Shadowbox.setup() in init(). This means that it must
307
* be called later manually.
314
* An object containing names of plugins and links to their respective
323
url: 'http://www.adobe.com/products/flashplayer/'
328
url: 'http://www.apple.com/quicktime/download/'
332
name: 'Windows Media Player',
333
url: 'http://www.microsoft.com/windows/windowsmedia/'
338
url: 'http://www.flip4mac.com/wmv_download.htm'
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.
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
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
366
img: ['png', 'jpg', 'jpeg', 'gif', 'bmp'],
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',
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.
393
* An object containing some regular expressions we'll need later. Compiled
394
* up front for speed.
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
410
* A cache of options for links that have been set up for use with
419
* An array containing the gallery objects currently being viewed. In the
420
* case of non-gallery items, this will only hold one object.
428
* The array index of the current gallery that is currently being viewed.
436
* The current content object.
444
* The id to use for content objects.
449
var content_id = 'shadowbox_content';
452
* Holds the current dimensions of Shadowbox as calculated by
453
* setDimensions(). Contains the following properties:
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)
470
* Keeps track of whether or not Shadowbox has been initialized. We never
471
* want to initialize twice.
476
var initialized = false;
479
* Keeps track of whether or not Shadowbox is activated.
484
var activated = false;
487
* The timeout id for the slideshow transition function.
495
* Keeps track of the time at which the current slideshow frame was
504
* The delay on which the next slide will display.
512
* These parameters for simple browser detection. Adapted from Ext.js.
517
var ua = navigator.userAgent.toLowerCase();
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
528
client.isBorderBox = client.isIE && !client.isStrict;
529
client.isSafari3 = client.isSafari && !!(document.evaluate);
530
client.isGecko = ua.indexOf('gecko') != -1 && !client.isSafari;
533
* You're not sill using IE6 are you?
538
var ltIE7 = client.isIE && !client.isIE7;
541
* Contains plugin support information. Each property of this object is a
542
* boolean indicating whether that plugin is supported.
544
* - fla: Flash player
545
* - qt: QuickTime player
546
* - wmp: Windows Media player
547
* - f4m: Flip4Mac plugin
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){
566
var f4m = detectPlugin('Flip4Mac');
568
fla: detectPlugin('Shockwave Flash'),
569
qt: detectPlugin('QuickTime'),
570
wmp: !f4m && detectPlugin('Windows Media'), // if it's Flip4Mac, it's not really WMP
574
var detectPlugin = function(plugin_name){
575
var detected = false;
577
var axo = new ActiveXObject(plugin_name);
578
if(axo) detected = true;
583
fla: detectPlugin('ShockwaveFlash.ShockwaveFlash'),
584
qt: detectPlugin('QuickTime.QuickTime'),
585
wmp: detectPlugin('wmplayer.ocx'),
591
* Applies all properties of e to o.
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
599
var apply = function(o, e){
600
for(var p in e) o[p] = e[p];
605
* Determines if the given object is an anchor/area element.
607
* @param mixed el The object to check
608
* @return Boolean True if the object is a link element
611
var isLink = function(el){
612
return el && typeof el.tagName == 'string' && (el.tagName.toUpperCase() == 'A' || el.tagName.toUpperCase() == 'AREA');
616
* Gets the height of the viewport in pixels. Note: This function includes
617
* scrollbars in Safari 3.
619
* @return Number The height of the viewport
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;
633
* Gets the width of the viewport in pixels. Note: This function includes
634
* scrollbars in Safari 3.
636
* @return Number The width of the viewport
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;
650
* Creates an HTML string from an object representing HTML elements. Based
651
* on Ext.DomHelper's createHtml.
653
* @param Object obj The HTML definition object
654
* @return String An HTML string
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;
663
html += ' class="' + obj['cls'] + '"';
665
html += ' ' + attr + '="' + obj[attr] + '"';
668
if(RE.empty.test(obj.tag)){
672
var cn = obj.children;
674
for(var i = 0, len = cn.length; i < len; ++i){
675
html += this.createHTML(cn[i]);
678
if(obj.html) html += obj.html;
679
html += '</' + obj.tag + '>';
685
* Easing function used for animations. Based on a cubic polynomial.
687
* @param Number x The state of the animation (% complete)
688
* @return Number The adjusted easing value
692
var ease = function(x){
693
return 1 + Math.pow(x - 1, 3);
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
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
706
* @param Function cb A callback function to call when the
707
* animation completes
712
var animate = function(el, p, to, d, cb){
713
var from = parseFloat(SL.getStyle(el, p));
714
if(isNaN(from)) from = 0;
717
if(typeof cb == 'function') cb();
718
return; // nothing to animate
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);
728
// cancel the animation here if set in the options
729
if(!options.animate && !op || op && !options.animateFade){
731
if(typeof cb == 'function') cb();
735
d *= 1000; // convert to milliseconds
736
var begin = new Date().getTime();
739
var timer = setInterval(function(){
740
var time = new Date().getTime();
741
if(time >= end){ // end of animation
742
clearInterval(timer);
744
if(typeof cb == 'function') cb();
746
fn(ease((time - begin) / d));
748
}, 10); // 10 ms interval is minimum on WebKit
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.
755
* @param HTMLElement el The DOM element
759
var clearOpacity = function(el){
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, '');
768
s['-moz-opacity'] = '';
769
s['-khtml-opacity'] = '';
774
* Gets the computed height of the given element, including padding and
777
* @param HTMLElement el The element
778
* @return Number The computed height of the element
781
var getComputedHeight = function(el){
782
var h = Math.max(el.offsetHeight, el.clientHeight);
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);
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).
802
* @param String url The url of the file
803
* @return String The name of the player to use
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)){
828
return 'unsupported'; // same domain, not supported
832
* Handles all clicks on links that have been set up to work with Shadowbox
833
* and cancels the default event behavior when appropriate.
835
* @param {Event} ev The click event object
839
var handleClick = function(ev){
840
// get anchor/area element
843
link = this; // jQuery, Prototype, YUI
845
link = SL.getTarget(ev); // Ext, standalone
846
while(!isLink(link) && link.parentNode){
847
link = link.parentNode;
851
//SL.preventDefault(ev); // good for debugging
855
if(gallery.length) SL.preventDefault(ev); // stop event
860
* Toggles the display of the nav control with the given id on and off.
862
* @param String id The id of the navigation control
863
* @param Boolean on True to toggle on, false to toggle off
867
var toggleNav = function(id, on){
868
var el = SL.get('shadowbox_nav_' + id);
869
if(el) el.style.display = on ? '' : 'none';
873
* Builds the content for the title and information bars.
875
* @param Function cb A callback function to execute after the
880
var buildBars = function(cb){
881
var obj = gallery[current];
882
var title_i = SL.get('shadowbox_title_inner');
885
title_i.innerHTML = obj.title || '';
888
var nav = SL.get('shadowbox_nav');
892
// need to build the nav?
893
if(options.displayNav){
895
// next & previous links
896
var len = gallery.length;
898
if(options.continuous){
899
n = p = true; // show both
901
n = (len - 1) > current; // not last in gallery, show next
902
p = current > 0; // not first in gallery, show previous
906
if(options.slideshowDelay > 0 && hasNext()){
907
pa = slide_timer != 'paused';
911
c = n = pl = pa = p = false;
914
toggleNav('close', c);
915
toggleNav('next', n);
916
toggleNav('play', pl);
917
toggleNav('pause', pa);
918
toggleNav('previous', p);
922
var counter = SL.get('shadowbox_counter');
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);
936
end = current + (limit - h);
937
if(end > len) end -= len;
941
co += '<a onclick="Shadowbox.change(' + i + ');"';
942
if(i == current) co += ' class="shadowbox_counter_current"';
943
co += '>' + (++i) + '</a>';
946
co = (current + 1) + ' ' + SB.LANG.of + ' ' + len;
950
counter.innerHTML = co;
957
* Hides the title and info bars.
959
* @param Boolean anim True to animate the transition
960
* @param Function cb A callback function to execute after the
961
* animation completes
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');
972
// build bars after they are hidden
977
var title_h = getComputedHeight(title);
978
var info_h = getComputedHeight(info) * -1;
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);
984
SL.setStyle(title_i, 'margin-top', title_h + 'px');
985
SL.setStyle(info_i, 'margin-top', info_h + 'px');
991
* Shows the title and info bars.
993
* @param Function cb A callback function to execute after the
994
* animation completes
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?
1003
if(t) animate(title_i, 'margin-top', 0, 0.35);
1004
animate(info_i, 'margin-top', 0, 0.35, cb);
1008
* Loads the Shadowbox with the current piece.
1013
var loadContent = function(){
1014
var obj = gallery[current];
1015
if(!obj) return; // invalid
1017
var changing = false;
1019
content.remove(); // remove old content first
1020
changing = true; // changing from some previous content
1023
// determine player, inline is really just HTML
1024
var p = obj.player == 'inline' ? 'html' : obj.player;
1026
// make sure player is loaded
1027
if(typeof SB[p] != 'function'){
1028
SB.raise('Unknown player ' + obj.player);
1030
content = new SB[p](content_id, obj); // instantiate new content object
1032
listenKeys(false); // disable the keyboard temporarily
1033
toggleLoading(true);
1035
hideBars(changing, function(){ // if changing, animate the bars transition
1036
if(!content) return;
1038
// if opening, clear #shadowbox display
1040
SL.get('shadowbox').style.display = '';
1043
var fn = function(){
1044
resizeContent(function(){
1045
if(!content) return;
1048
showBars(function(){
1049
if(!content) return;
1051
// append content just before hiding the loading layer
1052
SL.get('shadowbox_body_inner').innerHTML = SL.createHTML(content.markup(dims));
1054
toggleLoading(false, function(){
1055
if(!content) return;
1057
if(typeof content.onLoad == 'function'){
1058
content.onLoad(); // call onLoad callback if present
1060
if(options.onFinish && typeof options.onFinish == 'function'){
1061
options.onFinish(gallery[current]); // fire onFinish handler
1063
if(slide_timer != 'paused'){
1064
SB.play(); // kick off next slide
1066
listenKeys(true); // re-enable the keyboard
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
1076
clearInterval(id); // clean up
1080
}else{ // content has been removed
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;
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;
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.
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.
1120
var setDimensions = function(height, width, resizable){
1121
resizable = resizable || false;
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();
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;
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;
1147
// handle oversized content
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);
1161
// adjust resized height or width accordingly
1165
// drag on oversized images only
1166
var link = gallery[current];
1167
if(link) drag = link.player == 'img' && (change_h > 0 || change_w > 0);
1173
height: h + border_h + bar_h,
1174
width: w + border_w,
1177
top: (view_h - (h + extra_h)) / 2 + options.viewportPadding,
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.
1190
* @param Function cb A callback function to execute after the
1191
* content has been resized
1195
var resizeContent = function(cb){
1196
if(!content) return; // no content
1198
// set new dimensions
1199
setDimensions(content.height, content.width, content.resizable);
1202
switch(options.animSequence){
1204
adjustHeight(dims.inner_h, dims.top, true, function(){
1205
adjustWidth(dims.width, true, cb);
1209
adjustWidth(dims.width, true, function(){
1210
adjustHeight(dims.inner_h, dims.top, true, cb);
1215
adjustWidth(dims.width, true);
1216
adjustHeight(dims.inner_h, dims.top, true, cb);
1218
}else{ // window resize
1219
adjustWidth(dims.width, false);
1220
adjustHeight(dims.inner_h, dims.top, false);
1221
var c = SL.get(content_id);
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;
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');
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');
1244
* Adjusts the height of #shadowbox_body and centers #shadowbox vertically
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
1255
var adjustHeight = function(height, top, anim, cb){
1256
height = parseInt(height);
1258
// adjust the height
1259
var sb = SL.get('shadowbox_body');
1261
animate(sb, 'height', height, options.resizeDuration);
1263
SL.setStyle(sb, 'height', height + 'px');
1267
var s = SL.get('shadowbox');
1269
animate(s, 'top', top, options.resizeDuration, cb);
1271
SL.setStyle(s, 'top', top + 'px');
1272
if(typeof cb == 'function') cb();
1277
* Adjusts the width of #shadowbox.
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
1286
var adjustWidth = function(width, anim, cb){
1287
width = parseInt(width);
1290
var s = SL.get('shadowbox');
1292
animate(s, 'width', width, options.resizeDuration, cb);
1294
SL.setStyle(s, 'width', width + 'px');
1295
if(typeof cb == 'function') cb();
1300
* Sets up a listener on the document for keystrokes.
1302
* @param Boolean on True to enable the listener, false to turn
1307
var listenKeys = function(on){
1308
if(!options.enableKeys) return;
1309
SL[(on ? 'add' : 'remove') + 'Event'](document, 'keydown', handleKey);
1313
* A listener function that is fired when a key is pressed.
1315
* @param mixed e The event object
1319
var handleKey = function(e){
1320
var code = SL.keyCode(e);
1322
// attempt to prevent default key action
1323
SL.preventDefault(e);
1325
if(code == 81 || code == 88 || code == 27){ // q, x, or esc
1327
}else if(code == 37){ // left arrow
1329
}else if(code == 39){ // right arrow
1331
}else if(code == 32){ // space bar
1332
SB[(typeof slide_timer == 'number' ? 'pause' : 'play')]();
1337
* Toggles the visibility of the "loading" layer.
1339
* @param Boolean on True to toggle on, false to toggle off
1340
* @param Function cb The callback function to call when toggling
1345
var toggleLoading = function(on, cb){
1346
var loading = SL.get('shadowbox_loading');
1348
loading.style.display = '';
1349
if(typeof cb == 'function') cb();
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();
1359
animate(loading, 'opacity', 0, options.fadeDuration, fn);
1367
* Sets the top of the container element. This is only necessary in IE6
1368
* where the container uses absolute positioning instead of fixed.
1373
var fixTop = function(){
1374
SL.get('shadowbox_container').style.top = document.documentElement.scrollTop + 'px';
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.
1385
var fixHeight = function(){
1386
SL.get('shadowbox_overlay').style.height = SL.getViewportHeight() + 'px';
1390
* Determines if there is a next piece to display in the current gallery.
1392
* @return bool True if there is another piece, false otherwise
1395
var hasNext = function(){
1396
return gallery.length > 1 && (current != gallery.length - 1 || options.continuous);
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
1406
* @param Function cb A callback to call after toggling on, absent
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;
1421
// resize & show container
1422
var so = SL.get('shadowbox_overlay');
1423
var sc = SL.get('shadowbox_container');
1424
var sb = SL.get('shadowbox');
1426
// set overlay color/opacity
1428
backgroundColor: options.overlayColor,
1431
if(!options.modal) SL.addEvent(so, 'click', SB.close);
1433
// fix container top & overlay height before showing
1436
SL.addEvent(window, 'scroll', fixTop);
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);
1444
SL.removeEvent(so, 'click', SB.close);
1445
if(ltIE7) SL.removeEvent(window, 'scroll', fixTop);
1448
sb.style.display = 'none';
1449
animate(so, 'opacity', 0, options.fadeDuration, function(){
1450
sc.style.visibility = 'hidden';
1451
sb.style.display = '';
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.
1462
* @param Object opts (optional) The default options to use
1467
Shadowbox.init = function(opts){
1468
// don't initialize twice
1469
if(initialized) return;
1471
// make sure language is loaded
1472
if(typeof SB.LANG == 'undefined'){
1473
SB.raise('No Shadowbox language loaded');
1476
// make sure skin is loaded
1477
if(typeof SB.SKIN == 'undefined'){
1478
SB.raise('No Shadowbox skin loaded');
1482
// apply custom options
1483
apply(options, opts || {});
1486
var markup = SB.SKIN.markup.replace(/\{(\w+)\}/g, function(m, p){
1489
var bd = document.body || document.documentElement;
1490
SL.append(bd, markup);
1492
// several fixes for IE6
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]);
1504
var match = SL.getStyle(el, 'background-image').match(/url\("(.*\.png)"\)/);
1507
backgroundImage: 'none',
1508
filter: 'progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true,src=' + match[1] + ',sizingMethod=scale);'
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');
1521
// set up window resize event handler
1523
SL.addEvent(window, 'resize', function(){
1524
// use 50 ms event buffering to prevent jerky window resizing
1529
id = setTimeout(function(){
1530
if(ltIE7) fixHeight();
1535
if(!options.skipSetup) SB.setup();
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.
1545
* @param String skin The directory where the skin is located
1546
* @param String dir The directory where the Shadowbox skin
1552
Shadowbox.loadSkin = function(skin, dir){
1553
if(!(/\/$/.test(dir))) dir += '/';
1554
skin = dir + skin + '/';
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>');
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.
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
1574
Shadowbox.loadLanguage = function(lang, dir){
1575
if(!(/\/$/.test(dir))) dir += '/';
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>');
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.
1587
* @param Array players The player(s) to load
1588
* @param String dir The director where the Shadowbox player
1589
* file(s) is located
1594
Shadowbox.loadPlayer = function(players, dir){
1595
if(typeof players == 'string') players = [players];
1596
if(!(/\/$/.test(dir))) dir += '/';
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>');
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.
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
1617
Shadowbox.setup = function(links, opts){
1618
// get links if none specified
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];
1626
}else if(!links.length){
1627
links = [links]; // one link
1631
for(var i = 0, len = links.length; i < len; ++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
1639
cache[link.shadowboxCacheKey] = this.buildCacheObj(link, opts);
1644
* Builds an object from the original link element data to store in cache.
1645
* These objects contain (most of) the following keys:
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)
1656
* @param HTMLElement link The link element to process
1657
* @return Object An object representing the link
1661
Shadowbox.buildCacheObj = function(link, opts){
1662
var href = link.href; // don't use getAttribute() here
1665
title: link.getAttribute('title'),
1666
player: getPlayer(href),
1667
options: apply({}, opts || {}), // break the reference
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){
1675
if(typeof o.options[opt] != 'undefined'){
1676
o[opt] = o.options[opt];
1677
delete o.options[opt];
1681
// HTML options always trump JavaScript options, so do these last
1682
var rel = link.getAttribute('rel');
1684
// extract gallery name from shadowbox[name] format
1685
var match = rel.match(RE.gallery);
1686
if(match) o.gallery = escape(match[2]);
1689
var params = rel.split(';');
1690
for(var i = 0, len = params.length; i < len; ++i){
1691
match = params[i].match(RE.param);
1693
if(match[1] == 'options'){
1694
eval('apply(o.options, ' + match[2] + ')');
1696
o[match[1]] = match[2];
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).
1710
* @param Object opts The options to apply
1715
Shadowbox.applyOptions = function(opts){
1717
// use apply here to break references
1718
default_options = apply({}, options); // store default options
1719
options = apply(options, opts); // apply options
1724
* Reverts Shadowbox' options to the last default set in use before
1725
* Shadowbox.applyOptions() was called.
1731
Shadowbox.revertOptions = function(){
1732
if(default_options){
1733
options = default_options; // revert to default options
1734
default_options = null; // erase for next time
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().
1743
* @param mixed obj The object or link element that defines
1749
Shadowbox.open = function(obj, opts){
1751
this.revertOptions();
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);
1760
// link element that has been set up before, get from cache
1761
obj = cache[obj.shadowboxCacheKey];
1765
// is it already a gallery?
1766
if(obj.constructor == Array){
1770
// create a copy so it doesn't get modified later
1771
var copy = apply({}, obj);
1773
// is it part of a gallery?
1774
if(!obj.gallery){ // single item, no gallery
1778
current = null; // reset current
1779
gallery = []; // clear the current gallery
1781
for(var i = 0, len = cache.length; i < len; ++i){
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
1789
if(ci.gallery == obj.gallery){
1790
gallery.push(apply({}, ci));
1794
// if not found in cache, prepend to front of gallery
1795
if(current == null){
1796
gallery.unshift(copy);
1802
obj = gallery[current];
1804
// apply custom options
1805
if(obj.options || opts){
1806
// use apply here to break references
1807
this.applyOptions(apply(apply({}, obj.options || {}), opts || {}));
1810
// filter gallery for unsupported elements
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
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;
1824
a = [oe.qt.url, oe.qt.name, oe.wmp.url, oe.wmp.name];
1828
a = [oe.qt.url, oe.qt.name, oe.f4m.url, oe.f4m.name];
1832
if(match[1] == 'swf' || match[1] == 'flv') match[1] = 'fla';
1833
a = [oe[match[1]].url, oe[match[1]].name];
1835
var msg = SB.LANG.errors[s].replace(/\{(\d+)\}/g, function(m, i){
1838
gallery[i].content = '<div class="shadowbox_message">' + msg + '</div>';
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);
1847
if(el = SL.get(match[1])){
1848
gallery[i].content = el.innerHTML;
1850
SB.raise('Cannot find element with id ' + match[1]);
1853
SB.raise('Cannot find element id for inline content');
1857
gallery.splice(i, 1); // remove the element from the gallery
1860
}else if(i == current){
1861
// if current is unsupported, look for supported neighbor
1862
current = i > 0 ? current - 1 : i;
1864
--i; // decrement to account for splice
1865
len = gallery.length; // gallery.length has changed!
1872
if(options.onOpen && typeof options.onOpen == 'function'){
1873
options.onOpen(obj);
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);
1891
* Jumps to the piece in the current gallery with index num.
1893
* @param Number num The gallery index to view
1898
Shadowbox.change = function(num){
1899
if(!gallery) return; // no current gallery
1900
if(!gallery[num]){ // index does not exist
1901
if(!options.continuous){
1904
num = num < 0 ? (gallery.length - 1) : 0; // loop
1908
if(typeof slide_timer == 'number'){
1909
clearTimeout(slide_timer);
1911
slide_delay = slide_start = 0; // reset slideshow variables
1913
current = num; // update current
1915
if(options.onChange && typeof options.onChange == 'function'){
1916
options.onChange(gallery[current]); // fire onChange handler
1923
* Jumps to the next piece in the gallery.
1929
Shadowbox.next = function(){
1930
this.change(current + 1);
1934
* Jumps to the previous piece in the gallery.
1940
Shadowbox.previous = function(){
1941
this.change(current - 1);
1945
* Sets the timer for the next image in the slideshow to be displayed.
1951
Shadowbox.play = function(){
1952
if(!hasNext()) return;
1953
if(!slide_delay) slide_delay = options.slideshowDelay * 1000;
1955
slide_start = new Date().getTime();
1956
slide_timer = setTimeout(function(){
1957
slide_delay = slide_start = 0; // reset slideshow
1961
// change play nav to pause
1962
toggleNav('play', false);
1963
toggleNav('pause', true);
1968
* Pauses the current slideshow.
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));
1979
// any delay left on current slide? if so, stop the timer
1981
clearTimeout(slide_timer);
1982
slide_timer = 'paused';
1985
// change pause nav to play
1986
toggleNav('pause', false);
1987
toggleNav('play', true);
1992
* Deactivates Shadowbox.
1998
Shadowbox.close = function(){
1999
if(!activated) return; // already closed
2001
// stop listening for keys
2004
toggleVisible(false);
2005
// remove the content
2011
// clear slideshow variables
2012
if(typeof slide_timer == 'number') clearTimeout(slide_timer);
2016
// fire onClose handler
2017
if(options.onClose && typeof options.onClose == 'function'){
2018
options.onClose(gallery[current]);
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.
2033
Shadowbox.clearCache = function(){
2034
for(var i = 0, len = cache.length; i < len; ++i){
2036
SL.removeEvent(cache[i].el, 'click', handleClick);
2037
delete cache[i].el.shadowboxCacheKey; // remove expando
2044
* Gets an object that lists which plugins are supported by the client. The
2045
* keys of this object will be:
2047
* - fla: Adobe Flash Player
2048
* - qt: QuickTime Player
2049
* - wmp: Windows Media Player
2050
* - f4m: Flip4Mac QuickTime Player
2052
* @return Object The plugins object
2056
Shadowbox.getPlugins = function(){
2061
* Gets the current options object in use.
2063
* @return Object The options object
2067
Shadowbox.getOptions = function(){
2072
* Gets the current gallery object.
2074
* @return Object The current gallery item
2078
Shadowbox.getCurrent = function(){
2079
return gallery[current];
2083
* Gets the current version number of Shadowbox.
2085
* @return String The current version
2089
Shadowbox.getVersion = function(){
2094
* Returns an object containing information about the current client
2097
* @return Object The object containing client data
2101
Shadowbox.getClient = function(){
2106
* Returns the current content object in use.
2108
* @return Object The current content object
2112
Shadowbox.getContent = function(){
2117
* Gets the current dimensions of Shadowbox as calculated by
2120
* @return Object The current dimensions of Shadowbox
2124
Shadowbox.getDimensions = function(){
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.
2133
* @param String e The error message
2138
Shadowbox.raise = function(e){
2139
if(typeof options.handleException == 'function'){
2140
options.handleException(e);
b'\\ No newline at end of file'