-
Notifications
You must be signed in to change notification settings - Fork 3
/
event.js
360 lines (337 loc) · 13.2 KB
/
event.js
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
/*
* Copyright 2009, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @fileoverview This file contains various event related functions for
* o3d. It puts them in the 'event' module on the o3djs object.
*
* TODO Add selenium tests.
*
*
*/
o3djs.provide('o3djs.event');
/**
* A Module for handling events related to o3d and various browsers.
* @namespace
*/
o3djs.event = o3djs.event || {};
/**
* @param {string} inStr base string.
* @param {string} extraStr string to append.
* @return {string} inStr + ' ' + extraStr, or just inStr if extraStr is ''.
*/
o3djs.event.appendWithSpace = function(inStr, extraStr) {
return (inStr.length == 0) ? extraStr : inStr + ' ' + extraStr;
};
/**
* @param {boolean} state whether to append or not.
* @param {string} inStr base string.
* @param {string} extraStr string to append.
* @return {string} inStr + ' ' + extraStr, or just inStr if state is false.
*/
o3djs.event.appendWithSpaceIf = function(state, inStr, extraStr) {
return (state) ? o3djs.event.appendWithSpace(inStr, extraStr) : inStr;
};
/**
* Builds a DOM-level 3 modifier string for a KeyboardEvent - see
* http://www.w3.org/TR/DOM-Level-3-Events/events.html
* #Events-KeyboardEvents-Interfaces.
* @param {boolean} control whether the control key is down.
* @param {boolean} alt whether the alt/option key is down.
* @param {boolean} shift whether the shift key is down.
* @param {boolean} meta whether the meta/command key is down.
* @return {string} space delimited list of keys that are down.
*/
o3djs.event.getModifierString = function(control, alt, shift, meta) {
var modStr = o3djs.event.appendWithSpaceIf(control, '', 'Control');
modStr = o3djs.event.appendWithSpaceIf(alt, modStr, 'Alt');
modStr = o3djs.event.appendWithSpaceIf(shift, modStr, 'Shift');
return o3djs.event.appendWithSpaceIf(meta, modStr, 'Meta');
};
/**
* Pad a string with leading zeroes if needed until it is the length desired.
* @param {string} str The input string, probably representing a number.
* @param {number} to_length The desired minimum length of string with padding.
* @return {string} A string padded with leading zeroes as needed to be the
* length desired.
*/
o3djs.event.padWithLeadingZeroes = function(str, to_length) {
while (str.length < to_length)
str = '0' + str;
return str;
};
/**
* Creates a keyIdentifer string for a given keystroke as specified in the w3c
* spec on http://www.w3.org/TR/DOM-Level-3-Events/events.html.
* @param {number} charCode numeric unicode code point as reported by the OS.
* @param {number} keyCode numeric keyCode as reported by the OS, currently
* unused but will probably be necessary in the future.
* @return {string} eg 'Left' or 'U+0040'.
*/
o3djs.event.getKeyIdentifier = function(charCode, keyCode) {
if (!charCode) {
// TODO: This works for webkit for keydown and keyup, for basic
// alphanumeric keys, at least. Likely it needs lots of work to handle
// accented characters, various keyboards, etc., as does the rest of our
// keyboard event code.
charCode = keyCode;
}
switch (charCode) {
case 3: case 13: return 'Enter'; // spec merges these.
case 37: return 'Left';
case 39: return 'Right';
case 38: return 'Up';
case 40: return 'Down';
}
charCode = (charCode >= 97 && charCode <= 122) ? charCode - 32 : charCode;
var keyStr = charCode.toString(16).toUpperCase();
return 'U+' + o3djs.event.padWithLeadingZeroes(keyStr, 4);
};
/** Takes a keyIdentifier string and remaps it to an ASCII/Unicode value
* suitable for javascript event handling.
* @param {string} keyIdent a keyIdentifier string as generated above.
* @return {number} the numeric Unicode code point represented.
*/
o3djs.event.keyIdentifierToChar = function(keyIdent) {
if (keyIdent && typeof(keyIdent) == 'string') {
switch (keyIdent) {
case 'Enter': return 13;
case 'Left': return 37;
case 'Right': return 39;
case 'Up': return 38;
case 'Down': return 40;
}
if (keyIdent.indexOf('U+') == 0)
return parseInt(keyIdent.substr(2).toUpperCase(), 16);
}
return 0;
};
/**
* Extracts the key char in number form from the event, in a cross-browser
* manner.
* @param {!Event} event .
* @return {number} unicode code point for the key.
*/
o3djs.event.getEventKeyChar = function(event) {
if (!event) {
event = window.event;
}
var charCode = 0;
if (event.keyIdentifier)
charCode = o3djs.event.keyIdentifierToChar(event.keyIdentifier);
if (!charCode)
charCode = (window.event) ? window.event.keyCode : event.charCode;
if (!charCode)
charCode = event.keyCode;
return charCode;
};
/**
* Cancel an event we've handled so it stops propagating upwards.
* The cancelBubble is for IE, stopPropagation is for all other browsers.
* preventDefault ensures that the default action is also canceled.
* @param {!Event} event - the event to cancel.
*/
o3djs.event.cancel = function(event) {
if (!event)
event = window.event;
event.cancelBubble = true;
if (event.stopPropagation)
event.stopPropagation();
if (event.preventDefault)
event.preventDefault();
};
/**
* Convenience function to setup synthesizing and dispatching of keyboard events
* whenever the focussed plug-in calls Javascript to report a keyboard action.
* @param {!Element} pluginObject the <object> where the o3d plugin lives,
* which the caller probably obtained by calling getElementById.
*/
o3djs.event.startKeyboardEventSynthesis = function(pluginObject) {
var handler = function(event) {
o3djs.event.onKey(event, pluginObject);
};
o3djs.event.addEventListener(pluginObject, 'keypress', handler);
o3djs.event.addEventListener(pluginObject, 'keydown', handler);
o3djs.event.addEventListener(pluginObject, 'keyup', handler);
};
/**
* Dispatches a DOM-level 3 KeyboardEvent when called back by the plugin.
* see http://www.w3.org/TR/DOM-Level-3-Events/events.html
* #Events-KeyboardEvents-Interfaces
* see http://developer.mozilla.org/en/DOM/event.initKeyEvent
* @param {!Event} event an O3D event object.
* @param {!Element} pluginObject the plugin object on the page.
*/
o3djs.event.onKey = function(event, pluginObject) {
var k_evt = o3djs.event.createKeyEvent(event.type, event.charCode,
event.keyCode, event.ctrlKey, event.altKey, event.shiftKey,
event.metaKey);
if (k_evt) {
if (pluginObject.parentNode.dispatchEvent) {
// Using the pluginObject itself fails for non-capturing event listeners
// on keypress events on Firefox only, as far as I've been able to
// determine. I have no idea why.
pluginObject.parentNode.dispatchEvent(k_evt);
} else if (pluginObject.fireEvent) {
pluginObject.fireEvent('on' + event.type, k_evt);
}
}
};
/**
* Creates a DOM-level 3 KeyboardEvent.
* see http://www.w3.org/TR/DOM-Level-3-Events/events.html
* #Events-KeyboardEvents-Interfaces.
* see http://developer.mozilla.org/en/DOM/event.initKeyEvent
* @param {string} eventName one of 'keypress', 'keydown' or 'keyup'.
* @param {number} charCode the character code for the key.
* @param {number} keyCode the key code for the key.
* @param {boolean} control whether the control key is down.
* @param {boolean} alt whether the alt/option key is down.
* @param {boolean} shift whether the shift key is down.
* @param {boolean} meta whether the meta/command key is down.
*/
o3djs.event.createKeyEvent = function(eventName, charCode, keyCode,
control, alt, shift, meta) {
var k_evt;
var keyIdentifier = o3djs.event.getKeyIdentifier(charCode, keyCode);
if (document.createEvent) {
k_evt = document.createEvent('KeyboardEvent');
if (k_evt.initKeyboardEvent) { // WebKit.
k_evt.initKeyboardEvent(eventName, true, true, window,
keyIdentifier, 0,
control, alt, shift, meta);
// TODO: These actually fail to do anything in Chrome; those are
// read-only fields, and it's not setting them in initKeyboardEvent.
k_evt.charCode = charCode;
if (eventName == 'keypress')
k_evt.keyCode = charCode;
else
k_evt.keyCode = keyCode;
} else if (k_evt.initKeyEvent) { // FF.
k_evt.initKeyEvent(eventName, true, true, window,
control, alt, shift, meta, keyCode, charCode);
k_evt.keyIdentifier = keyIdentifier;
}
} else if (document.createEventObject) {
k_evt = document.createEventObject();
k_evt.ctrlKey = control;
k_evt.altKey = alt;
k_evt.shiftKey = shift;
k_evt.metaKey = meta;
k_evt.keyCode = charCode; // Emulate IE charcode-in-the-keycode onkeypress.
k_evt.keyIdentifier = keyIdentifier;
}
k_evt.synthetic = true;
return k_evt;
};
/*
* Function to create a closure that will call each event handler in an array
* whenever it gets called, passing its single argument through to the
* sub-handlers. The sub-handlers may either be functions or EventListeners.
* This is generally expected to be used only through
* o3djs.event.addEventListener.
* @param {!Array.<*>} listenerSet an array of handlers.
* @return {!function(*): void} a closure to be used to multiplex out
* event-handling.
*/
o3djs.event.createEventHandler = function(listenerSet) {
return function(event) {
var length = listenerSet.length;
for (var index = 0; index < length; ++index) {
var handler = listenerSet[index];
if (typeof(handler.handleEvent) == 'function') {
handler.handleEvent(event);
} else {
handler(event);
}
}
}
};
/**
* Convenience function to manage event listeners on the o3d plugin object,
* intended as a drop-in replacement for the DOM addEventListener [with slightly
* different arguments, but the same effect].
* @param {!Element} pluginObject the html object where the o3d plugin lives,
* which the caller probably obtained by calling getElementById or makeClients.
* @param {string} type the event type on which to trigger, e.g. 'mousedown',
* 'mousemove', etc.
* @param {!Object} handler either a function or an EventListener object.
*/
o3djs.event.addEventListener = function(pluginObject, type, handler) {
if (!handler || typeof(type) != 'string' ||
(typeof(handler) != 'function' &&
typeof(handler.handleEvent) != 'function')) {
throw new Error('Invalid argument.');
}
pluginObject.o3d_eventRegistry = pluginObject.o3d_eventRegistry || [];
var registry = pluginObject.o3d_eventRegistry;
var listenerSet = registry[type];
if (!listenerSet || listenerSet.length == 0) {
listenerSet = registry[type] = [];
pluginObject.client.setEventCallback(type,
o3djs.event.createEventHandler(listenerSet));
} else {
for (var index in listenerSet) {
if (listenerSet[index] == handler) {
return; // We're idempotent.
}
}
}
listenerSet.push(handler);
};
/**
* Convenience function to manage event listeners on the o3d plugin object,
* intended as a drop-in replacement for the DOM removeEventListener [with
* slightly different arguments, but the same effect].
* @param {!Element} pluginObject the <object> where the o3d plugin lives,
* which the caller probably obtained by calling getElementById.
* @param {string} type the event type on which the handler to be removed was to
* trigger, e.g. 'mousedown', 'mousemove', etc.
* @param {!Object} handler either a function or an EventListener object.
*/
o3djs.event.removeEventListener = function(pluginObject, type, handler) {
var registry = pluginObject.o3d_eventRegistry;
if (!registry) {
return;
}
var listenerSet = registry[type];
if (!listenerSet) {
return;
}
for (var index in listenerSet) {
if (listenerSet[index] == handler) {
if (listenerSet.length == 1) {
pluginObject.client.clearEventCallback(type);
}
listenerSet.splice(index, 1);
break;
}
}
};