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

ОБНОВЛЕНИЕ: (резюме, скрипка и награда)

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

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

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


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

Конечно, политика компании IE: должен поддерживаться IE8 - не запускает событие onchange, когда мне это нужно. Итак, я пытаюсь подделать событие onchange. То, что у меня есть, работает достаточно хорошо, за исключением одной вещи, которая меня действительно беспокоит.
Я использую onfocusin и onfocusout для регистрации событий. В некоторых случаях, когда пользователь выбирает новое значение из элемента 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);
}

Я попытался подключить другой прослушиватель событий внутри обработчика onfocusin, привязанного к событию onclick. Он запускал событие onfocusout для любого банкомата select, у которого есть фокус. Единственная проблема в том, что 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;

person Elias Van Ootegem    schedule 04.07.2012    source источник


Ответы (2)


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

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

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

Чтобы немного поговорить с вами:

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

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

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

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

person Sergiu Dumitriu    schedule 17.08.2012
comment
1. Да, я в этом уверен: событие изменения запускается в IE8, но не всплывает. Это известная проблема 2. Обработчик изменений вызывается в в обоих случаях проблема не в этом. Однако при щелчке по метке обработчик вызывается после изменения отмеченного состояния, тогда как отмеченное состояние собирается измениться при непосредственном щелчке флажка. Итак, если target.checked === true, он только что был проверен, или он будет отключен? Невозможно сказать. Закончились символы - person Elias Van Ootegem; 17.08.2012
comment
3. Добавление размытия и фокуса приводит к сбою IE: обратите внимание, что из-за того, что onchange не пузырится, я использую события onfocusin и onfocusout. Если обработчик размывается и фокусируется, он просто будет продолжать вызывать себя бесконечно 4. Под обработчиком я подразумеваю и то, и другое: строго говоря, changeDelegator - это просто функция, но я вызываю ее в контексте обработчиков (changeDelegator.apply(this,[e]);), поэтому я считаю он должен быть расширением / продолжением фактического обработчика. Я знаю, что focusin запускается, когда вкладка возрождена, но он запускается для каждого события, которое обрабатывается функцией делегата. символы .... - person Elias Van Ootegem; 17.08.2012
comment
Под последним я подразумеваю просто: я устанавливаю флажок (обработчик запускается, но отмеченное состояние не меняется!), Если я щелкаю этот флажок во второй раз, то обработчик не вызывается, но проверил состояние все равно меняется. Если я не нажимаю его второй раз, а переключаю вкладки и обратно, событие focusin появляется не для вызова этого флажка, который я щелкнул один раз. Очень странно. Что касается производительности: есть большая разница, особенно когда не запускается на V8. прикрепив его к каждому элементу на всей странице, вы добавили бы 333 (просто посчитал их все) элементов! символов ... - person Elias Van Ootegem; 17.08.2012
comment
Меня беспокоит не память, а масштаб цикла событий. прямая привязка усложнила бы мои обратные вызовы ajax onreadystatechange (присоединение новых обработчиков), не говоря уже об утечках памяти в IE со слушателями для несуществующих элементов. Тем не менее, да, мой код может быть сложным для некоторых из моих коллег, поэтому я нашел время, чтобы хорошо его задокументировать, и, слава Богу, я не единственный, кто здесь любит JS :). Что ж, это мои причины, по которым я утверждаю, что индивидуальная привязка не здесь - person Elias Van Ootegem; 17.08.2012
comment
По поводу 1: Верно; Из вашего вопроса я понял, что событие изменения не запускается для элемента select, если вы не сфокусируетесь, но, читая еще раз, я вижу, что вы имеете в виду, что вы хотели бы, чтобы ваш диспетчер настраиваемых событий запускался. - person Sergiu Dumitriu; 17.08.2012
comment
По 2 и 3: вы можете использовать событие propertychange для флажков. Он правильно срабатывает после изменения свойства checked, и вы получите правильное состояние с помощью флажка. - person Sergiu Dumitriu; 17.08.2012
comment
Повторные утечки памяти: вы можете исправить утечки памяти самостоятельно, вам просто нужно не забыть очистить зарегистрированные вами прослушиватели событий. - person Sergiu Dumitriu; 17.08.2012
comment
Событие propertychange не является вариантом, потому что -like onchange- не всплывает, поэтому удаление невозможно. Есть больше для предотвращения утечек памяти в IE, чем просто очистка слушателей событий, осмелюсь сказать, что знаю кое-что о них. Проверьте мой ответ, это просто для предотвращения утечки window.onload. Прости... - person Elias Van Ootegem; 18.08.2012
comment
Верно, propertychange не всплывает, но это лучший вариант, чем change для флажков. Будет ли это работать, если вместо добавления слушателей изменений ко всем элементам вы просто добавите их для одного элемента на focusin и снова удалите их на focusout? Таким образом, только один элемент одновременно имеет локальный прослушиватель изменений, вам не нужно писать много нестандартного хрупкого кода, и вы очищаете прослушиватели событий, как только они вам не нужны. - person Sergiu Dumitriu; 18.08.2012
comment
нет, это не сработает: когда флажок получает фокус, 9/10 он был нажат == ›значение уже изменилось. и событие propertychange не сработает, если я не сделаю это вручную (решив проблему). Единственное, что такого рода работает, - это добавить второй делегатор (onclick), который работает только с флажками. Однако: это решает только 1 из 4 проблем, которые у меня есть, главное, что я хотел бы исправить, - это выбор: если пользователь выбирает определенное значение, он должен быть немедленно перенаправлен. - person Elias Van Ootegem; 18.08.2012
comment
Нет, это неправда, propertychange всегда идет после focusin. Я настроил jsfiddle, чтобы попробовать это, и заметил, что для элемента select порядок событий всегда - focusin, focus, change, propchange (два раза), в то время как для флажка это зависит от того, где вы щелкаете: на метке вы получаете focusin, propchange, focus и изменение в более позднее время (focusout или нажмите еще раз); на самом флажке: focusin, focus, propchange. - person Sergiu Dumitriu; 18.08.2012
comment
теперь попробуйте свою скрипку на чистом JS и щелкните метку флажка. Также: делегируйте события, не привязывайте их напрямую, и тогда у вас будет такая же настройка, как у меня. Извините, но я должен спросить: вы в курсе, что я делегирую мероприятие сюда? - person Elias Van Ootegem; 18.08.2012

Что ж, у меня была еще одна проблема, и я придумал довольно приличный подход (на работе это помогло - я попытался воспроизвести код, который я написал, но после нескольких порций пива он может содержать некоторые ошибки, но < em> дух остается прежним)

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 вызывается только при необходимости, но функция anon, которую я разместил здесь, по-прежнему вызывается Waaaay больше, чем я хотел, но этот подход по-прежнему превосходит дубль отдельных обработчиков.

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

person Elias Van Ootegem    schedule 20.08.2012