Skip to content

Commit 1a5f049

Browse files
Make Event.extend work with legacy IE events in IE 9.
1 parent 704aa40 commit 1a5f049

File tree

3 files changed

+131
-35
lines changed

3 files changed

+131
-35
lines changed

CHANGELOG

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
* Make `Event.extend` work with legacy IE events in IE 9. (Andrew Dupont)
2+
13
* Stop appending `&_=` to the parameters for non-GET Ajax requests in Safari. We no longer support any version of Safari for which this is necessary. [#327 state:resolved] (John-David Dalton, Andrew Dupont)
24

35
* Ensure `Form.focusFirstElement` doesn't raise an exception on forms with no fields. [#341 state:resolved] (achernin, Andrew Dupont)

src/dom/event.js

Lines changed: 90 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -85,30 +85,71 @@
8585
var docEl = document.documentElement;
8686
var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl
8787
&& 'onmouseleave' in docEl;
88-
var IE_LEGACY_EVENT_SYSTEM = (window.attachEvent && !window.addEventListener);
89-
88+
89+
90+
// We need to support three different event "modes":
91+
// 1. browsers with only DOM L2 Events (WebKit, FireFox);
92+
// 2. browsers with only IE's legacy events system (IE 6-8);
93+
// 3. browsers with _both_ systems (IE 9 and arguably Opera).
94+
//
95+
// Groups 1 and 2 are easy; group three is trickier.
96+
97+
var isIELegacyEvent = function(event) { return false; };
98+
99+
if (window.attachEvent) {
100+
if (window.addEventListener) {
101+
// Both systems are supported. We need to decide at runtime.
102+
// (Though Opera supports both systems, the event object appears to be
103+
// the same no matter which system is used. That means that this function
104+
// will always return `true` in Opera, but that's OK; it keeps us from
105+
// having to do a browser sniff.
106+
isIELegacyEvent = function(event) {
107+
return !(event instanceof window.Event);
108+
};
109+
} else {
110+
// No support for DOM L2 events. All events will be legacy.
111+
isIELegacyEvent = function(event) { return true; };
112+
}
113+
}
114+
115+
// The two systems have different ways of indicating which button was used
116+
// for a mouse event.
90117
var _isButton;
91-
if (IE_LEGACY_EVENT_SYSTEM) {
92-
// IE's event system doesn't map left/right/middle the same way.
93-
var buttonMap = { 0: 1, 1: 4, 2: 2 };
94-
_isButton = function(event, code) {
95-
return event.button === buttonMap[code];
96-
};
97-
} else if (Prototype.Browser.WebKit) {
98-
// In Safari we have to account for when the user holds down
99-
// the "meta" key.
100-
_isButton = function(event, code) {
101-
switch (code) {
102-
case 0: return event.which == 1 && !event.metaKey;
103-
case 1: return event.which == 2 || (event.which == 1 && event.metaKey);
104-
case 2: return event.which == 3;
105-
default: return false;
118+
119+
function _isButtonForDOMEvents(event, code) {
120+
return event.which ? (event.which === code + 1) : (event.button === code);
121+
}
122+
123+
var legacyButtonMap = { 0: 1, 1: 4, 2: 2 };
124+
function _isButtonForLegacyEvents(event, code) {
125+
return event.button === legacyButtonMap[code];
126+
}
127+
128+
// In WebKit we have to account for when the user holds down the "meta" key.
129+
function _isButtonForWebKit(event, code) {
130+
switch (code) {
131+
case 0: return event.which == 1 && !event.metaKey;
132+
case 1: return event.which == 2 || (event.which == 1 && event.metaKey);
133+
case 2: return event.which == 3;
134+
default: return false;
135+
}
136+
}
137+
138+
if (window.attachEvent) {
139+
if (!window.addEventListener) {
140+
// Legacy IE events only.
141+
_isButton = _isButtonForLegacyEvents;
142+
} else {
143+
// Both systems are supported; decide at runtime.
144+
_isButton = function(event, code) {
145+
return isIELegacyEvent(event) ? _isButtonForLegacyEvents(event, code) :
146+
_isButtonForDOMEvents(event, code);
106147
}
107-
};
148+
}
149+
} else if (Prototype.Browser.WebKit) {
150+
_isButton = _isButtonForWebKit;
108151
} else {
109-
_isButton = function(event, code) {
110-
return event.which ? (event.which === code + 1) : (event.button === code);
111-
};
152+
_isButton = _isButtonForDOMEvents;
112153
}
113154

114155
/**
@@ -344,29 +385,31 @@
344385
event.stopped = true;
345386
}
346387

388+
347389
Event.Methods = {
348-
isLeftClick: isLeftClick,
390+
isLeftClick: isLeftClick,
349391
isMiddleClick: isMiddleClick,
350-
isRightClick: isRightClick,
392+
isRightClick: isRightClick,
351393

352-
element: element,
394+
element: element,
353395
findElement: findElement,
354396

355-
pointer: pointer,
397+
pointer: pointer,
356398
pointerX: pointerX,
357399
pointerY: pointerY,
358400

359401
stop: stop
360402
};
361403

362-
363404
// Compile the list of methods that get extended onto Events.
364405
var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
365406
m[name] = Event.Methods[name].methodize();
366407
return m;
367408
});
368409

369-
if (IE_LEGACY_EVENT_SYSTEM) {
410+
if (window.attachEvent) {
411+
// For IE's event system, we need to do some work to make the event
412+
// object behave like a standard event object.
370413
function _relatedTarget(event) {
371414
var element;
372415
switch (event.type) {
@@ -384,11 +427,12 @@
384427
return Element.extend(element);
385428
}
386429

387-
Object.extend(methods, {
430+
// These methods should be added _only_ to legacy IE event objects.
431+
var additionalMethods = {
388432
stopPropagation: function() { this.cancelBubble = true },
389433
preventDefault: function() { this.returnValue = false },
390434
inspect: function() { return '[object Event]' }
391-
});
435+
};
392436

393437
/**
394438
* Event.extend(@event) -> Event
@@ -405,9 +449,14 @@
405449
// IE's method for extending events.
406450
Event.extend = function(event, element) {
407451
if (!event) return false;
408-
if (event._extendedByPrototype) return event;
409452

453+
// If it's not a legacy event, it doesn't need extending.
454+
if (!isIELegacyEvent(event)) return event;
455+
456+
// Mark this event so we know not to extend a second time.
457+
if (event._extendedByPrototype) return event;
410458
event._extendedByPrototype = Prototype.emptyFunction;
459+
411460
var pointer = Event.pointer(event);
412461

413462
// The optional `element` argument gives us a fallback value for the
@@ -418,13 +467,20 @@
418467
pageX: pointer.x,
419468
pageY: pointer.y
420469
});
421-
422-
return Object.extend(event, methods);
470+
471+
Object.extend(event, methods);
472+
Object.extend(event, additionalMethods);
423473
};
424474
} else {
475+
// Only DOM events, so no manual extending necessary.
476+
Event.extend = Prototype.K;
477+
}
478+
479+
if (window.addEventListener) {
480+
// In all browsers that support DOM L2 Events, we can augment
481+
// `Event.prototype` directly.
425482
Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__;
426483
Object.extend(Event.prototype, methods);
427-
Event.extend = Prototype.K;
428484
}
429485

430486
function _createResponder(element, eventName, handler) {
@@ -922,7 +978,7 @@
922978
},
923979

924980
handleEvent: function(event) {
925-
var element = event.findElement(this.selector);
981+
var element = Event.findElement(event, this.selector);
926982
if (element) this.callback.call(this.element, event, element);
927983
}
928984
});

test/functional/event.html

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,9 @@ <h1>Prototype functional tests for the Event module</h1>
120120
$('hijack').observe('click', function(e){
121121
el = $(this.parentNode);
122122
log(e); // this makes it fail?!?
123+
123124
e.preventDefault();
124-
125+
125126
setTimeout(function() {
126127
if (window.location.hash == '#wrong') el.failed('Hijack failed (<a href="' +
127128
window.location.toString().replace(/#.+$/, '') + '">remove the fragment</a>)')
@@ -302,6 +303,7 @@ <h1>Prototype functional tests for the Event module</h1>
302303
<li id="delegation_result_3">Test 3</li>
303304
</ul>
304305
</div>
306+
305307
<script type="text/javascript">
306308
var msg = "Passed. Click to unregister.";
307309
var clickMsg = "Now try original event again to ensure observation was stopped."
@@ -331,8 +333,44 @@ <h1>Prototype functional tests for the Event module</h1>
331333
observer3.stop();
332334
});
333335
});
336+
</script>
337+
338+
339+
<p id="ie_legacy_event_support" style="display: none">
340+
Extending event objects (click to test)
341+
</p>
342+
343+
<script type="text/javascript">
344+
Event.observe(window, 'load', function() {
345+
// Ensures we can manually extend events in IE's legacy event system.
346+
// IE9 supports both the legacy system and DOM L2 events, so we have to
347+
// inspect an event object at runtime to determine if it needs to be
348+
// extended.
349+
if (!window.attachEvent) return;
350+
351+
function mouseButton(event) {
352+
if (event.isLeftClick()) return 'left';
353+
if (event.isRightClick()) return 'right';
354+
if (event.isMiddleClick()) return 'middle';
355+
return null;
356+
}
334357

358+
var container = $('ie_legacy_event_support');
359+
container.show();
335360

361+
container.attachEvent('onmouseup', function(event) {
362+
if ('stop' in event) container.failed('Custom property already on event! Something weird happened!');
363+
Event.extend(event);
364+
log(event);
365+
if (!('stop' in event)) {
366+
container.failed('Event not extended!')
367+
} else {
368+
// Ensure mouse buttons are recognized properly, since legacy event
369+
// objects report them in a different way.
370+
container.passed('button pressed: ' + mouseButton(event));
371+
}
372+
});
373+
});
336374
</script>
337375

338376

0 commit comments

Comments
 (0)