こんにちは。
Hamee Advent Calendar 2015 8日目の記事です。
Chromeの開発者ツールでHTMLの要素を眺めていたらvalidity
という属性を見つけました
validityプロパティの中にはpatternMismatch
, tooLong
などそれっぽいプロパティとbool値。
調べてみたら フォームのバリデーションはこれだけで十分なのでは
と思うほどに便利だったので詳しい機能の紹介と、より便利に扱う小ネタの紹介です。
情報源として主に以下のページを参考にしました。
今回の記事でご紹介する内容をまとめたデモをこちらに上げました。
記事の内容を読みつつデモの動きやコードをご参考ください。
単にHTML5のform周りの属性を使用してタグを書くだけでいいので、ライブラリやフレームワークのようにロックインが発生せず、HTML構造にも依存せず、「捨てる/捨てない」の話ではなく、最低限がそれです。非常に良いです。
独自の仕様が存在しないので、HTMLの属性やjsが汚れずに済みますし、幅広いライブラリを活用できるチャンスが生まれます。
ブラウザ内で入力チェックをしてくれるので、
などの処理が不要になります。
ちょっとしたバリデーションだし、と個々に自前で実装してしまうこともあるかと思います。そういったオレオレ実装をメンテナンスする手間が不要になります。
少なくとも自分で書くより、ブラウザ側に入力の検証をしてもらうほうが、手間が減るだけでなく圧倒的にバグのリスクやメンテナンスコストが下がります。
残念ながらエラー内容を表示する処理に関しては現段階では自由度がほぼない(後述)ので、そこは自前で実装する必要があると思います。
きちんと属性を定義しておくと、:valid
, :invalid
, :required
, :optional
といったフォーム周りの擬似クラスの恩恵が受けられるようになります。
IE10+なら対応しているので、新しいブラウザ向けのサービスを作る際には検討の余地があるかと思います。
では早速本題に入ります。まずは例から。
<input id="form-text" type="text" required />
といったHTMLを用意し、以下の様なjsを書いてみます。
console.log(document.getElementById('form-text').checkValidity()); // false
console.log(document.getElementById('form-text').validity); // ValidityState {}
console.log(document.getElementById('form-text').validity.valueMissing); // true
checkValidity
メソッドの戻り値はfalseで、validity.valueMissing
プロパティの値はtrue
にっていると思います。
checkValidityメソッドはその要素のバリデーション全てに通過した場合trueになります。
validity以下のオブジェクトは、バリデーションに通過した場合はfalse、バリデーションに引っかかった場合trueになります。
ちょっとややこしですが名前が肯定形になっているので、そこまで勘違いは起きないだろうと思います。
validity.valid
だけやや特殊で、checkValidityメソッドの戻り値と等しくなります。つまりこの値だけture/falseの意味が逆です。ご注意ください
どんどんいきます。
willValidateというプロパティもあります。
このプロパティは、その要素のバリデーションが必要か否かを表します。
候補の場合にtrueが、候補でない時にはfalseが、そもそもinput系のタグじゃない場合にはプロパティ自体が存在しません。
console.log(document.getElementById('form-text1').willValidate); // false
console.log(document.getElementById('form-text2').willValidate); // true
console.log(document.getElementById('form-text3').willValidate); // true
console.log(document.getElementById('form-text4').willValidate); // false
console.log(document.getElementById('form-text5').willValidate); // false
console.log(document.getElementById('form-text6').willValidate); // true
console.log(document.getElementById('form-text7').willValidate); // true
console.log(document.getElementById('form-text8').willValidate); // false
document.getElementById('form-text8').readOnly = false;
console.log(document.getElementById('form-text8').willValidate); // true
document.getElementById('form-text1').value = 'hogehoge';
console.log(document.getElementById('form-text1').willValidate); // false
console.log(document.forms[0].willValidate); // undefined
disabled
やreadonly
になっている要素はfalseになるようです。
jsや開発者ツールなどでHTMLを書き換え、readonlyやdisabledを外すと自動的にwillValidateの値が変わるようです。
disabledを保ったままvalue属性を書き換えた場合のwillValidateの値はfalse
になってしまいますが、悪意のある入力はどのみちサーバでも弾かなければならないので、
あくまでHTML+jsでのバリデーションはユーザへのフィードバック速度を向上させること。利便性・使い心地・体感速度を向上させるためのもの、として捉えれば十分だと思います。
ちなみにnovalidate
属性を指定してもwillValidateプロパティの値は変わりませんが、ブラウザデフォルトのバリデーションは行われうなくなり、値が空でも送信できるようになります。
参考に上げた記事内のCurrent Implementation Issues and Limitationsがとても参考になりました。
自分でもHTMLとjsを書いてみて不便に思ったことは以下のあたりでした。
ブラウザごとのバリデーションエラーの要素はwebkitがベンダープレフィックスつきで提供はしていますが、標準的な仕様はまだ出ていないようです。
ということで自由度が低く現状ではゴリ押しもできないので断念。
そして表示するエラー文言を生成する簡易的なテンプレート機構が欲しい。
カスタムエラーや、各ベンダーごとの独自実装はあるのですが、標準的なエラー文言のテンプレート的なものは存在しないようです。
簡単に設定できて、必要なら上書きできるようになっているとお手軽さがあるなーと思います。
バリデーションエラーをどう表示するか は使用しているCSSフレームワークやHTML構造に激しく依存するので、
ロックイン率が高くなるようなライブラリは使いたくない。
なのでエラーの一覧を配列で返してもらい、それをHTMLとして描画処理は実装する、くらいが抽象化できる落とし所かなと思っています。
エラーを表示するなら例えば「お名前」などlabelタグの値もメッセージに含めたいこともあると思います。
Chromeならinputタグに対応するlabelタグがlabels
プロパティで取れるのですが、他のブラウザにはない模様。
ということでinputに対応する項目名(label)を取得する処理をざっくり吸収したい。
あと、正規表現にマッチしなかった場合に、その正規表現をユーザに見せて理解できるわけがないので、うまいこと日本語にしたい。
例えば^[A-Za-z0-9-]+$
だとしたら半角英数とハイフンのみ
といった具合です。
自動で名前のマッピングは面倒なので、明示的に表示用の別名が指定できればいいなーという感じです。
上記の問題を解消するものをざっくり作ってみました。
具体的な実装は前述のデモにコードが有ります。
var errors = Validator.getErrors(input);
のように使用できます。
引数にはバリデーションするinput要素を渡します。
エラーの一覧を配列で返します。
HTML5についてはメディア系以外は追っかけているつもりだったのですが、触ったことないAPIがまだまだあるなーと痛感しました。ValidityState、めっちゃ便利です。
Angularなどのでかいフレームワークを使っているとこの辺一切触ることないですが、
素のReactなど薄いビュー層を触っているとついバリデーションをオレオレ実装してしまうことがあるので、こういった便利な標準仕様をもっと抑えていこうと思います。