Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Make `Event.extend` work with legacy IE events in IE 9.

  • Loading branch information...
commit afe4b4ab0d5e80917379fe369dbf064e64e13da4 1 parent ccb929d
Andrew Dupont authored October 19, 2010 samleb committed October 21, 2010
125  src/dom/event.js
@@ -85,30 +85,72 @@
85 85
   var docEl = document.documentElement;
86 86
   var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl
87 87
     && 'onmouseleave' in docEl;
88  
-  var IE_LEGACY_EVENT_SYSTEM = (window.attachEvent && !window.addEventListener);
89  
-
  88
+    
  89
+    
  90
+    
  91
+  // We need to support three different event "modes":
  92
+  //  1. browsers with only DOM L2 Events (WebKit, FireFox);
  93
+  //  2. browsers with only IE's legacy events system (IE 6-8);
  94
+  //  3. browsers with _both_ systems (IE 9 and arguably Opera).
  95
+  //
  96
+  // Groups 1 and 2 are easy; group three is trickier.
  97
+  
  98
+  function isLegacyEvent(event) { return false; }
  99
+
  100
+  if (window.attachEvent) {
  101
+    if (window.addEventListener) {
  102
+      // Both systems are supported. We need to decide at runtime.
  103
+      // (Though Opera supports both systems, the event object appears to be
  104
+      // the same no matter which system is used. That means that this function
  105
+      // will always return `true` in Opera, but that's OK; it keeps us from
  106
+      // having to do a browser sniff.
  107
+      isLegacyEvent = function(event) {
  108
+        return !(event instanceof window.Event);
  109
+      };
  110
+    } else {
  111
+      // No support for DOM L2 events. All events will be legacy.
  112
+      isLegacyEvent = function(event) { return true; };
  113
+    }
  114
+  }
  115
+  
  116
+  // The two systems have different ways of indicating which button was used
  117
+  // for a mouse event.
90 118
   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;
  119
+  
  120
+  function _isButtonForDOMEvents(event, code) {
  121
+    return event.which ? (event.which === code + 1) : (event.button === code);
  122
+  }
  123
+  
  124
+  var legacyButtonMap = { 0: 1, 1: 4, 2: 2 };
  125
+  function _isButtonForLegacyEvents(event, code) {
  126
+    return event.button === legacyButtonMap[code];
  127
+  }
  128
+  
  129
+  // In WebKit we have to account for when the user holds down the "meta" key.
  130
+  function _isButtonForWebKit(event, code) {
  131
+    switch (code) {
  132
+      case 0: return event.which == 1 && !event.metaKey;
  133
+      case 1: return event.which == 2 || (event.which == 1 && event.metaKey);
  134
+      case 2: return event.which == 3;
  135
+      default: return false;
  136
+    }
  137
+  }
  138
+  
  139
+  if (window.attachEvent) {
  140
+    if (!window.addEventListener) {
  141
+      // Legacy IE events only.
  142
+      _isButton = _isButtonForLegacyEvents;      
  143
+    } else {
  144
+      // Both systems are supported; decide at runtime.
  145
+      _isButton = function(event, code) {
  146
+        return isLegacyEvent(event) ? _isButtonForLegacyEvents(event, code) :
  147
+         _isButtonForDOMEvents(event, code);
106 148
       }
107  
-    };
  149
+    }
  150
+  } else if (Prototype.Browser.WebKit) {
  151
+    _isButton = _isButtonForWebKit;
108 152
   } else {
109  
-    _isButton = function(event, code) {
110  
-      return event.which ? (event.which === code + 1) : (event.button === code);
111  
-    };
  153
+    _isButton = _isButtonForDOMEvents;
112 154
   }
113 155
 
114 156
   /**
@@ -344,29 +386,31 @@
344 386
     event.stopped = true;
345 387
   }
346 388
 
  389
+
347 390
   Event.Methods = {
348  
-    isLeftClick: isLeftClick,
  391
+    isLeftClick:   isLeftClick,
349 392
     isMiddleClick: isMiddleClick,
350  
-    isRightClick: isRightClick,
  393
+    isRightClick:  isRightClick,
351 394
 
352  
-    element: element,
  395
+    element:     element,
353 396
     findElement: findElement,
354 397
 
355  
-    pointer: pointer,
  398
+    pointer:  pointer,
356 399
     pointerX: pointerX,
357 400
     pointerY: pointerY,
358 401
 
359 402
     stop: stop
360 403
   };
361 404
 
362  
-
363 405
   // Compile the list of methods that get extended onto Events.
364 406
   var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
365 407
     m[name] = Event.Methods[name].methodize();
366 408
     return m;
367 409
   });
368 410
 
369  
-  if (IE_LEGACY_EVENT_SYSTEM) {
  411
+  if (window.attachEvent) {
  412
+    // For IE's event system, we need to do some work to make the event
  413
+    // object behave like a standard event object.
370 414
     function _relatedTarget(event) {
371 415
       var element;
372 416
       switch (event.type) {
@@ -384,11 +428,12 @@
384 428
       return Element.extend(element);
385 429
     }
386 430
 
387  
-    Object.extend(methods, {
  431
+    // These methods should be added _only_ to legacy IE event objects.
  432
+    var additionalMethods = {
388 433
       stopPropagation: function() { this.cancelBubble = true },
389 434
       preventDefault:  function() { this.returnValue = false },
390 435
       inspect: function() { return '[object Event]' }
391  
-    });
  436
+    };
392 437
 
393 438
     /**
394 439
      *  Event.extend(@event) -> Event
@@ -405,9 +450,14 @@
405 450
     // IE's method for extending events.
406 451
     Event.extend = function(event, element) {
407 452
       if (!event) return false;
408  
-      if (event._extendedByPrototype) return event;
409 453
 
  454
+      // If it's not a legacy event, it doesn't need extending.
  455
+      if (!isLegacyEvent(event)) return event;
  456
+
  457
+      // Mark this event so we know not to extend a second time.
  458
+      if (event._extendedByPrototype) return event;
410 459
       event._extendedByPrototype = Prototype.emptyFunction;
  460
+      
411 461
       var pointer = Event.pointer(event);
412 462
 
413 463
       // The optional `element` argument gives us a fallback value for the
@@ -418,13 +468,20 @@
418 468
         pageX:  pointer.x,
419 469
         pageY:  pointer.y
420 470
       });
421  
-
422  
-      return Object.extend(event, methods);
  471
+      
  472
+      Object.extend(event, methods);
  473
+      Object.extend(event, additionalMethods);
423 474
     };
424 475
   } else {
  476
+    // Only DOM events, so no manual extending necessary.
  477
+    Event.extend = Prototype.K;
  478
+  }
  479
+  
  480
+  if (window.addEventListener) {
  481
+    // In all browsers that support DOM L2 Events, we can augment
  482
+    // `Event.prototype` directly.
425 483
     Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__;
426 484
     Object.extend(Event.prototype, methods);
427  
-    Event.extend = Prototype.K;
428 485
   }
429 486
 
430 487
   function _createResponder(element, eventName, handler) {
@@ -922,7 +979,7 @@
922 979
     },
923 980
 
924 981
     handleEvent: function(event) {
925  
-      var element = event.findElement(this.selector);
  982
+      var element = Event.findElement(event, this.selector);
926 983
       if (element) this.callback.call(this.element, event, element);
927 984
     }
928 985
   });
40  test/functional/event.html
@@ -120,8 +120,9 @@
120 120
     $('hijack').observe('click', function(e){
121 121
       el = $(this.parentNode);
122 122
       log(e); // this makes it fail?!?
  123
+    
123 124
       e.preventDefault();
124  
-
  125
+    
125 126
       setTimeout(function() {
126 127
         if (window.location.hash == '#wrong') el.failed('Hijack failed (<a href="' +
127 128
             window.location.toString().replace(/#.+$/, '') + '">remove the fragment</a>)')
@@ -302,6 +303,7 @@
302 303
       <li id="delegation_result_3">Test 3</li>
303 304
     </ul>
304 305
   </div>
  306
+    
305 307
   <script type="text/javascript">
306 308
       var msg = "Passed. Click to unregister.";
307 309
       var clickMsg = "Now try original event again to ensure observation was stopped."
@@ -331,8 +333,44 @@
331 333
           observer3.stop();
332 334
         });
333 335
       });
  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
+      }
334 357
       
  358
+      var container = $('ie_legacy_event_support');
  359
+      container.show();
335 360
       
  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
+    });
336 374
   </script>
337 375
   
338 376
   

1 note on commit afe4b4a

Tobie Langel

Nitpick: I'd rewrite that to isIELegacyEvent or similar for clarity's sake.

Tobie Langel

Awesome code. LGTM.

Tobie Langel

Also, I agree with Samleb. Cleaner to user a function expression when redefining it later.

Please sign in to comment.
Something went wrong with that request. Please try again.