diff --git a/src/buttongroup.js b/src/buttongroup.js index 3898da56..088964d5 100644 --- a/src/buttongroup.js +++ b/src/buttongroup.js @@ -115,7 +115,7 @@ $.ButtonGroup.prototype = { /** * Adds the given button to this button group. * - * @functions + * @function * @param {OpenSeadragon.Button} button */ addButton: function( button ){ diff --git a/src/tiledimage.js b/src/tiledimage.js index 7ed39f31..34c7f12b 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -147,6 +147,9 @@ $.TiledImage = function( options ) { var degrees = options.degrees || 0; delete options.degrees; + var ajaxHeaders = options.ajaxHeaders; + delete options.ajaxHeaders; + $.extend( true, this, { //internal state properties @@ -238,6 +241,9 @@ $.TiledImage = function( options ) { tiledImage: _this }, args)); }; + + this._ownAjaxHeaders = {}; + this.setAjaxHeaders(ajaxHeaders, false); }; $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.TiledImage.prototype */{ @@ -1003,6 +1009,90 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag }); }, + /** + * Update headers to include when making AJAX requests. + * + * Unless `propagate` is set to false (which is likely only useful in rare circumstances), + * the updated headers are propagated to all tiles and queued image loader jobs. + * + * Note that the rules for merging headers still apply, i.e. headers returned by + * {@link OpenSeadragon.TileSource#getTileAjaxHeaders} take precedence over + * the headers here in the tiled image (`TiledImage.ajaxHeaders`). + * + * @function + * @param {Object} ajaxHeaders Updated AJAX headers, which will be merged over any headers specified in {@link OpenSeadragon.Options}. + * @param {Boolean} [propagate=true] Whether to propagate updated headers to existing tiles and queued image loader jobs. + */ + setAjaxHeaders: function(ajaxHeaders, propagate) { + if (ajaxHeaders === null) { + ajaxHeaders = {}; + } + if (!$.isPlainObject(ajaxHeaders)) { + console.error('[TiledImage.setAjaxHeaders] Ignoring invalid headers, must be a plain object'); + return; + } + + this._ownAjaxHeaders = ajaxHeaders; + this._updateAjaxHeaders(propagate); + }, + + /** + * Update headers to include when making AJAX requests. + * + * This function has the same effect as calling {@link OpenSeadragon.TiledImage#setAjaxHeaders}, + * except that the headers for this tiled image do not change. This is especially useful + * for propagating updated headers from {@link OpenSeadragon.TileSource#getTileAjaxHeaders} + * to existing tiles. + * + * @private + * @function + * @param {Boolean} [propagate=true] Whether to propagate updated headers to existing tiles and queued image loader jobs. + */ + _updateAjaxHeaders: function(propagate) { + if (propagate === undefined) { + propagate = true; + } + + // merge with viewer's headers + if ($.isPlainObject(this.viewer.ajaxHeaders)) { + this.ajaxHeaders = $.extend({}, this.viewer.ajaxHeaders, this._ownAjaxHeaders); + } else { + this.ajaxHeaders = this._ownAjaxHeaders; + } + + // propagate header updates to all tiles and queued image loader jobs + if (propagate) { + var numTiles, xMod, yMod, tile; + + for (var level in this.tilesMatrix) { + numTiles = this.source.getNumTiles(level); + + for (var x in this.tilesMatrix[level]) { + xMod = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x; + + for (var y in this.tilesMatrix[level][x]) { + yMod = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y; + tile = this.tilesMatrix[level][x][y]; + + tile.loadWithAjax = this.loadTilesWithAjax; + if (tile.loadWithAjax) { + var tileAjaxHeaders = this.source.getTileAjaxHeaders( level, xMod, yMod ); + tile.ajaxHeaders = $.extend({}, this.ajaxHeaders, tileAjaxHeaders); + } else { + tile.ajaxHeaders = null; + } + } + } + } + + for (var i = 0; i < this._imageLoader.jobQueue.length; i++) { + var job = this._imageLoader.jobQueue[i]; + job.loadWithAjax = job.tile.loadWithAjax; + job.ajaxHeaders = job.tile.loadWithAjax ? job.tile.ajaxHeaders : null; + } + } + }, + // private _setScale: function(scale, immediately) { var sameTarget = (this._scaleSpring.target.value === scale); diff --git a/src/tilesource.js b/src/tilesource.js index b7782dbe..f0e4c0cd 100644 --- a/src/tilesource.js +++ b/src/tilesource.js @@ -663,6 +663,11 @@ $.TileSource.prototype = { * The headers returned here will override headers specified at the Viewer or TiledImage level. * Specifying a falsy value for a header will clear its existing value set at the Viewer or * TiledImage level (if any). + * + * Note that the headers of existing tiles don't automatically change when this function + * returns updated headers. To do that, you need to call {@link OpenSeadragon.Viewer#setAjaxHeaders} + * and propagate the changes. + * * @function * @param {Number} level * @param {Number} x diff --git a/src/viewer.js b/src/viewer.js index 78c1c35d..8d3fada5 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -966,7 +966,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * Turns debugging mode on or off for this viewer. * * @function - * @param {Boolean} true to turn debug on, false to turn debug off. + * @param {Boolean} debugMode true to turn debug on, false to turn debug off. */ setDebugMode: function(debugMode){ @@ -978,10 +978,57 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, this.forceRedraw(); }, + /** + * Update headers to include when making AJAX requests. + * + * Unless `propagate` is set to false (which is likely only useful in rare circumstances), + * the updated headers are propagated to all tiled images, each of which will subsequently + * propagate the changed headers to all their tiles. + * If applicable, the headers of the viewer's navigator and reference strip will also be updated. + * + * Note that the rules for merging headers still apply, i.e. headers returned by + * {@link OpenSeadragon.TileSource#getTileAjaxHeaders} take precedence over + * `TiledImage.ajaxHeaders`, which take precedence over the headers here in the viewer. + * + * @function + * @param {Object} ajaxHeaders Updated AJAX headers. + * @param {Boolean} [propagate=true] Whether to propagate updated headers to tiled images, etc. + */ + setAjaxHeaders: function(ajaxHeaders, propagate) { + if (ajaxHeaders === null) { + ajaxHeaders = {}; + } + if (!$.isPlainObject(ajaxHeaders)) { + console.error('[Viewer.setAjaxHeaders] Ignoring invalid headers, must be a plain object'); + return; + } + if (propagate === undefined) { + propagate = true; + } + + this.ajaxHeaders = ajaxHeaders; + + if (propagate) { + for (var i = 0; i < this.world.getItemCount(); i++) { + this.world.getItemAt(i)._updateAjaxHeaders(true); + } + + if (this.navigator) { + this.navigator.setAjaxHeaders(this.ajaxHeaders, true); + } + + if (this.referenceStrip && this.referenceStrip.miniViewers) { + for (var key in this.referenceStrip.miniViewers) { + this.referenceStrip.miniViewers[key].setAjaxHeaders(this.ajaxHeaders, true); + } + } + } + }, + /** * Adds the given button to this viewer. * - * @functions + * @function * @param {OpenSeadragon.Button} button */ addButton: function( button ){ @@ -1402,7 +1449,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * A set of headers to include when making tile AJAX requests. * Note that these headers will be merged over any headers specified in {@link OpenSeadragon.Options}. * Specifying a falsy value for a header will clear its existing value set at the Viewer level (if any). - * requests. * @param {Function} [options.success] A function that gets called when the image is * successfully added. It's passed the event object which contains a single property: * "item", which is the resulting instance of TiledImage. @@ -1450,10 +1496,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, if (options.loadTilesWithAjax === undefined) { options.loadTilesWithAjax = this.loadTilesWithAjax; } - if (options.ajaxHeaders === undefined || options.ajaxHeaders === null) { - options.ajaxHeaders = this.ajaxHeaders; - } else if ($.isPlainObject(options.ajaxHeaders) && $.isPlainObject(this.ajaxHeaders)) { - options.ajaxHeaders = $.extend({}, this.ajaxHeaders, options.ajaxHeaders); + if (!$.isPlainObject(options.ajaxHeaders)) { + options.ajaxHeaders = {}; } var myQueueItem = { diff --git a/test/modules/ajax-tiles.js b/test/modules/ajax-tiles.js index a77dfed8..39a37876 100644 --- a/test/modules/ajax-tiles.js +++ b/test/modules/ajax-tiles.js @@ -245,4 +245,130 @@ tileSource: staticHeaderTileSource }); }); + + QUnit.test('Viewer headers can be updated', function(assert) { + var done = assert.async(); + + var newHeaders = { + 'X-Viewer-Header': 'ViewerHeaderValue-Updated', + 'X-Viewer-Header2': 'ViewerHeaderValue2' + } + var newHeaders2 = { + Range: 'test', + } + + var tileLoaded = function tileLoaded(evt) { + viewer.removeHandler('tile-loaded', tileLoaded); + // set new Viewer headers and propagate to TiledImage and Tile + viewer.setAjaxHeaders(newHeaders); + viewer.addHandler('tile-loaded', tileLoaded2); + }; + + var tileLoaded2 = function tileLoaded(evt) { + viewer.removeHandler('tile-loaded', tileLoaded2); + assert.deepEqual(viewer.ajaxHeaders, newHeaders); + assert.deepEqual(evt.tiledImage.ajaxHeaders, newHeaders); + assert.deepEqual( + evt.tile.ajaxHeaders, + OpenSeadragon.extend( + {}, viewer.ajaxHeaders, evt.tiledImage.ajaxHeaders, + { Range: getTileRangeHeader(evt.tile.level, evt.tile.x, evt.tile.y) } + ) + ); + // set new Viewer headers and propagate to TiledImage and Tile + viewer.setAjaxHeaders(newHeaders2, true); + viewer.addHandler('tile-loaded', tileLoaded3); + }; + + var tileLoaded3 = function tileLoaded(evt) { + viewer.removeHandler('tile-loaded', tileLoaded3); + assert.deepEqual(viewer.ajaxHeaders, newHeaders2); + assert.deepEqual(evt.tiledImage.ajaxHeaders, newHeaders2); + assert.equal(evt.tile.ajaxHeaders['X-Viewer-Header'], undefined); + assert.equal(evt.tile.ajaxHeaders['X-Viewer-Header2'], undefined); + // 'Range' header entry set per tile and must not be overwritten by Viewer header + assert.equal(evt.tile.ajaxHeaders.Range, getTileRangeHeader(evt.tile.level, evt.tile.x, evt.tile.y)); + // set new Viewer headers but do not propagate to TiledImage and Tile + viewer.setAjaxHeaders(newHeaders, false); + viewer.addHandler('tile-loaded', tileLoaded4); + }; + + var tileLoaded4 = function tileLoaded(evt) { + viewer.removeHandler('tile-loaded', tileLoaded4); + assert.deepEqual(viewer.ajaxHeaders, newHeaders); + assert.deepEqual(evt.tiledImage.ajaxHeaders, newHeaders2); + assert.equal(evt.tile.ajaxHeaders['X-Viewer-Header'], undefined); + assert.equal(evt.tile.ajaxHeaders['X-Viewer-Header2'], undefined); + done(); + }; + + viewer.addHandler('tile-loaded', tileLoaded); + viewer.open(customTileSource); + }); + + QUnit.test('TiledImage headers can be updated', function(assert) { + var done = assert.async(); + + var tileSourceHeaders = { + 'X-Tile-Header': 'TileHeaderValue' + } + var newHeaders = { + 'X-TiledImage-Header': 'TiledImageHeaderValue-Updated', + 'X-TiledImage-Header2': 'TiledImageHeaderValue2' + } + var newHeaders2 = { + 'X-Viewer-Header': 'ViewerHeaderValue-Updated', + 'X-Tile-Header': 'TileHeaderValue-Updated' + } + + var tileLoaded = function tileLoaded(evt) { + viewer.removeHandler('tile-loaded', tileLoaded); + // set new TiledImage headers and propagate to Tile + evt.tiledImage.setAjaxHeaders(newHeaders); + viewer.addHandler('tile-loaded', tileLoaded2); + }; + + var tileLoaded2 = function tileLoaded(evt) { + viewer.removeHandler('tile-loaded', tileLoaded2); + assert.deepEqual(viewer.ajaxHeaders, { 'X-Viewer-Header': 'ViewerHeaderValue' }); + assert.deepEqual(evt.tiledImage._ownAjaxHeaders, newHeaders); + assert.deepEqual(evt.tiledImage.ajaxHeaders, OpenSeadragon.extend({}, viewer.ajaxHeaders, newHeaders)); + assert.deepEqual(evt.tile.ajaxHeaders, OpenSeadragon.extend({}, viewer.ajaxHeaders, newHeaders, tileSourceHeaders)); + // set new TiledImage headers (that overwrite header entries of Viewer and Tile) and propagate to Tile + evt.tiledImage.setAjaxHeaders(newHeaders2, true); + viewer.addHandler('tile-loaded', tileLoaded3); + }; + + var tileLoaded3 = function tileLoaded(evt) { + viewer.removeHandler('tile-loaded', tileLoaded3); + assert.deepEqual(viewer.ajaxHeaders, { 'X-Viewer-Header': 'ViewerHeaderValue' }); + assert.deepEqual(evt.tiledImage._ownAjaxHeaders, newHeaders2); + assert.deepEqual(evt.tiledImage.ajaxHeaders, OpenSeadragon.extend({}, viewer.ajaxHeaders, newHeaders2)); + assert.deepEqual(evt.tile.ajaxHeaders, OpenSeadragon.extend({}, viewer.ajaxHeaders, newHeaders2, tileSourceHeaders)); + // set new TiledImage headers but do not propagate to Tile + evt.tiledImage.setAjaxHeaders(null, false); + viewer.addHandler('tile-loaded', tileLoaded4); + }; + + var tileLoaded4 = function tileLoaded(evt) { + viewer.removeHandler('tile-loaded', tileLoaded4); + assert.deepEqual(viewer.ajaxHeaders, { 'X-Viewer-Header': 'ViewerHeaderValue' }); + assert.deepEqual(evt.tiledImage._ownAjaxHeaders, {}); + assert.deepEqual(evt.tiledImage.ajaxHeaders, viewer.ajaxHeaders); + assert.deepEqual(evt.tile.ajaxHeaders, OpenSeadragon.extend({}, viewer.ajaxHeaders, newHeaders2, tileSourceHeaders)); + done(); + }; + + viewer.addHandler('tile-loaded', tileLoaded); + viewer.addTiledImage({ + ajaxHeaders: { + 'X-TiledImage-Header': 'TiledImageHeaderValue' + }, + tileSource: OpenSeadragon.extend({}, customTileSource, { + getTileAjaxHeaders: function() { + return tileSourceHeaders; + } + }), + }); + }); })();