UPDATE: (recap, fiddle and bounty)
This question hasn't been getting too much attention, so I'm going to spend some rep on it. I know I tend to be overly verbose in both my answers and questions. That's why I went ahead and set up this fiddle, which is, in my view, a decent representation of the kind of code I'm currently having to use to come close to a bubbling change
event. A couple of issues I'm trying to resolve:
- The
pseudo-change
event doesn't fire on a select element, unless it looses focus. In some cases the client should be redirected upon selecting a new value. How do I achieve this?
- The handler is called both when the labels are clicked, as well as the checkboxes themselves. In itself that's what you'd expect, but due to the event bubbling it's (AFAIK) impossible to determine which element was clicked. IE's event object doesn't have a
realTarget
property.
- When changing the
checked
-state of a checkbox in IE by clicking the label, all is well (though it requires some nasty workarounds), but when clicking the checkbox directly, the handler is called, but the checked state remains unchanged, until I click a second time. Then the value changes, but the handler isn't called.
- When I switch to a different tab, and back again, the handler is called multiple times. Three times if the checked state actually changed, twice if I clicked the checbox directly only once.
Any information that could help me resolve one or more of the issues above would be greatly appreciated. Please, I didn't forget to add a jQuery tag, I like pure JS, so I'm looking for a pure JS answer.
I've got a web page with well over 250 select elements on it, and 20~30 checkboxes. I also have to track the users' actions, and take appropriate actions. It is therefore quite natural for me to delegate the change event, rather then adding hundreds of listeners, IMHO.
Of course, IE -company policy: IE8 has to be supported- doesn't fire the onchange event when I need it. So I'm trying to fake an onchange event. What I have thus far is working reasonably well apart from 1 thing that really bugs me.
I'm using onfocusin
and onfocusout
to register the events. In some cases, when the user selects a new value from the a select element, the script should respond immediately. However, as long as the select hasn't lost focus, this won't happen.
Here's what I came up with so far:
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);
}
I've tried attaching another event listener inside the onfocusin
handler, bound to the onclick
event. It fired the onfocusout
event of whatever select has focus ATM. The only problem is, 99.9% of the users will click on a select, so the focusin
event fires an onclick, too.
To get round that, I created closure, and passed the current select-in-focus and it's original value to it as arguments. But some users do use their keyboard, and these users often tab to the next select box without changing the value. Each time binding a new onclick listener... I do believe there HAS to be an easier way than to check all e.type
's and treat them separately.
Just as an example: the code with an extra onclick
listener: all code is the same as the first snippet, so I'm only pasting the case 'select':
block
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;
See Question&Answers more detail:
os