diff --git a/src/tile.js b/src/tile.js index 81bc8999..33e0cee4 100644 --- a/src/tile.js +++ b/src/tile.js @@ -44,7 +44,7 @@ * coordinates. * @param {Boolean} exists Is this tile a part of a sparse image? ( Also has * this tile failed to load? ) - * @param {String} url The URL of this tile's image. + * @param {String|() => String} url The URL of this tile's image or a function that returns a url. * @param {CanvasRenderingContext2D} context2D The context2D of this tile if it * is provided directly by the tile source. * @param {Boolean} loadWithAjax Whether this tile image should be loaded with an AJAX request . @@ -95,11 +95,13 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja */ this.exists = exists; /** - * The URL of this tile's image. - * @member {String} url + * Private property to hold string url or url retriever function. + * Consumers should access via Tile.getUrl() + * @private + * @member {String|() => String} url * @memberof OpenSeadragon.Tile# */ - this.url = url; + this._url = url; /** * Post parameters for this tile. For example, it can be an URL-encoded string * in k1=v1&k2=v2... format, or a JSON, or a FormData instance... or null if no POST request used @@ -269,7 +271,7 @@ $.Tile.prototype = { _hasTransparencyChannel: function() { console.warn("Tile.prototype._hasTransparencyChannel() has been " + "deprecated and will be removed in the future. Use TileSource.prototype.hasTransparency() instead."); - return !!this.context2D || this.url.match('.png'); + return !!this.context2D || this.getUrl().match('.png'); }, /** @@ -342,6 +344,18 @@ $.Tile.prototype = { return this.getImage(); }, + /** + * The URL of this tile's image. + * @member {String} url + * @memberof OpenSeadragon.Tile# + * @deprecated + * @returns {String} + */ + get url() { + $.console.error("[Tile.url] property has been deprecated. Use [Tile.prototype.getUrl] instead."); + return this.getUrl(); + }, + /** * Get the Image object for this tile. * @returns {Image} @@ -350,6 +364,18 @@ $.Tile.prototype = { return this.cacheImageRecord.getImage(); }, + /** + * Get the url string for this tile. + * @returns {String} + */ + getUrl: function() { + if (typeof this._url === 'function') { + return this._url(); + } + + return this._url; + }, + /** * Get the CanvasRenderingContext2D instance for tile image data drawn * onto Canvas if enabled and available diff --git a/src/tiledimage.js b/src/tiledimage.js index 2715730a..cbcdfc92 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -1480,7 +1480,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag bounds, sourceBounds, exists, - url, + urlOrGetter, post, ajaxHeaders, context2D, @@ -1501,7 +1501,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag bounds = this.getTileBounds( level, x, y ); sourceBounds = tileSource.getTileBounds( level, xMod, yMod, true ); exists = tileSource.tileExists( level, xMod, yMod ); - url = tileSource.getTileUrl( level, xMod, yMod ); + urlOrGetter = tileSource.getTileUrl( level, xMod, yMod ); post = tileSource.getTilePostData( level, xMod, yMod ); // Headers are only applicable if loadTilesWithAjax is set @@ -1524,13 +1524,13 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag y, bounds, exists, - url, + urlOrGetter, context2D, this.loadTilesWithAjax, ajaxHeaders, sourceBounds, post, - tileSource.getTileHashKey(level, xMod, yMod, url, ajaxHeaders, post) + tileSource.getTileHashKey(level, xMod, yMod, urlOrGetter, ajaxHeaders, post) ); if (this.getFlip()) { @@ -1569,7 +1569,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag var _this = this; tile.loading = true; this._imageLoader.addJob({ - src: tile.url, + src: tile.getUrl(), tile: tile, source: this.source, postData: tile.postData, @@ -1598,7 +1598,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag */ _onTileLoad: function( tile, time, data, errorMsg, tileRequest ) { if ( !data ) { - $.console.error( "Tile %s failed to load: %s - error: %s", tile, tile.url, errorMsg ); + $.console.error( "Tile %s failed to load: %s - error: %s", tile, tile.getUrl(), errorMsg ); /** * Triggered when a tile fails to load. * @@ -1624,7 +1624,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag } if ( time < this.lastResetTime ) { - $.console.warn( "Ignoring tile %s loaded before reset: %s", tile, tile.url ); + $.console.warn( "Ignoring tile %s loaded before reset: %s", tile, tile.getUrl() ); tile.loading = false; return; } @@ -1669,7 +1669,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag tile.loading = false; tile.loaded = true; tile.hasTransparency = _this.source.hasTransparency( - tile.context2D, tile.url, tile.ajaxHeaders, tile.postData + tile.context2D, tile.getUrl(), tile.ajaxHeaders, tile.postData ); if (!tile.context2D) { _this._tileCache.cacheTile({ @@ -1852,7 +1852,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag useSketch = this.opacity < 1 || (this.compositeOperation && this.compositeOperation !== 'source-over') || (!this._isBottomItem() && - this.source.hasTransparency(tile.context2D, tile.url, tile.ajaxHeaders, tile.postData)); + this.source.hasTransparency(tile.context2D, tile.getUrl(), tile.ajaxHeaders, tile.postData)); } var sketchScale; diff --git a/src/tilesource.js b/src/tilesource.js index 604308ff..7875d23a 100644 --- a/src/tilesource.js +++ b/src/tilesource.js @@ -618,6 +618,7 @@ $.TileSource.prototype = { * @param {Number} level * @param {Number} x * @param {Number} y + * @returns {String|() => string} url - A string for the url or a function that returns a url string. * @throws {Error} */ getTileUrl: function( level, x, y ) { diff --git a/test/modules/tilesource-dynamic-url.js b/test/modules/tilesource-dynamic-url.js new file mode 100644 index 00000000..d6216928 --- /dev/null +++ b/test/modules/tilesource-dynamic-url.js @@ -0,0 +1,184 @@ +/* global QUnit, testLog, Util */ + +//Testing of TileSource with getTileUrl that returns a function + +(function() { + var ASSERT = null; + var DYNAMIC_URL = ""; + var viewer = null; + var OriginalAjax = OpenSeadragon.makeAjaxRequest; + var OriginalTile = OpenSeadragon.Tile; + + /** + * Set up shared variables for test + */ + var configure = function(assert, url) { + ASSERT = assert; + DYNAMIC_URL = url; + }; + + QUnit.module('TileSourceDynamicUrl', { + beforeEach: function () { + testLog.reset(); + $("#qunit-fixture").html("
"); + + // Add test tile source to OSD + OpenSeadragon.DynamicUrlTestTileSource = function( options ) { + OpenSeadragon.TileSource.apply( this, [ options ] ); + }; + + OpenSeadragon.extend( OpenSeadragon.DynamicUrlTestTileSource.prototype, OpenSeadragon.TileSource.prototype, { + supports: function( data, url ){ + return url.indexOf('dynamic') !== -1; + }, + + configure: function( _data, url, postData ){ + //some default data to trigger painting + return { + postData: postData, + tilesUrl: url, + fileFormat: "jpg", + sizeData: {Width: 55, Height: 55}, + width: 55, + height: 55, + tileSize: 55, + tileOverlap: 55, + minLevel: 1, + maxLevel: 1, + displayRects: [] + }; + }, + + // getTileUrl return a function that must be called by Tile.getUrl + getTileUrl: function( _level, _x, _y ) { + // Assert that custom tile source is called correctly + ASSERT.ok(true, 'DynamicUrlTileSource.getTileUrl called'); + return () => DYNAMIC_URL; + }, + + tileExists: function ( _level, _x, _y ) { + return true; + } + }); + + var hasCompletedImageInfoRequest = false; + OpenSeadragon.makeAjaxRequest = function( url, onSuccess, onError ) { + // Note that our preferred API is that you pass in a single object; the named + // arguments are for legacy support. + if( $.isPlainObject( url ) ){ + onSuccess = url.success; + onError = url.error; + withCredentials = url.withCredentials; + headers = url.headers; + responseType = url.responseType || null; + postData = url.postData || null; + options = url; //save original stuff + url = url.url; + } + + //first AJAX firing is the image info getter, second is the first tile request: can exit + if (hasCompletedImageInfoRequest) { + // Assert dynamic url from tileSource is called + ASSERT.equal(url, DYNAMIC_URL, 'Called dynamic url correctly'); + viewer.close(); + return null; + } + + hasCompletedImageInfoRequest = true; + + var request = Promise.resolve(url); + //some required properties to pass through processResponse(...) + request.responseText = "some text"; + request.status = 200; + + onSuccess(request); + return request; + }; + + // Override Tile to ensure getUrl is called successfully. + var Tile = function(...params) { + OriginalTile.apply(this, params); + }; + + OpenSeadragon.extend( Tile.prototype, OpenSeadragon.Tile.prototype, { + getUrl: function() { + ASSERT.ok(true, 'Tile.getUrl called'); + return OriginalTile.prototype.getUrl.apply(this); + } + }); + OpenSeadragon.Tile = Tile; + }, + + afterEach: function () { + ASSERT = null; + + if (viewer && viewer.close) { + viewer.close(); + } + viewer = null; + + OpenSeadragon.makeAjaxRequest = OriginalAjax; + OpenSeadragon.Tile = OriginalTile; + } + }); + + + /** + * Create viewer for test + */ + var testUrlCall = function(tileSourceUrl) { + var timeWatcher = Util.timeWatcher(ASSERT, 7000); + + viewer = OpenSeadragon({ + id: 'example', + prefixUrl: '/build/openseadragon/images/', + tileSources: tileSourceUrl, + loadTilesWithAjax: true, + }); + + var failHandler = function (event) { + testPostData(event.postData, "event: 'open-failed'"); + viewer.removeHandler('open-failed', failHandler); + viewer.close(); + }; + viewer.addHandler('open-failed', failHandler); + + var readyHandler = function (event) { + //relies on Tilesource contructor extending itself with options object + testPostData(event.postData, "event: 'ready'"); + viewer.removeHandler('ready', readyHandler); + }; + viewer.addHandler('ready', readyHandler); + + + var openHandler = function(event) { + viewer.removeHandler('open', openHandler); + ASSERT.ok(true, 'Open event was sent'); + viewer.addHandler('close', closeHandler); + viewer.world.draw(); + }; + + var closeHandler = function(event) { + viewer.removeHandler('close', closeHandler); + $('#example').empty(); + ASSERT.ok(true, 'Close event was sent'); + timeWatcher.done(); + }; + viewer.addHandler('open', openHandler); + }; + + // ---------- + QUnit.test('TileSource.getTileUrl supports returning a function', function(assert) { + /** + * Expect 5 assertions to be called: + * 1. Open event was sent + * 2. DynamicUrlTileSource.getTileUrl called + * 3. Tile.getUrl called + * 4. Called dynamic url correctly + * 5. Close event was sent + */ + assert.expect(5); + configure(assert, 'dynamicUrl'); + testUrlCall('dynamicUrl'); + }); +})(); diff --git a/test/test.html b/test/test.html index b113626d..8b85a123 100644 --- a/test/test.html +++ b/test/test.html @@ -47,6 +47,7 @@ +