JavaScript:如何模拟Internet Explorer中的更改事件(委派)

Eli*_*gem 14 javascript internet-explorer javascript-events event-delegation

更新:( 回顾,小提琴和赏金)

这个问题并没有得到太多的关注,所以我要花一些代表.我知道我的答案和问题往往过于冗长.这就是为什么我继续设置这个小提琴,在我看来,这是我目前不得不用来接近冒泡change事件的代码.我正试图解决的几个问题:

  1. pseudo-change事件不会触发一个select元素,除非它失去焦点.在某些情况下,应在选择新值时重定向客户端.我该如何实现这一目标?
  2. 单击标签时调用处理程序,以及复选框本身.这本身就是你所期望的,但是由于事件冒泡它(AFAIK)无法确定点击了哪个元素.IE的事件对象没有realTarget属性.
  3. checked通过单击标签在IE中更改复选框的-state时,一切都很好(虽然它需要一些讨厌的解决方法),但是当直接单击复选框时,会调用处理程序,但是检查状态保持不变,直到我单击一个第二次.然后值会更改,但不会调用处理程序.
  4. 当我切换到另一个选项卡,然后再次返回时,会多次调用该处理程序.如果检查状态实际发生了变化,则三次,如果我直接单击一次,则两次.

任何可以帮助我解决上述一个或多个问题的信息将不胜感激.拜托,我没有忘记添加一个jQuery标签,我喜欢纯JS,所以我正在寻找一个纯粹的JS答案.


我有一个网页上有超过250个选择元素,以及20~30个复选框.我还必须跟踪用户的操作,并采取适当的措施.因此,我很自然地委派变更事件,而不是添加数百个听众,恕我直言.

当然,IE -company策略:IE8必须得到支持 - 在我需要时不会触发onchange事件.所以我想假装一个onchange事件.到目前为止,我所做的工作相当不错,除了一件真正让我烦恼的事情.
我正在使用onfocusinonfocusout注册活动.在某些情况下,当用户从select元素中选择一个新值时,脚本应立即响应.但是,只要选择没有失去焦点,就不会发生这种情况.

这是我到目前为止所提出的:

i = document.getElementById('content');
if (!i.addEventListener)
{
    i.attachEvent('onfocusin',(function(self)
    {
        return function(e)
        {
            e = e || window.event;
            var target = e.target || e.srcElement;
            switch (target.tagName.toLowerCase())
            {
                case 'input':
                    if (target.getAttribute('type') !== 'checkbox')
                    {
                        return true;
                    }
                    return changeDelegator.apply(self,[e]);//(as is
                case 'select':
                    self.attachEvent('onfocusout',(function(self,current)
                    {
                        return function(e)
                        {
                            e = e || window.event;
                            var target = e.target || e.srcElement;
                            if (target !== current)
                            {
                                return;
                            }
                            self.detachEvent('onfocusout',arguments.callee);//(remove closure
                            return changeDelegator.apply(self,[e]);
                        }
                    })(self,target));
                default: return;
            }
        }
    })(i));//(fake change event, buggy
}
else
{//(to put things into perspective: all major browsers:
    i.addEventListener('change',changeDelegator,false);
}
Run Code Online (Sandbox Code Playgroud)

我已经尝试在onfocusin处理程序中附加另一个事件监听器,绑定到onclick事件.它解雇了onfocusout任何具有焦点ATM的选择事件.唯一的问题是,99.9%的用户会点击一个选择,因此该focusin事件也会触发onclick.

为了解决这个问题,我创建了闭包,并将当前的select-in-focus和它的原始值作为参数传递给它.但是有些用户确实使用了他们的键盘,这些用户经常会在不更改值的情况下切换到下一个选择框.每次绑定一个新的onclick听众......我相信有HAS比检查所有更简单的方法e.type的,并分别对待.
举个例子:带有额外onclick监听器的代码:所有代码都与第一个代码段相同,所以我只粘贴case 'select':

                case 'select':
                    self.attachEvent('onfocusout',(function(self,current)
                    {
                        return function(e)
                        {
                            e = e || window.event;//(IE...
                            var target = e.target || e.srcElement;
                            if (!(target === current))
                            {
                                return;
                            }
                            self.detachEvent('onfocusout',arguments.callee);//(remove closure
                            return changeDelegator.apply(self,[e]);
                        };
                    })(self,target));
                    self.attachEvent('onclick',(function(self,current,oldVal)
                    {
                        return function(e)
                        {
                            e = e || window.event;
                            var target = e.target || e.srcElement;
                            if (target.tagName.toLowerCase() === 'option')
                            {
                                while(target.tagName.toLowerCase() !== 'select')
                                {
                                    target = target.parentNode;
                                }
                            }
                            if (target !== current)
                            {//focus lost, onfocusout triggered anyway:
                                self.detachEvent('onclick',arguments.callee);
                                return;
                            }
                            var val = target.options[target.selectedIndex].innerHTML;//works best in all browsers
                            if (oldVal !== target.options[target.selectedIndex].innerHTML)
                            {
                                self.detachEvent('onclick',arguments.callee);
                                return target.fireEvent('onfocusout');
                            }
                        };
                    })(self,target,target.options[target.selectedIndex].innerHTML));
                default: return;
Run Code Online (Sandbox Code Playgroud)

Eli*_*gem 3

好吧,我又进行了一次破解,并且我想出了一个相当不错的方法(在工作中它做到了这一点 - 我试图复制我编写的代码,但喝了几杯啤酒后,它可能包含一些错误,但精神依旧

window.attachEvent('onload',function ieLoad()
{
    var mainDiv = document.getElementById('main');//main div, from here events will be delegated
    var checks = mainDiv.getElementsByTagName('input');//node list of all inputs
    var checkStates = {};
    for (var i=0;i<checks.length;i++)
    {
        if (checks[i].type === 'checkbox')
        {//get their checked states on load, this object serves as a reference
            checkStates[checks[i].id] = checks[i].checked;
        }
    }
    mainDiv.attachEvent('onfocusin',(function(initState)
    {//initState holds a reference to the checkStates object
        return function(e)
        {
            e = e || window.event;
            var target = e.target || e.srcElement;
            //id of checkboxes used as key, so no checking for tagName or type required
            if (!initState.hasOwnProperty(target.id) || target.checked === initState[target.id])
            {//don't call method if checkstate is unchanged, either. I'll explain in a minute
                return e;
            }
            initState[target.id] = target.checked;//set new checked-state
            changeDelegator.apply(target,[e]);//delegate
        };
    })(checkStates));
    window.detachEvent('onload',ieLoad);//avoid mem-leak with onload handler!
});
Run Code Online (Sandbox Code Playgroud)

我发现在某些情况下,单选按钮和复选框的焦点事件会触发两次。使用保存所有复选框的实际选中状态的对象比单独的处理程序更便宜,并且它允许我仅在元素值更改后委托事件。

changeDelegator函数仅在需要时才被调用,但我在这里发布的匿名函数仍然被调用得比我想要的多,但这种方法仍然优于单独的处理程序。

我省略了选择,但我也让它们工作了(类似地,在我的代码的完整版本中,闭包有 2 个对象,并且我做到了,所以我可以标记一个 id,在需要时触发模糊事件,并且客户端被重定向)。
在跑步结束时,尽管我学到了一些新技巧,但我从这次练习中学到的主要东西是对那个叫做 IE 的可怕的、坚韧的傀儡的更深刻的仇恨......但如果其他人可能想要在 IE 中委托更改事件,知道这是(几乎)可能的