Created
November 6, 2012 08:36
-
-
Save termi/4023499 to your computer and use it in GitHub Desktop.
Add/Remove listeners
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void function(exports) { | |
var _browser_msie = window["eval"] && window["eval"]("/*@cc_on 1;@*/") && +((/msie (\d+)/i.exec(navigator.userAgent) || [])[1] || 0) || void 0 | |
, _document_documentElement = document.documentElement | |
, _document_body = document.body | |
, _append = function(obj, extention) { | |
for(var key in extention) | |
if(Object.prototype.hasOwnProperty.call(extention, key) && !Object.prototype.hasOwnProperty.call(obj, key)) | |
obj[key] = extention[key]; | |
return obj; | |
} | |
/** @const @type {string} */ | |
, _event_UUID_prop_name = "uuid" | |
/** @type {number} unique indentifier for event listener */ | |
, _event_UUID = 1//MUST be more then 0 | 0 - using for DOM0 events | |
/** @const @type {string} */ | |
, _event_handleUUID = "_h_9e2" | |
/** @const @type {string} */ | |
, _event_eventsUUID = "_e_8vj" | |
/** @const */ | |
, _hasOwnProperty = Function.prototype.call.bind(Object.prototype.hasOwnProperty) | |
/** @const */ | |
, _Function_call = Function.prototype.call | |
/** @const */ | |
, _throw = function(errStr) { | |
throw errStr instanceof Error ? errStr : new Error(errStr); | |
} | |
/** @type {boolean} @const */ | |
, IS_NEED_LISTENER_WRAP = !("createEvent" in document) || !("stopImmediatePropagation" in (document.createEvent("Event"))) | |
, __is__DOMContentLoaded | |
; | |
var _Event_prototype = { | |
/** @this {_ielt9_Event} */ | |
"preventDefault" : function() { | |
if(this.cancelable === false)return; | |
this["returnValue"] = false; | |
this["defaultPrevented"] = true; | |
} , | |
/** @this {_ielt9_Event} */ | |
"stopPropagation" : function() { | |
this["cancelBubble"] = true; | |
} | |
}; | |
/** @this {_ielt9_Event} */ | |
_Event_prototype["stopImmediatePropagation"] = function() { | |
this["__stopNow"] = true; | |
this.stopPropagation(); | |
}; | |
_Event_prototype["defaultPrevented"] = false; | |
function fixEvent(event) { | |
if("__isFixed" in event)return; | |
if(!_document_body) { | |
_document_body = document.body; | |
} | |
var thisObj = this | |
, _button = ("button" in event) && event.button | |
; | |
event["__isFixed"] = true;// mark event as fixed | |
//http://javascript.gakaa.com/event-detail.aspx | |
//http://www.w3.org/TR/2011/WD-DOM-Level-3-Events-20110531/#event-type-click | |
//indicates the current click count; the attribute value must be 1 when the user begins this action and increments by 1 for each click. | |
if(event.type === "click" || event.type === "dblclick") { | |
if(event.detail === void 0)event.detail = event.type === "click" ? 1 : 2; | |
if(!event.button && fixEvent._clickButton !== void 0)_button = fixEvent._clickButton; | |
} | |
_append(event, _Event_prototype); | |
if(!event["defaultPrevented"])event["defaultPrevented"] = false; | |
if(!event.target)event.target = event.srcElement || document;// target for IE | |
// add relatedTarget в IE, if needs | |
if(event.relatedTarget === void 0 && event.fromElement) | |
event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement; | |
// calculate pageX/pageY for IE | |
if("clientX" in event && event.pageX == null) { | |
event.pageX = event.clientX + (_document_documentElement.scrollLeft || _document_body && _document_body.scrollLeft || 0) - (_document_documentElement.clientLeft || 0); | |
event.pageY = event.clientY + (_document_documentElement.scrollTop || _document_body && _document_body.scrollTop || 0) - (_document_documentElement.clientTop || 0); | |
} | |
//Add 'which' for click: 1 == left; 2 == middle; 3 == right | |
if(!event.which && _button)event.which = _button & 1 ? 1 : _button & 2 ? 3 : _button & 4 ? 2 : 0; | |
"timeStamp" in event || (event.timeStamp = +new Date()); | |
"eventPhase" in event || (event.eventPhase = (event.target == thisObj) ? 2 : 3); // "AT_TARGET" = 2, "BUBBLING_PHASE" = 3 | |
"currentTarget" in event || (event.currentTarget = thisObj); | |
"isTrusted" in event || (event.isTrusted = true); | |
event.keyCode || (event.keyCode = event.charCode || event.which); | |
return event; | |
} | |
if(_browser_msie < 9) { | |
document.attachEvent("onmousedown", function(){ | |
fixEvent._clickButton = event.button; | |
}); | |
document.attachEvent("onclick", function(){ | |
fixEvent._clickButton = void 0; | |
}); | |
} | |
// Universal event handler, this == element | |
function commonHandler(nativeEvent) { | |
if(fixEvent === void 0) {//filtering rare issue in old IE when handler call after page is unload | |
return; | |
} | |
var thisObj = this | |
, _ = thisObj["_"] | |
, errors = [] | |
, errorsMessages = [] | |
, _event | |
, handlersKey = _event_eventsUUID | |
; | |
if((!_ || !_[handlersKey]))return; | |
// get event in IE | |
nativeEvent || (nativeEvent = window.event); | |
fixEvent.call(this, nativeEvent); | |
var handlers = _[handlersKey][nativeEvent.type]; | |
if(handlers) { | |
for(var g in handlers)if(_hasOwnProperty(handlers, g)) { | |
var handler = handlers[g] | |
, context | |
; | |
if(typeof handler === "object") { | |
context = handler; | |
handler = handler.handleEvent; | |
} | |
try { | |
// Call handler with needed context and fixed event as a parameter, result saved in event['result'] | |
if( handler && | |
( | |
nativeEvent['result'] = _Function_call.call(handler, context || thisObj, nativeEvent) | |
) | |
=== false | |
) {// If handler return false | |
nativeEvent.preventDefault(); | |
nativeEvent.stopPropagation(); | |
} | |
} | |
catch(e) { | |
errors.push(e);// Collect all exceptions without interrupting events queue call | |
errorsMessages.push(e.message); | |
if(console)console.error(e); | |
} | |
if(nativeEvent["__stopNow"])break;// Immediate stop propagation | |
} | |
handlers[0] = void 0;//cleanup | |
delete handlers[0]; | |
if(errors.length == 1) {// If there was only one error - throw it on | |
_throw(errors[0]) | |
} | |
else if(errors.length > 1) {// Otherwise, do a common object with a list of errors Error in property errors and throw it | |
var e = new Error("Multiple errors thrown : " + nativeEvent.type + " : " + " : " + errorsMessages.join("|")); | |
e["errors"] = errors; | |
_throw(e); | |
} | |
} | |
} | |
exports["addListener"] = IS_NEED_LISTENER_WRAP ? | |
function(thisObj, _type, _handler) { | |
if(typeof _handler != "function" && | |
!(typeof _handler === "object" && _handler.handleEvent)//Registering an EventListener with a function object that also has a handleEvent property -> Call EventListener as a function | |
) { | |
return; | |
} | |
var /** @type {Object} */ | |
_ = thisObj["_"] | |
/** @type {Function} */ | |
, _callback | |
/** @type {boolean} */ | |
, _useInteractive = false | |
/** @type {string} */ | |
, handlersKey = _event_eventsUUID | |
; | |
if(!_)_ = thisObj["_"] = {}; | |
if(_browser_msie < 9) { | |
if(_type == "DOMContentLoaded") { | |
if (document.readyState == 'complete')return; | |
if(thisObj === global)thisObj = document; | |
_useInteractive = true; | |
if(!__is__DOMContentLoaded) { | |
__is__DOMContentLoaded = true; | |
function poll() { | |
try { document.documentElement.doScroll('left'); } catch(e) { setTimeout(poll, 50); return; } | |
commonHandler.call(thisObj, {"type" : _type, "isTrusted" : true }); | |
} | |
if ("createEventObject" in document && "doScroll" in document.documentElement) { | |
try { if(!global["frameElement"])poll() } catch(e) { } | |
} | |
} | |
} | |
// fix little IE bug with window object as this object | |
if(thisObj.setInterval && (thisObj != global && !thisObj["frameElement"]))thisObj = global; | |
} | |
// Set unique number to handler function | |
if(!_handler[_event_UUID_prop_name])_handler[_event_UUID_prop_name] = ++_event_UUID; | |
// Init Инициализовать служебную структуру events и обработчик _[handleUUID]. | |
//Основная его задача - передать вызов универсальному обработчику commonHandle с правильным указанием текущего элемента this. | |
//Как и events, _[handleUUID] достаточно инициализовать один раз для любых событий. | |
if(!(_callback = _[_event_handleUUID])) { | |
_callback = _[_event_handleUUID] = commonHandler.bind(thisObj); | |
} | |
//Если обработчиков такого типа событий не существует - инициализуем events[type] и вешаем | |
// commonHandle как обработчик на elem для запуска браузером по событию type. | |
if(!_[handlersKey])_[handlersKey] = {}; | |
if(!_[handlersKey][_type]) { | |
_[handlersKey][_type] = {}; | |
if(_browser_msie < 9) { | |
if(!_useInteractive) { | |
thisObj.attachEvent('on' + _type, _callback); | |
} | |
} | |
else { | |
thisObj.addEventListener(_type, _callback, false); | |
} | |
} | |
//Добавляем пользовательский обработчик в список elem[handlersKey][type] под заданным номером. | |
//Так как номер устанавливается один раз, и далее не меняется - это приводит к ряду интересных фич. | |
// Например, запуск add с одинаковыми аргументами добавит событие только один раз. | |
_[handlersKey][_type][_handler[_event_UUID_prop_name]] = _handler; | |
} | |
: | |
function(thisObj, _type, _handler) { | |
thisObj.addEventListener(_type, _handler, false) | |
} | |
; | |
exports["removeListener"] = IS_NEED_LISTENER_WRAP ? | |
function(thisObj, _type, _handler) { | |
var /** @type {Object} */ | |
_ = thisObj["_"] | |
/** @type {string} */ | |
, handlersKey = _event_eventsUUID | |
/** @type {Function} */ | |
, _callback | |
/** @type {Array} */ | |
, handlers | |
/** @type {String} */ | |
, any | |
; | |
if(typeof _handler != "function" && !(typeof _handler === "object" && _handler.handleEvent) || !_handler[_event_UUID_prop_name] || !_)return; | |
if(!(_callback = _[_event_handleUUID]))return; | |
//Get handlers list | |
handlers = _[handlersKey] && _[handlersKey][_type]; | |
//Delete handler by ID | |
delete handlers[_handler[_event_UUID_prop_name]]; | |
//Check handlers list for emptiness | |
for(any in handlers)if(_hasOwnProperty(handlers, any))return; | |
//If handlers list is empty - detach native event handler | |
if(_browser_msie < 9) { | |
thisObj.detachEvent("on" + _type, _callback); | |
} | |
else { | |
thisObj.removeEventListener(_type, _callback, false); | |
} | |
// and delete handlers container | |
delete _[handlersKey][_type]; | |
//If no any handlers on that element | |
for(any in _[handlersKey])if(_hasOwnProperty(_[handlersKey], any))return; | |
// delete container of handlers containers | |
delete _[handlersKey]; | |
} | |
: | |
function(thisObj, _type, _handler) { | |
thisObj.removeEventListener(_type, _handler, false) | |
} | |
; | |
}(window["Events"] = {}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment