セレクトメニュー同士で連携する

セレクトメニュー同士で連携する

一つ目のセレクトメニューの内容に応じて二つ目のセレクトメニューの表示内容を
切り替えたい時のTipsです。

動作例
こういうやーつです。
(記事内では表記をセレクトメニューで統一してますが、)
プルダウンメニューやコンボボックスとも呼ばれるやつですね。
簡単そうに見えて意外とめんどくさいので躓きポイントも併せて紹介したいと思います。
と、その前に今回のレギュレーションです。

要件とデモ

便宜的に一つ目のセレクトメニューを第一項目
第一項目の内容を受けて表示内容が変動する二つ目のセレクトメニューを第二項目
と呼ぶことにします。

  • <option>部分は動的に吐き出されている想定で、第一・第二項目ともにvalue値は0から始まる連番。optionタグに対してHTML上で静的にdata-*属性を書くことはできない
  • 第一項目に「選択してください」といった旨のダミーの<option>を動的出力し、第一項目が未選択の時は第二項目を選択不能にする。また、第一項目選択後の第二項目の選択肢が一つもないパターンがあり、その場合も第二項目は選択不能にする
  • ついでに選択項目に応じてキャプションっぽいテキストも表示させてみる

こんな感じでいきましょう。
第一項目と第二項目で別途valueを指定可能だったり、
<option>にdata-*属性を任意に指定できればかなりラクかつ記述もシンプルになるんですけども。
ECやCMSサービスの標準機能として吐き出されてるようなやつだと
色々融通利かないことも多いんで・・・。(;^ω^)

optionごとにdata-*属性を振り分けても問題なかったりする場合、
今回の対応よりももうちょっとスマートな記述で対応できたりします。
参考:プラグイン不要! jQueryで複数のセレクトボックスの選択肢を連動させる

デモはこちら
選択肢をわりと適当に作ってしまったためサンプルとしては不適かも。
実際実装する時は
第一項目の選択肢は「北海道・東北地方」「関東地方」…、
第二項目の選択肢は「北海道」「青森県」…(略)…「東京都」「神奈川県」…
みたいなイメージでお願いします。

実装のための思考

第一項目が選択された時のvalue値から判断して第二項目で表示させる内容を出し分けましょう。
(<option>内のテキストで識別してもいいっちゃいいけど…)
下記のようにswitch文で識別します
あと今回はめんどいんでjQueryの例だけ載せてます。


「case 0:」すなわち第一項目で選択された内容が一つ目の選択肢「おすすめの駄菓子」だったら、
第二項目では「麩菓子」から「ひなあられ」までの9つの選択肢を表示させる、
といった思考です。
第二項目の関連value値が連番で続いているのでfor(var i=0; i<=8; i++)みたいな記述をしましたが、

select02.children(‘[value="1"], [value="5"]’).attr(‘data-exist’, true);

関連してる選択肢の並びがグチャグチャの場合は上記みたいな書き方で対応しましょう。

よくある罠

「remove();ではなく、hide();やdisplay: none;で対応すればいいのではないか。
項目変更のたびにわざわざDOM要素の追加や削除を繰り返したくない」

気持ちはわかる・・・(画像略)が、しかし。
IE/Edgeがポンコツなせいでこの目論見は頓挫することになります。
下記は実際にoptionにdisplay: none;をかけて実装してみた例です。

IE例
(IEでの挙動。全ての項目が表示されてしまった)

Edge例
(Edgeでの挙動。確かに項目のテキストは消えているが見た目が・・・)

できてへんやんけーッ!基本的なことが~~~~~~~!!(画像略)
仕様です。MSさんちのブラウザだとoptionにdisplay: none;とかかけてもダメらしいっす。
ちなみにAppleさんちのブラウザでもダメです。
参考:
セレクトボックス内の要素を非表示にしたい
Safariでoptionタグを非表示に……できない!?

非表示にしたいoptionタグをspanタグなどで囲んで消すという方法もあるようなので
そっちのやり方で解決したい方は下記のリンクも参考にするといいでしょう。
参考:jQueryでselect要素の特定のoptionを隠したい!
またはグレイスフル・デグラデーションってことで、
第一項目の選択内容に関係ない<option>を.prop("disabled", true)で選択不能にしてしまうとか。
参考:IEはjQueryの.hide()でoption要素を非表示にできない


今回のケースに話を戻します。仕方がないのでremove();で対応するとして、
第一項目が変更されるたびに

  1. 第一項目で選択されたvalue値を取得
  2. 第二項目内の全選択肢を表示(<select>内を初期状態に戻す)
  3. 第二項目の選択肢の中で、1.で得た選択肢情報と連動する<option>だけにdata-exist=”true”を付与し、それ以外のoptionタグは削除。また、第一項目の選択肢と連動する内容の第二項目の選択肢が空の場合はダミー用のオプションを表示

ってな感じの処理を書きました。
(ついでに第二項目の選択肢がない場合select自体をdisabledにして操作不能にしています)

完成品

こんな感じです(長いです)。

第一or第二項目で選ばれた選択肢のvalue値に応じて
キャプションとなるテキストをappendしています。
今回は雑に直接pタグをぶっ込んでますが、
「それぞれ全ての項目で異なるキャプションを表示させたい」
といったケースの場合、予めHTML上にdisplay: none;スタイルなどで不可視化してある
各キャプション用テキスト群を並べておいて、それぞれの選択肢に応じた
該当テキストを表示させるようなやり方がオススメ
です。
JSONからテキスト情報を引っ張ってきて切った貼ったしてもいいけど・・・めんどいよね

その他補足

初回処理で第一項目にdata-first=”true”を付与している理由は、
単に第一項目の初期表示状態の文字色をグレーにするためです。
(optionの文字色を変えるだけだと効かない模様)
ただ、項目選択後は色を戻したいので第一項目の内容が変更されたタイミングで削除しています。

第一項目初期状態でのみ、select部分の文字色を変更


IE・Edgeのための記述がゴチャゴチャ書かれてますけど、
最初からHTML上に初期選択状態用ダミー<option>を書けるという条件なら不要な記述です。
selectタグのselected初期値をどうこうしようとしたり
select内にoptionタグをappendしようとしたりするとIE/Edgeで上手くいかないようなので、
想定通りの動きをするように書いてたらちょっと煩雑になってしまいました。
(自分で書いててよく分かってないところがあります)

あとがき

一月サボってたのでとりあえず記事を書きました。以上。
あとがきというよりは反省ですが、
なんかダラダラと長い上に微妙に歯切れの悪い終わり方になってしまいました。
もっと簡潔でカッコいいアプローチを思いついたらリベンジします。"(-""-)"
まあ次投稿する記事は多分しっかりしたやつ書くからゆるして・・・

補足

第一項目の選択肢数が多い場合、今回のような解決法は不向きです。
(switch文の分岐をたくさん書けばいいっちゃいいんですけども・・・)
ただ、そのようなケースの場合
「ユーザーに膨大な選択肢の中から一つ選ばせて、
更にまたその中から絞り込んで選ばせようとしている」

という状態になわけで、ユーザビリティ的によろしくないと言えるでしょう。
絞り込みに拘泥しない、というのも一つの考えだと思います。
JSでスゴイことをやるのが正解ではなく、
結局ユーザーが使いやすいフォームにするのが正義なんでね。(‘ω’)ノ