label要素のイベントとinput要素のイベント

  jQueryの勉強をしていた時に、以下の様なコードに遭遇し、labelのイベント処理と対応するinput要素のイベント処理をちょっと考えた話。( コードは説明のため大幅に簡略化してあります)

<form action="どっか" method="get">		
  <input type="checkbox" name="html" value="HTML" id="html">
  <label for="html">HTML</label>
</form>
$("label, input[type=\"checkbox\"]").click(function(){
  $("label").css("background","");
  $(":checked").each(function(){
    $("label[for=\""+$(this).attr("id")+"\"]").css("background-color","#CCC")
  });
});		

上記のコードは、チェックボックスにチェックを入れる時、対応するラベルの背景色を変えてわかりやすくする、という意図で書かれたものです。下の画像の左がクリック前、右がクリック後です。
f:id:uniunimac:20150219142820p:plain

このコードで$("label, input[type=\"checkbox\"]")としている部分を見て、labelとinput要素のイベント処理は独立してるから2つともにイベントつけたのかなと思い、それならinputの方だけを消せば「ラベルを押した場合」だけ色がつくのかなと確認してみると
f:id:uniunimac:20150219150900p:plain
一回目のクリック(画像中央)でチェックボックスにチェックが入るも、色がつかない。二回目のクリック(画像右)で色がついたもののチェックがはずれました。

  なんでこんなことになったのか考えてみると、たぶんlabel要素のイベント処理は以下のように進むからだという結論に。

  1. labelにつけたユーザ定義のクリックイベント
  2. labelのデフォルトクリックイベント
  3. input要素のイベント(デフォルトとユーザ定義)

  上のコードでは、一回目のクリック時にまずlabel要素に定義したクリックイベントが起きる、この時まだチェックボックスのchecked属性がcheckedになっていない。よって$(":checked")には今クリックしたlabel要素は含まれないので、背景色が変わらない。次にlabel要素のデフォルトイベントで、inputのイベント処理が呼ばれる。ここでチェックボックスのchecked属性がcheckedになる。
  次に、二回目のクリック時、label要素に定義したクリックイベントが起きる段階では、まだchecked属性がcheckedのままなので、$(":checked")に今クリックしたlabel要素が含まれ、背景色が変わる。次にlabel要素のデフォルトイベントで、inputのイベント処理が呼ばれる。ここでチェックボックスのchecked属性が外され、チェックしてない状態になる。

  たぶんこんな感じのことが起きてるんだろうなと。ちなみにlabelがデフォルトの動作でinputのイベントを呼ぶことは、labelにつけたイベントの最後にデフォルトイベント抑止のreturn false;をつけて、input要素に適当なイベントをつけてみるとわかります。それと上記のlabel要素のイベント処理の進行で、「 input要素のイベント(デフォルトとユーザ定義)」と若干曖昧な表記になっているのは、最初はユーザ定義→デフォルトだと思っていたんですが、以下の様なコードを実行してみると

$("input[type=\"checkbox\"]").click(function(){
  alert($(":checked").attr("id"));
  return false;
});

まずチェックボックスにチェックが入り、alertでクリックしたinput要素のidが表示され、そしてチェックが消えます。つまりユーザ定義のイベント処理の前にchecked="checked"になり、そのあとまたデフォルトの処理で"checked"じゃなくなったことがわかります。このことからreturn false;はデフォルトのイベントを呼ばなくするのではなく、デフォルトのイベントの挙動を変えていることもわかります。

  要するに「クリックする→ユーザ定義→デフォルト」だと最初にチェックが入るのはおかしいので(デフォルトでチェックが入るとすると)、ユーザ定義の前後にチェックに関する処理が入ってるのかなと思ったためです。