Skip to content

Instantly share code, notes, and snippets.

@termi
Created November 6, 2012 08:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save termi/4023499 to your computer and use it in GitHub Desktop.
Save termi/4023499 to your computer and use it in GitHub Desktop.
Add/Remove listeners
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