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.