動的に動くセレクトボックスを無理やりiosに対応させた方法
動的に動くタイプのセレクトボックス(プルダウンメニュー)の依頼を受けた時に検索しても出てこなかったので自分なりに実装したときのメモ。
セレクトボックスの仕様
今回作ったセレクトボックスはWPサイト内にある予約フォーム内のパーツで以下のような要望のもと作成しました
- 月と日を別々に選択したい
- 初期値は「–」とする
- クリックorタップすると現在の月と日で選択済みの状態で表示したい
- 必須項目にする
更にこちらからは勝手に以下のような機能を追加で実装
- 選んだ月によって日数はバラバラなので選んだ月によって正しい日数を出力するように設定
- うるう年も考慮して年のデータと合わせて出力するような調整
- 現在の月よりも前の月を選んだら次の年の日数が表示できるように調整
セレクトボックスのベース
とりあえずベースとなるセレクトボックスの情報から
セレクトボックスのHTML
セレクトボックスで使うHTMLのコードはだいぶ端折っていますが、以下のような形で作りました。
select要素の矢印部分の変更をするためにselect要素をdiv要素で囲んでいます。
月を選択しないと日にちの選択ができないように「disabled」も付けています
1 2 3 4 5 6 7 8 9 10 11 12 |
<div class="date"> <div class="selsect_wrap month"> <select name="month"> <option value="--">--</option> </select> </div><span>月</span> <div class="selsect_wrap day"> <select name="day" disabled> <option value="--">--</option> </select> </div><span>日</span> </div> |
セレクトボックスのcss
select要素を囲んだdiv要素のafter疑似要素を使い対応させています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
.date { display: flex; align-items: center; } .selsect_wrap { margin-left: 18px; margin-right: 10px; position: relative; display: inline-block; } .selsect_wrap::after { content: ''; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 10px solid #333; position: absolute; right: 12px; top: 15px; } select::-ms-expand { display: none; } select { -webkit-appearance: none; -moz-appearance: none; appearance: none; outline: none; border: none; background-color: #f5f5f5; padding: 15px; width: 80px; } |
セレクトボックスを動的に生成する
今回の使用上、未入力時には「–」を表示し、未選択である場合はエラーを出していきたいので、予めPHPを使って当日のものを選択済みで生成するというのができません。
JSをつかって、月を選択するセレクトボックスにフォーカスした時に選択肢になるoption要素を生成しつつ、日にちの選択もできるように「disabled」を外す処理をしていきます。
この処理に関しては毎回起こすと、うざいだけなので、最初の一回だけにするため「oneメソッド」を使って設定しておきましょう
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
$(function () { var toDay = new Date(), thisYear = toDay.getFullYear(), thisMonth = (toDay.getMonth() + 1), thisDay = toDay.getDate(), $month = $('.month select'), $day = $('.day select'); $month.one('focus', function () { var monthOption = ''; for (var i = 1; i <= 12; i++) { if (i == thisMonth) { monthOption += '<option value="' + i + '" selected>' + i + '</option>\n'; } else { monthOption += '<option value="' + i + '">' + i + '</option>\n'; } } $(this).html(monthOption); var maxDay = new Array('', 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); if ((thisYear % 4 === 0 && thisYear % 100 !== 0) || thisYear % 400 === 0) { maxDay[2] = 29; } var max_day = maxDay[thisMonth]; var dayOption = ''; for (var i = 1; i <= max_day; i++) { if (i == thisDay) { dayOption += '<option value="' + i + '" selected>' + i + '</option>\n'; } else { dayOption += '<option value="' + i + '">' + i + '</option>\n'; } } $day.html(dayOption); $day.prop('disabled', false); }); }); |
このコードで一回目の処理は完成になりますが、改めて別の月を選択した時に日にちのリセットができません。
月を再選択したら日にちも再生成できるよう設定していきます。
2つの関数を作り、セレクトボックスの選択内容が変わった時に関数が動くように設定を追加していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
function todaySet() { if (thisMonth > $month.val()) { var year = Number(thisYear + 1); } else { var year = thisYear; } var lastday = LastDayCheck(year, $month.val()); var dayOption = ''; for (var i = 1; i <= lastday; i++) { if (i === $day.val()) { dayOption += '<option value="' + i + '" selected>' + i + '</option>\n'; } else { dayOption += '<option value="' + i + '">' + i + '</option>\n'; } } $day.html(dayOption); } function LastDayCheck(year, month) { var lastDay = new Array('', 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) { lastDay[2] = 29; } return lastDay[month]; } $month.change(function () { todaySet(); }); |
ここまででPCであればうまく動くのですが、iPhoneで操作するとバグります。
option要素の生成が追いつかず、選択用の窓が出てきてしまうので任意のものがうまく選択できない状況になってしまいます。
さらに、日にちの方のoption要素もうまく生成されないようになってしまいます。
無理やりiosに対応させてみた
iosの場合前述した状態になってしまいます。
バグってももう一度フォーカスを当てれば思い通りの動きをしてくれるので、かなり無理矢理ですが処理の中で一度フォーカスを外して、もう一度フォーカスする方法を取りました。
少しラグが出ますし、もっさりした感じになってしまいますが、iosでも一応対応可能な状態を作り出せました。
フォーカスが外れた時に日にちを再設定するコードを仕込まないとバグることが何度かあったので2重になってしまうのですが保険として入れておけば問題なく使えるはずです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
$(function () { var toDay = new Date(), thisYear = toDay.getFullYear(), thisMonth = (toDay.getMonth() + 1), thisDay = toDay.getDate(), $month = $('.month select'), $day = $('.day select'); $month.one('focus', function () { var monthOption = ''; for (var i = 1; i <= 12; i++) { if (i == thisMonth) { monthOption += '<option value="' + i + '" selected>' + i + '</option>\n'; } else { monthOption += '<option value="' + i + '">' + i + '</option>\n'; } } $(this).html(monthOption); var maxDay = new Array('', 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); if ((thisYear % 4 === 0 && thisYear % 100 !== 0) || thisYear % 400 === 0) { maxDay[2] = 29; } var max_day = maxDay[thisMonth]; var dayOption = ''; for (var i = 1; i <= max_day; i++) { if (i == thisDay) { dayOption += '<option value="' + i + '" selected>' + i + '</option>\n'; } else { dayOption += '<option value="' + i + '">' + i + '</option>\n'; } } $day.html(dayOption); $(this).blur(); $(this).focus(); }); $month.on('blur', function () { var selected = $month.val(); if (selected == thisMonth) { var maxday = new Array('', 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); if ((thisYear % 4 === 0 && thisYear % 100 !== 0) || thisYear % 400 === 0) { maxdayday[2] = 29; } var max_day = maxday[thisMonth]; var option2 = ''; for (var i = 1; i <= max_day; i++) { if (i == thisDay) { option2 += '<option value="' + i + '" selected>' + i + '</option>\n'; } else { option2 += '<option value="' + i + '">' + i + '</option>\n'; } } $day.html(option2); $day.prop('disabled', false); } }); function todaySet() { if (thisMonth > $month.val()) { var year = Number(thisYear + 1); } else { var year = thisYear; } var lastday = LastDayCheck(year, $month.val()); var option = ''; for (var i = 1; i <= lastday; i++) { if (i === $day.val()) { option += '<option value="' + i + '" selected>' + i + '</option>\n'; } else { option += '<option value="' + i + '">' + i + '</option>\n'; } } $day.html(option); } function LastDayCheck(year, month) { var lastDay = new Array('', 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) { lastDay[2] = 29; } return lastDay[month]; } $month.change(function () { todaySet(); }); }); |
これでokがもらえたのですが、もう少しいい方法があるのではと思っています