此模块用于提供自定义事件,并把实现此接口的对象变成一个事件发送器。
//================================================== // 事件发送器模块 //================================================== (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) });