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