原因
JavaScript上でフォーム部品は、同名の要素が複数あるときのみ配列として扱われます。従って、今回のバグのようにチェックボックスが1個の時は、配列の長さを表すlength
プロパティが未定義(undefined
)となり、for文の中に制御が移りません。
for (var i=0; i<sentakuChk.length; i++) {
結果、表示データが1件のみの時は選択状態にかかわらず、エラーメッセージが表示されてしまいます。
対策
動的に数が変わる同名のフォーム部品をJavaScriptで扱うときは、数が1個の時と複数の時で分けて処理を書きます。
プログラム修正例
チェックボックスが1個か複数かは、JavaScriptでのチェックボックスを表すプロパティが配列になっているかどうか(=length
プロパティがあるかどうか)で判断できます。
function check() { var sentakuChk = document.form1.sentakuChk; // チェックボックスが複数の時 if (sentakuChk.length) { for (var i=0; i<sentakuChk.length; i++) { if (sentakuChk[i].checked) { return true; } } // チェックボックスが1個の時 } else if (sentakuChk.checked) { return true; } alert("1件も選択されていません。"); return false; }
undefined
)のプロパティが渡された場合、真偽値としては「偽」になるため、if文の中は実行されません。プログラム修正例「0個対応」
上記のコードは、チェックボックスが0個の場合には対応していません。0個の場合はdocument.form1.sentakuChk
自体が未定義になるので、length
プロパティの呼び出しでエラーが発生します。
データが0件でも承認ボタンを表示する画面仕様ならば対応する必要があります。length
プロパティを参照する前に、sentakuChk
オブジェクトの存在確認が必要です。
function check() { var sentakuChk = document.form1.sentakuChk; // チェックボックスが複数の時 if (sentakuChk && sentakuChk.length) { for (var i=0; i<sentakuChk.length; i++) { if (sentakuChk[i].checked) { return true; } } // チェックボックスが1個の時 } else if (sentakuChk && sentakuChk.checked) { return true; } alert("1件も選択されていません。"); return false; }
チェックボックスの数と各プロパティの参照結果をまとめると、次のようになります。
プロパティ | 複数 | 1個 | 0個 |
document | ○ | ○ | ○ |
document.form1 | ○ | ○ | ○ |
document.form1.sentakuChk | ○ | ○ | undefined |
document.form1.sentakuChk.length | ○ | undefined | エラー |
document.form1.sentakuChk[0] | ○ | undefined | エラー |
document.form1.sentakuChk[0].checked | ○ | エラー | エラー |
document.form1.sentakuChk.checked | undefined | ○ | エラー |
未定義(undefined
)のプロパティから、さらに下位のプロパティを参照するときにJavaScriptエラーが発生しています。ブラウザのアドレスバーに、javascript:alert(document.form1.sentakuChk);
等と打ち込んで確認することもできます。
テストケースの修正例
実務では、コードの修正に伴い、テスト仕様書も修正する必要があるでしょう。データ表示件数が複数の時、1件の時、0件の時を分けてテストケースにするのがいいでしょう。
テストケース | 期待結果 |
表示データ複数・選択数複数 | 該当データが「承認済み」に切り替わる |
表示データ複数・選択数1件 | 該当データが「承認済み」に切り替わる |
表示データ複数・選択数0件 | 入力エラー |
表示データ1件・選択数1件 | 該当データが「承認済み」に切り替わる |
表示データ1件・選択数0件 | 入力エラー |
表示データ0件 | 入力エラー |
最後に
このバグは、多くのWebプログラマが一度はハマったことのあるバグではないでしょうか。正常系以外のテストで「0件」のケースを想定するのは基本ですが、「1件」のケースは漏れやすく、バグの発見が遅れることがあるので注意が必要です。
Struts等で利用されるJakarta commons validatorライブラリ内のJavaScript関数validateRequired()
は、セレクトボックスやラジオボタンにも対応する汎用的な必須チェック用関数です。エラーメッセージ表示後に未入力・未選択のフォーム部品をフォーカスする機能も備わっています。以下のURLでソースコードが閲覧できます。他の関数も呼び出しているため理解しにくいところもありますが、一読されることをお勧めします。
追記:DOMを利用した修正
今回のバグは、getElementsByName()
メソッドを利用するとより簡単に修正できます。getElementsByName()
メソッドは、指定したname属性値を持つフォーム部品を、要素のリストで返します。以下のコードで、チェックボックスの個数にかかわらず対応できます。
function check() { // var sentakuChk = document.form1.sentakuChk; var sentakuChk = document.getElementsByName('sentakuChk'); for (var i=0; i<sentakuChk.length; i++) { if (sentakuChk[i].checked) { return true; } } alert("1件も選択されていません。"); return false; }
ただし、getElementsByName()
メソッドはDOMのAPIであり、古いブラウザ(IE4以前)では正しく動作しません。しかし2008年現在、IE4以前のブラウザのシェアは1%未満のようであり、無視できるレベルかもしれません。システム要件が「IE5以上対応。IE4でJavaScriptエラーが出てもかまわない。」ということであれば、getElementsByName()
メソッドを利用するのが良さそうです。