1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 sc_require('system/core_query') ;
  9 
 10 /**
 11   The event class provides a simple cross-platform library for capturing and
 12   delivering events on DOM elements and other objects.  While this library
 13   is based on code from both jQuery and Prototype.js, it includes a number of
 14   additional features including support for handler objects and event
 15   delegation.
 16 
 17   Since native events are implemented very unevenly across browsers,
 18   SproutCore will convert all native events into a standardized instance of
 19   this special event class.
 20 
 21   SproutCore events implement the standard W3C event API as well as some
 22   additional helper methods.
 23 
 24   @constructor
 25   @param {Event} originalEvent
 26   @returns {SC.Event} event instance
 27 
 28   @since SproutCore 1.0
 29 */
 30 SC.Event = function(originalEvent) {
 31   this._sc_updateNormalizedEvent(originalEvent);
 32 
 33   return this;
 34 };
 35 
 36 SC.mixin(SC.Event, /** @scope SC.Event */ {
 37 
 38   /**
 39     We need this because some browsers deliver different values
 40     for mouse wheel deltas. Once the first mouse wheel event has
 41     been run, this value will get set.
 42 
 43     @field
 44     @type Number
 45     @default 1
 46   */
 47   MOUSE_WHEEL_MULTIPLIER: function() {
 48     var deltaMultiplier = 1,
 49         version = SC.browser.engineVersion;
 50 
 51     if (SC.browser.name === SC.BROWSER.safari) {
 52       deltaMultiplier = 0.4;
 53       // Safari 5.0.1 and up
 54       if (SC.browser.compare(version, '533.17') > 0 && SC.browser.compare(version, '534') < 0) {
 55         deltaMultiplier = 0.004;
 56       } else if (SC.browser.compare(version, '533') < 0) {
 57         // Scrolling in Safari 5.0
 58         deltaMultiplier = 40;
 59       }
 60     }else if(SC.browser.name === SC.BROWSER.ie){
 61       deltaMultiplier = 0.3;
 62     }else if(SC.browser.name === SC.BROWSER.chrome){
 63       deltaMultiplier = 0.4;
 64     }
 65     return deltaMultiplier;
 66   }(),
 67 
 68   /**
 69     This represents the limit in the delta before a different multiplier
 70     will be applied. Because we can't generated an accurate mouse
 71     wheel event ahead of time, and browsers deliver differing values
 72     for mouse wheel deltas, this is necessary to ensure that
 73     browsers that scale their values largely are dealt with correctly
 74     in the future.
 75 
 76     @type Number
 77     @default 1000
 78   */
 79   MOUSE_WHEEL_DELTA_LIMIT: 1000,
 80 
 81   /** @private
 82     We only want to invalidate once
 83   */
 84   _MOUSE_WHEEL_LIMIT_INVALIDATED: NO,
 85 
 86   /**
 87     Standard method to create a new event.  Pass the native browser event you
 88     wish to wrap if needed.
 89   */
 90   create: function(e) { return new SC.Event(e); },
 91 
 92   // the code below was borrowed from jQuery, Dean Edwards, and Prototype.js
 93 
 94   /**
 95     Bind an event to an element.
 96 
 97     This method will cause the passed handler to be executed whenever a
 98     relevant event occurs on the named element.  This method supports a
 99     variety of handler types, depending on the kind of support you need.
100 
101     ## Simple Function Handlers
102 
103         SC.Event.add(anElement, "click", myClickHandler) ;
104 
105     The most basic type of handler you can pass is a function.  This function
106     will be executed every time an event of the type you specify occurs on the
107     named element.  You can optionally pass an additional context object which
108     will be included on the event in the event.data property.
109 
110     When your handler function is called, the function's "this" property
111     will point to the element the event occurred on.
112 
113     The click handler for this method must have a method signature like:
114 
115         function(event) { return YES|NO; }
116 
117     ## Method Invocations
118 
119         SC.Event.add(anElement, "click", myObject, myObject.aMethod) ;
120 
121     Optionally you can specify a target object and a method on the object to
122     be invoked when the event occurs.  This will invoke the method function
123     with the target object you pass as "this".  The method should have a
124     signature like:
125 
126         function(event, targetElement) { return YES|NO; }
127 
128     Like function handlers, you can pass an additional context data paramater
129     that will be included on the event in the event.data property.
130 
131     ## Handler Return Values
132 
133     Both handler functions should return YES if you want the event to
134     continue to propagate and NO if you want it to stop.  Returning NO will
135     both stop bubbling of the event and will prevent any default action
136     taken by the browser.  You can also control these two behaviors separately
137     by calling the stopPropagation() or preventDefault() methods on the event
138     itself, returning YES from your method.
139 
140     ## Limitations
141 
142     Although SproutCore's event implementation is based on jQuery, it is
143     much simpler in design.  Notably, it does not support namespaced events
144     and you can only pass a single type at a time.
145 
146     If you need more advanced event handling, consider the SC.ClassicResponder
147     functionality provided by SproutCore or use your favorite DOM library.
148 
149     @param {Element} elem a DOM element, window, or document object
150     @param {String} eventType the event type you want to respond to
151     @param {Object} target The target object for a method call or a function.
152     @param {Object} method optional method or method name if target passed
153     @param {Object} context optional context to pass to the handler as event.data
154     @returns {Object} receiver
155   */
156   add: function(elem, eventType, target, method, context, useCapture) {
157 
158     // if a CQ object is passed in, either call add on each item in the
159     // matched set, or simply get the first element and use that.
160     if (elem && elem.isCoreQuery) {
161       if (elem.length > 0) {
162         elem.forEach(function(e) {
163           this.add(e, eventType, target, method, context);
164         }, this);
165         return this;
166       } else elem = elem[0];
167     }
168     if (!elem) return this; // nothing to do
169 
170 		if (!useCapture) {
171 			useCapture = NO;
172 		}
173 
174     // cannot register events on text nodes, etc.
175     if ( elem.nodeType === 3 || elem.nodeType === 8 ) return SC.Event;
176 
177     // For whatever reason, IE has trouble passing the window object
178     // around, causing it to be cloned in the process
179     if (SC.browser.name === SC.BROWSER.ie && elem.setInterval) elem = window;
180 
181     // if target is a function, treat it as the method, with optional context
182     if (SC.typeOf(target) === SC.T_FUNCTION) {
183       context = method;
184       method = target;
185       target = null;
186 
187     // handle case where passed method is a key on the target.
188     } else if (target && SC.typeOf(method) === SC.T_STRING) {
189       method = target[method];
190     }
191 
192     // Get the handlers queue for this element/eventType.  If the queue does
193     // not exist yet, create it and also setup the shared listener for this
194     // eventType.
195     var events = SC.data(elem, "sc_events") || SC.data(elem, "sc_events", {}),
196         handlers = events[eventType];
197 
198     if (!handlers) {
199       handlers = events[eventType] = {};
200       this._addEventListener(elem, eventType, useCapture);
201     }
202 
203     // Build the handler array and add to queue
204     handlers[SC.hashFor(target, method)] = [target, method, context];
205     SC.Event._global[eventType] = YES ; // optimization for global triggers
206 
207     // Nullify elem to prevent memory leaks in IE
208     elem = events = handlers = null ;
209     return this ;
210   },
211 
212   /**
213     Removes a specific handler or all handlers for an event or event+type.
214 
215     To remove a specific handler, you must pass in the same function or the
216     same target and method as you passed into SC.Event.add().  See that method
217     for full documentation on the parameters you can pass in.
218 
219     If you omit a specific handler but provide both an element and eventType,
220     then all handlers for that element will be removed.  If you provide only
221     and element, then all handlers for all events on that element will be
222     removed.
223 
224     ## Limitations
225 
226     Although SproutCore's event implementation is based on jQuery, it is
227     much simpler in design.  Notably, it does not support namespaced events
228     and you can only pass a single type at a time.
229 
230     If you need more advanced event handling, consider the SC.ClassicResponder
231     functionality provided by SproutCore or use your favorite DOM library.
232 
233     @param {Element} elem a DOM element, window, or document object
234     @param {String} eventType the event type to remove
235     @param {Object} target The target object for a method call.  Or a function.
236     @param {Object} method optional name of method
237     @returns {Object} receiver
238   */
239   remove: function(elem, eventType, target, method) {
240 
241     // if a CQ object is passed in, either call add on each item in the
242     // matched set, or simply get the first element and use that.
243     if (elem && elem.isCoreQuery) {
244       if (elem.length > 0) {
245         elem.forEach(function(e) {
246           this.remove(e, eventType, target, method);
247         }, this);
248         return this;
249       } else elem = elem[0];
250     }
251     if (!elem) return this; // nothing to do
252 
253     // don't do events on text and comment nodes
254     if ( elem.nodeType === 3 || elem.nodeType === 8 ) return SC.Event;
255 
256     var handlers, key, events = SC.data(elem, "sc_events") ;
257     if (!events) return this ; // nothing to do if no events are registered
258 
259     // if no type is provided, remove all types for this element.
260     if (eventType === undefined) {
261       for (var anEventType in events) this.remove(elem, anEventType) ;
262 
263     // otherwise, remove the handler for this specific eventType if found
264     } else if ((handlers = events[eventType])) {
265 
266       var cleanupHandlers = NO ;
267 
268       // if a target/method is provided, remove only that one
269       if (target || method) {
270 
271         // normalize the target/method
272         if (SC.typeOf(target) === SC.T_FUNCTION) {
273           method = target; target = null ;
274         } else if (SC.typeOf(method) === SC.T_STRING) {
275           method = target[method] ;
276         }
277 
278         delete handlers[SC.hashFor(target, method)];
279 
280         // check to see if there are handlers left on this event/eventType.
281         // if not, then cleanup the handlers.
282         key = null ;
283         for(key in handlers) break ;
284         if (key===null) cleanupHandlers = YES ;
285 
286       // otherwise, just cleanup all handlers
287       } else cleanupHandlers = YES ;
288 
289       // If there are no more handlers left on this event type, remove
290       // eventType hash from queue.
291       if (cleanupHandlers) {
292         delete events[eventType] ;
293         this._removeEventListener(elem, eventType) ;
294       }
295 
296       // verify that there are still events registered on this element.  If
297       // there aren't, cleanup the element completely to avoid memory leaks.
298       key = null ;
299       for (key in events) break;
300       if (!key) {
301         SC.removeData(elem, "sc_events") ;
302         delete this._elements[SC.guidFor(elem)]; // important to avoid leaks
303 
304         // Clean up the cached listener to prevent a memory leak.
305         SC.removeData(elem, 'listener');
306       }
307 
308     }
309 
310     elem = events = handlers = null ; // avoid memory leaks
311     return this ;
312   },
313 
314   NO_BUBBLE: ['blur', 'focus', 'change'],
315 
316   /**
317     Generates a simulated event object.  This is mostly useful for unit
318     testing.  You can pass the return value of this property into the
319     trigger() method to actually send the event.
320 
321     @param {Element} elem the element the event targets
322     @param {String} eventType event type.  mousedown, mouseup, etc
323     @param {Hash} attrs optional additional attributes to apply to event.
324     @returns {Hash} simulated event object
325   */
326   simulateEvent: function(elem, eventType, attrs) {
327     var ret = SC.Event.create({
328       type: eventType,
329       target: elem,
330       preventDefault: function(){ this.cancelled = YES; },
331       stopPropagation: function(){ this.bubbles = NO; },
332       allowDefault: function() { this.hasCustomEventHandling = YES; },
333       timeStamp: Date.now(),
334       bubbles: (this.NO_BUBBLE.indexOf(eventType)<0),
335       cancelled: NO,
336       normalized: YES,
337       simulated: true
338     });
339     if (attrs) SC.mixin(ret, attrs) ;
340     return ret ;
341   },
342 
343   /**
344     Trigger an event execution immediately.  You can use this method to
345     simulate arbitrary events on arbitrary elements.
346 
347     ## Limitations
348 
349     Note that although this is based on the jQuery implementation, it is
350     much simpler.  Notably namespaced events are not supported and you cannot
351     trigger events globally.
352 
353     If you need more advanced event handling, consider the SC.Responder
354     functionality provided by SproutCore or use your favorite DOM library.
355 
356     ## Example
357 
358         SC.Event.trigger(view.get('layer'), 'mousedown');
359 
360     @param elem {Element} the target element
361     @param eventType {String} the event type
362     @param event {SC.Event} [event] pre-normalized event to pass to handler
363     @param donative ??
364     @returns {Boolean} Return value of trigger or undefined if not fired
365   */
366   trigger: function(elem, eventType, event, donative) {
367 
368     // if a CQ object is passed in, either call add on each item in the
369     // matched set, or simply get the first element and use that.
370     if (elem && elem.isCoreQuery) {
371       if (elem.length > 0) {
372         elem.forEach(function(e) {
373           this.trigger(e, eventType, event, donative);
374         }, this);
375         return this;
376       } else elem = elem[0];
377     }
378     if (!elem) return this; // nothing to do
379 
380     // don't do events on text and comment nodes
381     if ( elem.nodeType === 3 || elem.nodeType === 8 ) return undefined;
382 
383     // Backwards-compatibility. Normalize from an Array.
384     if (SC.typeOf(event) === SC.T_ARRAY) { event = event[0]; }
385 
386     var ret, fn = SC.typeOf(elem[eventType] || null) === SC.T_FUNCTION ,
387         current, onfoo, isClick;
388 
389     // Get the event to pass, creating a fake one if necessary
390     if (!event || !event.preventDefault) {
391       event = this.simulateEvent(elem, eventType);
392     }
393 
394     event.type = eventType;
395 
396     // Trigger the event - bubble if enabled
397     current = elem;
398     do {
399       ret = SC.Event.handle.call(current, event);
400       current = (current===document) ? null : (current.parentNode || document);
401     } while(!ret && event.bubbles && current);
402     current = null ;
403 
404     // Handle triggering native .onfoo handlers
405     onfoo = elem["on" + eventType] ;
406     isClick = SC.$.nodeName(elem, 'a') && eventType === 'click';
407     if ((!fn || isClick) && onfoo && onfoo.call(elem, event) === NO) ret = NO;
408 
409     // Trigger the native events (except for clicks on links)
410     if (fn && donative !== NO && ret !== NO && !isClick) {
411       this.triggered = YES;
412       try {
413         elem[ eventType ]();
414       // prevent IE from throwing an error for some hidden elements
415       } catch (e) {}
416     }
417 
418     this.triggered = NO;
419 
420     return ret;
421   },
422 
423   /**
424     This method will handle the passed event, finding any registered listeners
425     and executing them.  If you have an event you want handled, you can
426     manually invoke this method.  This function expects it's "this" value to
427     be the element the event occurred on, so you should always call this
428     method like:
429 
430         SC.Event.handle.call(element, event) ;
431 
432     Note that like other parts of this library, the handle function does not
433     support namespaces.
434 
435     @param event {DOMEvent} the event to handle
436     @returns {Boolean}
437   */
438   handle: function (event) {
439 
440     // ignore events triggered after window is unloaded or if double-called
441     // from within a trigger.
442     if ((typeof SC === "undefined") || SC.Event.triggered) return true;
443 
444     // returned undefined or false
445     var val, ret, handlers, method, target;
446 
447     // get the handlers for this event type
448     handlers = (SC.data(this, "sc_events") || {})[event.type];
449 
450     // no handlers for the event
451     if (!handlers) {
452       val = false; // nothing to do
453 
454     } else {
455       // normalize event across browsers.  The new event will actually wrap the real event with a normalized API.
456       event = SC.Event.normalizeEvent(event || window.event);
457 
458       // invoke all handlers
459       for (var key in handlers) {
460         var handler = handlers[key];
461 
462         method = handler[1];
463 
464         // Pass in a reference to the handler function itself so that we can remove it later.
465         event.handler = method;
466         event.data = event.context = handler[2];
467 
468         target = handler[0] || this;
469         ret = method.call(target, event);
470 
471         if (val !== false) val = ret;
472 
473         // if method returned NO, do not continue.  Stop propagation and
474         // return default.  Note that we test explicitly for NO since
475         // if the handler returns no specific value, we do not want to stop.
476         if ( ret === false ) {
477           event.preventDefault();
478           event.stopPropagation();
479         }
480       }
481 
482       // Clean up the cached normalized SC.Event so that it's not holding onto extra memory.
483       if (event.originalEvent && !event.originalEvent.simulated) { event._sc_clearNormalizedEvent(); }
484     }
485 
486     return val;
487   },
488 
489   /**
490     This method is called just before the window unloads to unhook all
491     registered events.
492   */
493   unload: function() {
494     var key, elements = this._elements ;
495     for(key in elements) this.remove(elements[key]) ;
496 
497     // just in case some book-keeping was screwed up.  avoid memory leaks
498     for(key in elements) delete elements[key] ;
499     delete this._elements ;
500   },
501 
502   /**
503     This hash contains handlers for special or custom events.  You can add
504     your own handlers for custom events here by simply naming the event and
505     including a hash with the following properties:
506 
507      - setup: this function should setup the handler or return NO
508      - teardown: this function should remove the event listener
509 
510   */
511   special: {
512 
513     ready: {
514       setup: function() {
515         // Make sure the ready event is setup
516         SC._bindReady() ;
517         return;
518       },
519 
520       teardown: function() { return; }
521 
522     },
523 
524     /** @private
525         Implement support for mouseenter on browsers other than IE */
526     mouseenter: {
527       setup: function() {
528         if ( SC.browser.name === SC.BROWSER.ie ) return NO;
529         SC.Event.add(this, 'mouseover', SC.Event.special.mouseenter.handler);
530         return YES;
531       },
532 
533       teardown: function() {
534         if ( SC.browser.name === SC.BROWSER.ie ) return NO;
535         SC.Event.remove(this, 'mouseover', SC.Event.special.mouseenter.handler);
536         return YES;
537       },
538 
539       handler: function (event) {
540         // If we actually just moused on to a sub-element, ignore it
541         if ( SC.Event._withinElement(event, this) ) return YES;
542         // Execute the right handlers by setting the event type to mouseenter
543         event.type = "mouseenter";
544 
545         return SC.Event.handle.call(this, event);
546       }
547     },
548 
549     /** @private
550         Implement support for mouseleave on browsers other than IE */
551     mouseleave: {
552       setup: function() {
553         if ( SC.browser.name === SC.BROWSER.ie ) return NO;
554         SC.Event.add(this, "mouseout", SC.Event.special.mouseleave.handler);
555         return YES;
556       },
557 
558       teardown: function() {
559         if ( SC.browser.name === SC.BROWSER.ie ) return NO;
560         SC.Event.remove(this, "mouseout", SC.Event.special.mouseleave.handler);
561         return YES;
562       },
563 
564       handler: function (event) {
565         // If we actually just moused on to a sub-element, ignore it
566         if ( SC.Event._withinElement(event, this) ) return YES;
567         // Execute the right handlers by setting the event type to mouseleave
568         event.type = "mouseleave";
569         return SC.Event.handle.call(this, event);
570       }
571     }
572   },
573 
574   KEY_BACKSPACE: 8,
575   KEY_TAB:       9,
576   KEY_RETURN:   13,
577   KEY_ESC:      27,
578   KEY_SPACE:    32,
579   KEY_LEFT:     37,
580   KEY_UP:       38,
581   KEY_RIGHT:    39,
582   KEY_DOWN:     40,
583   KEY_DELETE:   46,
584   KEY_HOME:     36,
585   KEY_END:      35,
586   KEY_PAGEUP:   33,
587   KEY_PAGEDOWN: 34,
588   KEY_INSERT:   45,
589 
590   _withinElement: function(event, elem) {
591     // Check if mouse(over|out) are still within the same parent element
592     var parent = event.relatedTarget;
593 
594     // Traverse up the tree
595     while ( parent && parent !== elem ) {
596       try { parent = parent.parentNode; } catch(error) { parent = elem; }
597     }
598 
599     // Return YES if we actually just moused on to a sub-element
600     return parent === elem;
601   },
602 
603   /** @private
604     Adds the primary event listener for the named type on the element.
605 
606     If the event type has a special handler defined in SC.Event.special,
607     then that handler will be used.  Otherwise the normal browser method will
608     be used.
609 
610     @param elem {Element} the target element
611     @param eventType {String} the event type
612   */
613   _addEventListener: function(elem, eventType, useCapture) {
614     var listener,
615         special = this.special[eventType] ;
616 
617 		if (!useCapture) {
618 			useCapture = false;
619 		}
620 
621     // Check for a special event handler
622     // Only use addEventListener/attachEvent if the special
623     // events handler returns false
624     if ( !special || special.setup.call(elem) === false) {
625 
626       // Save element in cache.  This must be removed later to avoid
627       // memory leaks.
628       var guid = SC.guidFor(elem) ;
629       this._elements[guid] = elem;
630 
631       // Either retrieve the previously cached listener or cache a new one.
632       listener = SC.data(elem, "listener") || SC.data(elem, "listener",
633         function handle_event (event) {
634           return SC.Event.handle.call(SC.Event._elements[guid], event);
635         });
636 
637       // Bind the global event handler to the element
638       if (elem.addEventListener) {
639         elem.addEventListener(eventType, listener, useCapture);
640       } else if (elem.attachEvent) {
641         // attachEvent is not working for IE8 and xhr objects
642         // there is currently a hack in request , but it needs to fixed here.
643         elem.attachEvent("on" + eventType, listener);
644       }
645     }
646 
647     elem = special = listener = null; // avoid memory leak
648   },
649 
650   /** @private
651     Removes the primary event listener for the named type on the element.
652 
653     If the event type has a special handler defined in SC.Event.special,
654     then that handler will be used.  Otherwise the normal browser method will
655     be used.
656 
657     Note that this will not clear the _elements hash from the element.  You
658     must call SC.Event.unload() on unload to make sure that is cleared.
659 
660     @param elem {Element} the target element
661     @param eventType {String} the event type
662   */
663   _removeEventListener: function(elem, eventType) {
664     var listener, special = SC.Event.special[eventType] ;
665     if (!special || (special.teardown.call(elem)===NO)) {
666       // Retrieve the cached listener.
667       listener = SC.data(elem, "listener") ;
668       if (listener) {
669         if (elem.removeEventListener) {
670           elem.removeEventListener(eventType, listener, NO);
671         } else if (elem.detachEvent) {
672           elem.detachEvent("on" + eventType, listener);
673         }
674       }
675     }
676 
677     elem = special = listener = null ;
678   },
679 
680   _elements: {},
681 
682   _sc_normalizedEvents: null,
683 
684   // implement preventDefault() in a cross platform way
685 
686   /** @private Take an incoming event and convert it to a normalized event. */
687   normalizeEvent: function (event) {
688     var ret;
689 
690     // Create the cache the first time.
691     if (!this._sc_normalizedEvents) { this._sc_normalizedEvents = {}; }
692     ret = this._sc_normalizedEvents[event.type];
693 
694     // Create a new normalized SC.Event.
695     if (!ret) {
696       if (event === window.event) {
697         // IE can't do event.normalized on an Event object
698         ret = SC.Event.create(event) ;
699       } else {
700         ret = event.normalized ? event : SC.Event.create(event) ;
701       }
702 
703     // When passed an SC.Event, don't re-normalize it.
704     // TODO: This is hacky nonsense left over from a whole pile of bad decisions in SC.Event—
705     } else if (event.normalized) {
706       ret = event;
707 
708     // Update the cached normalized SC.Event with the new DOM event.
709     } else {
710       ret._sc_updateNormalizedEvent(event);
711     }
712 
713     // Cache the normalized event object for this type of event. This allows us to avoid recreating
714     // SC.Event objects constantly for noisy events such as 'mousemove' or 'mousewheel'.
715     this._sc_normalizedEvents[event.type] = ret;
716 
717     return ret;
718   },
719 
720   _global: {},
721 
722   /** @private properties to copy from native event onto the event */
723   // TODO: Remove this needless copy.
724   _props: ['altKey', 'attrChange', 'attrName', 'bubbles', 'button', 'cancelable', 'charCode', 'clientX', 'clientY', 'ctrlKey', 'currentTarget', 'data', 'detail', 'fromElement', 'handler', 'keyCode', 'metaKey', 'newValue', 'originalTarget', 'pageX', 'pageY', 'prevValue', 'relatedNode', 'relatedTarget', 'screenX', 'screenY', 'shiftKey', 'srcElement', 'target', 'timeStamp', 'toElement', 'type', 'view', 'which', 'touches', 'targetTouches', 'changedTouches', 'animationName', 'elapsedTime', 'dataTransfer']
725 
726 });
727 
728 SC.Event.prototype = {
729 
730   /**
731     Set to YES if you have called either preventDefault() or stopPropagation().
732     This allows a generic event handler to notice if you want to provide
733     detailed control over how the browser handles the real event.
734 
735     @type Boolean
736   */
737   hasCustomEventHandling: false,
738 
739   /** @private Clear out the originalEvent from the SC.Event instance. */
740   _sc_clearNormalizedEvent: function () {
741     // Remove the original event.
742     this.originalEvent = null;
743 
744     // Reset the custom event handling and normalized flag.
745     this.hasCustomEventHandling = false;
746     this.normalized = false;
747 
748     // Remove non-primitive properties copied over from the original event. While these will
749     // be overwritten, it's best to quickly null them out to avoid any issues.
750     var props = SC.Event._props,
751       idx;
752 
753     idx = props.length;
754     while(--idx >= 0) {
755       var key = props[idx];
756       this[key] = null;
757     }
758 
759     // Remove the custom properties associated with the previous original event. While these will
760     // be overwritten, it's best to quickly null them out to avoid any issues.
761     this.timeStamp = null;
762     this.target = null;
763     this.relatedTarget = null;
764     this.pageX = null;
765     this.pageY = null;
766     this.which = null;
767     this.metaKey = null;
768     this.wheelDelta = null;
769     this.wheelDeltaY = null;
770     this.wheelDeltaX = null;
771   },
772 
773   /** @private Update the SC.Event instance with the new originalEvent. */
774   _sc_updateNormalizedEvent: function (originalEvent) {
775     var idx, len;
776 
777     // Flag.
778     this.normalized = true;
779 
780     // copy properties from original event, if passed in.
781     if (originalEvent) {
782       this.originalEvent = originalEvent ;
783       var props = SC.Event._props,
784         key;
785 
786       len = props.length;
787       idx = len;
788       while(--idx >= 0) {
789         key = props[idx] ;
790         this[key] = originalEvent[key] ;
791       }
792     }
793 
794     // Fix timeStamp
795     this.timeStamp = this.timeStamp || Date.now();
796 
797     // Fix target property, if necessary
798     // Fixes #1925 where srcElement might not be defined either
799     if (!this.target) this.target = this.srcElement || document;
800 
801     // check if target is a textnode (safari)
802     if (this.target.nodeType === 3 ) this.target = this.target.parentNode;
803 
804     // Add relatedTarget, if necessary
805     if (!this.relatedTarget && this.fromElement) {
806       this.relatedTarget = (this.fromElement === this.target) ? this.toElement : this.fromElement;
807     }
808 
809     // Calculate pageX/Y if missing and clientX/Y available
810     if (SC.none(this.pageX) && !SC.none(this.clientX)) {
811       var doc = document.documentElement, body = document.body;
812       this.pageX = this.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
813       this.pageY = this.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
814     }
815 
816     // Add which for key events
817     if (!this.which && ((this.charCode || originalEvent.charCode === 0) ? this.charCode : this.keyCode)) {
818       this.which = this.charCode || this.keyCode;
819     }
820 
821     // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
822     if (!this.metaKey && this.ctrlKey) this.metaKey = this.ctrlKey;
823 
824     // Add which for click: 1 == left; 2 == middle; 3 == right
825     // Note: button is not normalized, so don't use it
826     if (!this.which && this.button) {
827       this.which = ((this.button & 1) ? 1 : ((this.button & 2) ? 3 : ( (this.button & 4) ? 2 : 0 ) ));
828     }
829 
830     // Normalize wheel delta values for mousewheel events.
831     /*
832       Taken from https://developer.mozilla.org/en-US/docs/Web/Events/mousewheel
833       IE and Opera (Presto) only support wheelDelta attribute and do not support horizontal scroll.
834 
835       The wheelDeltaX attribute value indicates the wheelDelta attribute value along the horizontal axis. When a user operates the device for scrolling to right, the value is negative. Otherwise, i.e., if it's to left, the value is positive.
836 
837       The wheelDeltaY attribute value indicates the wheelDelta attribute value along the vertical axis. The sign of the value is the same as the wheelDelta attribute value.
838 
839       IE
840 
841       The value is the same as the delta value of WM_MOUSEWHEEL or WM_MOUSEHWHEEL. It means that if the mouse wheel doesn't support high resolution scroll, the value is 120 per notch. The value isn't changed even if the scroll amount of system settings is page scroll.
842 
843       ## Chrome
844 
845       On Windows, the value is the same as the delta value of WM_MOUSEWHEEL or WM_MOUSEHWHEEL. And also, the value isn't changed even if the scroll amount of system settings is page scroll, i.e., the value is the same as IE on Windows.
846 
847       On Linux, the value is 120 or -120 per native wheel event. This makes the same behavior as IE and Chrome for Windows.
848 
849       On Mac, the value is complicated. The value is changed if the device that causes the native wheel event supports continuous scroll.
850 
851       If the device supports continuous scroll (e.g., trackpad of MacBook or mouse wheel which can be turned smoothly), the value is computed from accelerated scroll amount. In this case, the value is the same as Safari.
852 
853       If the device does not support continuous scroll (typically, old mouse wheel which cannot be turned smoothly), the value is computed from non-accelerated scroll amount (120 per notch). In this case, the value is different from Safari.
854 
855       This difference makes a serious issue for web application developers. That is, web developers cannot know if mousewheel event is caused by which device.
856 
857       See WebInputEventFactory::mouseWheelEvent of the Chromium's source code for the detail.
858 
859       ## Safari
860 
861       The value is always computed from accelerated scroll amount. This is really different from other browsers except Chrome with continuous scroll supported device.
862 
863       Note: tested with the Windows package, the earliest available version was Safari 3.0 from 2007. It could be that earlier versions (on Mac) support the properties too.
864 
865       ## Opera (Presto)
866 
867       The value is always the detail attribute value ✕ 40.
868 
869       On Windows, since the detail attribute value is computed from actual scroll amount, the value is different from other browsers except the scroll amount per notch is 3 lines in system settings or a page.
870 
871       On Linux, the value is 80 or -80 per native wheel event. This is different from other browsers.
872 
873       On Mac, the detail attribute value is computed from accelerated scroll amout of native event. The value is usually much bigger than Safari's or Chrome's value.
874     */
875     if (this.type === 'mousewheel' || this.type === 'DOMMouseScroll' || this.type === 'MozMousePixelScroll') {
876       var deltaMultiplier = SC.Event.MOUSE_WHEEL_MULTIPLIER;
877 
878       // normalize wheelDelta, wheelDeltaX, & wheelDeltaY for Safari
879       if (SC.browser.isWebkit && originalEvent.wheelDelta !== undefined) {
880         this.wheelDelta = 0 - (originalEvent.wheelDeltaY || originalEvent.wheelDeltaX);
881         this.wheelDeltaY = 0 - (originalEvent.wheelDeltaY || 0);
882         this.wheelDeltaX = 0 - (originalEvent.wheelDeltaX || 0);
883 
884       // normalize wheelDelta for Firefox (all Mozilla browsers)
885       // note that we multiple the delta on FF to make it's acceleration more natural.
886       } else if (!SC.none(originalEvent.detail) && SC.browser.isMozilla) {
887         if (originalEvent.axis && (originalEvent.axis === originalEvent.HORIZONTAL_AXIS)) {
888           this.wheelDeltaX = originalEvent.detail;
889           this.wheelDelta = this.wheelDeltaY = 0;
890         } else {
891           this.wheelDelta = this.wheelDeltaY = originalEvent.detail;
892           this.wheelDeltaX = 0;
893         }
894 
895       // handle all other legacy browser
896       } else {
897         this.wheelDelta = this.wheelDeltaY = SC.browser.isIE || SC.browser.isOpera ? 0 - originalEvent.wheelDelta : originalEvent.wheelDelta;
898         this.wheelDeltaX = 0;
899       }
900 
901       this.wheelDelta *= deltaMultiplier;
902       this.wheelDeltaX *= deltaMultiplier;
903       this.wheelDeltaY *= deltaMultiplier;
904     }
905   },
906 
907   /**
908     Returns the touches owned by the supplied view.
909 
910     @param {SC.View}
911     @returns {Array} touches an array of SC.Touch objects
912   */
913   touchesForView: function(view) {
914     if (this.touchContext) return this.touchContext.touchesForView(view);
915   },
916 
917   /**
918     Same as touchesForView, but sounds better for responders.
919 
920     @param {SC.RootResponder}
921     @returns {Array} touches an array of SC.Touch objects
922   */
923   touchesForResponder: function(responder) {
924     if (this.touchContext) return this.touchContext.touchesForView(responder);
925   },
926 
927   /**
928     Returns average data--x, y, and d (distance)--for the touches owned by the
929     supplied view.
930 
931     @param {SC.View}
932     @returns {Array} touches an array of SC.Touch objects
933   */
934   averagedTouchesForView: function(view) {
935     if (this.touchContext) return this.touchContext.averagedTouchesForView(view);
936     return null;
937   },
938 
939   /**
940     Indicates that you want to allow the normal default behavior.  Sets
941     the hasCustomEventHandling property to YES but does not cancel the event.
942 
943     @returns {SC.Event} receiver
944   */
945   allowDefault: function() {
946     this.hasCustomEventHandling = YES ;
947     return this ;
948   },
949 
950   /**
951     Implements W3C standard.  Will prevent the browser from performing its
952     default action on this event.
953 
954     @returns {SC.Event} receiver
955   */
956   preventDefault: function() {
957     var evt = this.originalEvent ;
958     if (evt) {
959       if (evt.preventDefault) evt.preventDefault() ;
960       else evt.returnValue = NO ; // IE8
961     }
962     this.hasCustomEventHandling = YES ;
963     return this ;
964   },
965 
966   /**
967     Implements W3C standard.  Prevents further bubbling of the event.
968 
969     @returns {SC.Event} receiver
970   */
971   stopPropagation: function() {
972     var evt = this.originalEvent ;
973     if (evt) {
974       if (evt.stopPropagation) evt.stopPropagation() ;
975       evt.cancelBubble = YES ; // IE
976     }
977     this.hasCustomEventHandling = YES ;
978     return this ;
979   },
980 
981   /**
982     Stops both the default action and further propagation.  This is more
983     convenient than calling both.
984 
985     @returns {SC.Event} receiver
986   */
987   stop: function() {
988     return this.preventDefault().stopPropagation();
989   },
990 
991   /**
992     Always YES to indicate the event was normalized.
993 
994     @type Boolean
995   */
996   normalized: YES,
997 
998   /**
999     Returns the pressed character as a String.
1000 
1001     @returns {String}
1002   */
1003   // Warning.
1004   // Older versions of IE don't support charCode, but on keypress return the
1005   // ASCII value in keyCode instead of the key code.  Therefore, if this code is
1006   // used on keyDown in IE versions prior to 9.0, it will fail.
1007   // Since SproutCore passes the keydown and keypress events as a keyDown
1008   // method, it's most likely that this code will cause unexpected problems
1009   // in IE 7 & IE 8.
1010   //
1011   // Reference: http://unixpapa.com/js/key.html
1012   getCharString: function() {
1013     if(SC.browser.name === SC.BROWSER.ie &&
1014         SC.browser.compare(SC.browser.version, '9.0') < 0) {
1015       // Return an empty String for backspace, tab, left, right, up or down.
1016       if(this.keyCode === 8 || this.keyCode === 9 ||
1017           (this.keyCode >= 37 && this.keyCode <= 40)) {
1018         return String.fromCharCode(0);
1019       } else {
1020         // This will only be accurate if the event is a keypress event.
1021         return (this.keyCode>0) ? String.fromCharCode(this.keyCode) : null;
1022       }
1023     } else {
1024       return (this.charCode>0) ? String.fromCharCode(this.charCode) : null;
1025     }
1026   },
1027 
1028   /**
1029     Returns characters with command codes for the event.
1030 
1031     The first value is a normalized command code identifying the modifier keys that are pressed in
1032     combination with a character key. Command codes can be used to map key combinations to an action
1033     in the application. A basic example of a normalized command code would be `ctrl_x`, which
1034     corresponds to the combination of the `command` key with the `x` key being pressed on OS X or
1035     the `ctrl` key with the `x` key in Windows.
1036 
1037     The second value is the character string by itself. So for `ctrl_x`, this would be simply `x`. However,
1038     for `alt_x` it would be `≈` and for `alt_shift_x`, it would be `˛`.
1039 
1040     ## Considerations for Different OS's
1041 
1042     At this time, the meta (command) key in OS X is mapped to the `ctrl_` command code prefix. This
1043     means that on OS X, command + s, and on Windows, ctrl + s, both become the same command code, `ctrl_s`.
1044 
1045     Note that the order of command code prefixes is important and goes in the order: `ctrl_`, `alt_`,
1046     `shift_`. The following are examples of command codes:
1047 
1048     * ctrl_x
1049     * alt_x
1050     * ctrl_shift_x
1051     * ctrl_alt_shift_x
1052     * alt_shift_x
1053 
1054     @returns {Array}
1055   */
1056   commandCodes: function () {
1057     var charCode = this.charCode,
1058       keyCode = this.keyCode,
1059       charString = null,
1060       commandCode = null,
1061       baseKeyName;
1062 
1063     // WebKit browsers have equal values for `keyCode` and `charCode` on the keypress event. For example,
1064     // the letter `r` and the function `f3` both have a `keyCode` of 114. But the `r` also has a `charCode`
1065     // of 114.
1066     // If there is a keyCode and no matching charCode it is a function key.
1067     if (keyCode && keyCode !== charCode) {
1068       commandCode = SC.FUNCTION_KEYS[keyCode];
1069     }
1070 
1071     // Use the function name as the key name in the command code (ex. `down` could become `shift_down`).
1072     if (commandCode) {
1073       baseKeyName = commandCode;
1074 
1075     // Find the base character for the key (i.e. `alt` + `a` becomes `å`, but we really want the key name, `a`).
1076     } else {
1077       baseKeyName = SC.PRINTABLE_KEYS[keyCode];
1078     }
1079 
1080     // If there is a base key name, append any modifiers to generate the command code.
1081     if (baseKeyName) {
1082       var modifiers = '';
1083 
1084       // Append the pressed modifier keys into a name used to identify command codes.
1085       // For example, holding the keys: shift, command & x, will map to the name "ctrl_shift_x".
1086       if (this.ctrlKey || this.metaKey) modifiers += 'ctrl_';
1087       // UNUSED. In a future version we should scrap ctrl vs. meta for the proper intent depending on the current OS.
1088       // if (this.ctrlKey) modifiers += 'ctrl_';
1089       // In OS X at least, when the ctrl key is pressed, both ctrlKey & metaKey are true. This makes it impossible to identify
1090       // ctrl + meta vs. just ctrl. We can identify just meta though.
1091       // else if (this.metaKey) modifiers += 'meta_';
1092 
1093       if (this.altKey) modifiers += 'alt_';
1094       if (this.shiftKey) modifiers += 'shift_';
1095 
1096       commandCode = modifiers + baseKeyName;
1097     }
1098 
1099     charString = this.getCharString();  // A character string or null.
1100 
1101     return [commandCode, charString];
1102   }
1103 
1104 };
1105 
1106 // Also provide a Prototype-like API so that people can use either one.
1107 
1108 /** Alias for add() method.  This provides a Prototype-like API. */
1109 SC.Event.observe = SC.Event.add ;
1110 
1111 /** Alias for remove() method.  This provides a Prototype-like API */
1112 SC.Event.stopObserving = SC.Event.remove ;
1113 
1114 /** Alias for trigger() method.  This provides a Prototype-like API */
1115 SC.Event.fire = SC.Event.trigger;
1116 
1117 // Register unload handler to eliminate any registered handlers
1118 // This avoids leaks in IE and issues with mouseout or other handlers on
1119 // other browsers.
1120 
1121 if(SC.browser.name === SC.BROWSER.ie) SC.Event.add(window, 'unload', SC.Event.prototype, SC.Event.unload) ;
1122 
1123 SC.MODIFIER_KEYS = {
1124   16:'shift', 17:'ctrl', 18: 'alt'
1125 };
1126 
1127 SC.FUNCTION_KEYS = {
1128   8: 'backspace',  9: 'tab',  13: 'return',  19: 'pause',  27: 'escape',
1129   33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home',
1130   37: 'left', 38: 'up', 39: 'right', 40: 'down', 44: 'printscreen',
1131   45: 'insert', 46: 'delete', 112: 'f1', 113: 'f2', 114: 'f3', 115: 'f4',
1132   116: 'f5', 117: 'f7', 119: 'f8', 120: 'f9', 121: 'f10', 122: 'f11',
1133   123: 'f12', 144: 'numlock', 145: 'scrolllock'
1134 } ;
1135 
1136 SC.PRINTABLE_KEYS = {
1137   32: ' ', 48:"0", 49:"1", 50:"2", 51:"3", 52:"4", 53:"5", 54:"6", 55:"7",
1138   56:"8", 57:"9", 59:";", 61:"=", 65:"a", 66:"b", 67:"c", 68:"d", 69:"e",
1139   70:"f", 71:"g", 72:"h", 73:"i", 74:"j", 75:"k", 76:"l", 77:"m", 78:"n",
1140   79:"o", 80:"p", 81:"q", 82:"r", 83:"s", 84:"t", 85:"u", 86:"v", 87:"w",
1141   88:"x", 89:"y", 90:"z", 107:"+", 109:"-", 110:".", 188:",", 190:".",
1142   191:"/", 192:"`", 219:"[", 220:"\\", 221:"]", 222:"\""
1143 } ;
1144