Подтвердить что ты не робот

JavaScript: как имитировать событие изменения в Internet Explorer (делегирование)

ОБНОВЛЕНИЕ: (recap, скрипка и баунти)

Этот вопрос не привлекает слишком много внимания, поэтому я собираюсь провести некоторое впечатление на него. Я знаю, что я склонен быть слишком многословным как в моих ответах, так и в вопросах. Вот почему я пошел вперед и настроил эту скрипту, которая, на мой взгляд, является достойным представлением того типа кода, который я сейчас имею чтобы приблизиться к пузырящему событию change. Несколько проблем, которые я пытаюсь решить:

  • Событие pseudo-change не срабатывает над элементом select, если только он не теряет фокус. В некоторых случаях клиент должен быть перенаправлен при выборе нового значения. Как мне это достичь?
  • Обработчик вызывается как при нажатии этикеток, так и в самих флажках. Само по себе то, что вы ожидаете, но из-за события, пузырящегося в нем (AFAIK), невозможно определить, какой элемент был нажат. Объект события IE не имеет свойства realTarget.
  • При изменении checked -state флажка в IE, нажав на метку, все хорошо (хотя для этого требуются некоторые неприятные обходные пути), но при непосредственном нажатии флажка вызывается обработчик, но проверенное состояние остается без изменений, пока я не щелкнул второй раз. Затем значение изменяется, но обработчик не вызывается.
  • Когда я переключаюсь на другую вкладку и обратно, обработчик вызывается несколько раз. Три раза, если проверенное состояние действительно изменилось, дважды, если я щелкнул чекбокс только один раз.

Приветствуется вся информация, которая могла бы помочь мне решить один или несколько вышеперечисленных вопросов. Пожалуйста, я не забыл добавить тег jQuery, мне нравится чистый JS, поэтому я ищу чистый ответ JS.


У меня есть веб-страница с более чем 250 элементами выбора на ней и 20-30 флажков. Я также должен отслеживать действия пользователей и предпринимать соответствующие действия. Поэтому для меня вполне естественно делегировать событие изменения, а не добавлять сотни слушателей, IMHO.

Конечно, политика IE-company: поддержка IE8 - не запускает событие onchange, когда мне это нужно. Поэтому я пытаюсь подделать событие onchange. То, что я до сих пор работает достаточно хорошо, кроме одной вещи, которая действительно меня беспокоит.
Я использую onfocusin и onfocusout для регистрации событий. В некоторых случаях, когда пользователь выбирает новое значение из элемента select, script должен немедленно ответить. Однако, пока выбор не потерял фокус, этого не произойдет.

Вот что я придумал до сих пор:

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);
}

Я попытался подключить другой прослушиватель событий внутри обработчика onfocusin, связанный с событием onclick. Он выпустил событие onfocusout для любого выбранного пункта с фокусным банкоматом. Единственная проблема заключается в том, что 99,9% пользователей будут щелкать по выбору, поэтому событие focusin также запускает onclick.

Чтобы обойти это, я создал закрытие и передал текущий select-in-focus и его исходное значение в качестве аргументов. Но некоторые пользователи используют свою клавиатуру, и эти пользователи часто переходят к следующему полю выбора без изменения значения. Каждый раз привязывая новый onclick-прослушиватель... Я действительно считаю, что это проще, чем проверять все 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;
4b9b3361

Ответ 1

Ну, у меня была еще одна трещина, и я придумал довольно приличный подход (на работе это сделал трюк - я пытался воспроизвести код, который я написал, но после нескольких сортов пива он может содержать некоторые ошибки, но дух остается тем же)

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!
});

Я узнал, что события focusin дважды срабатывают для радио и флажков. Использование объекта, который содержит фактические проверенные состояния всех флажков, является менее дорогостоящим, чем отдельные обработчики, и это позволяет мне делегировать событие только после изменения значения элемента.

Функция changeDelegator вызывается только тогда, когда это необходимо, но анонимная функция, которую я разместил здесь, по-прежнему получает имя Waaaay больше, чем я хотел, но этот подход по-прежнему превосходит отдельные обработчики.

Я оставил выборки, но я заставил их работать тоже (похоже, в полной версии моего кода у закрытия есть 2 объекта, и я сделал это, поэтому я могу указать id, запустить событие размытия, когда необходимо, и клиент перенаправляется).
В конце бега, хотя я узнал некоторые новые трюки, главное, что я убираю из этого упражнения, - это еще более глубокая ненависть к этому ужасному, мужественному голему вещи под названием IE... Но если кто-либо еще захочет делегировать события изменения в IE, узнайте, что он (почти) возможно

Ответ 2

Хотя я согласен, что было бы лучше иметь только одного прослушивателя событий на всей форме вместо многих слушателей, по одному для каждого элемента, вы должны оценить затраты и преимущества своего решения. Преимущество одного слушателя - сокращение объема памяти. Недостатком является то, что вам нужно делать такие сложные кодовые трюки, чтобы обойти один браузер, некорректную реализацию событий, увеличивая время выполнения, неоднократно злоупотребляя типами событий, регистрируя и отменяя прослушиватели событий, возможно, создавая путаницу для некоторых пользователей.

Возвращаясь к выгоде, объем памяти не так велик, если вы просто присоединяете ту же функцию, что и слушатель для всех элементов. И память - это то, чего не хватает текущим компьютерам.

Даже если это не ответит на ваш вопрос, я бы посоветовал вам перестать пытаться выполнить эту работу и вместо этого подключать слушателей к каждому элементу формы.

Чтобы немного коснуться ваших точек:

  • Вы уверены в этом? Я пробовал базовый пример из документации Microsoft, и как IE7, так и IE8 запускают прослушиватель onchange после того, как я нажму на параметр из раскрывающегося списка, Он даже срабатывает при изменении выбора клавишами вверх/вниз.

  • Хотя верно, что вы не можете легко подключить событие на ярлыке к соответствующему флажку, почему вы хотите это сделать? Событие change будет запускаться в флажке в любом случае, и вы должны заботиться только об этом. Однако, если вы должны установить флажок из события на ярлыке, вы можете сделать это вручную. Если событие нацелено на метку, то вы знаете, что метка каким-то образом связана с вводом. В зависимости от того, как вы используете метки, вы можете выбрать элемент input, вложенный внутри label, или получить элемент с идентификатором, найденным в атрибуте for label.

  • Один из способов фиксации флажков вернуть предыдущее значение в ошибке изменения - это сделать this.blur() on focus events on checkboxes.

  • Когда вы говорите "обработчик", вы имеете в виду обработчик события onfocusin или changeDelegator? При повторной активации вкладки нормально видеть огонь focusin. Я не знаю точно, почему событие запускается более одного раза, поэтому я просто догадываюсь: может быть, фокус, который получает активный вход; второй может быть фокусом, который сам документ получает; Я понятия не имею, почему происходит третий звонок. Я не понимаю, что вы подразумеваете под "если измененное состояние действительно изменилось, [...], если я установил флажок непосредственно только один раз".