Ruby's Louvre

每天学习一点点算法

导航

mass Framework event模块

此模块此用于屏蔽DOM事件的浏览器差异的,并提供强大的事件代理机制(delegate,undelegate,live,die):


//==========================================
//  事件模块 by 司徒正美 2011.8.21
//==========================================

(function(global,DOC){
    var dom = global[DOC.URL.replace(/(#.+|\W)/g,'')];
    dom.define("event", "emitter,travel",function(){
        dom.log("加载dom.event模块成功")
        var types = "contextmenu,click,dblclick,mouseout,mouseover,mouseenter,mouseleave,mousemove,mousedown,mouseup,mousewheel," +
        "abort,error,load,unload,resize,scroll,change,input,select,reset,submit,"+"blur,focus,focusin,focusout,"+"keypress,keydown,keyup";
        dom.eventSupport = function( eventName,el ) {
            el = el || DOC.createElement("div");
            eventName = "on" + eventName;
            var ret = eventName in el;
            if (el.setAttribute && !ret ) {
                el.setAttribute(eventName, "return;");
                ret = typeof el[eventName] === "function";
            }
            el = null;
            return ret;
        };
        var events = dom.events, specials = events.special, rword = dom.rword;
        //用于在标准浏览器下模拟mouseenter与mouseleave
        //现在除了IE系列支持mouseenter/mouseleave/focusin/focusout外
        //opera11也支持这四个事件,同时它们也成为w3c DOM3 Event的规范
        //详见http://www.filehippo.com/pl/download_opera/changelog/9476/
        //http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html
        var brokenMouseEnter = !dom.eventSupport("mouseenter");
        "mouseenter_mouseover,mouseleave_mouseout".replace(rword,function(types){
            types = types.split("_");
            var obj = specials[ types[0] ]  = {
                setup:function(target){
                    dom.bind(target, types[1],function(){
                        obj[uuid(target)+"_handle"] = arguments.callee;
                        e = events.fix(e);
                        var parent = e.relatedTarget;
                        try {
                            while ( parent && parent !== target ) {
                                parent = parent.parentNode;
                            }
                            if ( parent !== target ) {
                                e.type = types[0];
                                events.handle.call(target,e);
                            }
                        } catch(e) { };
                    });
                },
                teardown :function(target){
                    events.teardown(target, types[1],obj[uuid(target)+"_handle"]);
                    delete obj[uuid(target)+"_handle"]
                }
            };
            obj.liveSetup = obj.setup;
            obj.liveTeardown = obj.teardown;
            if(!brokenMouseEnter){
                delete obj.setup;
                delete obj.teardown;
            }
        });

        if(!dom.eventSupport("focusin")){
            "focusin_focus,focusout_blur".replace(rword,function(types){
                types = types.split("_");
                var attaches = 0;
                function handler(e) {
                    events.fire(e.target, types[0]);
                }
                specials[  types[0] ]  = {
                    type: types[1],
                    setup:function(){
                        if ( attaches++ === 0 ) {
                            DOC.addEventListener(  types[1], handler, true );
                        }
                    },
                    teardown: function() {
                        if ( --attaches === 0 ) {
                            DOC.removeEventListener( types[1], handler, true );
                        }
                    }
                }
            });
        }
        //http://www.w3help.org/zh-cn/causes/SD9013
        //https://prototype.lighthouseapp.com/projects/8886/tickets/697-eventfire-to-support-all-events-not-only-custom-ones
        //http://www.quirksmode.org/dom/events/scroll.html
        if(!dom.eventSupport("mousewheel")){
            specials.mousewheel = {
                type    : "DOMMouseScroll"//FF
            }
        }
        //取得事件发送器的uniqueID
        function uuid(target){
            return dom.data(target,"uuid")
        }
        //submit事件的冒泡情况----IE6-9 :form ;FF: document; chrome: window;safari:window;opera:window
        if(!dom.eventSupport("submit") && !DOC.dispatchEvent ){
            var submitCode = dom.oneObject("13,108");
            var submitButton = dom.oneObject("submit,image");
            var submitInput = dom.oneObject("text,password,textarea");
            var submitRoom = specials.submit = {
                liveSetup: function(target){
                    //将事件回调函数保存到一个在卸载时可以找到它的地方
                    submitRoom[uuid(target)+"_handle"] =  function(){
                        var e = events.fix(event), el = e.target, type = el.type;
                        if( el.form &&  ( submitButton[type] || submitCode[ e.which ] && submitInput[type]) ){
                            dom.log("模拟IE下的submit事件冒泡 ");
                            e.type = "submit";
                            events.handle.call(target, e);
                        }
                    }
                    "onclick,onkeypress".replace(rword,function(type){
                        target.attachEvent(type ,submitRoom[uuid(target)+"_handle"] );
                    })
                },
                liveTeardown: function(target){
                    "onclick,onkeypress".replace(rword,function(type){
                        target.detachEvent(type ,submitRoom[uuid(target)+"_handle"]||dom.noop );
                    });
                    delete submitRoom[uuid(target)+"_handle"];
                }
            }
        }
        //reset事件的冒泡情况----FF与opera能冒泡到document,其他浏览器只能到form
        if(!dom.eventSupport("reset") ){
            var resetRoom = specials.reset = {
                liveSetup:DOC.dispatchEvent ? 0 : function(target){
                    resetRoom[uuid(target)+"_handle"] =  function(){
                        var e = events.fix(event), el = e.target;
                        if(  el.form && (e.which === 27  ||  el.type == "reset") ){
                            dom.log("模拟reset事件冒泡 ");
                            e.type = "reset";
                            events.handle.call(target, e);
                        }
                    }
                    "onclick,onkeypress".replace(rword,function(type){
                        target.attachEvent(type , resetRoom[uuid(target)+"_handle"]);
                    });
                },
                liveTeardown:DOC.dispatchEvent ? 0 : function(target){
                    "onclick,onkeypress".replace(rword,function(type){
                        target.dettachEvent(type ,resetRoom[uuid(target)+"_handle"]);
                    })
                    delete resetRoom[uuid(target)+"_handle"];
                }
            }
        }
        //change事件的冒泡情况----FF与opera能冒泡到document,其他浏览器完全不冒泡
        if(!dom.eventSupport("change") ){
            function getVal( node ) {
                var type = node.type, val = node.value;
                if ( type === "radio" || type === "checkbox" ) {
                    val = node.checked;
                } else if ( type === "select-multiple" ) {
                    val = node.selectedIndex === -1 ? "":
                    dom.lang( node.options).map(function( node ) {
                        return node.selected;
                    }).join("-") ;
                } else if ( type === "select-one" ) {
                    val = node.selectedIndex;
                }
                return val;
            }
            function changeHandle( e ) {
                var  eType = e.type,node = e.srcElement,nType = node.type
                //如果不是表单元素,又或者处于只读状态下,直接返回
                if ( !node.form || node.readOnly ) {
                    return;
                }
                if ( eType === "keydown" ) {
                    var flag = false;
                    if ( (e.keyCode === 13 && nType !== "textarea"  ) ||
                        (e.keyCode === 32 && ( nType === "checkbox" || nType === "radio")) ||
                        nType === "select-multiple" ) {
                        flag = true;
                    }
                    if(!flag){
                        return;
                    }
                }
                //取得之前的数据
                var oldData = dom.data( node, "_change_data" );
                //获取现在的数据
                var newData = getVal(node);
                // focusout是不用重设数据的
                if ( eType !== "focusout" || nType !== "radio" ) {
                    dom.data( node, "_change_data", newData );
                }
                if ( newData === undefined || newData === oldData ) {
                    return;
                }
                if ( oldData != null || newData ) {
                    e = events.fix(e);
                    e.type = "change";
                    events.handle.call(node, e);
                }
            }


            var changeRoom = specials.change = {

                liveSetup: function(node){
                    //此事件用于收集数据
                    node.attachEvent("onbeforeactivate", function( ) {
                        changeRoom[uuid(node)+event.type+"_handler"] = arguments.callee;
                        dom.data( node, "_change_data", getVal(node) );
                    });
                    ////当点击目标元素时,再点击页面的其他位置时,依次会发生如下事件beforeactivate  click | beforedeactivate focusout
                    "onfocusout,onbeforedeactivate,onclick,onkeydown".replace(rword,function(type){
                        node.attachEvent(type ,function(){
                            changeRoom[uuid(node)+event.type+"_handler"] = arguments.callee;
                            changeHandle.call(node, event);
                        });
                    });
                },
                liveTeardown:function(node){
                    var uid = uuid(node);
                    "focusout,beforedeactivate,click,keydown,beforeactivate".replace(rword,function(type){
                        node.detachEvent("on"+type,changeRoom[uid+type+"_handler"]||dom.noop );
                        delete changeRoom[uid+type+"_handler"]
                    });
                }
            }
        }

        //当一个元素,或者其内部任何一个元素获得焦点的时候会触发这个事件。
        //这跟focus事件区别在于,他可以在父元素上检测子元素获取焦点的情况。
        //=================================
        dom.include({
            toggleClick:function(/*fn1,fn2,fn3*/){
                var fns = [].slice.call(arguments), i = 0;
                return this.click(function(e){
                    var fn  = fns[i++] || fns[i = 0, i++];
                    fn.call(this,e);
                })
            },
            hover: function( fnOver, fnOut ) {
                return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
            }
        });
        "bind,unbind,fire".replace(rword, function(method){
            dom.fn[method] = function(){
                var args = arguments;
                return this.each(function(target){
                    events[method].apply(target, args);
                });
            }

        });
        //事件代理的原理就是把事件侦听器绑定在顶层元素上,通过判定下层冒泡或捕获上来的事件源元素的特定,执行相应的回调
        "delegate,undelegate".replace(rword,function(method){
            dom.fn[method] = function(expr,type,callback){
                var special = events.special[ type ] || {};
                var bag = {
                    live: expr,
                    type: type,
                    callback:callback
                }
                bag.uuid =  callback.uuid = dom.uuid++;
                //选取可用的事件类型,如mouseenter在标准浏览器下就不能用,需要用mouseover冒充
                type = special.type || type
                return this.each(function(node){
                    events[method === "delegate" ? "bind" : "unbind"].apply(node, [type, bag, true]);
                });
            }
        });
        "live,die".replace(rword,function(method){
            dom.fn[method] = function(type,callback){
                var expr = this.selector;
                var doc = this.doc || DOC;
                if(!expr){
                    expr = dom.noop;
                }
                return dom(doc)[method === "live" ? "delegate" : "undelegate"](expr, type, callback);
            }
        });
        types.replace(rword,function(type){
            dom.fn[type] = function(callback, phase, trust){
                return this.bind(type, callback, phase, trust);
            }
        });
    });
})(this,this.document);

相关链接:dom Framework emitter模块

posted on 2011-08-21 13:14  司徒正美  阅读(1131)  评论(0编辑  收藏  举报