summaryrefslogtreecommitdiff
path: root/lib/dijit/a11yclick.js.uncompressed.js
blob: ed92308dd074e48d0ebe5608bf979fccf7ad059b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
define("dijit/a11yclick", [
	"dojo/on",
	"dojo/_base/array", // array.forEach
	"dojo/keys", // keys.ENTER keys.SPACE
	"dojo/_base/declare", // declare
	"dojo/has", // has("dom-addeventlistener")
	"dojo/_base/unload", // unload.addOnWindowUnload
	"dojo/_base/window" // win.doc.addEventListener win.doc.attachEvent win.doc.detachEvent
], function(on, array, keys, declare, has, unload, win){

	// module:
	//		dijit/a11yclick

	// Keep track of where the last keydown event was, to help avoid generating
	// spurious ondijitclick events when:
	// 1. focus is on a <button> or <a>
	// 2. user presses then releases the ENTER key
	// 3. onclick handler fires and shifts focus to another node, with an ondijitclick handler
	// 4. onkeyup event fires, causing the ondijitclick handler to fire
	var lastKeyDownNode = null;
	if(has("dom-addeventlistener")){
		win.doc.addEventListener('keydown', function(evt){
			lastKeyDownNode = evt.target;
		}, true);
	}else{
		// Fallback path for IE6-8
		(function(){
			var keydownCallback = function(evt){
				lastKeyDownNode = evt.srcElement;
			};
			win.doc.attachEvent('onkeydown', keydownCallback);
			unload.addOnWindowUnload(function(){
				win.doc.detachEvent('onkeydown', keydownCallback);
			});
		})();
	}

	function clickKey(/*Event*/ e){
		return (e.keyCode === keys.ENTER || e.keyCode === keys.SPACE) &&
			!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey;
	}

	return function(node, listener){
		// summary:
		//		Custom a11yclick (a.k.a. ondijitclick) event
		//		which triggers on a mouse click, touch, or space/enter keyup.

		if(/input|button/i.test(node.nodeName)){
			// pass through, the browser already generates click event on SPACE/ENTER key
			return on(node, "click", listener);
		}else{
			// Don't fire the click event unless both the keydown and keyup occur on this node.
			// Avoids problems where focus shifted to this node or away from the node on keydown,
			// either causing this node to process a stray keyup event, or causing another node
			// to get a stray keyup event.

			var handles = [
				on(node, "keydown", function(e){
					//console.log(this.id + ": onkeydown, e.target = ", e.target, ", lastKeyDownNode was ", lastKeyDownNode, ", equality is ", (e.target === lastKeyDownNode));
					if(clickKey(e)){
						// needed on IE for when focus changes between keydown and keyup - otherwise dropdown menus do not work
						lastKeyDownNode = e.target;

						// Prevent viewport scrolling on space key in IE<9.
						// (Reproducible on test_Button.html on any of the first dijit/form/Button examples)
						e.preventDefault();
					}
				}),

				on(node, "keyup", function(e){
					//console.log(this.id + ": onkeyup, e.target = ", e.target, ", lastKeyDownNode was ", lastKeyDownNode, ", equality is ", (e.target === lastKeyDownNode));
					if(clickKey(e) && e.target == lastKeyDownNode){	// === breaks greasemonkey
						//need reset here or have problems in FF when focus returns to trigger element after closing popup/alert
						lastKeyDownNode = null;
						on.emit(e.target, "click", {
							cancelable: true,
							bubbles: true
						});
					}
				}),

				on(node, "click", function(e){
					// catch mouse clicks, plus the on.emit() calls from above and below
					listener.call(this, e);
				})
			];

			if(has("touch")){
				// touchstart-->touchend will automatically generate a click event, but there are problems
				// on iOS after focus has been programatically shifted (#14604, #14918), so setup a failsafe
				// if click doesn't fire naturally.

				var clickTimer;
				handles.push(
					on(node, "touchend", function(e){
						var target = e.target;
						clickTimer = setTimeout(function(){
							clickTimer = null;
							on.emit(target, "click", {
								cancelable: true,
								bubbles: true
							});
						}, 600);
					}),
					on(node, "click", function(e){
						// If browser generates a click naturally, clear the timer to fire a synthetic click event
						if(clickTimer){
							clearTimeout(clickTimer);
						}
					})
					// TODO: if the touchstart and touchend were <100ms apart, and then there's another touchstart
					// event <300ms after the touchend event, then clear the synthetic click timer, because user
					// is doing a zoom.   Alternately monitor screen.deviceXDPI (or something similar) to see if
					// zoom level has changed.
				);
			}

			return {
				remove: function(){
					array.forEach(handles, function(h){ h.remove(); });
					if(clickTimer){
						clearTimeout(clickTimer);
						clickTimer = null;
					}
				}
			};
		}
	};

	return ret;
});