Merge lp:~divmod-dev/divmod.org/athena-events-806545 into lp:divmod.org
- athena-events-806545
- Merge into trunk
Proposed by
Jonathan Jacobs
Status: | Needs review |
---|---|
Proposed branch: | lp:~divmod-dev/divmod.org/athena-events-806545 |
Merge into: | lp:divmod.org |
Diff against target: |
989 lines (+870/-13) 8 files modified
Nevow/nevow/athena.py (+1/-1) Nevow/nevow/js/Divmod/Runtime/__init__.js (+33/-0) Nevow/nevow/js/Divmod/Test/TestRuntime.js (+94/-0) Nevow/nevow/js/Nevow/Athena/__init__.js (+231/-11) Nevow/nevow/js/Nevow/Test/TestEvent.js (+504/-0) Nevow/nevow/js/Nevow/Test/TestWidget.js (+2/-0) Nevow/nevow/test/test_athena.py (+1/-1) Nevow/nevow/test/test_javascript.py (+4/-0) |
To merge this branch: | bzr merge lp:~divmod-dev/divmod.org/athena-events-806545 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Divmod-dev | Pending | ||
Review via email: mp+67455@code.launchpad.net |
Commit message
Description of the change
Wrap DOM events in a universal way and pass these to Athena event handlers.
To post a comment you must log in.
- 2680. By Jonathan Jacobs
-
Fix typo.
- 2681. By Jonathan Jacobs
-
Fix failing test related to event changes.
Unmerged revisions
- 2681. By Jonathan Jacobs
-
Fix failing test related to event changes.
- 2680. By Jonathan Jacobs
-
Fix typo.
- 2679. By Jonathan Jacobs
-
Fix failing tests.
- 2678. By Jonathan Jacobs
-
Wrap DOM events in a universal event wrapper.
- 2677. By Jonathan Jacobs
-
Implement getMouseButtons
FromEvent for Platform and InternetExplorer in Divmod.Runtime. - 2676. By Jonathan Jacobs
-
Pass the raw browser event object to Athena event handlers.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Nevow/nevow/athena.py' |
2 | --- Nevow/nevow/athena.py 2010-07-12 19:00:11 +0000 |
3 | +++ Nevow/nevow/athena.py 2011-07-10 13:56:31 +0000 |
4 | @@ -1511,7 +1511,7 @@ |
5 | |
6 | |
7 | handler = stan.Proto('athena:handler') |
8 | -_handlerFormat = "return Nevow.Athena.Widget.handleEvent(this, %(event)s, %(handler)s);" |
9 | +_handlerFormat = "return Nevow.Athena.Widget.handleEvent(this, %(event)s, %(handler)s, event);" |
10 | |
11 | def _rewriteEventHandlerToAttribute(tag): |
12 | """ |
13 | |
14 | === modified file 'Nevow/nevow/js/Divmod/Runtime/__init__.js' |
15 | --- Nevow/nevow/js/Divmod/Runtime/__init__.js 2008-12-31 18:44:01 +0000 |
16 | +++ Nevow/nevow/js/Divmod/Runtime/__init__.js 2011-07-10 13:56:31 +0000 |
17 | @@ -709,6 +709,25 @@ |
18 | */ |
19 | function addBeforeUnloadHandler(self, aWindow, handler) { |
20 | Divmod.Base.addToCallStack(aWindow, 'onbeforeunload', handler); |
21 | + }, |
22 | + |
23 | + |
24 | + /** |
25 | + * Determine which mouse buttons were pressed from a browser event object. |
26 | + * |
27 | + * The default implementation matches the W3C DOM Level 2 Events |
28 | + * specifications. |
29 | + * |
30 | + * @return: A mapping of C{'left'}, C{'middle'}, C{'right'} to C{Boolean} |
31 | + * values indicating the state of the named mouse buttons. |
32 | + * |
33 | + * @see: <http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-MouseEvent> |
34 | + */ |
35 | + function getMouseButtonsFromEvent(self, event) { |
36 | + return { |
37 | + 'left': event.button == 0, |
38 | + 'middle': event.button == 1, |
39 | + 'right': event.button == 2}; |
40 | }); |
41 | |
42 | |
43 | @@ -1205,6 +1224,20 @@ |
44 | */ |
45 | function addBeforeUnloadHandler(self, aWindow, handler) { |
46 | Divmod.Base.addToCallStack(aWindow.document.body, 'onbeforeunload', handler); |
47 | + }, |
48 | + |
49 | + |
50 | + /** |
51 | + * Internet Explorer specific handling of the C{event.button} property. |
52 | + * |
53 | + * @see: L{Divmod.Runtime.Platform.getMouseButtonsFromEvent} |
54 | + * @see: <http://msdn.microsoft.com/en-us/library/ms533544%28v=vs.85%29.aspx> |
55 | + */ |
56 | + function getMouseButtonsFromEvent(self, event) { |
57 | + return { |
58 | + 'left': (event.button & 1) != 0, |
59 | + 'middle': (event.button & 4) != 0, |
60 | + 'right': (event.button & 2) != 0}; |
61 | }); |
62 | |
63 | |
64 | |
65 | === modified file 'Nevow/nevow/js/Divmod/Test/TestRuntime.js' |
66 | --- Nevow/nevow/js/Divmod/Test/TestRuntime.js 2008-12-31 18:44:01 +0000 |
67 | +++ Nevow/nevow/js/Divmod/Test/TestRuntime.js 2011-07-10 13:56:31 +0000 |
68 | @@ -713,6 +713,100 @@ |
69 | }); |
70 | |
71 | |
72 | + |
73 | +/** |
74 | + * Tests for L{Divmod.Runtime.Platform}. |
75 | + */ |
76 | +Divmod.UnitTest.TestCase.subclass(Divmod.Test.TestRuntime, |
77 | + 'PlatformTests').methods( |
78 | + function setUp(self) { |
79 | + self.runtime = Divmod.Runtime.Platform('name'); |
80 | + }, |
81 | + |
82 | + |
83 | + /** |
84 | + * Assert that C{evt.button} when processed with |
85 | + * C{getMouseButtonsFromEvent} yields the same values for its C{'left'}, |
86 | + * C{'middle'} and C{'right'} attributes as the parameters with these same |
87 | + * names. |
88 | + */ |
89 | + function assertMouseButtons(self, evt, left, middle, right) { |
90 | + var buttons = self.runtime.getMouseButtonsFromEvent(evt); |
91 | + self.assertIdentical(buttons.left, left) |
92 | + self.assertIdentical(buttons.middle, middle) |
93 | + self.assertIdentical(buttons.right, right) |
94 | + }, |
95 | + |
96 | + |
97 | + /** |
98 | + * Most platforms follow the W3C definitions for C{event.button} values. |
99 | + * |
100 | + * These values cannot be combined to indicate multiple buttons being |
101 | + * pushed. |
102 | + */ |
103 | + function test_getMouseButtonsFromEvent(self) { |
104 | + var evt = {}; |
105 | + // Unknown value. |
106 | + evt.button = 99; |
107 | + self.assertMouseButtons(evt, false, false, false); |
108 | + // Left mouse button. |
109 | + evt.button = 0; |
110 | + self.assertMouseButtons(evt, true, false, false); |
111 | + // Middle mouse button. |
112 | + evt.button = 1; |
113 | + self.assertMouseButtons(evt, false, true, false); |
114 | + // Right mouse button. |
115 | + evt.button = 2; |
116 | + self.assertMouseButtons(evt, false, false, true); |
117 | + }); |
118 | + |
119 | + |
120 | + |
121 | +/** |
122 | + * Tests for L{Divmod.Runtime.InternetExplorer}. |
123 | + */ |
124 | +Divmod.Test.TestRuntime.PlatformTests.subclass( |
125 | + Divmod.Test.TestRuntime, |
126 | + 'InternetExplorerPlatformTests').methods( |
127 | + function setUp(self) { |
128 | + self.runtime = Divmod.Runtime.InternetExplorer(); |
129 | + }, |
130 | + |
131 | + |
132 | + /** |
133 | + * Internet Explorer uses different values for L{event.button} and can |
134 | + * combine these values to indicate multiple buttons being pressed. |
135 | + */ |
136 | + function test_getMouseButtonsFromEvent(self) { |
137 | + var evt = {}; |
138 | + // No buttons. |
139 | + evt.button = 0; |
140 | + self.assertMouseButtons(evt, false, false, false); |
141 | + // Left mouse button. |
142 | + evt.button = 1; |
143 | + self.assertMouseButtons(evt, true, false, false); |
144 | + // Right mouse button. |
145 | + evt.button = 2; |
146 | + self.assertMouseButtons(evt, false, false, true); |
147 | + // Middle mouse button. |
148 | + evt.button = 4; |
149 | + self.assertMouseButtons(evt, false, true, false); |
150 | + // Left and right mouse buttons. |
151 | + evt.button = 3; |
152 | + self.assertMouseButtons(evt, true, false, true); |
153 | + // Left and middle mouse buttons. |
154 | + evt.button = 5; |
155 | + self.assertMouseButtons(evt, true, true, false); |
156 | + // Right and middle mouse buttons. |
157 | + evt.button = 6; |
158 | + self.assertMouseButtons(evt, false, true, true); |
159 | + // Left, middle and right mouse buttons. |
160 | + evt.button = 7; |
161 | + self.assertMouseButtons(evt, true, true, true); |
162 | + }); |
163 | + |
164 | + |
165 | + |
166 | Divmod.Test.TestRuntime.SpidermonkeyRuntimeTests = Divmod.UnitTest.TestCase.subclass( |
167 | 'Divmod.Test.TestRuntime.SpidermonkeyRuntimeTests'); |
168 | /** |
169 | |
170 | === modified file 'Nevow/nevow/js/Nevow/Athena/__init__.js' |
171 | --- Nevow/nevow/js/Nevow/Athena/__init__.js 2009-01-21 22:58:03 +0000 |
172 | +++ Nevow/nevow/js/Nevow/Athena/__init__.js 2011-07-10 13:56:31 +0000 |
173 | @@ -1176,7 +1176,8 @@ |
174 | */ |
175 | Nevow.Athena.Widget._makeEventHandler = function (domEventName, methodName) { |
176 | return function () { |
177 | - return Nevow.Athena.Widget.handleEvent(this, domEventName, methodName); |
178 | + return Nevow.Athena.Widget.handleEvent( |
179 | + this, domEventName, methodName, event); |
180 | }; |
181 | }; |
182 | |
183 | @@ -1205,15 +1206,233 @@ |
184 | return Nevow.Athena.page.dispatchEvent(widget, eventName, handlerName, callable); |
185 | }; |
186 | |
187 | -/** |
188 | - * Given a node and a method name in an event handling context, dispatch the |
189 | - * event to the named method on the widget which owns the given node. This |
190 | - * also sets up error handling and does return value translation as |
191 | - * appropriate for an event handler. It also pauses the outgoing message |
192 | - * queue to allow multiple messages from the event handler to be batched up |
193 | - * into a single request. |
194 | - */ |
195 | -Nevow.Athena.Widget.handleEvent = function handleEvent(node, eventName, handlerName) { |
196 | + |
197 | + |
198 | +/** |
199 | + * Lowest-common denominator DOM event wrapper. |
200 | + * |
201 | + * DOM event objects should wrapped by calling |
202 | + * L{Nevow.Athena.Event.fromDOMEvent}. |
203 | + * |
204 | + * @type knownEventTypes: C{Array} of C{String} |
205 | + * @cvar knownEventTypes: Array of event types that this event wrapper can wrap. |
206 | + * |
207 | + * @ivar event: Original DOM event object. |
208 | + * |
209 | + * @ivar type: C{String} |
210 | + * @ivar type: Event type. |
211 | + * |
212 | + * @ivar target: Element to which the DOM event was originally dispatched. |
213 | + * |
214 | + * @see: <http://www.w3.org/TR/DOM-Level-2-Events/events.html> |
215 | + * @see: <http://www.quirksmode.org/js/introevents.html> |
216 | + */ |
217 | +Divmod.Class.subclass(Nevow.Athena, 'Event').methods( |
218 | + function __init__(self, event) { |
219 | + self.event = event; |
220 | + self.type = self.event.type; |
221 | + self.target = self.event.target; |
222 | + if (self.target === undefined) { |
223 | + self.target = self.event.srcElement; |
224 | + } |
225 | + }, |
226 | + |
227 | + |
228 | + /** |
229 | + * Cancels the event if it is cancelable, without stopping further |
230 | + * propagation of the event. |
231 | + */ |
232 | + function preventDefault(self) { |
233 | + if (self.event.preventDefault) { |
234 | + self.event.preventDefault(); |
235 | + } else { |
236 | + self.event.returnValue = false; |
237 | + } |
238 | + }, |
239 | + |
240 | + |
241 | + /** |
242 | + * Stops the propagation of events further along in the DOM. |
243 | + */ |
244 | + function stopPropagation(self) { |
245 | + if (self.event.stopPropagation) { |
246 | + self.event.stopPropagation(); |
247 | + } else { |
248 | + self.event.cancelBubble = true; |
249 | + } |
250 | + }); |
251 | + |
252 | + |
253 | + |
254 | +/** |
255 | + * Specific subclass of L{Nevow.Athena.Event} relating to key events. |
256 | + * |
257 | + * @ivar altKey: Was the I{alt} key pressed when the event fired? |
258 | + * |
259 | + * @ivar ctrlKey: Was the I{ctrl} key pressed when the event fired? |
260 | + * |
261 | + * @ivar shiftKey: Was the I{shift} key pressed when the event fired? |
262 | + * |
263 | + * @ivar metaKey: Was the I{meta} key pressed when the event fire? |
264 | + */ |
265 | +Nevow.Athena.Event.subclass(Nevow.Athena, 'KeyEvent').methods( |
266 | + function __init__(self, event) { |
267 | + Nevow.Athena.KeyEvent.upcall(self, '__init__', event); |
268 | + self.altKey = !!self.event.altKey; |
269 | + self.ctrlKey = !!self.event.ctrlKey; |
270 | + self.shiftKey = !!self.event.shiftKey; |
271 | + self.metaKey = !!self.event.metaKey; // Not in IE < 9. |
272 | + }, |
273 | + |
274 | + |
275 | + /** |
276 | + * Get the Unicode value of key press. |
277 | + * |
278 | + * For the I{keydown} or I{keyup} events this is the virtual key code of the |
279 | + * physical button pushed. For I{keypress} event this is the character code |
280 | + * for an alphanumeric key. |
281 | + * |
282 | + * @see: <https://developer.mozilla.org/en/DOM/event.keyCode> |
283 | + * @see: <http://msdn.microsoft.com/en-us/library/ms533927%28v=VS.85%29.aspx> |
284 | + */ |
285 | + function getKeyCode(self) { |
286 | + return self.event.keyCode || self.event.which; |
287 | + }, |
288 | + |
289 | + |
290 | + /** |
291 | + * Set the Unicode value of key press. |
292 | + */ |
293 | + function setKeyCode(self, value) { |
294 | + self.event.keyCode = value; |
295 | + }); |
296 | + |
297 | +Nevow.Athena.KeyEvent.knownEventTypes = ['keydown', 'keypress', 'keyup']; |
298 | + |
299 | + |
300 | + |
301 | +/** |
302 | + * Specific subclass of L{Nevow.Athena.Event} relating to mouse events. |
303 | + * |
304 | + * @ivar altKey: Was the I{alt} key pressed when the event fired? |
305 | + * |
306 | + * @ivar ctrlKey: Was the I{ctrl} key pressed when the event fired? |
307 | + * |
308 | + * @ivar shiftKey: Was the I{shift} key pressed when the event fired? |
309 | + * |
310 | + * @ivar metaKey: Was the I{meta} key pressed when the event fire? |
311 | + */ |
312 | +Nevow.Athena.Event.subclass(Nevow.Athena, 'MouseEvent').methods( |
313 | + function __init__(self, event) { |
314 | + Nevow.Athena.MouseEvent.upcall(self, '__init__', event); |
315 | + self.altKey = self.event.altKey; |
316 | + self.ctrlKey = self.event.ctrlKey; |
317 | + self.shiftKey = self.event.shiftKey; |
318 | + self.metaKey = self.event.metaKey; // Not in IE < 9. |
319 | + }, |
320 | + |
321 | + |
322 | + /** |
323 | + * Determine which mouse buttons were pressed in this event. |
324 | + * |
325 | + * @see: L{Divmod.Runtime.Platform.getMouseButtonsFromEvent} |
326 | + */ |
327 | + function getMouseButtons(self) { |
328 | + return Divmod.Runtime.theRuntime.getMouseButtonsFromEvent(self.event); |
329 | + }, |
330 | + |
331 | + |
332 | + /** |
333 | + * Get the coordinates of the event relative to the whole document. |
334 | + * |
335 | + * @return: Mapping of C{'x'} and C{'y'} to the horizontal and vertical |
336 | + * coordinates respectively. |
337 | + */ |
338 | + function getPagePosition(self) { |
339 | + return Divmod.Runtime.theRuntime.getEventCoords(self.event); |
340 | + }, |
341 | + |
342 | + |
343 | + /** |
344 | + * Get the coordinates within the browser's client area at which the event |
345 | + * occurred (as opposed to the coordinates within the page). |
346 | + * |
347 | + * For example, clicking in the top-left corner of the client area will |
348 | + * always result in a mouse event with a clientX value of 0, regardless of |
349 | + * whether the page is scrolled horizontally. |
350 | + * |
351 | + * @return: Mapping of C{'x'} and C{'y'} to the horizontal and vertical |
352 | + * coordinates respectively. |
353 | + */ |
354 | + function getClientPosition(self) { |
355 | + return { |
356 | + 'x': self.event.clientX, |
357 | + 'y': self.event.clientY}; |
358 | + }, |
359 | + |
360 | + |
361 | + /** |
362 | + * Get the coordinates of the event within the screen as a whole. |
363 | + * |
364 | + * @return: Mapping of C{'x'} and C{'y'} to the horizontal and vertical |
365 | + * coordinates respectively. |
366 | + */ |
367 | + function getScreenPosition(self) { |
368 | + return { |
369 | + 'x': self.event.screenX, |
370 | + 'y': self.event.screenY}; |
371 | + }); |
372 | + |
373 | +Nevow.Athena.MouseEvent.knownEventTypes = [ |
374 | + 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', |
375 | + 'mousewheel']; |
376 | + |
377 | + |
378 | + |
379 | +/** |
380 | + * Mapping of event types to known event handlers. |
381 | + */ |
382 | +Nevow.Athena.Event._eventHandlerMapping = (function() { |
383 | + var handlers = [ |
384 | + Nevow.Athena.KeyEvent, |
385 | + Nevow.Athena.MouseEvent]; |
386 | + var mapping = {} |
387 | + for (var i = 0; i < handlers.length; ++i) { |
388 | + var handler = handlers[i]; |
389 | + var knownEventTypes = handler.knownEventTypes; |
390 | + for (var j = 0; j < knownEventTypes.length; ++j) { |
391 | + mapping[knownEventTypes[j]] = handler; |
392 | + } |
393 | + } |
394 | + return mapping; |
395 | +})(); |
396 | + |
397 | + |
398 | + |
399 | +/** |
400 | + * Wrap a DOM event object with an appropriate L{Nevow.Athena.Event} subclass |
401 | + * or L{Nevow.Athena.Event} if there is no specific handler for the event type. |
402 | + */ |
403 | +Nevow.Athena.Event.fromDOMEvent = function fromDOMEvent(event) { |
404 | + var handler = Nevow.Athena.Event._eventHandlerMapping[event.type]; |
405 | + if (handler === undefined) { |
406 | + handler = Nevow.Athena.Event; |
407 | + } |
408 | + return handler(event); |
409 | +}; |
410 | + |
411 | + |
412 | + |
413 | +/** |
414 | + * Given a node, a method name in an event handling context and an event |
415 | + * object, dispatch the event to the named method on the widget which owns the |
416 | + * given node. This also sets up error handling and does return value |
417 | + * translation as appropriate for an event handler. It also pauses the |
418 | + * outgoing message queue to allow multiple messages from the event handler to |
419 | + * be batched up into a single request. |
420 | + */ |
421 | +Nevow.Athena.Widget.handleEvent = function handleEvent(node, eventName, |
422 | + handlerName, event) { |
423 | var widget = Nevow.Athena.Widget.get(node); |
424 | var method = widget[handlerName]; |
425 | var result = false; |
426 | @@ -1223,7 +1442,8 @@ |
427 | result = Nevow.Athena.Widget.dispatchEvent( |
428 | widget, eventName, handlerName, |
429 | function() { |
430 | - return method.call(widget, node); |
431 | + return method.call( |
432 | + widget, node, Nevow.Athena.Event.fromDOMEvent(event)); |
433 | }); |
434 | } |
435 | return result; |
436 | |
437 | === added file 'Nevow/nevow/js/Nevow/Test/TestEvent.js' |
438 | --- Nevow/nevow/js/Nevow/Test/TestEvent.js 1970-01-01 00:00:00 +0000 |
439 | +++ Nevow/nevow/js/Nevow/Test/TestEvent.js 2011-07-10 13:56:31 +0000 |
440 | @@ -0,0 +1,504 @@ |
441 | +// import Divmod |
442 | +// import Divmod.UnitTest |
443 | +// import Nevow.Athena |
444 | + |
445 | + |
446 | + |
447 | +/** |
448 | + * A mock DOM event that behaves according to the W3C guide that most browsers |
449 | + * follow. |
450 | + * |
451 | + * @ivar type: Event type. |
452 | + * |
453 | + * @ivar target: Element to which the DOM event was originally dispatched. |
454 | + * |
455 | + * @ivar cancelled: Has the default action been cancelled? |
456 | + * |
457 | + * @ivar stopped: Has event propagation been stopped? |
458 | + */ |
459 | +Divmod.Class.subclass(Nevow.Test.TestEvent, 'W3CEvent').methods( |
460 | + function __init__(self, type, target) { |
461 | + self.type = type; |
462 | + self.target = target; |
463 | + self.cancelled = false; |
464 | + self.stopped = false; |
465 | + }, |
466 | + |
467 | + |
468 | + /** |
469 | + * If an event is cancelable, the preventDefault method is used to signify |
470 | + * that the event is to be canceled, meaning any default action normally |
471 | + * taken by the implementation as a result of the event will not occur. |
472 | + */ |
473 | + function preventDefault(self) { |
474 | + self.cancelled = true; |
475 | + }, |
476 | + |
477 | + |
478 | + /** |
479 | + * The stopPropagation method is used prevent further propagation of an |
480 | + * event during event flow. |
481 | + */ |
482 | + function stopPropagation(self) { |
483 | + self.stopped = true; |
484 | + }); |
485 | + |
486 | + |
487 | + |
488 | +/** |
489 | + * A mock DOM event that behaves according to IE before IE9. |
490 | + * |
491 | + * @ivar type: Event type. |
492 | + * |
493 | + * @ivar srcElement: Element to which the DOM event was originally dispatched. |
494 | + * |
495 | + * @ivar returnValue: Has the default action been cancelled? |
496 | + * |
497 | + * @ivar cancelBubble: Has event propagation been stopped? |
498 | + */ |
499 | +Divmod.Class.subclass(Nevow.Test.TestEvent, 'IEEvent').methods( |
500 | + function __init__(self, type, srcElement) { |
501 | + self.type = type; |
502 | + self.srcElement = srcElement; |
503 | + self.returnValue = true; |
504 | + self.cancelBubble = false; |
505 | + }); |
506 | + |
507 | + |
508 | + |
509 | +/** |
510 | + * Tests for L{Nevow.Athena.Event} when using W3C-style event objects. |
511 | + */ |
512 | +Divmod.UnitTest.TestCase.subclass(Nevow.Test.TestEvent, 'TestEventW3C').methods( |
513 | + function createDOMEvent(self, type, target) { |
514 | + return Nevow.Test.TestEvent.W3CEvent(type, target); |
515 | + }, |
516 | + |
517 | + |
518 | + /** |
519 | + * L{Nevow.Athena.Event.fromDOMEvent} creates a specific I{Event} subclass |
520 | + * if it supports the DOM event otherwise L{Nevow.Athena.Event} is used. |
521 | + */ |
522 | + function test_fromDOMEvent(self) { |
523 | + function assertInstanceOf(inst, type, msg) { |
524 | + self.assertIdentical( |
525 | + inst instanceof type, |
526 | + true, |
527 | + 'Expected ' + inst.toString() + |
528 | + ' to be an instance of ' + type.toString()); |
529 | + } |
530 | + |
531 | + var handlers = [ |
532 | + Nevow.Athena.KeyEvent, |
533 | + Nevow.Athena.MouseEvent]; |
534 | + |
535 | + for (var i = 0; i < handlers.length; ++i) { |
536 | + var handler = handlers[i]; |
537 | + var knownEventTypes = handler.knownEventTypes; |
538 | + for (var j = 0; j < knownEventTypes.length; ++j) { |
539 | + var domEvent = self.createDOMEvent( |
540 | + knownEventTypes[j], null); |
541 | + var evt = Nevow.Athena.Event.fromDOMEvent(domEvent); |
542 | + assertInstanceOf(evt, handler); |
543 | + } |
544 | + } |
545 | + |
546 | + var evt = Nevow.Athena.Event.fromDOMEvent( |
547 | + self.createDOMEvent('definitelyunknown', null)); |
548 | + assertInstanceOf(evt, Nevow.Athena.Event); |
549 | + }, |
550 | + |
551 | + |
552 | + /** |
553 | + * L{Nevow.Athena.Event} extracts information from different kinds of DOM |
554 | + * events and puts them into a universal API. |
555 | + */ |
556 | + function test_attributes(self) { |
557 | + var eventType = 'eventType'; |
558 | + var target = 42; |
559 | + var domEvent = self.createDOMEvent(eventType, target); |
560 | + var event = Nevow.Athena.Event.fromDOMEvent(domEvent); |
561 | + self.assertIdentical(event.event, domEvent); |
562 | + self.assertIdentical(event.type, eventType); |
563 | + self.assertIdentical(event.target, target); |
564 | + }, |
565 | + |
566 | + |
567 | + /** |
568 | + * L{Nevow.Athena.Event.preventDefault} calls the method or sets the |
569 | + * attribute on the underlying DOM event that prevents the event's default |
570 | + * return value from being used. |
571 | + */ |
572 | + function test_preventDefault(self) { |
573 | + var domEvent = self.createDOMEvent('eventtype', null); |
574 | + var event = Nevow.Athena.Event.fromDOMEvent(domEvent); |
575 | + self.assertIdentical(domEvent.cancelled, false); |
576 | + event.preventDefault(); |
577 | + self.assertIdentical(domEvent.cancelled, true); |
578 | + }, |
579 | + |
580 | + |
581 | + /** |
582 | + * L{Nevow.Athena.Event.stopPropagation} calls the method or sets the |
583 | + * attribute on the underlying DOM event that stops the event from |
584 | + * propogating futher along in the DOM. |
585 | + */ |
586 | + function test_stopPropagation(self) { |
587 | + var domEvent = self.createDOMEvent('eventtype', null); |
588 | + var event = Nevow.Athena.Event.fromDOMEvent(domEvent); |
589 | + self.assertIdentical(domEvent.stopped, false); |
590 | + event.stopPropagation(); |
591 | + self.assertIdentical(domEvent.stopped, true); |
592 | + }); |
593 | + |
594 | + |
595 | + |
596 | +/** |
597 | + * Tests for L{Nevow.Athena.Event} when using IE-style event objects. |
598 | + */ |
599 | +Nevow.Test.TestEvent.TestEventW3C.subclass(Nevow.Test.TestEvent, |
600 | + 'TestEventIE').methods( |
601 | + function createDOMEvent(self, type, target) { |
602 | + return Nevow.Test.TestEvent.IEEvent(type, target); |
603 | + }, |
604 | + |
605 | + |
606 | + function test_preventDefault(self) { |
607 | + var domEvent = self.createDOMEvent('eventtype', null); |
608 | + var event = Nevow.Athena.Event.fromDOMEvent(domEvent); |
609 | + self.assertIdentical(domEvent.returnValue, true); |
610 | + event.preventDefault(); |
611 | + self.assertIdentical(domEvent.returnValue, false); |
612 | + }, |
613 | + |
614 | + |
615 | + function test_stopPropagation(self) { |
616 | + var domEvent = self.createDOMEvent('eventtype', null); |
617 | + var event = Nevow.Athena.Event.fromDOMEvent(domEvent); |
618 | + self.assertIdentical(domEvent.cancelBubble, false); |
619 | + event.stopPropagation(); |
620 | + self.assertIdentical(domEvent.cancelBubble, true); |
621 | + }); |
622 | + |
623 | + |
624 | + |
625 | +/** |
626 | + * Tests for L{Nevow.Athena.KeyEvent} when using W3C-style event objects. |
627 | + */ |
628 | +Divmod.UnitTest.TestCase.subclass(Nevow.Test.TestEvent, |
629 | + 'TestKeyEventW3C').methods( |
630 | + function setUp(self) { |
631 | + self.supportsMeta = true; |
632 | + self.eventType = Nevow.Test.TestEvent.W3CEvent; |
633 | + }, |
634 | + |
635 | + |
636 | + function createDOMEvent(self, type, target, args) { |
637 | + var evt = self.eventType(type, target); |
638 | + for (var key in args) { |
639 | + evt[key] = args[key]; |
640 | + } |
641 | + return evt; |
642 | + }, |
643 | + |
644 | + |
645 | + /** |
646 | + * Properties relating to modifier keys on the original DOM are accessible |
647 | + * on L{Nevow.Athena.KeyEvent}. |
648 | + */ |
649 | + function test_modifiers(self) { |
650 | + function createEvent(altKey, ctrlKey, shiftKey, metaKey) { |
651 | + return Nevow.Athena.Event.fromDOMEvent( |
652 | + self.createDOMEvent('keypress', null, { |
653 | + 'altKey': !!altKey, |
654 | + 'ctrlKey': !!ctrlKey, |
655 | + 'shiftKey': !!shiftKey, |
656 | + 'metaKey': !!metaKey})); |
657 | + } |
658 | + |
659 | + function assertModifiers(evt, altKey, ctrlKey, shiftKey, metaKey) { |
660 | + self.assertIdentical(evt.altKey, altKey); |
661 | + self.assertIdentical(evt.ctrlKey, ctrlKey); |
662 | + self.assertIdentical(evt.shiftKey, shiftKey); |
663 | + self.assertIdentical(evt.metaKey, metaKey); |
664 | + } |
665 | + |
666 | + assertModifiers( |
667 | + createEvent(true), |
668 | + true, false, false, false); |
669 | + assertModifiers( |
670 | + createEvent(true, true), |
671 | + true, true, false, false); |
672 | + assertModifiers( |
673 | + createEvent(true, true, true), |
674 | + true, true, true, false); |
675 | + |
676 | + if (self.supportsMeta) { |
677 | + assertModifiers( |
678 | + createEvent(true, true, true, true), |
679 | + true, true, true, true); |
680 | + } else { |
681 | + assertModifiers( |
682 | + createEvent(true, true, true, true), |
683 | + true, true, true, false) |
684 | + }; |
685 | + }, |
686 | + |
687 | + |
688 | + /** |
689 | + * L{Nevow.Athena.KeyEvent.getKeyCode} returns the Unicode key code for the |
690 | + * DOM event, preferring I{keyCode} over I{which}. |
691 | + */ |
692 | + function test_getKeyCode(self) { |
693 | + function createEvent(keyCode, which) { |
694 | + return Nevow.Athena.Event.fromDOMEvent( |
695 | + self.createDOMEvent('keypress', null, { |
696 | + 'keyCode': keyCode, |
697 | + 'which': which})); |
698 | + } |
699 | + |
700 | + function assertKeyCode(evt, keyCode) { |
701 | + self.assertIdentical(evt.getKeyCode(), keyCode); |
702 | + } |
703 | + |
704 | + assertKeyCode(createEvent(65), 65); |
705 | + assertKeyCode(createEvent(65, 97), 65); |
706 | + assertKeyCode(createEvent(0, 65), 65); |
707 | + }, |
708 | + |
709 | + |
710 | + /** |
711 | + * L{Nevow.Athena.KeyEvent.retKeyCode} sets the Unicode key code for the |
712 | + * DOM event. |
713 | + */ |
714 | + function test_setKeyCode(self) { |
715 | + var evt = Nevow.Athena.Event.fromDOMEvent( |
716 | + self.createDOMEvent('keypress', null, { |
717 | + 'keyCode': 65})); |
718 | + |
719 | + self.assertIdentical(evt.getKeyCode(), 65); |
720 | + evt.setKeyCode(97); |
721 | + self.assertIdentical(evt.event.keyCode, 97); |
722 | + self.assertIdentical(evt.getKeyCode(), 97); |
723 | + }); |
724 | + |
725 | + |
726 | + |
727 | +/** |
728 | + * Tests for L{Nevow.Athena.KeyEvent} when using IE-style event objects. |
729 | + */ |
730 | +Nevow.Test.TestEvent.TestKeyEventW3C.subclass(Nevow.Test.TestEvent, |
731 | + 'TestKeyEventIE').methods( |
732 | + function setUp(self) { |
733 | + Nevow.Test.TestEvent.TestKeyEventIE.upcall(self, 'setUp'); |
734 | + self.supportsMeta = false; |
735 | + self.eventType = Nevow.Test.TestEvent.IEEvent; |
736 | + }, |
737 | + |
738 | + |
739 | + function createDOMEvent(self, type, target, args) { |
740 | + // IE < 9 doesn't support "metaKey". |
741 | + delete args['metaKey']; |
742 | + return Nevow.Test.TestEvent.TestKeyEventIE.upcall( |
743 | + self, 'createDOMEvent', type, target, args); |
744 | + }); |
745 | + |
746 | + |
747 | + |
748 | +/** |
749 | + * Tests for L{Nevow.Athena.MouseEvent} when using W3C-style event objects. |
750 | + */ |
751 | +Divmod.UnitTest.TestCase.subclass(Nevow.Test.TestEvent, |
752 | + 'TestMouseEventW3C').methods( |
753 | + function setUp(self) { |
754 | + self.eventType = Nevow.Test.TestEvent.W3CEvent; |
755 | + }, |
756 | + |
757 | + |
758 | + function createDOMEvent(self, type, target, args) { |
759 | + var evt = self.eventType(type, target); |
760 | + for (var key in args) { |
761 | + evt[key] = args[key]; |
762 | + } |
763 | + return evt; |
764 | + }, |
765 | + |
766 | + |
767 | + /** |
768 | + * Get the platform-specific value for the specified mouse button |
769 | + * configuration. |
770 | + */ |
771 | + function getButtonValue(self, left, middle, right) { |
772 | + if (left) { |
773 | + return 0; |
774 | + } else if (middle) { |
775 | + return 1; |
776 | + } else if (right) { |
777 | + return 2; |
778 | + } |
779 | + }, |
780 | + |
781 | + |
782 | + /** |
783 | + * Assert that L{Nevow.Athena.MouseEvent.getMouseButtons} produces a mapping |
784 | + * that matches an expected mouse button configuration. |
785 | + */ |
786 | + function assertMouseButtons(self, evt, left, middle, right) { |
787 | + var buttons = evt.getMouseButtons(); |
788 | + self.assertIdentical(buttons.left, !!left); |
789 | + self.assertIdentical(buttons.middle, !!middle); |
790 | + self.assertIdentical(buttons.right, !!right); |
791 | + }, |
792 | + |
793 | + |
794 | + /** |
795 | + * L{Nevow.Athena.MouseEvent.getMouseButtons} decodes the platform-specific |
796 | + * C{event.button} value into a mapping that expresses the mouse button |
797 | + * configuration. |
798 | + */ |
799 | + function test_getMouseButtons(self) { |
800 | + function createEvent(left, middle, right) { |
801 | + return Nevow.Athena.Event.fromDOMEvent( |
802 | + self.createDOMEvent('mouseup', null, { |
803 | + 'button': self.getButtonValue(left, middle, right)})); |
804 | + } |
805 | + |
806 | + self.assertMouseButtons( |
807 | + createEvent(true, false, false), |
808 | + true, false, false); |
809 | + self.assertMouseButtons( |
810 | + createEvent(false, true, false), |
811 | + false, true, false); |
812 | + self.assertMouseButtons( |
813 | + createEvent(false, false, true), |
814 | + false, false, true); |
815 | + }, |
816 | + |
817 | + |
818 | + /** |
819 | + * L{Nevow.Athena.MouseEvent.getPagePosition} gets the coordinates of the |
820 | + * event relative to the whole document. |
821 | + */ |
822 | + function test_getPagePosition(self) { |
823 | + var evt = Nevow.Athena.Event.fromDOMEvent( |
824 | + self.createDOMEvent('mouseup', null, { |
825 | + 'pageX': 51, |
826 | + 'pageY': 44})); |
827 | + var pt = evt.getPagePosition(); |
828 | + self.assertIdentical(pt.x, 51); |
829 | + self.assertIdentical(pt.y, 44); |
830 | + }, |
831 | + |
832 | + |
833 | + /** |
834 | + * L{Nevow.Athena.MouseEvent.getClientPosition} gets the coordinates of the |
835 | + * event within the browse's client area. |
836 | + */ |
837 | + function test_getClientPosition(self) { |
838 | + var evt = Nevow.Athena.Event.fromDOMEvent( |
839 | + self.createDOMEvent('mouseup', null, { |
840 | + 'clientX': 51, |
841 | + 'clientY': 44})); |
842 | + var pt = evt.getClientPosition(); |
843 | + self.assertIdentical(pt.x, 51); |
844 | + self.assertIdentical(pt.y, 44); |
845 | + }, |
846 | + |
847 | + |
848 | + /** |
849 | + * L{Nevow.Athena.MouseEvent.getScreenPosition} gets the coordinates of the |
850 | + * event within the screen as a whole. |
851 | + */ |
852 | + function test_getScreenPosition(self) { |
853 | + var evt = Nevow.Athena.Event.fromDOMEvent( |
854 | + self.createDOMEvent('mouseup', null, { |
855 | + 'screenX': 51, |
856 | + 'screenY': 44})); |
857 | + var pt = evt.getScreenPosition(); |
858 | + self.assertIdentical(pt.x, 51); |
859 | + self.assertIdentical(pt.y, 44); |
860 | + }); |
861 | + |
862 | + |
863 | + |
864 | +/** |
865 | + * Tests for L{Nevow.Athena.MouseEvent} when using IE-style event objects. |
866 | + */ |
867 | +Nevow.Test.TestEvent.TestMouseEventW3C.subclass( |
868 | + Nevow.Test.TestEvent, |
869 | + 'TestMouseEventIE').methods( |
870 | + function setUp(self) { |
871 | + self.eventType = Nevow.Test.TestEvent.IEEvent; |
872 | + self.oldRuntime = Divmod.Runtime.theRuntime; |
873 | + Divmod.Runtime.theRuntime = Divmod.Runtime.InternetExplorer(); |
874 | + }, |
875 | + |
876 | + |
877 | + function tearDown(self) { |
878 | + Divmod.Runtime.theRuntime = self.oldRuntime; |
879 | + }, |
880 | + |
881 | + |
882 | + /** |
883 | + * Get the platform-specific value for the specified mouse button |
884 | + * configuration. |
885 | + */ |
886 | + function getButtonValue(self, left, middle, right) { |
887 | + var button = 0; |
888 | + if (left) { |
889 | + button |= 1; |
890 | + } |
891 | + if (middle) { |
892 | + button |= 4; |
893 | + } |
894 | + if (right) { |
895 | + button |= 2; |
896 | + } |
897 | + return button; |
898 | + }, |
899 | + |
900 | + |
901 | + /** |
902 | + * Internet Explorer can express configurations where multiple mouse |
903 | + * buttons are pushed. |
904 | + */ |
905 | + function test_getMouseButtonsMultiple(self) { |
906 | + function createEvent(left, middle, right) { |
907 | + return Nevow.Athena.Event.fromDOMEvent( |
908 | + self.createDOMEvent('mouseup', null, { |
909 | + 'button': self.getButtonValue(left, middle, right)})); |
910 | + } |
911 | + |
912 | + self.assertMouseButtons( |
913 | + createEvent(true, true, false), |
914 | + true, true, false); |
915 | + self.assertMouseButtons( |
916 | + createEvent(false, true, true), |
917 | + false, true, true); |
918 | + self.assertMouseButtons( |
919 | + createEvent(true, true, true), |
920 | + true, true, true); |
921 | + }, |
922 | + |
923 | + |
924 | + /** |
925 | + * L{Nevow.Athena.MouseEvent.getPagePosition} gets the coordinates of the |
926 | + * event relative to the whole document. In Internet Explorer < 9 there are |
927 | + * no C{'pageX'} or C{'pageY'} attributes instead a page position is |
928 | + * derived from client position and the document and body scroll offsets. |
929 | + */ |
930 | + function test_getPagePosition(self) { |
931 | + var evt = Nevow.Athena.Event.fromDOMEvent( |
932 | + self.createDOMEvent('mouseup', null, { |
933 | + 'clientX': 41, |
934 | + 'clientY': 34})); |
935 | + document.documentElement = { |
936 | + 'scrollLeft': 4, |
937 | + 'scrollTop': 6}; |
938 | + document.body.scrollLeft = 6; |
939 | + document.body.scrollTop = 4; |
940 | + |
941 | + var pt = evt.getPagePosition(); |
942 | + self.assertIdentical(pt.x, 51); |
943 | + self.assertIdentical(pt.y, 44); |
944 | + }); |
945 | |
946 | === modified file 'Nevow/nevow/js/Nevow/Test/TestWidget.js' |
947 | --- Nevow/nevow/js/Nevow/Test/TestWidget.js 2008-12-31 18:44:01 +0000 |
948 | +++ Nevow/nevow/js/Nevow/Test/TestWidget.js 2011-07-10 13:56:31 +0000 |
949 | @@ -128,6 +128,7 @@ |
950 | * the browser, and the explicitly selected handler will be invoked. |
951 | */ |
952 | function test_connectDOMEventCustomMethod(self) { |
953 | + event = {}; |
954 | self.widget.connectDOMEvent("onclick", "explicitClick"); |
955 | self.node.onclick(); |
956 | self.assertIdentical(self.widget.clicked, "explicitly"); |
957 | @@ -138,6 +139,7 @@ |
958 | * the browser, and the explicitly selected node will be used. |
959 | */ |
960 | function test_connectDOMEventCustomNode(self) { |
961 | + event = {}; |
962 | self.widget.connectDOMEvent("onclick", "explicitClick", self.otherNode); |
963 | self.otherNode.onclick(); |
964 | self.assertIdentical(self.widget.clicked, "explicitly"); |
965 | |
966 | === modified file 'Nevow/nevow/test/test_athena.py' |
967 | --- Nevow/nevow/test/test_athena.py 2010-07-12 19:00:11 +0000 |
968 | +++ Nevow/nevow/test/test_athena.py 2011-07-10 13:56:31 +0000 |
969 | @@ -554,7 +554,7 @@ |
970 | """ |
971 | expectedOutput = ( |
972 | 'return Nevow.Athena.Widget.handleEvent(' |
973 | - 'this, "onclick", "bar");') |
974 | + 'this, "onclick", "bar", event);') |
975 | tag = tags.span[athena.handler(event='onclick', handler='bar')] |
976 | mutated = athena._rewriteEventHandlerToAttribute(tag) |
977 | output = flat.flatten(mutated) |
978 | |
979 | === modified file 'Nevow/nevow/test/test_javascript.py' |
980 | --- Nevow/nevow/test/test_javascript.py 2008-07-07 08:58:45 +0000 |
981 | +++ Nevow/nevow/test/test_javascript.py 2011-07-10 13:56:31 +0000 |
982 | @@ -51,3 +51,7 @@ |
983 | |
984 | def test_tabbedPane(self): |
985 | return 'Nevow.Test.TestTabbedPane' |
986 | + |
987 | + |
988 | + def test_event(self): |
989 | + return 'Nevow.Test.TestEvent' |