此模块用于提供自定义事件,并把实现此接口的对象变成一个事件发送器。
//==================================================
// 事件发送器模块
//==================================================
(function(global,DOC){
var dom = global[DOC.URL.replace(/(#.+|\W)/g,'')];
dom.define("emitter","data", function(){
var fireType = "", blank = ""
var ensure = function(array,neo){
var obj = {}
for(var i=0,el;el = array[i++];){
obj[ el.uuid ] =1
}
if(!obj[neo.uuid]){
array.push(neo)
};
}
var events = dom.events = {
special:{},//用于处理个别的DOM事件
bind : function(type, callback, phase){
//它将在原生事件发送器或任何能成为事件发送器的普通JS对象添加一个名叫uniqueID的属性,用于关联一个缓存体,
//把需要的数据储存到里面,而现在我们就把一个叫@events的对象储放都它里面,
//而这个@event的表将用来放置各种事件类型与对应的回调函数
var target = this, table = dom.data( target,"@events") || dom.data( target,"@events",{});
if(!table || !callback) return ;
var bag = callback.callback ? callback :{
callback:callback,
uuid: dom.uuid++,
type:type
}
//确保UUID,bag与callback的UUID一致
bag.callback.uuid = bag.uuid;
//原生的DOM事件是不允许绑定同一个回调函数,详见下面的测试
// function callback(){ alert(1) }
// document.addEventListener("click",callback,false)
// document.addEventListener("click",callback,false)
type = bag.type;
var queue = table[ type ] = table[ type ] || [];
ensure(queue,bag);
if(dom["@emitter"] in target){//如果是原生的事件发送体
var special = events.special[ bag.type ] , setup = events.setup, tag = "@"+type
if(special){
type = special.type || type
tag = (bag.live ? "@live_" :"@special_" )+type;
setup = bag.live ? special.liveSetup : special.setup
}
bag.tag = tag;
if(!table[tag]){
dom.log("setup "+type+" event...")
setup(target, type, events.handle, !!phase);
table[tag] = 1
}
}
},
unbind:function(type ,bag, phase){
var target = this, table = dom.data( target,"@events") ;
if(!table) return;
if(typeof type === "string"){//如果指定了要移除何种事件类型
type = bag && bag.type || type;
var queue = table[ type ];
if(queue){
var callback = bag.callback || bag;
queue = callback ? table[type].filter(function(bag) {
return bag.callback != callback;
}) : [];
if(dom["@emitter"] in target){//如果是原生的事件发送体
var special = events.special[ type ] , teardown = events.teardown, tag = "@"+type
if(special){
type = special.type || type
tag = (bag.live ? "@live_" :"@special_" )+type;
teardown = bag.live ? special.liveTeardown : special.teardown
}
var length = queue.filter(function(bag){
return bag.tag == tag
}).length;
if(!length){
teardown(target, type, events.handle, !!phase) ;
dom.log("teardown "+type+" event...")
delete table[tag]
}
}
}
}else{
for (type in table ) {
if(type.charAt(0) !== "@")
events.unbind( target, type );
}
}
},
fire:function(type){
var target = this, table = dom.data( target,"@events") ,args = dom.slice(arguments,1), event
if(!table) return;
event = type instanceof jEvent ? type : new jEvent(type);
event.target = target;
event.fireArgs = args;
if( dom["@emitter"] in target){
var cur = target, ontype = "on" + type;
do{//模拟事件冒泡与执行内联事件
events.handle.call(cur, event);
if (cur[ ontype ] && cur[ ontype ].call(cur) === false) {
event.preventDefault();
}
cur = cur.parentNode ||
cur.ownerDocument ||
cur === target.ownerDocument && global;
} while (cur && !event.isPropagationStopped);
if (!event.isDefaultPrevented) {//模拟默认行为 click() submit() reset() focus() blur()
var old;
try {
if (ontype && target[ type ]) {
// 不用再触发内事件
old = target[ ontype ];
if (old) {
target[ ontype ] = null;
}
fireType = type;
target[ type ]();
}
} catch (e) {
dom.log("dom.events.fire("+type+") throw errer " + e);
}
if (old) {
target[ ontype ] = old;
}
fireType = blank;
}
}else{//普通对象的自定义事件
events.handle.call(target, event);
}
},
filter:function(target, parent, expr){
if(dom.contains(parent,target) ){
if(typeof expr === "function" ){
return expr.call(target)
}else{
return dom.matchesSelector(target, expr) ;//需要travel模块
}
}
},
handle: function( event ) {
if(fireType === event.type)
return undefined;
var queue = dom.data(this,"@events")[event.type];
if ( queue ) {
if(!event.uuid){
event = events.fix(event);
}
event.currentTarget = this;
var emitter = event.target, result,
//取得参数(只有自定义才有多个参数)
args = "fireArgs" in event ? [event].concat(event.fireArgs) : arguments;
//复制数组以防影响下一次的操作
queue = queue.concat();
//开始进行拆包操作
for ( var i = 0, bag; bag = queue[i++]; ) {
//如果是事件代理,确保元素处于enabled状态,并且满足过滤条件
if(bag.live && emitter.disabled && !events.filter(emitter, this, bag.live) ){
continue;
}
//取得回调函数
result = bag.callback.apply( emitter, args );
if ( result !== undefined ) {
event.result = result;
if ( result === false ) {
event.preventDefault();
event.stopPropagation();
}
}
if ( event.isImmediatePropagationStopped ) {
break;
}
}
}
return event.result;
},
fix :function(event){
if(event.uuid){
return event;
}
var originalEvent = event
event = new jEvent(originalEvent);
for(var prop in originalEvent){
if(typeof originalEvent[prop] !== "function"){
event[prop] = originalEvent[prop]
}
}
event.wheelDelta = 0;
//mousewheel
if ("wheelDelta" in originalEvent){
var detail = originalEvent.wheelDelta;
//opera 9x系列的滚动方向与IE保持一致,10后修正
if(global.opera && global.opera.version() < 10)
detail = -detail;
event.wheelDelta = Math.round(detail); //修正safari的浮点 bug
}else {
//DOMMouseScroll
event.wheelDelta = -originalEvent.detail*40;
}
//如果不存在target属性,为它添加一个
if ( !event.target ) {
// 判定鼠标事件按下的是哪个键,1 === left; 2 === middle; 3 === right
event.which = event.button === 2 ? 3 : event.button === 4 ? 2 : 1;
event.target = event.srcElement;
}
//如果事件源对象为文本节点,则置入其父元素
if ( event.target.nodeType === 3 ) {
event.target = event.target.parentNode;
}
//如果不存在relatedTarget属性,为它添加一个
if ( !event.relatedTarget && event.fromElement ) {
event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
}
//如果不存在pageX/Y则结合clientX/Y做一双出来
if ( event.pageX == null && event.clientX != null ) {
var html = dom.HTML, body = DOC.body;
event.pageX = event.clientX + (html && html.scrollLeft || body && body.scrollLeft || 0) - (html && html.clientLeft || body && body.clientLeft || 0);
event.pageY = event.clientY + (html && html.scrollTop || body && body.scrollTop || 0) - (html && html.clientTop || body && body.clientTop || 0);
}
// 为键盘事件添加which事件
if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) {
event.which = event.charCode || event.keyCode;
}
// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
if ( !event.metaKey && event.ctrlKey ) {
event.metaKey = event.ctrlKey;
}
return event;
},
setup: dom.bind,
teardown:DOC.dispatchEvent ? function(target, type, fn,phase){
target.removeEventListener( type, fn,phase );
} : function(target, type, fn) {
target.detachEvent( "on" + type, fn );
}
}
function jEvent( event ) {
this.originalEvent = event.substr ? {} : event
this.type = event.type || event
this.timeStamp = Date.now();
this.uuid = dom.uuid++;
};
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jEvent.prototype = {
constructor:jEvent,
//http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/events.html#Conformance
toString:function(){
return "[object Event]"
},
preventDefault: function() {
this.isDefaultPrevented = true;
var e = this.originalEvent;
// 如果存在preventDefault 那么就调用它
if ( e.preventDefault ) {
e.preventDefault();
}
// 如果存在returnValue 那么就将它设为false
e.returnValue = false;
return this;
},
stopPropagation: function() {
this.isPropagationStopped = true;
var e = this.originalEvent;
// 如果存在preventDefault 那么就调用它
if ( e.stopPropagation ) {
e.stopPropagation();
}
// 如果存在returnValue 那么就将它设为true
e.cancelBubble = true;
return this;
},
stopImmediatePropagation: function() {
this.isImmediatePropagationStopped = true;
this.stopPropagation();
return this;
}
};
//事件发射体emitter的接口
//实现了这些接口的对象将具有注册事件和触发事件的功能
dom.emitter = {};
"bind,unbind,fire".replace(dom.rword,function(name){
dom.emitter[name] = function(){
events[name].apply(this, arguments);
return this;
}
});
dom.emitter.uniqueID = ++dom.uuid;
dom.emitter.defineEvents = function(names){
var events = [];
if(typeof names == "string"){
events = names.match(dom.rword);
}else if(dom.isArray(names)){
events = names;
}
events.forEach(function(name){
var method = 'on'+name.replace(/(^|_|:)([a-z])/g,function($, $1, $2) {
return $2.toUpperCase();
});
if (!(method in this)) {
this[method] = function() {
return this.bind.apply(this, Array.prototype.concat.apply([name],arguments));
};
}
},this);
}
});
})(this,this.document);
//2011.8.14
//更改隐藏namespace,让自定义对象的回调函数也有事件对象
//2011.8.17
//事件发送器增加一个uniqueID属性
//2011.8.21
//重构bind与unbind方法
示例:
dom.require("ready,class,emitter",function(){
var A = dom.factory({
include:dom.emitter,
init:function(){
this.defineEvents("click,mouseover")
}
});
var a = new A;
a.bind("click",function(){
alert([].slice.call(arguments))//[object Event],1,2,3
})
a.fire("click",1,2,3);
a.onMouseover(function(){
alert("司徒正美")
});
a.fire("mouseover")
dom.log(a)
})
依次弹出两个窗口,第一个为"1,2,3",第二个为"司徒正美".在firebug下查看a实例的构造如下:
第二个例子,只依赖emitter模块.
dom.require("emitter",function(){
var a = {};
dom.mix(a,dom.emitter);
a.bind("data",function(){
alert("11111111")
});
a.bind("data",function(e){
alert([].slice.call(arguments));//[object Event],3,5
alert(e.type) //data
});
a.fire("data",3,5)
});