/** * @version OpenSeadragon @VERSION@ * * @fileOverview *
* OpenSeadragon is provides an html interface for creating * deep zoom user interfaces. The simplest examples include deep * zoom for large resolution images, and complex examples include * zoomable map interfaces driven by SVG files. *
* * @author
* Original license preserved below:
*
* ---------------------------------------------------------------------------- * * License: New BSD License (BSD) * Copyright (c) 2010, OpenSeadragon * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of OpenSeadragon nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * ---------------------------------------------------------------------------- ** **/ /** * The root namespace for OpenSeadragon, this function also serves as a single * point of instantiation for an {@link OpenSeadragon.Viewer}, including all * combinations of out-of-the-box configurable features. All utility methods * and classes are defined on or below this namespace. * * @namespace * @function * @name OpenSeadragon * @exports $ as OpenSeadragon * * @param {Object} options All required and optional settings for instantiating * a new instance of an OpenSeadragon image viewer. * * @param {String} options.xmlPath * DEPRECATED. A relative path to load a DZI file from the server. * Prefer the newer options.tileSources. * * @param {Array|String|Function|Object[]|Array[]|String[]|Function[]} options.tileSources * As an Array, the tileSource can hold either be all Objects or mixed * types of Arrays of Objects, String, Function. When a value is a String, * the tileSource is used to create a {@link OpenSeadragon.DziTileSource}. * When a value is a Function, the function is used to create a new * {@link OpenSeadragon.TileSource} whose abstract method * getUrl( level, x, y ) is implemented by the function. Finally, when it * is an Array of objects, it is used to create a * {@link OpenSeadragon.LegacyTileSource}. * * @param {Boolean} [options.debugMode=true] * Currently does nothing. TODO: provide an in-screen panel providing event * detail feedback. * * @param {Number} [options.animationTime=1.5] * Specifies the animation duration per each {@link OpenSeadragon.Spring} * which occur when the image is dragged or zoomed. * * @param {Number} [options.blendTime=0.5] * Specifies the duration of animation as higher or lower level tiles are * replacing the existing tile. * * @param {Boolean} [options.alwaysBlend=false] * Forces the tile to always blend. By default the tiles skip blending * when the blendTime is surpassed and the current animation frame would * not complete the blend. * * @param {Boolean} [options.autoHideControls=true] * If the user stops interacting with the viewport, fade the navigation * controls. Useful for presentation since the controls are by default * floated on top of the image the user is viewing. * * @param {Boolean} [options.immediateRender=false] * * @param {Boolean} [options.wrapHorizontal=false] * Should the image wrap horizontally within the viewport. Useful for * maps or images representing the surface of a sphere or cylinder. * * @param {Boolean} [options.wrapVertical=false] * Should the image wrap vertically within the viewport. Useful for * maps or images representing the surface of a sphere or cylinder. * * @param {Number} [options.minZoomImageRatio=0.8] * @param {Number} [options.maxZoomPixelRatio=2] * * @param {Number} [options.visibilityRatio=0.5] * The percentage ( as a number from 0 to 1 ) of the source image which * must be kept within the viewport. If the image is dragged beyond that * limit, it will 'bounce' back until the minimum visibility ration is * achieved. Setting this to 0 and wrapHorizontal ( or wrapVertical ) to * true will provide the effect of an infinitely scrolling viewport. * * @param {Number} [options.springStiffness=5.0] * @param {Number} [options.imageLoaderLimit=0] * @param {Number} [options.clickTimeThreshold=200] * @param {Number} [options.clickDistThreshold=5] * @param {Number} [options.zoomPerClick=2.0] * @param {Number} [options.zoomPerScroll=1.2] * @param {Number} [options.zoomPerSecond=2.0] * * @param {Boolean} [options.showNavigationControl=true] * * @param {Number} [options.controlsFadeDelay=2000] * The number of milliseconds to wait once the user has stopped interacting * with the interface before begining to fade the controls. Assumes * showNavigationControl and autoHideControls are both true. * * @param {Number} [options.controlsFadeLength=1500] * The number of milliseconds to animate the controls fading out. * * @param {Number} [options.maxImageCacheCount=100] * The max number of images we should keep in memory (per drawer). * * @param {Number} [options.minPixelRatio=0.5] * The higher the minPixelRatio, the lower the quality of the image that * is considered sufficient to stop rendering a given zoom level. For * example, if you are targeting mobile devices with less bandwith you may * try setting this to 1.5 or higher. * * @param {Boolean} [options.mouseNavEnabled=true] * Is the user able to interact with the image via mouse or touch. Default * interactions include draging the image in a plane, and zooming in toward * and away from the image. * * @param {String} [options.prefixUrl=''] * Appends the prefixUrl to navImages paths, which is very useful * since the default paths are rarely useful for production * environments. * * @param {Object} [options.navImages=] * An object with a property for each button or other built-in navigation * control, eg the current 'zoomIn', 'zoomOut', 'home', and 'fullpage'. * Each of those in turn provides an image path for each state of the botton * or navigation control, eg 'REST', 'GROUP', 'HOVER', 'PRESS'. Finally the * image paths, by default assume there is a folder on the servers root path * called '/images', eg '/images/zoomin_rest.png'. If you need to adjust * these paths, prefer setting the option.prefixUrl rather than overriding * every image path directly through this setting. * * @returns {OpenSeadragon.Viewer} */ OpenSeadragon = window.OpenSeadragon || function( options ){ return new OpenSeadragon.Viewer( options ); }; (function( $ ){ /** * Taken from jquery 1.6.1 * [[Class]] -> type pairs * @private */ var class2type = { '[object Boolean]': 'boolean', '[object Number]': 'number', '[object String]': 'string', '[object Function]': 'function', '[object Array]': 'array', '[object Date]': 'date', '[object RegExp]': 'regexp', '[object Object]': 'object' }, // Save a reference to some core methods toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty, push = Array.prototype.push, slice = Array.prototype.slice, trim = String.prototype.trim, indexOf = Array.prototype.indexOf; /** * Taken from jQuery 1.6.1 * @name $.isFunction * @function * @see jQuery */ $.isFunction = function( obj ) { return $.type(obj) === "function"; }; /** * Taken from jQuery 1.6.1 * @name $.isArray * @function * @see jQuery */ $.isArray = Array.isArray || function( obj ) { return $.type(obj) === "array"; }; /** * A crude way of determining if an object is a window. * Taken from jQuery 1.6.1 * @name $.isWindow * @function * @see jQuery */ $.isWindow = function( obj ) { return obj && typeof obj === "object" && "setInterval" in obj; }; /** * Taken from jQuery 1.6.1 * @name $.type * @function * @see jQuery */ $.type = function( obj ) { return obj == null ? String( obj ) : class2type[ toString.call(obj) ] || "object"; }; /** * Taken from jQuery 1.6.1 * @name $.isPlainObject * @function * @see jQuery */ $.isPlainObject = function( obj ) { // Must be an Object. // Because of IE, we also have to check the presence of the constructor property. // Make sure that DOM nodes and window objects don't pass through, as well if ( !obj || OpenSeadragon.type(obj) !== "object" || obj.nodeType || $.isWindow( obj ) ) { return false; } // Not own constructor property must be Object if ( obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { return false; } // Own properties are enumerated firstly, so to speed up, // if last one is own, then all properties are own. var key; for ( key in obj ) {} return key === undefined || hasOwn.call( obj, key ); }; /** * Taken from jQuery 1.6.1 * @name $.isEmptyObject * @function * @see jQuery */ $.isEmptyObject = function( obj ) { for ( var name in obj ) { return false; } return true; }; }( OpenSeadragon )); /** * This closure defines all static methods available to the OpenSeadragon * namespace. Many, if not most, are taked directly from jQuery for use * to simplify and reduce common programming patterns. More static methods * from jQuery may eventually make their way into this though we are * attempting to avoid substaintial plagarism or the more explicit dependency * on jQuery only because OpenSeadragon is a broadly useful code base and * would be made less broad by requiring jQuery fully. * * Some static methods have also been refactored from the original OpenSeadragon * project. */ (function( $ ){ /** * Taken from jQuery 1.6.1 * @see jQuery */ $.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[ 0 ] || {}, length = arguments.length, deep = false, i = 1; // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; target = arguments[ 1 ] || {}; // skip the boolean and the target i = 2; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !OpenSeadragon.isFunction( target ) ) { target = {}; } // extend jQuery itself if only one argument is passed if ( length === i ) { target = this; --i; } for ( ; i < length; i++ ) { // Only deal with non-null/undefined values if ( ( options = arguments[ i ] ) != null ) { // Extend the base object for ( name in options ) { src = target[ name ]; copy = options[ name ]; // Prevent never-ending loop if ( target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( OpenSeadragon.isPlainObject( copy ) || ( copyIsArray = OpenSeadragon.isArray( copy ) ) ) ) { if ( copyIsArray ) { copyIsArray = false; clone = src && OpenSeadragon.isArray( src ) ? src : []; } else { clone = src && OpenSeadragon.isPlainObject( src ) ? src : {}; } // Never move original objects, clone them target[ name ] = OpenSeadragon.extend( deep, clone, copy ); // Don't bring in undefined values } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // Return the modified object return target; }; $.extend( $, { /** * These are the default values for the optional settings documented * in the {@link OpenSeadragon} constructor detail. * @name $.DEFAULT_SETTINGS * @static */ DEFAULT_SETTINGS: { xmlPath: null, tileSources: null, debugMode: true, animationTime: 1.5, blendTime: 0.5, alwaysBlend: false, autoHideControls: true, immediateRender: false, wrapHorizontal: false, wrapVertical: false, minZoomImageRatio: 0.8, maxZoomPixelRatio: 2, visibilityRatio: 0.5, springStiffness: 5.0, imageLoaderLimit: 0, clickTimeThreshold: 200, clickDistThreshold: 5, zoomPerClick: 2.0, zoomPerScroll: 1.2, zoomPerSecond: 2.0, showNavigationControl: true, //These two were referenced but never defined controlsFadeDelay: 2000, controlsFadeLength: 1500, maxImageCacheCount: 100, minPixelRatio: 0.5, mouseNavEnabled: true, prefixUrl: null, navImages: { zoomIn: { REST: '/images/zoomin_rest.png', GROUP: '/images/zoomin_grouphover.png', HOVER: '/images/zoomin_hover.png', DOWN: '/images/zoomin_pressed.png' }, zoomOut: { REST: '/images/zoomout_rest.png', GROUP: '/images/zoomout_grouphover.png', HOVER: '/images/zoomout_hover.png', DOWN: '/images/zoomout_pressed.png' }, home: { REST: '/images/home_rest.png', GROUP: '/images/home_grouphover.png', HOVER: '/images/home_hover.png', DOWN: '/images/home_pressed.png' }, fullpage: { REST: '/images/fullpage_rest.png', GROUP: '/images/fullpage_grouphover.png', HOVER: '/images/fullpage_hover.png', DOWN: '/images/fullpage_pressed.png' } } }, /** * TODO: get rid of this. I can't see how it's required at all. Looks * like an early legacy code artifact. * @static * @ignore */ SIGNAL: "----seadragon----", /** * Invokes the the method as if it where a method belonging to the object. * @name $.delegate * @function * @param {Object} object * @param {Function} method */ delegate: function( object, method ) { return function() { if ( arguments === undefined ) arguments = []; return method.apply( object, arguments ); }; }, /** * An enumeration of Browser vendors including UNKNOWN, IE, FIREFOX, * SAFARI, CHROME, and OPERA. * @name $.BROWSERS * @static */ BROWSERS: { UNKNOWN: 0, IE: 1, FIREFOX: 2, SAFARI: 3, CHROME: 4, OPERA: 5 }, /** * Returns a DOM Element for the given id or element. * @function * @name OpenSeadragon.getElement * @param {String|Element} element Accepts an id or element. * @returns {Element} The element with the given id, null, or the element itself. */ getElement: function( element ) { if ( typeof ( element ) == "string" ) { element = document.getElementById( element ); } return element; }, /** * Determines the position of the upper-left corner of the element. * @function * @name OpenSeadragon.getElementPosition * @param {Element|String} element - the elemenet we want the position for. * @returns {Point} - the position of the upper left corner of the element. */ getElementPosition: function( element ) { var result = new $.Point(), isFixed, offsetParent; element = $.getElement( element ); isFixed = $.getElementStyle( element ).position == "fixed"; offsetParent = getOffsetParent( element, isFixed ); while ( offsetParent ) { result.x += element.offsetLeft; result.y += element.offsetTop; if ( isFixed ) { result = result.plus( $.getPageScroll() ); } element = offsetParent; isFixed = $.getElementStyle( element ).position == "fixed"; offsetParent = getOffsetParent( element, isFixed ); } return result; }, /** * Determines the height and width of the given element. * @function * @name OpenSeadragon.getElementSize * @param {Element|String} element * @returns {Point} */ getElementSize: function( element ) { element = $.getElement( element ); return new $.Point( element.clientWidth, element.clientHeight ); }, /** * Returns the CSSStyle object for the given element. * @function * @name OpenSeadragon.getElementStyle * @param {Element|String} element * @returns {CSSStyle} */ getElementStyle: function( element ) { element = $.getElement( element ); if ( element.currentStyle ) { return element.currentStyle; } else if ( window.getComputedStyle ) { return window.getComputedStyle( element, "" ); } else { throw new Error( "Unknown element style, no known technique." ); } }, /** * Gets the latest event, really only useful internally since its * specific to IE behavior. TODO: Deprecate this from the api and * use it internally. * @function * @name OpenSeadragon.getEvent * @param {Event} [event] * @returns {Event} */ getEvent: function( event ) { return event ? event : window.event; }, /** * Gets the position of the mouse on the screen for a given event. * @function * @name OpenSeadragon.getMousePosition * @param {Event} [event] * @returns {Point} */ getMousePosition: function( event ) { var result = new $.Point(); event = $.getEvent( event ); if ( typeof( event.pageX ) == "number" ) { result.x = event.pageX; result.y = event.pageY; } else if ( typeof( event.clientX ) == "number" ) { result.x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; result.y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; } else { throw new Error( "Unknown event mouse position, no known technique." ); } return result; }, /** * Determines the pages current scroll position. * @function * @name OpenSeadragon.getPageScroll * @returns {Point} */ getPageScroll: function() { var result = new $.Point(), docElement = document.documentElement || {}, body = document.body || {}; if ( typeof( window.pageXOffset ) == "number" ) { result.x = window.pageXOffset; result.y = window.pageYOffset; } else if ( body.scrollLeft || body.scrollTop ) { result.x = body.scrollLeft; result.y = body.scrollTop; } else if ( docElement.scrollLeft || docElement.scrollTop ) { result.x = docElement.scrollLeft; result.y = docElement.scrollTop; } return result; }, /** * Determines the size of the browsers window. * @function * @name OpenSeadragon.getWindowSize * @returns {Point} */ getWindowSize: function() { var result = new $.Point(), docElement = document.documentElement || {}, body = document.body || {}; if ( typeof( window.innerWidth ) == 'number' ) { result.x = window.innerWidth; result.y = window.innerHeight; } else if ( docElement.clientWidth || docElement.clientHeight ) { result.x = docElement.clientWidth; result.y = docElement.clientHeight; } else if ( body.clientWidth || body.clientHeight ) { result.x = body.clientWidth; result.y = body.clientHeight; } else { throw new Error("Unknown window size, no known technique."); } return result; }, /** * Wraps the given element in a nest of divs so that the element can * be easily centered. * @function * @name OpenSeadragon.makeCenteredNode * @param {Element|String} element * @returns {Element} */ makeCenteredNode: function( element ) { var div = $.makeNeutralElement( "div" ), html = [], innerDiv, innerDivs; element = $.getElement( element ); //TODO: I dont understand the use of # inside the style attributes // below. Invetigate the results of the constructed html in // the browser and clean up the mark-up to make this clearer. html.push(' '); div.innerHTML = html.join( '' ); div = div.firstChild; innerDiv = div; innerDivs = div.getElementsByTagName( "div" ); while ( innerDivs.length > 0 ) { innerDiv = innerDivs[ 0 ]; innerDivs = innerDiv.getElementsByTagName( "div" ); } innerDiv.appendChild( element ); return div; }, /** * Creates an easily positionable element of the given type that therefor * serves as an excellent container element. * @function * @name OpenSeadragon.makeNeutralElement * @param {String} tagName * @returns {Element} */ makeNeutralElement: function( tagName ) { var element = document.createElement( tagName ), style = element.style; style.background = "transparent none"; style.border = "none"; style.margin = "0px"; style.padding = "0px"; style.position = "static"; return element; }, /** * Ensures an image is loaded correctly to support alpha transparency. * Generally only IE has issues doing this correctly for formats like * png. * @function * @name OpenSeadragon.makeTransparentImage * @param {String} src * @returns {Element} */ makeTransparentImage: function( src ) { var img = $.makeNeutralElement( "img" ), element = null; if ( $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 7 ) { element = $.makeNeutralElement("span"); element.style.display = "inline-block"; img.onload = function() { element.style.width = element.style.width || img.width + "px"; element.style.height = element.style.height || img.height + "px"; img.onload = null; img = null; // to prevent memory leaks in IE }; img.src = src; element.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + src + "', sizingMethod='scale')"; } else { element = img; element.src = src; } return element; }, /** * Sets the opacity of the specified element. * @function * @name OpenSeadragon.setElementOpacity * @param {Element|String} element * @param {Number} opacity * @param {Boolean} [usesAlpha] */ setElementOpacity: function( element, opacity, usesAlpha ) { var previousFilter, ieOpacity, ieFilter; element = $.getElement( element ); if ( usesAlpha && !$.Browser.alpha ) { opacity = Math.round( opacity ); } if ( opacity < 1 ) { element.style.opacity = opacity; } else { element.style.opacity = ""; } if ( opacity == 1 ) { prevFilter = element.style.filter || ""; element.style.filter = prevFilter.replace(/alpha\(.*?\)/g, ""); return; } ieOpacity = Math.round( 100 * opacity ); ieFilter = " alpha(opacity=" + ieOpacity + ") "; //TODO: find out why this uses a try/catch instead of a predetermined // routine or at least an if/elseif/else try { if ( element.filters && element.filters.alpha ) { element.filters.alpha.opacity = ieOpacity; } else { element.style.filter += ieFilter; } } catch ( e ) { element.style.filter += ieFilter; } }, /** * Adds an event listener for the given element, eventName and handler. * @function * @name OpenSeadragon.addEvent * @param {Element|String} element * @param {String} eventName * @param {Function} handler * @param {Boolean} [useCapture] * @throws {Error} */ addEvent: function( element, eventName, handler, useCapture ) { element = $.getElement( element ); //TODO: Why do this if/else on every method call instead of just // defining this function once based on the same logic if ( element.addEventListener ) { element.addEventListener( eventName, handler, useCapture ); } else if ( element.attachEvent ) { element.attachEvent( "on" + eventName, handler ); if ( useCapture && element.setCapture ) { element.setCapture(); } } else { throw new Error( "Unable to attach event handler, no known technique." ); } }, /** * Remove a given event listener for the given element, event type and * handler. * @function * @name OpenSeadragon.removeEvent * @param {Element|String} element * @param {String} eventName * @param {Function} handler * @param {Boolean} [useCapture] * @throws {Error} */ removeEvent: function( element, eventName, handler, useCapture ) { element = $.getElement( element ); //TODO: Why do this if/else on every method call instead of just // defining this function once based on the same logic if ( element.removeEventListener ) { element.removeEventListener( eventName, handler, useCapture ); } else if ( element.detachEvent ) { element.detachEvent("on" + eventName, handler); if ( useCapture && element.releaseCapture ) { element.releaseCapture(); } } else { throw new Error( "Unable to detach event handler, no known technique." ); } }, /** * Cancels the default browser behavior had the event propagated all * the way up the DOM to the window object. * @function * @name OpenSeadragon.cancelEvent * @param {Event} [event] */ cancelEvent: function( event ) { event = $.getEvent( event ); if ( event.preventDefault ) { // W3C for preventing default event.preventDefault(); } // legacy for preventing default event.cancel = true; // IE for preventing default event.returnValue = false; }, /** * Stops the propagation of the event up the DOM. * @function * @name OpenSeadragon.stopEvent * @param {Event} [event] */ stopEvent: function( event ) { event = $.getEvent( event ); if ( event.stopPropagation ) { event.stopPropagation(); // W3C for stopping propagation } event.cancelBubble = true; // IE for stopping propagation }, /** * Similar to OpenSeadragon.delegate, but it does not immediately call * the method on the object, returning a function which can be called * repeatedly to delegate the method. It also allows additonal arguments * to be passed during construction which will be added during each * invocation, and each invocation can add additional arguments as well. * * @function * @name OpenSeadragon.createCallback * @param {Object} object * @param {Function} method * @param [args] any additional arguments are passed as arguments to the * created callback * @returns {Function} */ createCallback: function( object, method ) { //TODO: This pattern is painful to use and debug. It's much cleaner // to use pinning plus anonymous functions. Get rid of this // pattern! var initialArgs = [], i; for ( i = 2; i < arguments.length; i++ ) { initialArgs.push( arguments[ i ] ); } return function() { var args = initialArgs.concat( [] ), i; for ( i = 0; i < arguments.length; i++ ) { args.push( arguments[ i ] ); } return method.apply( object, args ); }; }, /** * Retreives the value of a url parameter from the window.location string. * @function * @name OpenSeadragon.getUrlParameter * @param {String} key * @returns {String} The value of the url parameter or null if no param matches. */ getUrlParameter: function( key ) { var value = URLPARAMS[ key ]; return value ? value : null; }, /** * Makes an AJAX request. * @function * @name OpenSeadragon.makeAjaxRequest * @param {String} url - the url to request * @param {Function} [callback] - a function to call when complete * @throws {Error} */ makeAjaxRequest: function( url, callback ) { var async = typeof( callback ) == "function", request = null, actual, i; if ( async ) { actual = callback; callback = function() { window.setTimeout( $.createCallback( null, actual, request ), 1 ); }; } if ( window.ActiveXObject ) { //TODO: very bad...Why check every time using try/catch when // we could determine once at startup which activeX object // was supported. This will have significant impact on // performance for IE Browsers for ( i = 0; i < ACTIVEX.length; i++ ) { try { request = new ActiveXObject( ACTIVEX[ i ] ); break; } catch (e) { continue; } } } else if ( window.XMLHttpRequest ) { request = new XMLHttpRequest(); } if ( !request ) { throw new Error( "Browser doesn't support XMLHttpRequest." ); } if ( async ) { /** @ignore */ request.onreadystatechange = function() { if ( request.readyState == 4) { request.onreadystatechange = new function() { }; callback(); } }; } try { request.open( "GET", url, async ); request.send( null ); } catch (e) { $.console.log( "%s while making AJAX request: %s", e.name, e.message ); request.onreadystatechange = null; request = null; if ( async ) { callback(); } } return async ? null : request; }, /** * Loads a Deep Zoom Image description from a url or XML string and * provides a callback hook for the resulting Document * @function * @name OpenSeadragon.createFromDZI * @param {String} xmlUrl * @param {String} xmlString * @param {Function} callback */ createFromDZI: function( dzi, callback ) { var async = typeof ( callback ) == "function", xmlUrl = dzi.substring(0,1) != '<' ? dzi : null, xmlString = xmlUrl ? null : dzi, error = null, urlParts, filename, lastDot, tilesUrl; if( xmlUrl ){ urlParts = xmlUrl.split( '/' ); filename = urlParts[ urlParts.length - 1 ]; lastDot = filename.lastIndexOf( '.' ); if ( lastDot > -1 ) { urlParts[ urlParts.length - 1 ] = filename.slice( 0, lastDot ); } tilesUrl = urlParts.join( '/' ) + "_files/"; } function finish( func, obj ) { try { return func( obj, tilesUrl ); } catch ( e ) { if ( async ) { return null; } else { throw e; } } } if ( async ) { if ( xmlString ) { window.setTimeout( function() { var source = finish( processDZIXml, parseXml( xmlString ) ); // call after finish sets error callback( source, error ); }, 1); } else { $.makeAjaxRequest( xmlUrl, function( xhr ) { var source = finish( processDZIResponse, xhr ); // call after finish sets error callback( source, error ); }); } return null; } if ( xmlString ) { return finish( processDZIXml, parseXml( xmlString ) ); } else { return finish( processDZIResponse, $.makeAjaxRequest( xmlUrl ) ); } } }); /** * The current browser vendor, version, and related information regarding * detected features. Features include