selectとoptionのメニューをスクリプト制御で表示しないようにする
忙しさに追われて久しぶりの更新となるが、現行でシステム開発を行っている際に、フロントエンド側のスクリプトを弄っていた時の話。
通常、select要素で設定した選択ボックスをクリックしたりすると、option要素に登録したブラウザ依存のメニューが表示される。
まぁ、そりゃ当然そうだろうと思う。
が、自分の開発しているシステムで画面インターフェイスの兼ね合い上、こいつ自体を制御してやりたい。
selectとoptionのデフォルトで表示されるメニューを表示しないようにしたかった。
今回のこのお題目でやりたいことの形としては、
- 選択ボックスの内容変更は飽くまでも別の要素から行う
- 選択ボックスをフォーカスした際はカーソルキーで内容だけは変更できるようにする
こういったもの。
ブラウザ依存のメニューのまま見た目だけを弄る方法は今日びいくらでもあるが、選択ボックスの内容が100とか200とかなると、例えどれだけCSSで手の込んだ装飾しようが、ただきれいなだけで全くもって実用に足りない。
だって、47都道府県の選択肢を選ぶのでさえ大変なのに、100とか200とか行ったら、それこそもう空白でもいいかな、ってなってくる。
やはり、大量の選択肢がある場合、それを見やすくした別の要素の選択肢からselect要素本体を変えるべきである。
・・・とはいえ、それをやろうとすると問題が多発。
外側の要素からvalueの内容だけ変更しようにも、disabled属性をかければselectのデータが必要なときに値が飛ばんわ、readonly属性はそもそもselectタグには使えんわ・・・。
optionにdisplay: none;かければ選択したくても出来んわ、IEにはどのみち対応していないわ・・・。
CSS3のプロパティでuser-selectをかけても扱いとしてはdisabled属性と同じ、当然のことながらCSS3なのでレガシーブラウザでサポートされていないわ・・・。
網掛け的にselect要素の上に余計な要素をかぶせて選択させないようにするのは何の解決にもならんわ・・・。
ネットで調べてもわかんない・・・^q^
詰まるところが、selectにHTML属性やCSS等の小細工で、手抜きで制御を加えようとすること自体がそもそもの間違いだということだろう。
そこで、一度、select要素が何をトリガーとしてoptionに対応するメニューを出してきているかを検証してみた。
- Google Chrome、Firefoxでは選択ボックスをクリックするとメニューが表示される
- Microsoft IE9~11、Edgeでも同じ寸法でメニューが表示される
- Android系端末のブラウザではタッチすることでメニューが表示される
- iOS系端末のブラウザではタッチすることで対応するドラムロールが表示される。
ただし、ドラムロールが表示される条件としては、タッチではなくフォーカスが入った時である。 - レガシーIEではiOS系端末と同様、フォーカスが入った時にメニューが表示される。
以上が洗い出した検証結果。
・・・・。
これって全部イベントなんじゃね?
そう。制約が多いselect要素と言えども、元を正せば所詮はただのHTMLノード。
洗い出した内容から閃いたことは、これらのイベントがハンドリングされているのであれば、それらを全て無効化してやればいいということである。
(ネットで方法がないか調べてもわからんときに結局最後に一番頼りになるのはやっぱ自分でひねり出した知恵やね!)
イベントを無効化したselectからは、例えoptionがプレーンな状態だとしてもメニューは恐らく表示されないであろう。
そこで、毎度おなじみjQueryでスクリプト制御を作ってみた。
function menuCancel(target){ var ua = window.navigator.userAgent.toLowerCase(); target.focus(); // iOSのドラムロール避け。レガシーIEはおまけ程度(一瞬ちらつく)。 if ( ua.indexOf('iphone') > -1 || ua.indexOf('ipod') > -1 || ua.indexOf('ipad') > -1 || ua.indexOf('msie 7.') > -1 || ua.indexOf('msie 8.') > -1 ) { target.blur(); } } // これらイベントのハンドリングを無効化する $('select').on('mousedown', function(){ menuCancel($(this)); return false; }); $('select').on('mouseup', function(){ menuCancel($(this)); return false; }); $('select').on('click', function(){ menuCancel($(this)); return false; }); $('select').on('keydown', function(e) { if(( e.which && 13 == e.which ) || ( e.keyCode && 13 == e.keyCode )){ menuCancel($(this)); return false; } });
一・二時間悩んだことが嘘のように、あっさりと解決した。
実際はもう少しスッキリ見えるように書いているが、おおよそこんなところである。
iOSのドラムロールは「超・変な仕様」であり、タッチしたときではなくフォーカスしたときに発火するのでフォーカスを外す。
一応、レガシーIEの場合も見ているが、Windows CEを除けば現行のブラウザではないためおまけ程度。
Windows CEが搭載されたロースペックな携帯入力端末で複雑なデータを触ることはまぁまずないだろうので、この辺はかなりテキトー。
通常の端末であれば必要のない所だと思われるので、要らなければ消してしまっても問題は特に無いと思う。
つか、そんなことよりも・・・
iOSのドラムロールの表示条件がレガシーIEの選択ボックスの表示条件と同じって、一体全体どういうことや・・・(´・ω・`)
ちなみに、readonly属性と同じような挙動をさせたい場合は、’keydown’イベントをいじって、方向キーやHOME・ENDキー等を取得するようにしてやればいいはず。
あ、これをやったからと言って、フロントエンドの処理に満足してそのままデータをサーバーに飛ばすようなことは絶対にしないように。
プログラム側での最終的なバリデーションはお忘れなく。