jQuery

индекс
283,92

Анатомия .click() или история одной проблемы

Доброго времени суток, товарищи.

Столкнулся с одной проблемой в JQuery и теперь хочу выяснить: все как всегда (сам дурак и прощай остатки кармы) или все же это такая особенность JQuery?

Положим, есть следующий HTML-код:
<input id="dis" type="checkbox" />
<input id="chb" type="checkbox" /><input id="txt" type="text" />


* This source code was highlighted with Source Code Highlighter.


Схема работы предельно простая: чекбокс #chb управляет активностью текстового поля #txt (когда checked — !disabled и наоборот), а чекбокс #dis отключает (disabled=«disabled») чекбокс #chb и переключает его состояние.

Вот примерный код:
$("#chb").click(function() {
    $("#txt").attr("disabled", ! this.checked);
});

$("#dis").click(function() {
    if ( $("#chb").attr("checked") )
    {
        $("#chb").click();
    }

    $("#chb").attr("disabled", this.checked);
});


* This source code was highlighted with Source Code Highlighter.


Казалось бы, ничего необычного?

Кликая по #chb, текстовое поле будет включаться/выключаться как и надо. А вот кликая по #dis, мы получим лишь отключение #chb. А как же #txt? Ведь выполнение $("#chb").click() должно гарантировать и его отключение тоже. (Если нет — поправьте меня, пожалуйста, и дайте ссылку на объясняющую статью. Спасибо.)

Как показал тест, существует некое несоответствие в последовательности действий.

При клике на чекбоксе мышью:
1) переключить checked;
2) выполнить повешенные на клик обработчики.

При программном вызове click():
1) выполнить повешенные на клик обработчики;
2) переключить checked.

(вы можете проверить, повесив на click alert)

То есть, одна и та же (казалось бы) операция вызывает разные последовательности действий.

Теперь понятно, почему не отключается #txt: в момент, когда выполняется обработчик (проверяющий, а не checked ли управляющий чекбокс?), чекбокс все еще checked.

Так что же делать?



Раз мы знаем, что происходит, значит можем решить проблему.

Все было бы просто замечательно, если бы где-то хранилось не только текущее состоянии элемента (checked), но и изменение состояния (+1 — checked изменился с false на true, -1 — c true на false). К сожалению, насколько мне известно, JQuery за этим не следит.

Немного матчасти для понимания дальнейшего. При клике на элемент последовательно генерируются три события:
1) mousedown
2) mouseup
3) click
За переключение состояния чекбокса отвечает именно click. А раз он ведет себя не так, как нам хочется, мы отключим его совсем и будем использовать mouseup.

Вот так мне видится решение:
$("#chb").click(function(e) {
    // отключаем стандартную обработку клика, сами справимся
    e.preventDefault();
});

$("#dis").click(function() {
    if ( $("#chb").attr("checked") )
    {
        $("#chb").mouseup();
    }

    $("#chb").attr("disabled", this.checked);
});

$("#chb").mouseup(function() {
    // имитация клика — переключение
    this.checked = ! this.checked;

    $("#txt").attr("disabled", ! this.checked);
});


* This source code was highlighted with Source Code Highlighter.


Таким образом, из mouseup мы сделали click с предсказуемым поведением.

Прошу прощения за стиль изложения, читать трудно, но я не умею писать по-другому. Надеюсь, когда-нибудь кому-нибудь это пригодится.

А в комментариях буду рад объяснениям столь необычного явления, критике и более простым решениям (если они вообще требуются). Спасибо за внимание.

upd: Хабраюзер Yeah подсказал функцию triggerHandler, которая может вызывать обработчики, повешенные на события, без срабатывания стандартного поведения. Тогда можно сделать проще, изменив в начальном варианте пару строк:

$("#dis").click(function() {
  if ( $("#chb").attr("checked") )
  {
    // сделаем поведение однообразным: сначала снимем галочку, а затем вызовем обработчики
    $("#chb").attr("checked", false);
    $("#chb").triggerHandler('click');
  }

  $("#chb").attr("disabled", this.checked);
});

* This source code was highlighted with Source Code Highlighter.

И никаких mouseup. Но остается вопрос в причинах такого поведения.

_________
Текст подготовлен в ХабраРедакторе
+4
9 февраля 2010, 20:23
9

комментарии (11)

+2
1amer #
полная имитация клика это mousedown+mouseup. иначе думаю если нажмете на другой элемент а кнопку отпустите над chb то тоже сработает
0
MaxxArts #
Да, здесь имитация неполная, но в данном случае это не критично.
+6
Yeah #
Вместо $("#chb").click(); попробуйте $("#chb").trigger('click'); или $("#chb").triggerHandler('click');
+2
MaxxArts #
Документация jQuery говорит нам, что .click() «is a shortcut for .bind('click', handler) in the first variation, and .trigger('click') in the second.» А вот с triggerHandler интереснее: «The .triggerHandler() method behaves similarly to .trigger(), with the following exceptions:
— The .triggerHandler() method does not cause the default behavior of an event to occur (such as a form submission).»
То есть, можно использовать тот же click(), дополняя имитацией стандартного поведения. Спасибо, попробую, возможно, так будет чуть проще.
+2
akzhan #
jQuery тут сбоку припёка, то же самое получите и при использовании onclick.

я просто по клику делал setTimeout(handler, 0)
0
MaxxArts #
Возможно, но мне показалось, что это проблема именно jQuery. Неужели все js-движки работают в подобных ситуациях одинаково? Это стандарт такой что ли?)
+1
Wott #
имхо проще было бы использовать в обработчике #chb не this.checked а внутреннюю переменную. Добавилась бы только ее инициализация вначале.
0
MaxxArts #
Это лишь абстрактный пример, на самом деле элементов очень много, пока не вижу смысла отводить на них отдельный массив переменных.
0
Wott #
Зачем массив? в обьекте jQuery на элементе заводите свою переменную и ей оперируете.
0
garex #
Что-то знакомое.

Помню что-то похоже меня удивило и причем оно по разному в IE и FF по порядку обрабатывалось.

Я в итоге добавлял на события change и keypress, а изначально пытался на click.
–1
CTAPbIu_MABP #
все очень просто.
есть чекбокс и его родной онклик который меняет его состояние
есть навешаное тобой событие которое по клику меняет его еще раз

1 если ты реально кликаешь на чекбокас происходят оба события и состояние меняться два раза тоесть становиться снова тем которое было
2 если ты програмно вызыввешь триггер то происходят оба события с тем же результатом что и прошлый раз
3 если ты вызываешь тригер хендлер то происходит только то событие которое ты навесил через жуквери.

возможно тебе будет интересно как я воевал с этим в свое время
mabp.kiev.ua/2007/07/03/onmouseover_checkbox_toggler/

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.