diff --git a/src/openseadragon.js b/src/openseadragon.js index 4f4e9752..d5688aed 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -882,47 +882,49 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ /** * Wraps the given element in a nest of divs so that the element can - * be easily centered. + * be easily centered using CSS tables * @function * @name OpenSeadragon.makeCenteredNode * @param {Element|String} element - * @returns {Element} + * @returns {Element} outermost wrapper element */ makeCenteredNode: function( element ) { - - var div = $.makeNeutralElement( "div" ), - html = [], - innerDiv, - innerDivs; - + // Convert a possible ID to an actual HTMLElement 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('
'); + /* + CSS tables require you to have a display:table/row/cell hierarchy so we need to create + three nested wrapper divs: + */ - div.innerHTML = html.join( '' ); - div = div.firstChild; + var wrappers = [ + $.makeNeutralElement( 'div' ), + $.makeNeutralElement( 'div' ), + $.makeNeutralElement( 'div' ) + ]; - innerDiv = div; - innerDivs = div.getElementsByTagName( "div" ); - while ( innerDivs.length > 0 ) { - innerDiv = innerDivs[ 0 ]; - innerDivs = innerDiv.getElementsByTagName( "div" ); - } + // It feels like we should be able to pass style dicts to makeNeutralElement: + $.extend(wrappers[0].style, { + display: "table", + height: "100%", + width: "100%" + }); - innerDiv.appendChild( element ); + $.extend(wrappers[1].style, { + display: "table-row" + }); - return div; + $.extend(wrappers[2].style, { + display: "table-cell", + verticalAlign: "middle", + textAlign: "center" + }); + + wrappers[0].appendChild(wrappers[1]); + wrappers[1].appendChild(wrappers[2]); + wrappers[2].appendChild(element); + + return wrappers[0]; }, @@ -1313,7 +1315,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ makeAjaxRequest: function( url, onSuccess, onError ) { var request = $.createAjaxRequest(); - if (!$.isFunction(onSuccess)) { + if ( !$.isFunction( onSuccess ) ) { throw new Error( "makeAjaxRequest requires a success callback" ); } @@ -1325,9 +1327,9 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ if ( request.status == 200 ) { onSuccess( request ); } else { - $.console.log("AJAX request returned %s: %s", request.status, url); + $.console.log( "AJAX request returned %s: %s", request.status, url ); - if ($.isFunction(onError)) { + if ( $.isFunction( onError ) ) { onError( request ); } } @@ -1338,11 +1340,11 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ request.open( "GET", url, true ); request.send( null ); } catch (e) { - $.console.log("%s while making AJAX request: %s", e.name, e.message); + $.console.log( "%s while making AJAX request: %s", e.name, e.message ); request.onreadystatechange = function(){}; - if ($.isFunction(onError)) { + if ( $.isFunction( onError ) ) { onError( request, e ); } } diff --git a/src/strings.js b/src/strings.js index 2b1f0c79..9616850a 100644 --- a/src/strings.js +++ b/src/strings.js @@ -45,7 +45,8 @@ var I18N = { ImageFormat: "Sorry, we don't support {0}-based Deep Zoom Images.", Security: "It looks like a security restriction stopped us from " + "loading this Deep Zoom Image.", - Status: "This space unintentionally left blank ({0} {1})." + Status: "This space unintentionally left blank ({0} {1}).", + "Open-Failed": "Unable to open {0}: {1}" }, Tooltips: { @@ -80,7 +81,8 @@ $.extend( $, { string = container[ props[ i ] ]; if ( typeof( string ) != "string" ) { - string = ""; + $.console.debug( "Untranslated source string:", prop ); + string = ""; // FIXME: this breaks gettext()-style convention, which would return source } return string.replace(/\{\d+\}/g, function(capture) { diff --git a/src/tilesource.js b/src/tilesource.js index 3168606e..fb22b21c 100644 --- a/src/tilesource.js +++ b/src/tilesource.js @@ -293,6 +293,11 @@ $.TileSource.prototype = { callback = function( data ){ var $TileSource = $.TileSource.determineType( _this, data, url ); + if ( !$TileSource ) { + _this.raiseEvent( 'open-failed', { message: "Unable to load TileSource", source: url } ); + return; + } + options = $TileSource.prototype.configure.apply( _this, [ data, url ]); readySource = new $TileSource( options ); _this.ready = true; @@ -315,6 +320,11 @@ $.TileSource.prototype = { $.makeAjaxRequest( url, function( xhr ) { var data = processResponse( xhr ); callback( data ); + }, function ( xhr ) { + _this.raiseEvent( 'open-failed', { + message: "HTTP " + xhr.status + " attempting to load TileSource", + source: url + }); }); } @@ -458,6 +468,8 @@ $.TileSource.determineType = function( tileSource, data, url ){ return OpenSeadragon[ property ]; } } + + $.console.error( "No TileSource was able to open %s %s", url, data ); }; diff --git a/src/viewer.js b/src/viewer.js index cf5c9aa1..8ebbd2b3 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -171,6 +171,12 @@ $.Viewer = function( options ) { //Inherit some behaviors and properties $.EventHandler.call( this ); + + this.addHandler( 'open-failed', function (source, args) { + var msg = $.getString( "Errors.Open-Failed", args.source, args.message); + _this._showMessage( msg ); + }); + $.ControlDock.call( this, options ); //Deal with tile sources @@ -417,6 +423,8 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, $TileSource, options; + _this._hideMessage(); + //allow plain xml strings or json strings to be parsed here if( $.type( tileSource ) == 'string' ){ if( tileSource.match(/\s*<.*/) ){ @@ -433,6 +441,9 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, tileSource = new $.TileSource( tileSource, function( readySource ){ openTileSource( _this, readySource ); }); + tileSource.addHandler( 'open-failed', function ( name, args ) { + _this.raiseEvent( 'open-failed', args ); + }); } else if ( $.isPlainObject( tileSource ) || tileSource.nodeType ){ if( $.isFunction( tileSource.getTileUrl ) ){ @@ -443,6 +454,13 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, } else { //inline configuration $TileSource = $.TileSource.determineType( _this, tileSource ); + if ( !$TileSource ) { + _this.raiseEvent( 'open-failed', { + message: "Unable to load TileSource", + source: tileSource + }); + return; + } options = $TileSource.prototype.configure.apply( _this, [ tileSource ]); readySource = new $TileSource( options ); openTileSource( _this, readySource ); @@ -1054,8 +1072,39 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, this.referenceStrip.setFocus( page ); } return this; - } + }, + /** + * Display a message in the viewport + * @function + * @private + * @param {String} text message + */ + _showMessage: function ( message ) { + this._hideMessage(); + + var div = $.makeNeutralElement( "div" ); + div.appendChild( document.createTextNode( message ) ); + + this.messageDiv = $.makeCenteredNode( div ); + + $.addClass(this.messageDiv, "openseadragon-message"); + + this.container.appendChild( this.messageDiv ); + }, + + /** + * Hide any currently displayed viewport message + * @function + * @private + */ + _hideMessage: function () { + var div = this.messageDiv; + if (div) { + div.parentNode.removeChild(div); + delete this.messageDiv; + } + } }); /** diff --git a/test/basic.js b/test/basic.js index bd8b731f..61f36673 100644 --- a/test/basic.js +++ b/test/basic.js @@ -1,144 +1,211 @@ -/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util */ +/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */ (function() { + var viewer; - module('Basic'); + module('Basic', { + setup: function () { + var example = $('').appendTo("#qunit-fixture"); - // TODO: Test drag + testLog.reset(); - var viewer = null; - - // ---------- - asyncTest('Open', function() { - $(document).ready(function() { viewer = OpenSeadragon({ id: 'example', prefixUrl: '/build/openseadragon/images/', - tileSources: '/test/data/testpattern.dzi', springStiffness: 100 // Faster animation = faster tests }); + }, + teardown: function () { + if (viewer && viewer.close) { + viewer.close(); + } - ok(viewer, 'Viewer exists'); + viewer = null; + } + }); - var openHandler = function(eventSender, eventData) { - viewer.removeHandler('open', openHandler); - ok(true, 'Open event was sent'); - equal(eventSender, viewer, 'Sender of open event was viewer'); - ok(eventData, 'Handler also received event data'); - ok(viewer.viewport, 'Viewport exists'); - ok(viewer.source, 'source exists'); - ok(viewer._updateRequestId, 'timer is on'); - start(); - }; + // ---------- + asyncTest('Open', function() { + ok(viewer, 'Viewer exists'); - viewer.addHandler('open', openHandler); + var openHandler = function(eventSender, eventData) { + viewer.removeHandler('open', openHandler); + ok(true, 'Open event was sent'); + equal(eventSender, viewer, 'Sender of open event was viewer'); + ok(eventData, 'Handler also received event data'); + ok(viewer.viewport, 'Viewport exists'); + ok(viewer.source, 'source exists'); + ok(viewer._updateRequestId, 'timer is on'); + start(); + }; + + viewer.addHandler('open', openHandler); + viewer.open('/test/data/testpattern.dzi'); + }); + + asyncTest('Open Error Handling', function() { + ok(viewer, 'Viewer exists'); + + viewer.addHandler('open', function(eventSender, eventData) { + ok(false, "The open event should not fire for failed opens"); + start(); }); + + viewer.addHandler('open-failed', function(eventSender, eventData) { + ok(true, "The open-failed event should be fired when the source 404s"); + + equal($(".openseadragon-message").length, 1, "Open failures should display a message"); + + ok(testLog.log.contains('["AJAX request returned %s: %s",404,"/test/data/not-a-real-file"]'), + "AJAX failures should be logged to the console"); + + start(); + }); + + viewer.open('/test/data/not-a-real-file'); }); // ---------- asyncTest('Zoom', function() { - var viewport = viewer.viewport; - equal(viewport.getZoom(), 1, 'We start out unzoomed'); + viewer.addHandler("open", function () { + var viewport = viewer.viewport; - var zoomHandler = function() { - viewer.removeHandler('animationfinish', zoomHandler); - equal(viewport.getZoom(), 2, 'Zoomed correctly'); - start(); - }; + equal(viewport.getZoom(), 1, 'We start out unzoomed'); - viewer.addHandler('animationfinish', zoomHandler); - viewport.zoomTo(2); + var zoomHandler = function() { + viewer.removeHandler('animationfinish', zoomHandler); + equal(viewport.getZoom(), 2, 'Zoomed correctly'); + start(); + }; + + viewer.addHandler('animationfinish', zoomHandler); + viewport.zoomTo(2); + }); + viewer.open('/test/data/testpattern.dzi'); }); // ---------- asyncTest('Pan', function() { - var viewport = viewer.viewport; - var center = viewport.getCenter(); - ok(center.x === 0.5 && center.y === 0.5, 'We start out unpanned'); + viewer.addHandler("open", function () { + var viewport = viewer.viewport, + center = viewport.getCenter(); - var panHandler = function() { - viewer.removeHandler('animationfinish', panHandler); - center = viewport.getCenter(); - ok(center.x === 0.1 && center.y === 0.1, 'Panned correctly'); - start(); - }; + ok(center.x === 0.5 && center.y === 0.5, 'We start out unpanned'); - viewer.addHandler('animationfinish', panHandler); - viewport.panTo(new OpenSeadragon.Point(0.1, 0.1)); + var panHandler = function() { + viewer.removeHandler('animationfinish', panHandler); + center = viewport.getCenter(); + ok(center.x === 0.1 && center.y === 0.1, 'Panned correctly'); + start(); + }; + + viewer.addHandler('animationfinish', panHandler); + viewport.panTo(new OpenSeadragon.Point(0.1, 0.1)); + }); + + viewer.open('/test/data/testpattern.dzi'); }); // ---------- asyncTest('Home', function() { - var viewport = viewer.viewport; - var center = viewport.getCenter(); - ok(center.x !== 0.5 && center.y !== 0.5, 'We start out panned'); - notEqual(viewport.getZoom(), 1, 'We start out zoomed'); + // Test setup: + function opener() { + var viewport = viewer.viewport; + viewport.panTo(new OpenSeadragon.Point(0.1, 0.1)); + viewport.zoomTo(2); + } - var homeHandler = function() { - viewer.removeHandler('animationfinish', homeHandler); - center = viewport.getCenter(); - ok(center.x === 0.5 && center.y === 0.5, 'We end up unpanned'); - equal(viewport.getZoom(), 1, 'We end up unzoomed'); - start(); - }; + function stage1() { + var viewport = viewer.viewport, + center = viewport.getCenter(); - viewer.addHandler('animationfinish', homeHandler); - viewport.goHome(true); + viewer.removeHandler('animationfinish', stage1); + + ok(center.x !== 0.5 && center.y !== 0.5, 'We start out panned'); + notEqual(viewport.getZoom(), 1, 'We start out zoomed'); + + var homeHandler = function() { + viewer.removeHandler('animationfinish', homeHandler); + center = viewport.getCenter(); + ok(center.x === 0.5 && center.y === 0.5, 'We end up unpanned'); + equal(viewport.getZoom(), 1, 'We end up unzoomed'); + start(); + }; + + viewer.addHandler('animationfinish', homeHandler); + viewport.goHome(true); + } + + viewer.addHandler("open", opener); + viewer.addHandler("animationfinish", stage1); + + viewer.open('/test/data/testpattern.dzi'); }); // ---------- asyncTest('Click', function() { - var viewport = viewer.viewport, + viewer.addHandler("open", function () { + var viewport = viewer.viewport, center = viewport.getCenter(); - ok(center.x === 0.5 && center.y === 0.5, 'We start out unpanned'); - equal(viewport.getZoom(), 1, 'We start out unzoomed'); + ok(center.x === 0.5 && center.y === 0.5, 'We start out unpanned'); + equal(viewport.getZoom(), 1, 'We start out unzoomed'); - var clickHandler = function() { - viewer.removeHandler('animationfinish', clickHandler); - center = viewport.getCenter(); - ok(center.x > 0.37 && center.x < 0.38 && center.y > 0.37 && center.y < 0.38, 'Panned correctly'); - equal(viewport.getZoom(), 2, 'Zoomed correctly'); - start(); - }; + var clickHandler = function() { + viewer.removeHandler('animationfinish', clickHandler); + center = viewport.getCenter(); + ok(center.x > 0.37 && center.x < 0.38 && center.y > 0.37 && center.y < 0.38, 'Panned correctly'); + equal(viewport.getZoom(), 2, 'Zoomed correctly'); + start(); + }; - viewer.addHandler('animationfinish', clickHandler); - Util.simulateViewerClick(viewer, 0.25, 0.25); + viewer.addHandler('animationfinish', clickHandler); + Util.simulateViewerClick(viewer, 0.25, 0.25); + }); + + viewer.open('/test/data/testpattern.dzi'); }); // ---------- - test('Fullscreen', function() { - ok(!viewer.isFullPage(), 'Started out not fullpage'); - ok(!$(viewer.element).hasClass('fullpage'), - 'No fullpage class on div'); + asyncTest('Fullscreen', function() { + viewer.addHandler("open", function () { + ok(!viewer.isFullPage(), 'Started out not fullpage'); + ok(!$(viewer.element).hasClass('fullpage'), + 'No fullpage class on div'); - viewer.setFullPage(true); - ok(viewer.isFullPage(), 'Enabled fullpage'); - ok($(viewer.element).hasClass('fullpage'), - 'Fullpage class added to div'); + viewer.setFullPage(true); + ok(viewer.isFullPage(), 'Enabled fullpage'); + ok($(viewer.element).hasClass('fullpage'), + 'Fullpage class added to div'); - viewer.setFullPage(false); - ok(!viewer.isFullPage(), 'Disabled fullpage'); - ok(!$(viewer.element).hasClass('fullpage'), - 'Fullpage class removed from div'); + viewer.setFullPage(false); + ok(!viewer.isFullPage(), 'Disabled fullpage'); + ok(!$(viewer.element).hasClass('fullpage'), + 'Fullpage class removed from div'); + start(); + }); + + viewer.open('/test/data/testpattern.dzi'); }); // ---------- asyncTest('Close', function() { - var closeHandler = function() { - viewer.removeHandler('close', closeHandler); - ok(!viewer.source, 'no source'); - $('#example').empty(); - ok(true, 'Close event was sent'); - ok(!viewer._updateRequestId, 'timer is off'); - setTimeout(function() { - ok(!viewer._updateRequestId, 'timer is still off'); - start(); - }, 100); - }; + viewer.addHandler("open", function () { + var closeHandler = function() { + viewer.removeHandler('close', closeHandler); + ok(!viewer.source, 'no source'); + ok(true, 'Close event was sent'); + ok(!viewer._updateRequestId, 'timer is off'); + setTimeout(function() { + ok(!viewer._updateRequestId, 'timer is still off'); + start(); + }, 100); + }; - viewer.addHandler('close', closeHandler); - viewer.close(); + viewer.addHandler('close', closeHandler); + viewer.close(); + }); + viewer.open('/test/data/testpattern.dzi'); }); })(); diff --git a/test/demo/basic.html b/test/demo/basic.html index b9a02aef..19572b86 100644 --- a/test/demo/basic.html +++ b/test/demo/basic.html @@ -1,3 +1,4 @@ +