From 69e9326d4f9fc7478fe1543f0f085c2cecbff5b1 Mon Sep 17 00:00:00 2001 From: Adam Carruthers Date: Thu, 4 Dec 2014 17:06:33 -0500 Subject: [PATCH 01/16] fix #500 - requests keyboard focus when canvas is clicked The canvas click listener will now check if keyboard-command-area has focus, and if it does not, it will request it. --- src/viewer.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/viewer.js b/src/viewer.js index 39ed6294..b0ac0e47 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2265,6 +2265,13 @@ function onBlur(){ function onCanvasClick( event ) { var gestureSettings; + var haveKeyboardFocus = document.activeElement == this.keyboardCommandArea; + + // If we don't have keyboard focus, request it. + if ( !haveKeyboardFocus ) { + this.keyboardCommandArea.focus(); + } + if ( !event.preventDefaultAction && this.viewport && event.quick ) { gestureSettings = this.gestureSettingsByDeviceType( event.pointerType ); if ( gestureSettings.clickToZoom ) { From 6e49f802487e0f45aecc25b9d45ecd9e296da47a Mon Sep 17 00:00:00 2001 From: Adam Carruthers Date: Mon, 22 Dec 2014 20:29:44 -0700 Subject: [PATCH 02/16] Prevent mobile keyboard from opening Adds readonly property to .keyboard-command-area to prevent input, but allow keys to be captured. --- src/viewer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/viewer.js b/src/viewer.js index b0ac0e47..1ff4e1c9 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -300,6 +300,8 @@ $.Viewer = function( options ) { style.left = "0px"; style.resize = "none"; }( this.keyboardCommandArea.style )); + // Set read-only - hides keyboard on mobile devices, still allows input. + this.keyboardCommandArea.readOnly = true; this.container.insertBefore( this.canvas, this.container.firstChild ); this.container.insertBefore( this.keyboardCommandArea, this.container.firstChild ); From c820f9f918eeb9f2ddc2f5f10369ae7a48cb2832 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Fri, 2 Jan 2015 15:45:46 -0800 Subject: [PATCH 03/16] Added ajaxWithCredentials option --- src/openseadragon.js | 21 +++++++- src/tilesource.js | 124 +++++++++++++++++++++++++------------------ src/viewer.js | 12 ++++- 3 files changed, 100 insertions(+), 57 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index 07abcd42..81028d19 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -551,8 +551,11 @@ * If collectionMode is true, specifies the margin, in viewport coordinates, between each TiledImage. * * @property {String|Boolean} [crossOriginPolicy=false] - * Valid values are 'Anonymous', 'use-credentials', and false. If false, canvas requests will - * not use CORS, and the canvas will be tainted. + * Valid values are 'Anonymous', 'use-credentials', and false. If false, canvas requests will + * not use CORS, and the canvas will be tainted. + * + * @property {Boolean} [ajaxWithCredentials=false] + * Whether to set the withCredentials XHR flag for AJAX requests (when loading tile sources). * */ @@ -911,6 +914,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ tileHost: null, initialPage: 0, crossOriginPolicy: false, + ajaxWithCredentials: false, //PAN AND ZOOM SETTINGS AND CONSTRAINTS panHorizontal: true, @@ -1923,6 +1927,15 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ * @throws {Error} */ makeAjaxRequest: function( url, onSuccess, onError ) { + var withCredentials; + + if( $.isPlainObject( url ) ){ + onSuccess = url.success; + onError = url.error; + withCredentials = url.withCredentials; + url = url.url; + } + var protocol = $.getUrlProtocol( url ); var request = $.createAjaxRequest( protocol === "file:" ); @@ -1949,6 +1962,10 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ } }; + if (withCredentials) { + request.withCredentials = true; + } + try { request.open( "GET", url, true ); request.send( null ); diff --git a/src/tilesource.js b/src/tilesource.js index deecc742..8d6ab5fb 100644 --- a/src/tilesource.js +++ b/src/tilesource.js @@ -74,8 +74,9 @@ * The maximum level to attempt to load. */ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLevel ) { - var callback = null, - args = arguments, + var _this = this; + + var args = arguments, options, i; @@ -102,19 +103,23 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve //source $.extend( true, this, options ); - //Any functions that are passed as arguments are bound to the ready callback - /*jshint loopfunc:true*/ - for ( i = 0; i < arguments.length; i++ ) { - if ( $.isFunction( arguments[ i ] ) ) { - callback = arguments[ i ]; - this.addHandler( 'ready', function ( event ) { - callback( event ); - } ); - //only one callback per constructor - break; + if (!this.success) { + //Any functions that are passed as arguments are bound to the ready callback + for ( i = 0; i < arguments.length; i++ ) { + if ( $.isFunction( arguments[ i ] ) ) { + this.success = arguments[ i ]; + //only one callback per constructor + break; + } } } + if (this.success) { + this.addHandler( 'ready', function ( event ) { + _this.success( event ); + } ); + } + /** * Ratio of width to height * @member {Number} aspectRatio @@ -127,7 +132,7 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve */ /** * The size of the image tiles used to compose the image. - * Please note that tileSize may be deprecated in a future release. + * Please note that tileSize may be deprecated in a future release. * Instead the getTileSize(level) function should be used. * @member {Number} tileSize * @memberof OpenSeadragon.TileSource# @@ -148,12 +153,16 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve * @memberof OpenSeadragon.TileSource# */ /** - * + * * @member {Boolean} ready * @memberof OpenSeadragon.TileSource# */ if( 'string' == $.type( arguments[ 0 ] ) ){ + this.url = arguments[0]; + } + + if (this.url) { //in case the getImageInfo method is overriden and/or implies an //async mechanism set some safe defaults first this.aspectRatio = 1; @@ -165,7 +174,7 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve this.ready = false; //configuration via url implies the extending class //implements and 'configure' - this.getImageInfo( arguments[ 0 ] ); + this.getImageInfo( this.url ); } else { @@ -185,8 +194,8 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve Math.log( 2 ) ) : 0 ); - if( callback && $.isFunction( callback ) ){ - callback( this ); + if( this.success && $.isFunction( this.success ) ){ + this.success( this ); } } @@ -197,7 +206,7 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{ /** - * Return the tileSize for a given level. + * Return the tileSize for a given level. * Subclasses should override this if tileSizes can be different at different levels * such as in IIIFTileSource. Code should use this function rather than reading * from .tileSize directly. tileSize may be deprecated in a future release. @@ -355,6 +364,10 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{ } options = $TileSource.prototype.configure.apply( _this, [ data, url ]); + if (options.ajaxWithCredentials === undefined) { + options.ajaxWithCredentials = _this.ajaxWithCredentials; + } + readySource = new $TileSource( options ); _this.ready = true; /** @@ -383,45 +396,50 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{ }); } else { // request info via xhr asynchronously. - $.makeAjaxRequest( url, function( xhr ) { - var data = processResponse( xhr ); - callback( data ); - }, function ( xhr, exc ) { - var msg; + $.makeAjaxRequest( { + url: url, + withCredentials: this.ajaxWithCredentials, + success: function( xhr ) { + var data = processResponse( xhr ); + callback( data ); + }, + error: function ( xhr, exc ) { + var msg; - /* - IE < 10 will block XHR requests to different origins. Any property access on the request - object will raise an exception which we'll attempt to handle by formatting the original - exception rather than the second one raised when we try to access xhr.status - */ - try { - msg = "HTTP " + xhr.status + " attempting to load TileSource"; - } catch ( e ) { - var formattedExc; - if ( typeof( exc ) == "undefined" || !exc.toString ) { - formattedExc = "Unknown error"; - } else { - formattedExc = exc.toString(); + /* + IE < 10 will block XHR requests to different origins. Any property access on the request + object will raise an exception which we'll attempt to handle by formatting the original + exception rather than the second one raised when we try to access xhr.status + */ + try { + msg = "HTTP " + xhr.status + " attempting to load TileSource"; + } catch ( e ) { + var formattedExc; + if ( typeof( exc ) == "undefined" || !exc.toString ) { + formattedExc = "Unknown error"; + } else { + formattedExc = exc.toString(); + } + + msg = formattedExc + " attempting to load TileSource"; } - msg = formattedExc + " attempting to load TileSource"; + /*** + * Raised when an error occurs loading a TileSource. + * + * @event open-failed + * @memberof OpenSeadragon.TileSource + * @type {object} + * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event. + * @property {String} message + * @property {String} source + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + _this.raiseEvent( 'open-failed', { + message: msg, + source: url + }); } - - /*** - * Raised when an error occurs loading a TileSource. - * - * @event open-failed - * @memberof OpenSeadragon.TileSource - * @type {object} - * @property {OpenSeadragon.TileSource} eventSource - A reference to the TileSource which raised the event. - * @property {String} message - * @property {String} source - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - _this.raiseEvent( 'open-failed', { - message: msg, - source: url - }); }); } diff --git a/src/viewer.js b/src/viewer.js index 781af981..5dcdd1cd 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2051,14 +2051,22 @@ function getTileSourceImplementation( viewer, tileSource, successCallback, setTimeout( function() { if ( $.type( tileSource ) == 'string' ) { //If its still a string it means it must be a url at this point - tileSource = new $.TileSource( tileSource, function( event ) { - successCallback( event.tileSource ); + tileSource = new $.TileSource({ + url: tileSource, + ajaxWithCredentials: viewer.ajaxWithCredentials, + success: function( event ) { + successCallback( event.tileSource ); + } }); tileSource.addHandler( 'open-failed', function( event ) { failCallback( event ); } ); } else if ( $.isPlainObject( tileSource ) || tileSource.nodeType ) { + if (tileSource.ajaxWithCredentials === undefined) { + tileSource.ajaxWithCredentials = viewer.ajaxWithCredentials; + } + if ( $.isFunction( tileSource.getTileUrl ) ) { //Custom tile source var customTileSource = new $.TileSource( tileSource ); From a336b2366701ca44dbc92ea6dcdfe10447128795 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Fri, 2 Jan 2015 16:07:11 -0800 Subject: [PATCH 04/16] Documentation for ajaxWithCredentials-related changes --- changelog.txt | 1 + src/openseadragon.js | 12 ++++++++---- src/tilesource.js | 41 ++++++++++++++++++++++++----------------- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/changelog.txt b/changelog.txt index 98a94fd0..9649171b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -39,6 +39,7 @@ OPENSEADRAGON CHANGELOG * Rect and Point toString() functions are now consistent: rounding values to nearest hundredth * Overlays appear in the DOM immediately on open or addOverlay (#507) * imageLoaderLimit now works (#544) +* Added ajaxWithCredentials option (#543) 1.2.0: (in progress) diff --git a/src/openseadragon.js b/src/openseadragon.js index 81028d19..dc7a5638 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -556,6 +556,7 @@ * * @property {Boolean} [ajaxWithCredentials=false] * Whether to set the withCredentials XHR flag for AJAX requests (when loading tile sources). + * Note that this can be overridden at the {@link OpenSeadragon.TileSource} level. * */ @@ -1920,15 +1921,18 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ /** * Makes an AJAX request. - * @function - * @param {String} url - the url to request - * @param {Function} onSuccess - a function to call on a successful response - * @param {Function} onError - a function to call on when an error occurs + * @param {Object} options + * @param {String} options.url - the url to request + * @param {Function} options.success - a function to call on a successful response + * @param {Function} options.error - a function to call on when an error occurs + * @param {Boolean} [options.withCredentials=false] - whether to set the XHR's withCredentials * @throws {Error} */ makeAjaxRequest: function( url, onSuccess, onError ) { var withCredentials; + // 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; diff --git a/src/tilesource.js b/src/tilesource.js index 8d6ab5fb..1c0d29d2 100644 --- a/src/tilesource.js +++ b/src/tilesource.js @@ -38,39 +38,46 @@ /** * @class TileSource * @classdesc The TileSource contains the most basic implementation required to create a - * smooth transition between layer in an image pyramid. It has only a single key - * interface that must be implemented to complete it key functionality: + * smooth transition between layers in an image pyramid. It has only a single key + * interface that must be implemented to complete its key functionality: * 'getTileUrl'. It also has several optional interfaces that can be * implemented if a new TileSource wishes to support configuration via a simple * object or array ('configure') and if the tile source supports or requires - * configuration via retreival of a document on the network ala AJAX or JSONP, + * configuration via retrieval of a document on the network ala AJAX or JSONP, * ('getImageInfo'). *
- * By default the image pyramid is split into N layers where the images longest + * By default the image pyramid is split into N layers where the image's longest * side in M (in pixels), where N is the smallest integer which satisfies * 2^(N+1) >= M. * * @memberof OpenSeadragon * @extends OpenSeadragon.EventSource - * @param {Number|Object|Array|String} width - * If more than a single argument is supplied, the traditional use of - * positional parameters is supplied and width is expected to be the width - * source image at its max resolution in pixels. If a single argument is supplied and - * it is an Object or Array, the construction is assumed to occur through - * the extending classes implementation of 'configure'. Finally if only a - * single argument is supplied and it is a String, the extending class is - * expected to implement 'getImageInfo' and 'configure'. - * @param {Number} height + * @param {Object} options + * You can either specify a URL, or literally define the TileSource (by specifying + * width, height, tileSize, tileOverlap, minLevel, and maxLevel). For the former, + * the extending class is expected to implement 'getImageInfo' and 'configure'. + * For the latter, the construction is assumed to occur through + * the extending classes implementation of 'configure'. + * @param {String} [options.url] + * The URL for the data necessary for this TileSource. + * @param {Function} [options.success] + * A function to be called upon successful creation. + * @param {Boolean} [options.ajaxWithCredentials] + * If this TileSource needs to make an AJAX call, this specifies whether to set + * the XHR's withCredentials (for accessing secure data). + * @param {Number} [options.width] * Width of the source image at max resolution in pixels. - * @param {Number} tileSize + * @param {Number} [options.height] + * Height of the source image at max resolution in pixels. + * @param {Number} [options.tileSize] * The size of the tiles to assumed to make up each pyramid layer in pixels. * Tile size determines the point at which the image pyramid must be * divided into a matrix of smaller images. - * @param {Number} tileOverlap + * @param {Number} [options.tileOverlap] * The number of pixels each tile is expected to overlap touching tiles. - * @param {Number} minLevel + * @param {Number} [options.minLevel] * The minimum level to attempt to load. - * @param {Number} maxLevel + * @param {Number} [options.maxLevel] * The maximum level to attempt to load. */ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLevel ) { From d26d3f939319b5bbd1e943c201c9e606c1f8d8ca Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Wed, 14 Jan 2015 08:44:14 -0800 Subject: [PATCH 05/16] MouseTracker - Improved IE 9+ compatibility --- src/mousetracker.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index 09b3c751..c90382f9 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -215,7 +215,6 @@ MSPointerOver: function ( event ) { onPointerOver( _this, event ); }, pointerout: function ( event ) { onPointerOut( _this, event ); }, MSPointerOut: function ( event ) { onPointerOut( _this, event ); }, - pointerdown: function ( event ) { onPointerDown( _this, event ); }, MSPointerDown: function ( event ) { onPointerDown( _this, event ); }, pointerup: function ( event ) { onPointerUp( _this, event ); }, @@ -1609,7 +1608,7 @@ function onMouseOver( tracker, event ) { event = $.getEvent( event ); - if ( this === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { + if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { return; } @@ -1654,7 +1653,7 @@ function onMouseOut( tracker, event ) { event = $.getEvent( event ); - if ( this === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { + if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { return; } @@ -2054,7 +2053,7 @@ function onPointerOver( tracker, event ) { var gPoint; - if ( this === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { + if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { return; } @@ -2077,7 +2076,7 @@ function onPointerOut( tracker, event ) { var gPoint; - if ( this === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { + if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) { return; } @@ -2092,6 +2091,7 @@ updatePointersExit( tracker, event, [ gPoint ] ); } + /** * @private * @inner From 960b4a0d14ab66df7447559336c3cd1db0994d2f Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Wed, 14 Jan 2015 08:46:06 -0800 Subject: [PATCH 06/16] changelog update --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index 04986505..2e64452b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -7,6 +7,7 @@ OPENSEADRAGON CHANGELOG * Fixed: DZI tilesource was broken on IE8/IE9 (#563) * Exposed secondary pointer button (middle, right, etc.) events from MouseTracker and through viewer (#479) * MouseTracker - Improved IE 8 compatibility (#562) +* MouseTracker - Improved IE 9+ compatibility (#564) 1.2.0: From cb56e352cfce113af68d67b9247a12247638e80d Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Thu, 15 Jan 2015 12:15:22 -0800 Subject: [PATCH 07/16] Mousetracker Improvements 1) MouseTracker - Simulated touchenter/touchleave events now bubble to parent element MouseTrackers 2) MouseTracker - Improved multitouch support in enter/exit event handlers 3) MouseTracker - orphaned tracked touch pointers removed (fix for #539) 4) MouseTracker - removed touchenter/touchleave event support since the events don't exist on any known platform and have been removed from the W3C specification 5) Removed Viewer onContainerPress/onContainerRelease handlers (and the associated 'container-release' event ) that were never fired due to the canvas (child) element capturing the DOM events 6) Added 'canvas-enter', 'canvas-exit', and 'canvas-press' events to Viewer 7) ButtonGroup - removed obsolete MouseTracker event handlers --- src/buttongroup.js | 16 ---- src/mousetracker.js | 159 +++++++++++++++++++------------ src/viewer.js | 190 ++++++++++++++++++++++++-------------- test/legacy.mouse.shim.js | 15 +-- 4 files changed, 228 insertions(+), 152 deletions(-) diff --git a/src/buttongroup.js b/src/buttongroup.js index 2837805a..7505af0a 100644 --- a/src/buttongroup.js +++ b/src/buttongroup.js @@ -105,22 +105,6 @@ $.ButtonGroup = function( options ) { } } }, - pressHandler: function ( event ) { - if ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) { - var i; - for ( i = 0; i < _this.buttons.length; i++ ) { - _this.buttons[ i ].notifyGroupEnter(); - } - } - }, - releaseHandler: function ( event ) { - var i; - if ( !event.insideElementReleased || ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) ) { - for ( i = 0; i < _this.buttons.length; i++ ) { - _this.buttons[ i ].notifyGroupExit(); - } - } - } }).setTracking( true ); }; diff --git a/src/mousetracker.js b/src/mousetracker.js index c90382f9..8d2c0b74 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -34,7 +34,10 @@ (function ( $ ) { - // dictionary from hash to private properties + // All MouseTracker instances + var MOUSETRACKERS = []; + + // dictionary from hash to private properties var THIS = {}; @@ -103,6 +106,8 @@ */ $.MouseTracker = function ( options ) { + MOUSETRACKERS.push( this ); + var args = arguments; if ( !$.isPlainObject( options ) ) { @@ -199,8 +204,6 @@ mousemove: function ( event ) { onMouseMove( _this, event ); }, mousemovecaptured: function ( event ) { onMouseMoveCaptured( _this, event ); }, - touchenter: function ( event ) { onTouchEnter( _this, event ); }, - touchleave: function ( event ) { onTouchLeave( _this, event ); }, touchstart: function ( event ) { onTouchStart( _this, event ); }, touchend: function ( event ) { onTouchEnd( _this, event ); }, touchendcaptured: function ( event ) { onTouchEndCaptured( _this, event ); }, @@ -255,9 +258,18 @@ * @function */ destroy: function () { + var i; + stopTracking( this ); this.element = null; + for ( i = 0; i < MOUSETRACKERS.length; i++ ) { + if ( MOUSETRACKERS[ i ] === this ) { + MOUSETRACKERS.splice( i, 1 ); + break; + } + } + THIS[ this.hash ] = null; delete THIS[ this.hash ]; }, @@ -312,6 +324,24 @@ return list; }, + /** + * Returns the total number of pointers currently active on the tracked element. + * @function + * @returns {Number} + */ + getActivePointerCount: function () { + var delegate = THIS[ this.hash ], + i, + len = delegate.activePointersLists.length, + count = 0; + + for ( i = 0; i < len; i++ ) { + count += delegate.activePointersLists[ i ].getLength(); + } + + return count; + }, + /** * Implement or assign implementation to these handlers during or after * calling the constructor. @@ -326,6 +356,8 @@ * @param {Number} event.buttons * Current buttons pressed. * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. + * @param {Number} event.pointers + * Number of pointers (all types) active in the tracked element. * @param {Boolean} event.insideElementPressed * True if the left mouse button is currently being pressed and was * initiated inside the tracked element, otherwise false. @@ -356,6 +388,8 @@ * @param {Number} event.buttons * Current buttons pressed. * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. + * @param {Number} event.pointers + * Number of pointers (all types) active in the tracked element. * @param {Boolean} event.insideElementPressed * True if the left mouse button is currently being pressed and was * initiated inside the tracked element, otherwise false. @@ -887,7 +921,6 @@ } else { $.MouseTracker.maxTouchPoints = 0; } - $.MouseTracker.haveTouchEnter = false; $.MouseTracker.haveMouseEnter = false; } else if ( window.MSPointerEvent ) { // IE10 @@ -899,7 +932,6 @@ } else { $.MouseTracker.maxTouchPoints = 0; } - $.MouseTracker.haveTouchEnter = false; $.MouseTracker.haveMouseEnter = false; } else { // Legacy W3C mouse events @@ -913,19 +945,14 @@ } $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" ); if ( 'ontouchstart' in window ) { - // iOS, Android, and other W3c Touch Event implementations (see http://www.w3.org/TR/2011/WD-touch-events-20110505) + // iOS, Android, and other W3c Touch Event implementations + // (see http://www.w3.org/TR/touch-events/) + // (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) + // (see https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" ); - if ( 'ontouchenter' in window ) { - $.MouseTracker.subscribeEvents.push( "touchenter", "touchleave" ); - $.MouseTracker.haveTouchEnter = true; - } else { - $.MouseTracker.haveTouchEnter = false; - } - } else { - $.MouseTracker.haveTouchEnter = false; } if ( 'ongesturestart' in window ) { - // iOS (see https://developer.apple.com/library/safari/documentation/UserExperience/Reference/GestureEventClassReference/GestureEvent/GestureEvent.html) + // iOS (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) // Subscribe to these to prevent default gesture handling $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" ); } @@ -1822,45 +1849,21 @@ * @private * @inner */ - function onTouchEnter( tracker, event ) { + function abortTouchContacts( tracker, event, pointsList ) { var i, - touchCount = event.changedTouches.length, - gPoints = []; + gPointCount = pointsList.getLength(), + abortGPoints = []; - for ( i = 0; i < touchCount; i++ ) { - gPoints.push( { - id: event.changedTouches[ i ].identifier, - type: 'touch', - // isPrimary not set - let the updatePointers functions determine it - currentPos: getMouseAbsolute( event.changedTouches[ i ] ), - currentTime: $.now() - } ); + for ( i = 0; i < gPointCount; i++ ) { + abortGPoints.push( pointsList.getByIndex( i ) ); } - updatePointersEnter( tracker, event, gPoints ); - } - - - /** - * @private - * @inner - */ - function onTouchLeave( tracker, event ) { - var i, - touchCount = event.changedTouches.length, - gPoints = []; - - for ( i = 0; i < touchCount; i++ ) { - gPoints.push( { - id: event.changedTouches[ i ].identifier, - type: 'touch', - // isPrimary not set - let the updatePointers functions determine it - currentPos: getMouseAbsolute( event.changedTouches[ i ] ), - currentTime: $.now() - } ); + if ( abortGPoints.length > 0 ) { + // simulate touchend + updatePointersUp( tracker, event, abortGPoints, 0 ); // 0 means primary button press/release or touch contact + // simulate touchleave + updatePointersExit( tracker, event, abortGPoints ); } - - updatePointersExit( tracker, event, gPoints ); } @@ -1871,11 +1874,19 @@ function onTouchStart( tracker, event ) { var time, i, + j, touchCount = event.changedTouches.length, - gPoints = []; + gPoints = [], + parentGPoints, + pointsList = tracker.getActivePointersListByType( 'touch' ); time = $.now(); + if ( pointsList.getLength() > event.touches.length - touchCount ) { + $.console.warn('Tracked touch contact count doesn\'t match event.touches.length. Removing all tracked touch pointers.'); + abortTouchContacts( tracker, event, pointsList ); + } + for ( i = 0; i < touchCount; i++ ) { gPoints.push( { id: event.changedTouches[ i ].identifier, @@ -1886,9 +1897,24 @@ } ); } - // simulate touchenter if not natively available - if ( !$.MouseTracker.haveTouchEnter ) { - updatePointersEnter( tracker, event, gPoints ); + // simulate touchenter on our tracked element + updatePointersEnter( tracker, event, gPoints ); + + // simulate touchenter on our tracked element's tracked ancestor elements + for ( i = 0; i < MOUSETRACKERS.length; i++ ) { + if ( MOUSETRACKERS[ i ] !== tracker && MOUSETRACKERS[ i ].isTracking() && isParentChild( MOUSETRACKERS[ i ].element, tracker.element ) ) { + parentGPoints = []; + for ( j = 0; j < touchCount; j++ ) { + parentGPoints.push( { + id: event.changedTouches[ j ].identifier, + type: 'touch', + // isPrimary not set - let the updatePointers functions determine it + currentPos: getMouseAbsolute( event.changedTouches[ j ] ), + currentTime: time + } ); + } + updatePointersEnter( MOUSETRACKERS[ i ], event, parentGPoints ); + } } if ( updatePointersDown( tracker, event, gPoints, 0 ) ) { // 0 means primary button press/release or touch contact @@ -1929,8 +1955,10 @@ function handleTouchEnd( tracker, event ) { var time, i, + j, touchCount = event.changedTouches.length, - gPoints = []; + gPoints = [], + parentGPoints; time = $.now(); @@ -1948,9 +1976,24 @@ releasePointer( tracker, 'touch' ); } - // simulate touchleave if not natively available - if ( !$.MouseTracker.haveTouchEnter && touchCount > 0 ) { - updatePointersExit( tracker, event, gPoints ); + // simulate touchleave on our tracked element + updatePointersExit( tracker, event, gPoints ); + + // simulate touchleave on our tracked element's tracked ancestor elements + for ( i = 0; i < MOUSETRACKERS.length; i++ ) { + if ( MOUSETRACKERS[ i ] !== tracker && MOUSETRACKERS[ i ].isTracking() && isParentChild( MOUSETRACKERS[ i ].element, tracker.element ) ) { + parentGPoints = []; + for ( j = 0; j < touchCount; j++ ) { + parentGPoints.push( { + id: event.changedTouches[ j ].identifier, + type: 'touch', + // isPrimary not set - let the updatePointers functions determine it + currentPos: getMouseAbsolute( event.changedTouches[ j ] ), + currentTime: time + } ); + } + updatePointersExit( MOUSETRACKERS[ i ], event, parentGPoints ); + } } $.cancelEvent( event ); @@ -2344,6 +2387,7 @@ pointerType: curGPoint.type, position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ), buttons: pointsList.buttons, + pointers: tracker.getActivePointerCount(), insideElementPressed: curGPoint.insideElementPressed, buttonDownAny: pointsList.buttons !== 0, isTouchEvent: curGPoint.type === 'touch', @@ -2407,6 +2451,7 @@ pointerType: curGPoint.type, position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ), buttons: pointsList.buttons, + pointers: tracker.getActivePointerCount(), insideElementPressed: updateGPoint ? updateGPoint.insideElementPressed : false, buttonDownAny: pointsList.buttons !== 0, isTouchEvent: curGPoint.type === 'touch', diff --git a/src/viewer.js b/src/viewer.js index 2d2604d9..b26b1837 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -389,6 +389,9 @@ $.Viewer = function( options ) { dblClickHandler: $.delegate( this, onCanvasDblClick ), dragHandler: $.delegate( this, onCanvasDrag ), dragEndHandler: $.delegate( this, onCanvasDragEnd ), + enterHandler: $.delegate( this, onCanvasEnter ), + exitHandler: $.delegate( this, onCanvasExit ), + pressHandler: $.delegate( this, onCanvasPress ), releaseHandler: $.delegate( this, onCanvasRelease ), nonPrimaryPressHandler: $.delegate( this, onCanvasNonPrimaryPress ), nonPrimaryReleaseHandler: $.delegate( this, onCanvasNonPrimaryRelease ), @@ -403,9 +406,7 @@ $.Viewer = function( options ) { dblClickTimeThreshold: this.dblClickTimeThreshold, dblClickDistThreshold: this.dblClickDistThreshold, enterHandler: $.delegate( this, onContainerEnter ), - exitHandler: $.delegate( this, onContainerExit ), - pressHandler: $.delegate( this, onContainerPress ), - releaseHandler: $.delegate( this, onContainerRelease ) + exitHandler: $.delegate( this, onContainerExit ) }).setTracking( this.mouseNavEnabled ? true : false ); // always tracking if( this.toolbar ){ @@ -2430,6 +2431,92 @@ function onCanvasDragEnd( event ) { }); } +function onCanvasEnter( event ) { + /** + * Raised when a pointer enters the {@link OpenSeadragon.Viewer#canvas} element. + * + * @event canvas-enter + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. + * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. + * @property {String} pointerType - "mouse", "touch", "pen", etc. + * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. + * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. + * @property {Number} pointers - Number of pointers (all types) active in the tracked element. + * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. + * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. Deprecated. Use buttons instead. + * @property {Object} originalEvent - The original DOM event. + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.raiseEvent( 'canvas-enter', { + tracker: event.eventSource, + pointerType: event.pointerType, + position: event.position, + buttons: event.buttons, + pointers: event.pointers, + insideElementPressed: event.insideElementPressed, + buttonDownAny: event.buttonDownAny, + originalEvent: event.originalEvent + }); +} + +function onCanvasExit( event ) { + /** + * Raised when a pointer leaves the {@link OpenSeadragon.Viewer#canvas} element. + * + * @event canvas-exit + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. + * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. + * @property {String} pointerType - "mouse", "touch", "pen", etc. + * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. + * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. + * @property {Number} pointers - Number of pointers (all types) active in the tracked element. + * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. + * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. Deprecated. Use buttons instead. + * @property {Object} originalEvent - The original DOM event. + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.raiseEvent( 'canvas-exit', { + tracker: event.eventSource, + pointerType: event.pointerType, + position: event.position, + buttons: event.buttons, + pointers: event.pointers, + insideElementPressed: event.insideElementPressed, + buttonDownAny: event.buttonDownAny, + originalEvent: event.originalEvent + }); +} + +function onCanvasPress( event ) { + /** + * Raised when the primary mouse button is pressed or touch starts on the {@link OpenSeadragon.Viewer#canvas} element. + * + * @event canvas-press + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. + * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. + * @property {String} pointerType - "mouse", "touch", "pen", etc. + * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. + * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. + * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released. + * @property {Object} originalEvent - The original DOM event. + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.raiseEvent( 'canvas-press', { + tracker: event.eventSource, + pointerType: event.pointerType, + position: event.position, + insideElementPressed: event.insideElementPressed, + insideElementReleased: event.insideElementReleased, + originalEvent: event.originalEvent + }); +} + function onCanvasRelease( event ) { /** * Raised when the primary mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#canvas} element. @@ -2439,6 +2526,7 @@ function onCanvasRelease( event ) { * @type {object} * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. + * @property {String} pointerType - "mouse", "touch", "pen", etc. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released. @@ -2447,6 +2535,7 @@ function onCanvasRelease( event ) { */ this.raiseEvent( 'canvas-release', { tracker: event.eventSource, + pointerType: event.pointerType, position: event.position, insideElementPressed: event.insideElementPressed, insideElementReleased: event.insideElementReleased, @@ -2612,8 +2701,36 @@ function onCanvasScroll( event ) { return false; } +function onContainerEnter( event ) { + THIS[ this.hash ].mouseInside = true; + abortControlsAutoHide( this ); + /** + * Raised when the cursor enters the {@link OpenSeadragon.Viewer#container} element. + * + * @event container-enter + * @memberof OpenSeadragon.Viewer + * @type {object} + * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. + * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. + * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. + * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. + * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. + * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. Deprecated. Use buttons instead. + * @property {Object} originalEvent - The original DOM event. + * @property {?Object} userData - Arbitrary subscriber-defined object. + */ + this.raiseEvent( 'container-enter', { + tracker: event.eventSource, + position: event.position, + buttons: event.buttons, + insideElementPressed: event.insideElementPressed, + buttonDownAny: event.buttonDownAny, + originalEvent: event.originalEvent + }); +} + function onContainerExit( event ) { - if ( !event.insideElementPressed ) { + if ( event.pointers < 1 ) { THIS[ this.hash ].mouseInside = false; if ( !THIS[ this.hash ].animating ) { beginControlsAutoHide( this ); @@ -2644,71 +2761,6 @@ function onContainerExit( event ) { }); } -function onContainerPress( event ) { - if ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) { - THIS[ this.hash ].mouseInside = true; - abortControlsAutoHide( this ); - } -} - -function onContainerRelease( event ) { - if ( !event.insideElementReleased || ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) ) { - THIS[ this.hash ].mouseInside = false; - if ( !THIS[ this.hash ].animating ) { - beginControlsAutoHide( this ); - } - } - /** - * Raised when the primary mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#container} element. - * - * @event container-release - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. - * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. - * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. - * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. - * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released. - * @property {Object} originalEvent - The original DOM event. - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - this.raiseEvent( 'container-release', { - tracker: event.eventSource, - position: event.position, - insideElementPressed: event.insideElementPressed, - insideElementReleased: event.insideElementReleased, - originalEvent: event.originalEvent - }); -} - -function onContainerEnter( event ) { - THIS[ this.hash ].mouseInside = true; - abortControlsAutoHide( this ); - /** - * Raised when the cursor enters the {@link OpenSeadragon.Viewer#container} element. - * - * @event container-enter - * @memberof OpenSeadragon.Viewer - * @type {object} - * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event. - * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. - * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. - * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. - * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. - * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. Deprecated. Use buttons instead. - * @property {Object} originalEvent - The original DOM event. - * @property {?Object} userData - Arbitrary subscriber-defined object. - */ - this.raiseEvent( 'container-enter', { - tracker: event.eventSource, - position: event.position, - buttons: event.buttons, - insideElementPressed: event.insideElementPressed, - buttonDownAny: event.buttonDownAny, - originalEvent: event.originalEvent - }); -} - /////////////////////////////////////////////////////////////////////////////// // Page update routines ( aka Views - for future reference ) diff --git a/test/legacy.mouse.shim.js b/test/legacy.mouse.shim.js index 535f8815..3609ed85 100644 --- a/test/legacy.mouse.shim.js +++ b/test/legacy.mouse.shim.js @@ -21,19 +21,14 @@ } $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" ); if ( 'ontouchstart' in window ) { - // iOS, Android, and other W3c Touch Event implementations (see http://www.w3.org/TR/2011/WD-touch-events-20110505) + // iOS, Android, and other W3c Touch Event implementations + // (see http://www.w3.org/TR/touch-events/) + // (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) + // (see https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" ); - if ( 'ontouchenter' in window ) { - $.MouseTracker.subscribeEvents.push( "touchenter", "touchleave" ); - $.MouseTracker.haveTouchEnter = true; - } else { - $.MouseTracker.haveTouchEnter = false; - } - } else { - $.MouseTracker.haveTouchEnter = false; } if ( 'ongesturestart' in window ) { - // iOS (see https://developer.apple.com/library/safari/documentation/UserExperience/Reference/GestureEventClassReference/GestureEvent/GestureEvent.html) + // iOS (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html) // Subscribe to these to prevent default gesture handling $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" ); } From 3d8ddcf1ff1c8afa9f3f8e7e30059760d4b14ea7 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Thu, 15 Jan 2015 12:18:35 -0800 Subject: [PATCH 08/16] changelog update --- changelog.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/changelog.txt b/changelog.txt index 2e64452b..ad7ff86f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -8,6 +8,13 @@ OPENSEADRAGON CHANGELOG * Exposed secondary pointer button (middle, right, etc.) events from MouseTracker and through viewer (#479) * MouseTracker - Improved IE 8 compatibility (#562) * MouseTracker - Improved IE 9+ compatibility (#564) +* MouseTracker - Simulated touchenter/touchleave events now bubble to parent element MouseTrackers (#566) +* MouseTracker - Improved multitouch support in enter/exit event handlers (#566) +* MouseTracker - orphaned tracked touch pointers removed (fix for #539) +* MouseTracker - removed touchenter/touchleave event support since the events don't exist on any known platform and have been removed from the W3C specification (#566) +* Removed Viewer onContainerPress/onContainerRelease handlers (and the associated 'container-release' event ) that were never fired due to the canvas (child) element capturing the DOM events (#566) +* Added 'canvas-enter', 'canvas-exit', and 'canvas-press' events to Viewer (#566) +* ButtonGroup - removed obsolete MouseTracker event handlers (#566) 1.2.0: From 00aae52a08619f12fb8b81f9b2df49bfac868de0 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Thu, 15 Jan 2015 12:37:54 -0800 Subject: [PATCH 09/16] Fix container-enter/container-exit event properties --- src/viewer.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/viewer.js b/src/viewer.js index b26b1837..215f9432 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2714,6 +2714,7 @@ function onContainerEnter( event ) { * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. + * @property {Number} pointers - Number of pointers (all types) active in the tracked element. * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. Deprecated. Use buttons instead. * @property {Object} originalEvent - The original DOM event. @@ -2723,6 +2724,7 @@ function onContainerEnter( event ) { tracker: event.eventSource, position: event.position, buttons: event.buttons, + pointers: event.pointers, insideElementPressed: event.insideElementPressed, buttonDownAny: event.buttonDownAny, originalEvent: event.originalEvent @@ -2746,6 +2748,7 @@ function onContainerExit( event ) { * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event. * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element. * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser. + * @property {Number} pointers - Number of pointers (all types) active in the tracked element. * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false. * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. Deprecated. Use buttons instead. * @property {Object} originalEvent - The original DOM event. @@ -2755,6 +2758,7 @@ function onContainerExit( event ) { tracker: event.eventSource, position: event.position, buttons: event.buttons, + pointers: event.pointers, insideElementPressed: event.insideElementPressed, buttonDownAny: event.buttonDownAny, originalEvent: event.originalEvent From 8e5e2168c832135f182b8b85eddb014e17ea6f1e Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Fri, 16 Jan 2015 08:33:34 -0800 Subject: [PATCH 10/16] Ensure capture released in abortTouchContacts() --- src/mousetracker.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mousetracker.js b/src/mousetracker.js index 8d2c0b74..20748ba0 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -1861,6 +1861,9 @@ if ( abortGPoints.length > 0 ) { // simulate touchend updatePointersUp( tracker, event, abortGPoints, 0 ); // 0 means primary button press/release or touch contact + // release pointer capture + pointsList.captureCount = 1; + releasePointer( tracker, 'touch' ); // simulate touchleave updatePointersExit( tracker, event, abortGPoints ); } From 2831771af5a74f556e9d430104e411cbc6b28622 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Fri, 16 Jan 2015 16:26:30 -0800 Subject: [PATCH 11/16] MouseTracker - keyboard handling 1) MouseTracker - added keydown and keyup handlers 2) Modifier keys ignored in keyboard navigation handlers (#503) 3) Arrow key navigation fixed across platforms (#565) --- src/mousetracker.js | 133 +++++++++++++++++++++++++++++++++++++++++- src/referencestrip.js | 90 +++++++++++++++++++--------- src/viewer.js | 44 ++++++++++++-- 3 files changed, 231 insertions(+), 36 deletions(-) diff --git a/src/mousetracker.js b/src/mousetracker.js index 20748ba0..9e8f25ef 100644 --- a/src/mousetracker.js +++ b/src/mousetracker.js @@ -95,6 +95,10 @@ * An optional handler for after a drag gesture. * @param {OpenSeadragon.EventHandler} [options.pinchHandler=null] * An optional handler for the pinch gesture. + * @param {OpenSeadragon.EventHandler} [options.keyDownHandler=null] + * An optional handler for keydown. + * @param {OpenSeadragon.EventHandler} [options.keyUpHandler=null] + * An optional handler for keyup. * @param {OpenSeadragon.EventHandler} [options.keyHandler=null] * An optional handler for keypress. * @param {OpenSeadragon.EventHandler} [options.focusHandler=null] @@ -170,6 +174,8 @@ this.dragEndHandler = options.dragEndHandler || null; this.pinchHandler = options.pinchHandler || null; this.stopHandler = options.stopHandler || null; + this.keyDownHandler = options.keyDownHandler || null; + this.keyUpHandler = options.keyUpHandler || null; this.keyHandler = options.keyHandler || null; this.focusHandler = options.focusHandler || null; this.blurHandler = options.blurHandler || null; @@ -185,6 +191,8 @@ THIS[ this.hash ] = { click: function ( event ) { onClick( _this, event ); }, dblclick: function ( event ) { onDblClick( _this, event ); }, + keydown: function ( event ) { onKeyDown( _this, event ); }, + keyup: function ( event ) { onKeyUp( _this, event ); }, keypress: function ( event ) { onKeyPress( _this, event ); }, focus: function ( event ) { onFocus( _this, event ); }, blur: function ( event ) { onBlur( _this, event ); }, @@ -743,8 +751,66 @@ * A reference to the tracker instance. * @param {Number} event.keyCode * The key code that was pressed. + * @param {Boolean} event.ctrl + * True if the ctrl key was pressed during this event. * @param {Boolean} event.shift * True if the shift key was pressed during this event. + * @param {Boolean} event.alt + * True if the alt key was pressed during this event. + * @param {Boolean} event.meta + * True if the meta key was pressed during this event. + * @param {Object} event.originalEvent + * The original event object. + * @param {Boolean} event.preventDefaultAction + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. + * @param {Object} event.userData + * Arbitrary user-defined object. + */ + keyDownHandler: function () { }, + + /** + * Implement or assign implementation to these handlers during or after + * calling the constructor. + * @function + * @param {Object} event + * @param {OpenSeadragon.MouseTracker} event.eventSource + * A reference to the tracker instance. + * @param {Number} event.keyCode + * The key code that was pressed. + * @param {Boolean} event.ctrl + * True if the ctrl key was pressed during this event. + * @param {Boolean} event.shift + * True if the shift key was pressed during this event. + * @param {Boolean} event.alt + * True if the alt key was pressed during this event. + * @param {Boolean} event.meta + * True if the meta key was pressed during this event. + * @param {Object} event.originalEvent + * The original event object. + * @param {Boolean} event.preventDefaultAction + * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false. + * @param {Object} event.userData + * Arbitrary user-defined object. + */ + keyUpHandler: function () { }, + + /** + * Implement or assign implementation to these handlers during or after + * calling the constructor. + * @function + * @param {Object} event + * @param {OpenSeadragon.MouseTracker} event.eventSource + * A reference to the tracker instance. + * @param {Number} event.keyCode + * The key code that was pressed. + * @param {Boolean} event.ctrl + * True if the ctrl key was pressed during this event. + * @param {Boolean} event.shift + * True if the shift key was pressed during this event. + * @param {Boolean} event.alt + * True if the alt key was pressed during this event. + * @param {Boolean} event.meta + * True if the meta key was pressed during this event. * @param {Object} event.originalEvent * The original event object. * @param {Boolean} event.preventDefaultAction @@ -904,7 +970,7 @@ /** * Detect browser pointer device event model(s) and build appropriate list of events to subscribe to. */ - $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ]; + $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keydown", "keyup", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ]; if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) { // Older Firefox @@ -1440,12 +1506,72 @@ } + /** + * @private + * @inner + */ + function onKeyDown( tracker, event ) { + //$.console.log( "keydown %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey ); + var propagate; + if ( tracker.keyDownHandler ) { + event = $.getEvent( event ); + propagate = tracker.keyDownHandler( + { + eventSource: tracker, + position: getMouseRelative( event, tracker.element ), + keyCode: event.keyCode ? event.keyCode : event.charCode, + ctrl: event.ctrlKey, + shift: event.shiftKey, + alt: event.altKey, + meta: event.metaKey, + originalEvent: event, + preventDefaultAction: false, + userData: tracker.userData + } + ); + if ( !propagate ) { + $.cancelEvent( event ); + } + } + } + + + /** + * @private + * @inner + */ + function onKeyUp( tracker, event ) { + //$.console.log( "keyup %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey ); + var propagate; + if ( tracker.keyUpHandler ) { + event = $.getEvent( event ); + propagate = tracker.keyUpHandler( + { + eventSource: tracker, + position: getMouseRelative( event, tracker.element ), + keyCode: event.keyCode ? event.keyCode : event.charCode, + ctrl: event.ctrlKey, + shift: event.shiftKey, + alt: event.altKey, + meta: event.metaKey, + originalEvent: event, + preventDefaultAction: false, + userData: tracker.userData + } + ); + if ( !propagate ) { + $.cancelEvent( event ); + } + } + } + + /** * @private * @inner */ function onKeyPress( tracker, event ) { - //console.log( "keypress %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey ); + //$.console.log( "keypress %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey ); var propagate; if ( tracker.keyHandler ) { event = $.getEvent( event ); @@ -1454,7 +1580,10 @@ eventSource: tracker, position: getMouseRelative( event, tracker.element ), keyCode: event.keyCode ? event.keyCode : event.charCode, + ctrl: event.ctrlKey, shift: event.shiftKey, + alt: event.altKey, + meta: event.metaKey, originalEvent: event, preventDefaultAction: false, userData: tracker.userData diff --git a/src/referencestrip.js b/src/referencestrip.js index f68744b2..a872ca58 100644 --- a/src/referencestrip.js +++ b/src/referencestrip.js @@ -125,6 +125,7 @@ $.ReferenceStrip = function ( options ) { scrollHandler: $.delegate( this, onStripScroll ), enterHandler: $.delegate( this, onStripEnter ), exitHandler: $.delegate( this, onStripExit ), + keyDownHandler: $.delegate( this, onKeyDown ), keyHandler: $.delegate( this, onKeyPress ) } ).setTracking( true ); @@ -511,6 +512,37 @@ function onStripExit( event ) { } +/** + * @private + * @inner + * @function + */ +function onKeyDown( event ) { + //console.log( event.keyCode ); + + if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) { + switch ( event.keyCode ) { + case 38: //up arrow + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); + return false; + case 40: //down arrow + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); + return false; + case 37: //left arrow + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); + return false; + case 39: //right arrow + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); + return false; + default: + //console.log( 'navigator keycode %s', event.keyCode ); + return true; + } + } else { + return true; + } +} + /** * @private @@ -520,35 +552,35 @@ function onStripExit( event ) { function onKeyPress( event ) { //console.log( event.keyCode ); - switch ( event.keyCode ) { - case 61: //=|+ - onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); - return false; - case 45: //-|_ - onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); - return false; - case 48: //0|) - case 119: //w - case 87: //W - case 38: //up arrow - onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); - return false; - case 115: //s - case 83: //S - case 40: //down arrow - onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); - return false; - case 97: //a - case 37: //left arrow - onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); - return false; - case 100: //d - case 39: //right arrow - onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); - return false; - default: - //console.log( 'navigator keycode %s', event.keyCode ); - return true; + if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) { + switch ( event.keyCode ) { + case 61: //=|+ + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); + return false; + case 45: //-|_ + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); + return false; + case 48: //0|) + case 119: //w + case 87: //W + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); + return false; + case 115: //s + case 83: //S + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); + return false; + case 97: //a + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } ); + return false; + case 100: //d + onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } ); + return false; + default: + //console.log( 'navigator keycode %s', event.keyCode ); + return true; + } + } else { + return true; } } diff --git a/src/viewer.js b/src/viewer.js index 215f9432..1a7490d0 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -325,8 +325,44 @@ $.Viewer = function( options ) { } }, + keyDownHandler: function( event ){ + if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) { + switch( event.keyCode ){ + case 38://up arrow + if ( event.shift ) { + _this.viewport.zoomBy(1.1); + } else { + _this.viewport.panBy(new $.Point(0, -0.05)); + } + _this.viewport.applyConstraints(); + return false; + case 40://down arrow + if ( event.shift ) { + _this.viewport.zoomBy(0.9); + } else { + _this.viewport.panBy(new $.Point(0, 0.05)); + } + _this.viewport.applyConstraints(); + return false; + case 37://left arrow + _this.viewport.panBy(new $.Point(-0.05, 0)); + _this.viewport.applyConstraints(); + return false; + case 39://right arrow + _this.viewport.panBy(new $.Point(0.05, 0)); + _this.viewport.applyConstraints(); + return false; + default: + //console.log( 'navigator keycode %s', event.keyCode ); + return true; + } + } else { + return true; + } + }, + keyHandler: function( event ){ - if ( !event.preventDefaultAction ) { + if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) { switch( event.keyCode ){ case 61://=|+ _this.viewport.zoomBy(1.1); @@ -342,7 +378,6 @@ $.Viewer = function( options ) { return false; case 119://w case 87://W - case 38://up arrow if ( event.shift ) { _this.viewport.zoomBy(1.1); } else { @@ -352,7 +387,6 @@ $.Viewer = function( options ) { return false; case 115://s case 83://S - case 40://down arrow if ( event.shift ) { _this.viewport.zoomBy(0.9); } else { @@ -361,12 +395,10 @@ $.Viewer = function( options ) { _this.viewport.applyConstraints(); return false; case 97://a - case 37://left arrow _this.viewport.panBy(new $.Point(-0.05, 0)); _this.viewport.applyConstraints(); return false; case 100://d - case 39://right arrow _this.viewport.panBy(new $.Point(0.05, 0)); _this.viewport.applyConstraints(); return false; @@ -374,6 +406,8 @@ $.Viewer = function( options ) { //console.log( 'navigator keycode %s', event.keyCode ); return true; } + } else { + return true; } } }).setTracking( true ); // default state From abacc50820f1142bda40246d7e9cf0ba0f3216a3 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Fri, 16 Jan 2015 16:31:23 -0800 Subject: [PATCH 12/16] changelog update --- changelog.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog.txt b/changelog.txt index ad7ff86f..2f0b38a6 100644 --- a/changelog.txt +++ b/changelog.txt @@ -15,6 +15,9 @@ OPENSEADRAGON CHANGELOG * Removed Viewer onContainerPress/onContainerRelease handlers (and the associated 'container-release' event ) that were never fired due to the canvas (child) element capturing the DOM events (#566) * Added 'canvas-enter', 'canvas-exit', and 'canvas-press' events to Viewer (#566) * ButtonGroup - removed obsolete MouseTracker event handlers (#566) +* MouseTracker - added keydown and keyup handlers (#568) +* Modifier keys ignored in keyboard navigation handlers (#503) +* Arrow key navigation fixed across platforms (#565) 1.2.0: From 412ebce94d8b8eca0f3ddeb2d9a128f7b9d5de87 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Sat, 17 Jan 2015 11:18:55 -0800 Subject: [PATCH 13/16] Keyboard navigation fixes Removed textarea element from Viewer DOM. Viewer.canvas now handles keyboard navigation. --- changelog.txt | 1 + src/viewer.js | 226 ++++++++++++++++++++++---------------------------- 2 files changed, 100 insertions(+), 127 deletions(-) diff --git a/changelog.txt b/changelog.txt index 2f0b38a6..ef16389a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -18,6 +18,7 @@ OPENSEADRAGON CHANGELOG * MouseTracker - added keydown and keyup handlers (#568) * Modifier keys ignored in keyboard navigation handlers (#503) * Arrow key navigation fixed across platforms (#565) +* Removed textarea element from viewer DOM. Viewer.canvas now handles keyboard navigation 1.2.0: diff --git a/src/viewer.js b/src/viewer.js index 7e20ec85..0e2fa818 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -108,14 +108,6 @@ $.Viewer = function( options ) { * @memberof OpenSeadragon.Viewer# */ container: null, - /** - * A <textarea> element, the element where keyboard events are handled.

- * Child element of {@link OpenSeadragon.Viewer#container}, - * positioned below {@link OpenSeadragon.Viewer#canvas}. - * @member {Element} keyboardCommandArea - * @memberof OpenSeadragon.Viewer# - */ - keyboardCommandArea: null, /** * A <div> element, the element where user-input events are handled for panning and zooming.

* Child element of {@link OpenSeadragon.Viewer#container}, @@ -263,7 +255,6 @@ $.Viewer = function( options ) { this.element = this.element || document.getElementById( this.id ); this.canvas = $.makeNeutralElement( "div" ); - this.keyboardCommandArea = $.makeNeutralElement( "textarea" ); this.drawersContainer = $.makeNeutralElement( "div" ); this.overlaysContainer = $.makeNeutralElement( "div" ); @@ -277,6 +268,7 @@ $.Viewer = function( options ) { style.left = "0px"; }(this.canvas.style)); $.setElementTouchActionNone( this.canvas ); + this.canvas.tabIndex = 0; //the container is created through applying the ControlDock constructor above this.container.className = "openseadragon-container"; @@ -290,21 +282,7 @@ $.Viewer = function( options ) { style.textAlign = "left"; // needed to protect against }( this.container.style )); - this.keyboardCommandArea.className = "keyboard-command-area"; - (function( style ){ - style.width = "100%"; - style.height = "100%"; - style.overflow = "hidden"; - style.position = "absolute"; - style.top = "0px"; - style.left = "0px"; - style.resize = "none"; - }( this.keyboardCommandArea.style )); - // Set read-only - hides keyboard on mobile devices, still allows input. - this.keyboardCommandArea.readOnly = true; - this.container.insertBefore( this.canvas, this.container.firstChild ); - this.container.insertBefore( this.keyboardCommandArea, this.container.firstChild ); this.element.appendChild( this.container ); this.canvas.appendChild( this.drawersContainer ); this.canvas.appendChild( this.overlaysContainer ); @@ -317,110 +295,15 @@ $.Viewer = function( options ) { this.bodyOverflow = document.body.style.overflow; this.docOverflow = document.documentElement.style.overflow; - this.keyboardCommandArea.innerTracker = new $.MouseTracker({ - _this : this, - element: this.keyboardCommandArea, - focusHandler: function( event ){ - if ( !event.preventDefaultAction ) { - var point = $.getElementPosition( this.element ); - window.scrollTo( 0, point.y ); - } - }, - - keyDownHandler: function( event ){ - if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) { - switch( event.keyCode ){ - case 38://up arrow - if ( event.shift ) { - _this.viewport.zoomBy(1.1); - } else { - _this.viewport.panBy(new $.Point(0, -0.05)); - } - _this.viewport.applyConstraints(); - return false; - case 40://down arrow - if ( event.shift ) { - _this.viewport.zoomBy(0.9); - } else { - _this.viewport.panBy(new $.Point(0, 0.05)); - } - _this.viewport.applyConstraints(); - return false; - case 37://left arrow - _this.viewport.panBy(new $.Point(-0.05, 0)); - _this.viewport.applyConstraints(); - return false; - case 39://right arrow - _this.viewport.panBy(new $.Point(0.05, 0)); - _this.viewport.applyConstraints(); - return false; - default: - //console.log( 'navigator keycode %s', event.keyCode ); - return true; - } - } else { - return true; - } - }, - - keyHandler: function( event ){ - if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) { - switch( event.keyCode ){ - case 61://=|+ - _this.viewport.zoomBy(1.1); - _this.viewport.applyConstraints(); - return false; - case 45://-|_ - _this.viewport.zoomBy(0.9); - _this.viewport.applyConstraints(); - return false; - case 48://0|) - _this.viewport.goHome(); - _this.viewport.applyConstraints(); - return false; - case 119://w - case 87://W - if ( event.shift ) { - _this.viewport.zoomBy(1.1); - } else { - _this.viewport.panBy(new $.Point(0, -0.05)); - } - _this.viewport.applyConstraints(); - return false; - case 115://s - case 83://S - if ( event.shift ) { - _this.viewport.zoomBy(0.9); - } else { - _this.viewport.panBy(new $.Point(0, 0.05)); - } - _this.viewport.applyConstraints(); - return false; - case 97://a - _this.viewport.panBy(new $.Point(-0.05, 0)); - _this.viewport.applyConstraints(); - return false; - case 100://d - _this.viewport.panBy(new $.Point(0.05, 0)); - _this.viewport.applyConstraints(); - return false; - default: - //console.log( 'navigator keycode %s', event.keyCode ); - return true; - } - } else { - return true; - } - } - }).setTracking( true ); // default state - - this.innerTracker = new $.MouseTracker({ element: this.canvas, clickTimeThreshold: this.clickTimeThreshold, clickDistThreshold: this.clickDistThreshold, dblClickTimeThreshold: this.dblClickTimeThreshold, dblClickDistThreshold: this.dblClickDistThreshold, + focusHandler: $.delegate( this, onCanvasFocus ), + keyDownHandler: $.delegate( this, onCanvasKeyDown ), + keyHandler: $.delegate( this, onCanvasKeyPress ), clickHandler: $.delegate( this, onCanvasClick ), dblClickHandler: $.delegate( this, onCanvasDblClick ), dragHandler: $.delegate( this, onCanvasDrag ), @@ -648,9 +531,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, } // destroy the mouse trackers - if (this.keyboardCommandArea){ - this.keyboardCommandArea.innerTracker.destroy(); - } if (this.innerTracker){ this.innerTracker.destroy(); } @@ -663,7 +543,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, // clear all our references to dom objects this.canvas = null; - this.keyboardCommandArea = null; this.container = null; // clear our reference to the main element - they will need to pass it in again, creating a new viewer @@ -2309,14 +2188,107 @@ function onBlur(){ } +function onCanvasFocus( event ) { + if ( !event.preventDefaultAction ) { + var point = $.getElementPosition( this.element ); + window.scrollTo( 0, point.y ); + } +} + +function onCanvasKeyDown( event ) { + if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) { + switch( event.keyCode ){ + case 38://up arrow + if ( event.shift ) { + _this.viewport.zoomBy(1.1); + } else { + _this.viewport.panBy(new $.Point(0, -0.05)); + } + _this.viewport.applyConstraints(); + return false; + case 40://down arrow + if ( event.shift ) { + _this.viewport.zoomBy(0.9); + } else { + _this.viewport.panBy(new $.Point(0, 0.05)); + } + _this.viewport.applyConstraints(); + return false; + case 37://left arrow + _this.viewport.panBy(new $.Point(-0.05, 0)); + _this.viewport.applyConstraints(); + return false; + case 39://right arrow + _this.viewport.panBy(new $.Point(0.05, 0)); + _this.viewport.applyConstraints(); + return false; + default: + //console.log( 'navigator keycode %s', event.keyCode ); + return true; + } + } else { + return true; + } +} + +function onCanvasKeyPress( event ) { + if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) { + switch( event.keyCode ){ + case 61://=|+ + _this.viewport.zoomBy(1.1); + _this.viewport.applyConstraints(); + return false; + case 45://-|_ + _this.viewport.zoomBy(0.9); + _this.viewport.applyConstraints(); + return false; + case 48://0|) + _this.viewport.goHome(); + _this.viewport.applyConstraints(); + return false; + case 119://w + case 87://W + if ( event.shift ) { + _this.viewport.zoomBy(1.1); + } else { + _this.viewport.panBy(new $.Point(0, -0.05)); + } + _this.viewport.applyConstraints(); + return false; + case 115://s + case 83://S + if ( event.shift ) { + _this.viewport.zoomBy(0.9); + } else { + _this.viewport.panBy(new $.Point(0, 0.05)); + } + _this.viewport.applyConstraints(); + return false; + case 97://a + _this.viewport.panBy(new $.Point(-0.05, 0)); + _this.viewport.applyConstraints(); + return false; + case 100://d + _this.viewport.panBy(new $.Point(0.05, 0)); + _this.viewport.applyConstraints(); + return false; + default: + //console.log( 'navigator keycode %s', event.keyCode ); + return true; + } + } else { + return true; + } +} + function onCanvasClick( event ) { var gestureSettings; - var haveKeyboardFocus = document.activeElement == this.keyboardCommandArea; + var haveKeyboardFocus = document.activeElement == this.canvas; // If we don't have keyboard focus, request it. if ( !haveKeyboardFocus ) { - this.keyboardCommandArea.focus(); + this.canvas.focus(); } if ( !event.preventDefaultAction && this.viewport && event.quick ) { From b5b4131f49fee7b06933219b7f88aaa9283b0bc2 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Sat, 17 Jan 2015 11:21:16 -0800 Subject: [PATCH 14/16] changelog update --- changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index ef16389a..28631801 100644 --- a/changelog.txt +++ b/changelog.txt @@ -18,7 +18,7 @@ OPENSEADRAGON CHANGELOG * MouseTracker - added keydown and keyup handlers (#568) * Modifier keys ignored in keyboard navigation handlers (#503) * Arrow key navigation fixed across platforms (#565) -* Removed textarea element from viewer DOM. Viewer.canvas now handles keyboard navigation +* Removed textarea element from viewer DOM. Viewer.canvas now handles keyboard navigation (#569) 1.2.0: From 0f5c205656c1f68db0d8f42f63c2a4e64afc2c05 Mon Sep 17 00:00:00 2001 From: Mark Salsbery Date: Sat, 17 Jan 2015 11:25:24 -0800 Subject: [PATCH 15/16] Cut/pasted code fix --- src/viewer.js | 52 +++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/viewer.js b/src/viewer.js index 0e2fa818..04587719 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -2200,27 +2200,27 @@ function onCanvasKeyDown( event ) { switch( event.keyCode ){ case 38://up arrow if ( event.shift ) { - _this.viewport.zoomBy(1.1); + this.viewport.zoomBy(1.1); } else { - _this.viewport.panBy(new $.Point(0, -0.05)); + this.viewport.panBy(new $.Point(0, -0.05)); } - _this.viewport.applyConstraints(); + this.viewport.applyConstraints(); return false; case 40://down arrow if ( event.shift ) { - _this.viewport.zoomBy(0.9); + this.viewport.zoomBy(0.9); } else { - _this.viewport.panBy(new $.Point(0, 0.05)); + this.viewport.panBy(new $.Point(0, 0.05)); } - _this.viewport.applyConstraints(); + this.viewport.applyConstraints(); return false; case 37://left arrow - _this.viewport.panBy(new $.Point(-0.05, 0)); - _this.viewport.applyConstraints(); + this.viewport.panBy(new $.Point(-0.05, 0)); + this.viewport.applyConstraints(); return false; case 39://right arrow - _this.viewport.panBy(new $.Point(0.05, 0)); - _this.viewport.applyConstraints(); + this.viewport.panBy(new $.Point(0.05, 0)); + this.viewport.applyConstraints(); return false; default: //console.log( 'navigator keycode %s', event.keyCode ); @@ -2235,42 +2235,42 @@ function onCanvasKeyPress( event ) { if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) { switch( event.keyCode ){ case 61://=|+ - _this.viewport.zoomBy(1.1); - _this.viewport.applyConstraints(); + this.viewport.zoomBy(1.1); + this.viewport.applyConstraints(); return false; case 45://-|_ - _this.viewport.zoomBy(0.9); - _this.viewport.applyConstraints(); + this.viewport.zoomBy(0.9); + this.viewport.applyConstraints(); return false; case 48://0|) - _this.viewport.goHome(); - _this.viewport.applyConstraints(); + this.viewport.goHome(); + this.viewport.applyConstraints(); return false; case 119://w case 87://W if ( event.shift ) { - _this.viewport.zoomBy(1.1); + this.viewport.zoomBy(1.1); } else { - _this.viewport.panBy(new $.Point(0, -0.05)); + this.viewport.panBy(new $.Point(0, -0.05)); } - _this.viewport.applyConstraints(); + this.viewport.applyConstraints(); return false; case 115://s case 83://S if ( event.shift ) { - _this.viewport.zoomBy(0.9); + this.viewport.zoomBy(0.9); } else { - _this.viewport.panBy(new $.Point(0, 0.05)); + this.viewport.panBy(new $.Point(0, 0.05)); } - _this.viewport.applyConstraints(); + this.viewport.applyConstraints(); return false; case 97://a - _this.viewport.panBy(new $.Point(-0.05, 0)); - _this.viewport.applyConstraints(); + this.viewport.panBy(new $.Point(-0.05, 0)); + this.viewport.applyConstraints(); return false; case 100://d - _this.viewport.panBy(new $.Point(0.05, 0)); - _this.viewport.applyConstraints(); + this.viewport.panBy(new $.Point(0.05, 0)); + this.viewport.applyConstraints(); return false; default: //console.log( 'navigator keycode %s', event.keyCode ); From 2bd105042af80586500e3b39c8b9d786c443e108 Mon Sep 17 00:00:00 2001 From: Ian Gilman Date: Mon, 19 Jan 2015 10:31:30 -0800 Subject: [PATCH 16/16] Changelog for #537 --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index 28631801..e3bd3156 100644 --- a/changelog.txt +++ b/changelog.txt @@ -17,6 +17,7 @@ OPENSEADRAGON CHANGELOG * ButtonGroup - removed obsolete MouseTracker event handlers (#566) * MouseTracker - added keydown and keyup handlers (#568) * Modifier keys ignored in keyboard navigation handlers (#503) +* Requesting keyboard focus when viewer is clicked (#537) * Arrow key navigation fixed across platforms (#565) * Removed textarea element from viewer DOM. Viewer.canvas now handles keyboard navigation (#569)