JavaScript 常见的事件冲突解决方案,日历Demo

web课布置了个作业,给一份全是bug的日历,要解决chrome上的事件冲突问题

ps:事件冲突解决起来真的很麻烦

1.onblur 和 onclick冲突

onclick 相当于 在某一元素上触发了 onmousedown(即鼠标按下)后 任然在该元素 上 触发了onmouseup(鼠标按键弹起)才触发 onclick;

对于某元素A 绑定了 click事件 并同时对另外 的元素B 绑定onblur事件,

这时候,当在A上mousedown后,即触发了B元素的onblur事件,该事件函数执行后的效果是 改变了DOM结构,

使得鼠标已经不在在元素A之上。 这时鼠标任然没有mouseup ,在mouseup之后,以为会触发click事件,实际上却不能触发。

解决方案:要保证onclick先于给onblur被触发的话,给onblur弄个setTimeout ,即让它延时一会,等onclick处理完后,立刻clear这个Timeout,就能立即触发onblur时间

2.子节点和父节点同时触发onclick

如果子节点和父节点同时都注册了onclick等事件,那么在chrome上,单击子节点时会同时触发父子结点的onclick

解决方案:如果仅仅想触发子节点的而不触发父节点的,那么就在子节点的onmousedown事件里,把父节点的onclick事件取消了(如设一个空函数)

     再在子节点的onmouseup事件里,把父节点原来的onclick事件注册给它就可以了

<!DOCTYPE html>
<html>
    <head>
        <meta charset=”gbk">
    </head>
    <body>
    </body>
</html>

<style type="text/css">
.body, td{
    font-family:"Arial";
    font-size:8pt;
    color:#000000;
}
.TrOut{
    border-left:1px solid #f4f4f4; 
    border-right:1px solid #999999; 
    border-top:1px solid #f4f4f4; 
    border-bottom:1px solid #999999; 
    background:#dddddd; 
    height:26;
}
.TdOver{
    border-left:1px 
    solid #9c9c9c; 
    border-right:1px 
    solid #ffffff; 
    border-top:1px 
    solid #9c9c9c; 
    border-bottom:1px solid #ffffff; 
    background:#eeeeee; 
    height:20;
}
.TdOut{
    border-left:1px solid #ffffff; 
    border-right:1px solid #9c9c9c; 
    border-top:1px solid #ffffff; 
    border-bottom:1px solid #9c9c9c; 
    background:#eeeeee; height:20;
}
</style>
<script language="JScript">
/*********************************************************
                迷你绽?.0版本

如果下列代码发现bug请联系 Flashsoft2000@hotmail.com

函数 TableFunction 提供以下方法和属性:

1.GetDateStr()返回指定年月的日期的数组,包括空字符.
参数:
    y是指年
    m是指月
    调用方式:TableFunction().GetDateStr(Year,Month)

2.GetTableStr()返回指定年月的已经格式化了的表格
参数:
    y是指年
    m是指月
    调用方式:TableFunction().GetTableStr(Year,Month)

3.WriteSelect()返回年月的选择框
参数:
    obj是指需要加入选择框的容器
    values是指需要加亮的项目
    action是指参数,带入参数y表示是年选择框,带入参数m表示月选择框
    getobj是指在哪个控件上触发的事件,其中true为非当前选择框触发
    而false则为当前选择框触发
    调用方式:TableFunction().WriteSelect(obj,values,action,getobj)

4.RewriteTableStr()    复位重写表格中的日期
参数:
    y是指年
    m是指月
    调用方式:TableFunction().RewriteTableStr(Year,Month)

5.JumpToRun()    左右控制表格中的月变化
参数:
    action表示日历是前进还是后退
    调用方式:TableFunction().JumpToRun(action)

6.AlertDay()提示当前点击的位置的日期
参数:无
调用方式:TableFunction().AlertDay()

*********************************************************/
function TableFunction(){
    /*
    1.GetDateStr()返回指定年月的日期的数组,包括空字符.
    参数:
        y是指年
        m是指月
        调用方式:TableFunction().GetDateStr(Year,Month)
    */
    this.GetDateStr=function(y,m){
        this.DayArray=[];
        for(var i=0;i<42;i++)this.DayArray[i]=" ";
        for(var i=0;i<new Date(y,m,0).getDate();i++) // new Date(y,m,0).getDate():得到该月最大天数
            this.DayArray[i+new Date(y,m-1,1).getDay()]=i+1; // new Date(y,m-1,1).getDay() 该月第一天是星期几
        return this.DayArray;
    }
    
    /*
    2.GetTableStr()返回指定年月的已经格式化了的表格
    参数:
        y是指年
        m是指月
        调用方式:TableFunction().GetTableStr(Year,Month)
        
        oncontextmenu:右键单击触发
        onselectstart:触发时间为目标对象被开始选中时
        colspan:列数
        webdings:类似图标控件库,可用数字代替图标
        onmouseover:当鼠标放上时
        onmouseout:当鼠标移出时
        TdOver:当前天或鼠标滑过的那个格子
        TdOut:非当前天或鼠标未滑过的那个格子
    */
    this.GetTableStr=function(y,m){
        this.DateArray=["日","一","二","三","四","五","六"];
        this.DStr=
            "<table oncontextmenu='return false' onselectstart='return false'   cellpadding='0' cellspacing='0'>\n"+
                "<tr>"+
                    "<td colspan='7' class='TrOut'>"+ 
                        //带有前进后退按钮的首行
                        "<table width='100%' height='100%' cellpadding='0' cellspacing='0'>"+
                            "<tr align='center'>\n"+
                                "<td width='20' webdings\";font-size:9pt' onclick='TableFunction().JumpToRun(\"b\")' onmouseover='this.style.color=\"#ff9900\"' onmouseout='this.style.color=\"\"'>3</td>\n"+
                                "<td  width='70' onmouseover='this.style.background=\"#cccccc\"' onmouseout='this.style.background=\"\"' onmouseup='TableFunction().WriteSelect(this,this.innerText.split(\" \")[0],\"y\",false)'>"+y+" 年</td>\n"+
                                "<td  width='47' onmouseover='this.style.background=\"#cccccc\"' onmouseout='this.style.background=\"\"' onmouseup='TableFunction().WriteSelect(this,this.innerText.split(\" \")[0],\"m\",false)'>"+m+" 月</td>\n"+
                                "<td width='20' webdings\";font-size:9pt' onclick='TableFunction().JumpToRun(\"n\")' onmouseover='this.style.color=\"#ff9900\"' onmouseout='this.style.color=\"\"'>4</td>"+
                            "</tr>"+
                        "</table>\n"+
                    "</td>"+
                "</tr>\n"+
                "<tr align='center'>\n";
        
        //添加一星期每天的名称格子
        for(var i=0;i<7;i++)
            this.DStr+="<td class='TrOut'>"+DateArray[i]+"</td>\n";
        this.DStr+="</tr>\n";
        
        //添加下面六行日期格子,当前日期需要特别标注出来
        for(var i=0;i<6;i++){
            this.DStr+="<tr align='center'>\n";
            for(var j=0;j<7;j++){
                var CS=new Date().getDate()==this.GetDateStr(y,m)[i*7+j]?"TdOver":"TdOut";
                this.DStr+="<td  class='"+CS+"' cs='"+CS+"' onmouseover='this.className=\"TdOver\"' onmouseout='if(this.cs!=\"TdOver\")this.className=\"TdOut\"' onclick='TableFunction().AlertDay()'>"+this.GetDateStr(y,m)[i*7+j]+"</td>\n";
            }
            this.DStr+="</tr>\n";
        }
        
        this.DStr+="</table>";
        return this.DStr;
    }
    
    
    /*
    3.WriteSelect()返回年月的选择框
    参数:
        obj是指需要加入选择框的容器
        values是指需要加亮的项目
        action是指参数,带入参数y表示是年选择框,带入参数m表示月选择框
        getobj是指在哪个控件上触发的事件,其中true为非当前选择框触发
        而false则为当前选择框触发
        调用方式:TableFunction().WriteSelect(obj,values,action,getobj)
        
        这里的流程:首先YearTD在未点击前的innerHTML是“120年”这样的内容
                    在点击后,即触发了onmouseup,就会进入该函数,此时参数getobj是false
                    进入函数后,由于getobj=false,所以YearTD的innerHtml被改为select标签,并且给select设置onblur,onchange事件
                        当选择了option的某一项后,需要更新下放日期格子的值,
                        所以调用onblur,将YearTD的innerHTML值改为选中的那一项option的值
                        然后再次调用该函数,此时getobj是true,表示要进行的操作是更新日历
                        此时传入的obj任然是YearTD,然后我们在函数中调用RewriteTableStr去更新日历就可以
    */
    this.WriteSelect=function(obj,values,action,getobj){
        console.log(obj);
        if(values=="")return;
        if(getobj){//如果是要显示更新的日历,则直接更新下方日期
            if(action=="y")YearTD.innerHTML=values+" 年";
            else MonthTD.innerHTML=values+" 月";
            var y=YearTD.innerHTML.split(" ")[0],m=MonthTD.innerHTML.split(" ")[0];
            this.RewriteTableStr(y,m);
            return false;
        }
        var StrArray=[];
        if(action=="y"){
            for(var i=0;i<15;i++){
                var year=values-7+i;
                StrArray[i]="<option value='"+year+"' "+(values==year?"selected":"")+"> "+year+"年</option>\n";
            }
            // 当点击了某选项时就重新将下方日历重写,当焦点移开时就将其值变为一个select下拉框,注意此时select仍处于onblur状态
            // 在chrome 中,onclick被onblur冲突,导致点了一下下拉列表,弹出后马上被onblur冲突导致收回
            /*onclick 相当于 在某一元素上触发了 onmousedown(即鼠标按下)后 任然在该元素 上 触发了onmouseup(鼠标按键弹起)才触发 onclick; 
                对于某元素A 绑定了 click事件 并同时对另外 的元素B 绑定onblur事件, 
                这时候,当在A上mousedown后,即触发了B元素的onblur事件,该事件函数执行后的效果是 改变了DOM结构,
                使得鼠标已经不在在元素A之上。 这时鼠标任然没有mouseup ,在mouseup之后,以为会触发click事件,实际上却不能触发。
            */
            
            obj.innerHTML="<select  >\n"+StrArray.join("")+"</select>";
            
            /*
                这一块的逻辑:此时YearTD的innerHTML已经是select标签,而select标签有onmousedown类似事件用来弹出下拉列表
                正常操作下,onmousedown操作后必定有onmouseup操作
                而select的onmouseup又会触发父节点YearTD注册的onmouseup事件(如果在select处按下鼠标,然后再把鼠标移到其他地方释放,就不会触发YearTD的onmouseup,但是这不是正常人的操作。。),
                导致又重新进入了一次这个函数,这显然是不正确的
                因此我们要在select触发onmousedown事件后,将父节点YearTD的onmouseup先给取消掉防止其触发
                然后在select触发onchange后,我们再将父节点YearTD的onmouseup给注册回来,这样就不影响后续其他操作
            */
            
            select1.onmousedown=function(){
                var parent=this.parentNode;
                parent.onmouseup=function(){};
            }
            
            select1.onchange=function(){
                var parent=this.parentNode;
                YearTD.innerText=this.value+" 年";
                TableFunction().WriteSelect(parent,this.value,"y",true);
                
                parent.onmouseup=function(){
                    TableFunction().WriteSelect(this,this.innerText.split(" ")[0],"y",false);
                }
            };
            select1.focus();
        }
        if(action=="m"){
            for(var i=1;i<13;i++)
                StrArray[i]="<option value='"+i+"' "+(i==values?"selected":"")+"> "+i+"月</option>\n";
            obj.innerHTML="<select  >\n"+StrArray.join("")+"</select>";
            
            select2.onmousedown=function(){
                var parent=this.parentNode;
                parent.onmouseup=function(){};
            }
            
            select2.onchange=function(){
                var parent=this.parentNode;
                MonthTD.innerText=this.value+" 月";
                TableFunction().WriteSelect(parent,this.value,"m",true);
                
                parent.onmouseup=function(){
                    TableFunction().WriteSelect(this,this.innerText.split(" ")[0],"m",false);
                }
            };
            select2.focus();
        }
    }
    
                
    /*
    4.RewriteTableStr()    复位重写表格中的日期
    参数:
        y是指年
        m是指月
        调用方式:TableFunction().RewriteTableStr(Year,Month)
    */            
    this.RewriteTableStr=function(y,m){
        var TArray=this.GetDateStr(y,m);
        var len=TArray.length;
        for(var i=0;i<len;i++){
            TD[i].innerHTML=TArray[i];
            TD[i].className="TdOut";
            TD[i].cs="TdOut";
            if(new Date().getYear()==y&&new Date().getMonth()+1==m&&new Date().getDate()==TArray[i]){
                TD[i].className="TdOver";
                TD[i].cs="TdOver";
            }
        }
    }
                
                
    /*
    5.JumpToRun()    左右控制表格中的月变化
    参数:
        action表示日历是前进还是后退
        调用方式:TableFunction().JumpToRun(action)
    */    
    this.JumpToRun=function(action){
        var YearNO=YearTD.innerText.split(' ')[0];
        var MonthNO=MonthTD.innerText.split(' ')[0];
        if(action=="b"){ //后退一个月
            if(MonthNO=="1"){
                MonthNO=13;
                YearNO=YearNO-1;
            }
            MonthTD.innerText=MonthNO-1+" 月";
            YearTD.innerText=YearNO+" 年";
            this.RewriteTableStr(YearNO,MonthNO-1);//更新下面格子里的日期
        }
        if(action=="n"){ //前进一个月
            if(MonthNO=="12"){
                MonthNO=0;
                YearNO=YearNO-(-1);
            }
            YearTD.innerText=YearNO+" 年";
            MonthTD.innerText=MonthNO-(-1)+" 月";
            this.RewriteTableStr(YearNO,MonthNO-(-1));
        }
    }
        
    /*
    6.AlertDay()提示当前点击的位置的日期
    参数:无
        调用方式:TableFunction().AlertDay()
    */    
    this.AlertDay=function(){
        if(event.srcElement.innerText!=" ")
        alert(YearTD.innerText.split(' ')[0]+"年"+MonthTD.innerText.split(' ')[0]+"月"+event.srcElement.innerText+"日");
    }
    return this;
}

document.write(TableFunction().GetTableStr(new Date().getYear(),new Date().getMonth()+1));
</script>