Skip to content

Commit

Permalink
Make Event.extend work with legacy IE events in IE 9.
Browse files Browse the repository at this point in the history
  • Loading branch information
savetheclocktower committed Nov 1, 2010
1 parent 704aa40 commit 1a5f049
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 35 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
@@ -1,3 +1,5 @@
* Make `Event.extend` work with legacy IE events in IE 9. (Andrew Dupont)

* 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)

* Ensure `Form.focusFirstElement` doesn't raise an exception on forms with no fields. [#341 state:resolved] (achernin, Andrew Dupont)
Expand Down
124 changes: 90 additions & 34 deletions src/dom/event.js
Expand Up @@ -85,30 +85,71 @@
var docEl = document.documentElement;
var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl
&& 'onmouseleave' in docEl;
var IE_LEGACY_EVENT_SYSTEM = (window.attachEvent && !window.addEventListener);



// We need to support three different event "modes":
// 1. browsers with only DOM L2 Events (WebKit, FireFox);
// 2. browsers with only IE's legacy events system (IE 6-8);
// 3. browsers with _both_ systems (IE 9 and arguably Opera).
//
// Groups 1 and 2 are easy; group three is trickier.

var isIELegacyEvent = function(event) { return false; };

if (window.attachEvent) {
if (window.addEventListener) {
// Both systems are supported. We need to decide at runtime.
// (Though Opera supports both systems, the event object appears to be
// the same no matter which system is used. That means that this function
// will always return `true` in Opera, but that's OK; it keeps us from
// having to do a browser sniff.
isIELegacyEvent = function(event) {
return !(event instanceof window.Event);
};
} else {
// No support for DOM L2 events. All events will be legacy.
isIELegacyEvent = function(event) { return true; };
}
}

// The two systems have different ways of indicating which button was used
// for a mouse event.
var _isButton;
if (IE_LEGACY_EVENT_SYSTEM) {
// IE's event system doesn't map left/right/middle the same way.
var buttonMap = { 0: 1, 1: 4, 2: 2 };
_isButton = function(event, code) {
return event.button === buttonMap[code];
};
} else if (Prototype.Browser.WebKit) {
// In Safari we have to account for when the user holds down
// the "meta" key.
_isButton = function(event, code) {
switch (code) {
case 0: return event.which == 1 && !event.metaKey;
case 1: return event.which == 2 || (event.which == 1 && event.metaKey);
case 2: return event.which == 3;
default: return false;

function _isButtonForDOMEvents(event, code) {
return event.which ? (event.which === code + 1) : (event.button === code);
}

var legacyButtonMap = { 0: 1, 1: 4, 2: 2 };
function _isButtonForLegacyEvents(event, code) {
return event.button === legacyButtonMap[code];
}

// In WebKit we have to account for when the user holds down the "meta" key.
function _isButtonForWebKit(event, code) {
switch (code) {
case 0: return event.which == 1 && !event.metaKey;
case 1: return event.which == 2 || (event.which == 1 && event.metaKey);
case 2: return event.which == 3;
default: return false;
}
}

if (window.attachEvent) {
if (!window.addEventListener) {
// Legacy IE events only.
_isButton = _isButtonForLegacyEvents;
} else {
// Both systems are supported; decide at runtime.
_isButton = function(event, code) {
return isIELegacyEvent(event) ? _isButtonForLegacyEvents(event, code) :
_isButtonForDOMEvents(event, code);
}
};
}
} else if (Prototype.Browser.WebKit) {
_isButton = _isButtonForWebKit;
} else {
_isButton = function(event, code) {
return event.which ? (event.which === code + 1) : (event.button === code);
};
_isButton = _isButtonForDOMEvents;
}

/**
Expand Down Expand Up @@ -344,29 +385,31 @@
event.stopped = true;
}


Event.Methods = {
isLeftClick: isLeftClick,
isLeftClick: isLeftClick,
isMiddleClick: isMiddleClick,
isRightClick: isRightClick,
isRightClick: isRightClick,

element: element,
element: element,
findElement: findElement,

pointer: pointer,
pointer: pointer,
pointerX: pointerX,
pointerY: pointerY,

stop: stop
};


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

if (IE_LEGACY_EVENT_SYSTEM) {
if (window.attachEvent) {
// For IE's event system, we need to do some work to make the event
// object behave like a standard event object.
function _relatedTarget(event) {
var element;
switch (event.type) {
Expand All @@ -384,11 +427,12 @@
return Element.extend(element);
}

Object.extend(methods, {
// These methods should be added _only_ to legacy IE event objects.
var additionalMethods = {
stopPropagation: function() { this.cancelBubble = true },
preventDefault: function() { this.returnValue = false },
inspect: function() { return '[object Event]' }
});
};

/**
* Event.extend(@event) -> Event
Expand All @@ -405,9 +449,14 @@
// IE's method for extending events.
Event.extend = function(event, element) {
if (!event) return false;
if (event._extendedByPrototype) return event;

// If it's not a legacy event, it doesn't need extending.
if (!isIELegacyEvent(event)) return event;

// Mark this event so we know not to extend a second time.
if (event._extendedByPrototype) return event;
event._extendedByPrototype = Prototype.emptyFunction;

var pointer = Event.pointer(event);

// The optional `element` argument gives us a fallback value for the
Expand All @@ -418,13 +467,20 @@
pageX: pointer.x,
pageY: pointer.y
});

return Object.extend(event, methods);

Object.extend(event, methods);
Object.extend(event, additionalMethods);
};
} else {
// Only DOM events, so no manual extending necessary.
Event.extend = Prototype.K;
}

if (window.addEventListener) {
// In all browsers that support DOM L2 Events, we can augment
// `Event.prototype` directly.
Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__;
Object.extend(Event.prototype, methods);
Event.extend = Prototype.K;
}

function _createResponder(element, eventName, handler) {
Expand Down Expand Up @@ -922,7 +978,7 @@
},

handleEvent: function(event) {
var element = event.findElement(this.selector);
var element = Event.findElement(event, this.selector);
if (element) this.callback.call(this.element, event, element);
}
});
Expand Down
40 changes: 39 additions & 1 deletion test/functional/event.html
Expand Up @@ -120,8 +120,9 @@ <h1>Prototype functional tests for the Event module</h1>
$('hijack').observe('click', function(e){
el = $(this.parentNode);
log(e); // this makes it fail?!?

e.preventDefault();

setTimeout(function() {
if (window.location.hash == '#wrong') el.failed('Hijack failed (<a href="' +
window.location.toString().replace(/#.+$/, '') + '">remove the fragment</a>)')
Expand Down Expand Up @@ -302,6 +303,7 @@ <h1>Prototype functional tests for the Event module</h1>
<li id="delegation_result_3">Test 3</li>
</ul>
</div>

<script type="text/javascript">
var msg = "Passed. Click to unregister.";
var clickMsg = "Now try original event again to ensure observation was stopped."
Expand Down Expand Up @@ -331,8 +333,44 @@ <h1>Prototype functional tests for the Event module</h1>
observer3.stop();
});
});
</script>


<p id="ie_legacy_event_support" style="display: none">
Extending event objects (click to test)
</p>

<script type="text/javascript">
Event.observe(window, 'load', function() {
// Ensures we can manually extend events in IE's legacy event system.
// IE9 supports both the legacy system and DOM L2 events, so we have to
// inspect an event object at runtime to determine if it needs to be
// extended.
if (!window.attachEvent) return;

function mouseButton(event) {
if (event.isLeftClick()) return 'left';
if (event.isRightClick()) return 'right';
if (event.isMiddleClick()) return 'middle';
return null;
}

var container = $('ie_legacy_event_support');
container.show();

container.attachEvent('onmouseup', function(event) {
if ('stop' in event) container.failed('Custom property already on event! Something weird happened!');
Event.extend(event);
log(event);
if (!('stop' in event)) {
container.failed('Event not extended!')
} else {
// Ensure mouse buttons are recognized properly, since legacy event
// objects report them in a different way.
container.passed('button pressed: ' + mouseButton(event));
}
});
});
</script>


Expand Down

0 comments on commit 1a5f049

Please sign in to comment.