この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。
railsのビューで、フォームを使って日付を入力させたい時、
datetimepickerを使っていました。
datetimepickerはJSでテキストフィールドに付加する事で、カレンダー表示で日付・時間を選択させる事のできる便利なプラグインです。
ただ、テキストエリアに追加するので、ユーザーがテキストフィールドに直接日付を入力する事も出来てしまいます。
それ自体は別に問題ない事ですが、日付じゃないテキストや、あり得ない日付を入力されてしまう場合もあります。
日付じゃないテキストについては、datetimepickerが勝手に判断してテキストフィールドの中を空に置き換えてくれるのですが、
あり得ない日付に関しては、
「2000-04-31」-> 「2000-05-01」に変換してくれる
「2000-05-32」-> 変換してくれない!
となるので、フォームでもらった値を、
モデルインスタンスのDateTime型のカラムに代入しようとするとエラーを吐きます。
@model = Model.new(date: param[:model][:date])
-> ArgumentError
どうやら31日までは,datetimepicker側で勝手に判断して変換してくれるようですが、
32日を超えると「ありえない日付」となり、変換はしてくれないようです。
これでは困るので、色々と解消しようとした顛末です。
■ 1. validationで弾けないか?
モデルにvalidateを設置して、フォームを送ったら「不正な値です」などのアラートを表示させたいと思いましたが、
よくよく考えると、validate発火前の、インスタンスへの代入時点でエラーになっているので、ちょっと厳しそうです。
コントローラの方でチェックするのも考えましたが、フォームを使うコントローラ全てにチェックを入れないといけなかったりするので、
あまり良くなさそうです。
■ 2. JSで弾く(onBlur編)
そもそもそのような入力が出来なければ良い話なので、JSで入力制御を入れる事にしました。
ちなみに、datetimepicker導入部のJSはこんな感じ。
$('.date_text_field').datetimepicker({
format: 'YYYY-MM-DD',
language: 'ja'
});
今回は日付だけ取りたいので、時間までは取らないようにしています。
このままだと入力制御がかかってないので、onBlurのイベントハンドラを使ってみます。
$('.date_text_field').datetimepicker({
format: 'YYYY-MM-DD',
language: 'ja'
});
$('.date_text_field').onBlur = function(){
if ($(this).val() != '') {
var inputDate = new Date($(this).val());
if (isNaN(inputDate.getTime())) {
$(this).val('');
}
}
}
「isNaN(inputDate.getTime())」でテキストフィールドに入力された値が日付として正当か判断しています。
これでテキストフィールドに「2000-03-34」のようなおかしい日付を入力して、フォーム送信ボタンを押すと・・・・
テキストフィールドの中身が空になりました!
・・・しかし、onBlurを使っているので、テキストフィールドからフォーカスが外れれば空になってくれるのですが、
テキストフィールドに入力した状態で、エンターキーを押してそのままフォーム送信したりすると、、
フォーカスは外れないので、そのまま値が送られてしまい、やはりArgumentErrorになってしまいました。
■ 3. JSで弾く(onsubmit編)
ならば、フォーム全体がsubmitされたタイミングで入力判定すれば良いじゃないか!
という訳で、フォーム全体のイベント監視をさせるようにしてみます。
$('.date_text_field').datetimepicker({
format: 'YYYY-MM-DD',
language: 'ja'
});
$('form').on('submit', function(){
picker();
});
function picker(){
$('.date_text_field').each(function(){
if ($(this).val() != '') {
var inputDate = new Date($(this).val());
if (isNaN(inputDate.getTime())) {
$(this).val('');
}
}
});
}
formタグを監視するようにしてみました。
フォームがsubmitされると、date_text_fieldクラスを探し出し、日付として不正の場合値を空にします。
これで再度日付入力用のテキストフィールドに「2000-03-34」を入力してフォーム送信・・・
-> 送信直前にフォームの内容が空になりました!
■ 4. JSで弾く(解決編)
上記の方法で良いかと思ったのですが、
一画面に複数formタグがあったときは、そもそもformにJS監視させたくないときは?などの疑問が残ります。
そんな中、とてもスマートな解決法を教えて頂きました。
$('.picker_date').datetimepicker({
format: 'YYYY-MM-DD',
language: 'ja'
}).on('dp.error', function(e) {
$(e.target).val('');
});
これだけです。formに対してのイベントリスナーもいりません。
dp.errorイベントを拾うことで、入力された値に不整合があると、値を空にすることが出来ます。
フォーム送信時だけでなく、テキストフィールドからフォーカスが移ったときにも評価が走ります。
ということで無事、datetimepickerへの入力制御を入れる事が出来ました!