本投稿は株式会社ピーアールオー(あったらいいな!を作ります) Advent Calendar 2021の12日目の予定でした
昨年に引き続き「でした。」になっちゃいました。遅延は昨年より更にひどい。
絶賛年末進行中&緊急対応してたんで許してくださいwww
ここ数年は、ほとんどコーディングをせずに過ごしているんですが、久々にHTMLフォームを作ったので覚書として記事を書きます。
#どちらが「吸収するか」問題
フォームを設置する場合、デザイナーとプログラマーで「どこまでを担当する」問題というのがありまして、今までの経験では「エラーがどこに出るか、内容を全出ししてコーディングして」までがほとんど。
バリデーションを走らせたり、状態を変化させる仕組みまでは考えない。
とはいえ。
仕事をしていると、簡易のバリデーションを走らせたり、状態を変化させたりする仕組みを作らなくてはいけないこともある。
#フォームを作る
まあ、普通にコーディングします。IEは当然ガン無視1です。
本当はアクセシビリティを考慮した内容にもしたかったけど、時間がないので普通にコーディング。
今回は裏側なしなので、「送信」ボタンを押下してもエラー表示が出てくるだけですw
ターゲットによりますが、モバイルファーストで画面を作るのがデフォなので、今回もスマホで使うことを前提にコーディングします。
指でタップしやすいよう、セレクトボックスやラジオボタンもCSSを使って装飾♪装飾♪
HTML自体は何の変哲もない姿をしていますが、CSSは嫌な感じになりますw
<dl class="contactForm">
<dt class="contactForm_term">
<label for="approach">
連絡方法
<span class="contactForm_term_requiredMark">
必須
</span>
</label>
</dt>
<dd class="contactForm_desc contactForm_desc-radio">
<input type="radio" name="otherContact" value="メールアドレス" id="approach01">
<label for="approach01">メールアドレス</label>
<div class="contactForm_desc_comments">
<input type="radio" name="otherContact" value="電話番号" id="approach02">
<label for="approach02">電話番号</label>
<div class="contactForm_desc_comments_input">
<input type="tel" name="otherContactComent">
</div>
</div><!-- /.contactForm_desc_comments -->
<div class="contactForm_desc_comments">
<input type="radio" name="otherContact" value="その他" id="approach03">
<label for="approach03">その他</label>
<div class="contactForm_desc_comments_input">
<input type="text" name="otherContactComent">
</div>
</div><!-- /.contactForm_desc_comments -->
</dd>
</dl>
デフォルトのラジオボタンを消して、label
要素に::before
やら::after
やらでラジオボタンぽいものを作ります。
HTMLやCSSに強いプログラマーもいますが、これを探したり・書いたり・改善したりなんて無理だよねえ。デザイナーでも「無茶な!」って思うことがありますw
.contactForm_desc {
position: relative;
padding: .5rem;
margin-bottom: .5rem;
background: #edf0f5;
}
.contactForm_desc-radio [type="radio"] {
visibility:hidden;
position: absolute;
top: 1rem;
left: .5rem;
}
.contactForm_desc-radio label {
display:block;
position: relative;
padding: 1rem 1rem 1rem 2.8rem;
margin-top: .5rem;
text-align: left;
cursor: pointer;
}
.contactForm_desc-radio label::before,
.contactForm_desc-radio label::after {
content: "";
display: block;
border-radius: 50%;
position: absolute;
transform: translateY(-50%);
top: 50%;
}
.contactForm_desc-radio label::before {
width: 1.5rem;
height: 1.5rem;
left: .5rem;
background: #fff;
border: 1px solid #ddd;
border-radius: 50%;
}
.contactForm_desc-radio label::after {
width: 0;
height: 0;
background: #80bced;
border-radius: 50%;
opacity: 0;
}
.contactForm_desc-radio [type="radio"]:checked + label::after {
width: 1rem;
height: 1rem;
left: .8rem;
opacity: 1;
-webkit-transition: all .12s, border-color .08s;
transition: all .12s, border-color .08s;
}
.contactForm_desc-radio [type="radio"]:checked + label {
background: #80bced;
color: #fff;
font-weight: bold;
}
.contactForm_desc_comments_input {
padding: calc(0.65rem - 2px);
}
.contactForm_desc-radio [type="radio"]:checked + label + .contactForm_desc_comments_input {
background: #80bced;
}
#バリデーションはjQueryのプラグインで
一部でオワコン言われているけど私は使うぞ。
jQuery Validation Engine
言語ファイルも用意されているので、他言語化されたサイトでも利用可能。
今回のような、ボタンの状態に合わせてテキストフォームにエラーを出すとかもできる。
<head>
<!-- CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jQuery-Validation-Engine/2.6.4/validationEngine.jquery.min.css">
<!-- Javascript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/posabsolute/jQuery-Validation-Engine@3.1.0/js/jquery.validationEngine.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/posabsolute/jQuery-Validation-Engine@3.1.0/js/languages/jquery.validationEngine-ja.js"></script>
</head>
必要なjsとcssを読み込んで、最低限こう書けば良い。
$(document).ready(function(){
$("#contact").validationEngine();
});
#contact
は form
タグについているID。フォームががひとつだけならform
タグを直接入れてもいいかもしれないけど、あまりお勧めしない。
後でフォームが複数になったら超面倒だからw
あとは必須項目にCLASSを追加する。
<input type="radio" name="otherContact" value="メールアドレス" id="approach01" class="validate[required]">
<label for="approach01">メールアドレス</label>
CLASSのvalidate[required]
はベーシックなエラーが表示されます。
オプションも色々あるので、詳しくはjQuery Validation Engineを確認されたし。
いにしえのテーブルコーディング時代2からHTMLを書いているので、ちょっとしたコーディングはまあまあできると思いますが、jQueryとそのプラグインを使えばメロっとこんな感じまではできる。
バリデーションのテキストを変更したい
「*必須項目です」の頭の「*」が気になるし、「選択してください」「名前を入れてください」に変えたいところもある。
jQuery Validation Engineは痒いところに手が届くので、オプションを書いてみます。今回は外部jsに書く方法。
個別にテキストを変えたい場合は、IDを割り振って、それに対してテキストを変更します。
$("#contact").validationEngine('attach', {
promptPosition : "topLeft: 0", // エラーの位置指定
showArrowOnRadioAndCheckbox: true, // ラジオボタンやチェックボックスのエラーに吹き出しを付ける
"custom_error_messages": { //エラーテキストオプション
"required": { //デフォルトのエラーテキスト
"message": "必須項目です"
},
"#contactDetail": {
"required": {
"message": "選択してください"
}
},
"#name": {
"required": {
"message": "名前を入れてください"
}
},
"#approach01": {
"required": {
"message": "選択してください"
}
},
"custom[email]": {
"message": "正しいメールアドレスを入れてください"
},
"#agree": {
"required": {
"message": "確認してください"
}
}
}
});
1箇所だけとかならdata-errormessage
を使う方法がいいかも。
<input type="email" name="userMail" id="email" class="validate[required, custom[email]]" data-errormessage="メールアドレスが間違っていますよ">
フォーム部品に装飾を施すとちょっと面倒になる
すんなり実装できた風ですが、実際は違います。
フォーム部品に装飾を施すと、プラグインがうまく動かないことがあります。
今回のプラグインの場合、input
に対してエラーを表示するので、input
を不用意に消すとエラーが表示されなくなります。
そのため、ちょっとした工夫が必要。
.contactForm_desc {
position: relative;
padding: .5rem;
margin-bottom: .5rem;
background: #edf0f5;
}
.contactForm_desc-radio [type="radio"] {
visibility: hidden;
position: absolute;
top: 1rem;
left: .5rem;
}
装飾したいがために、本来のラジオボタンをvisibility: hidden;
で不可視にしています。
こういう場合はdisplay: none;
で消すことがほとんどなのですが、今回のjQueryプラグインでは、不用意に要素を消すと「エラーが表示されない」というドツボにハマります。
なぜなら、見た目はどちらも「消えてる」のですが、その**「消え方」に違い**があります。
レイアウトを変更せず要素を不可視にする visibilityプロパティ
visibility は CSS のプロパティで、文書のレイアウトを変更することなく要素を表示したり非表示にしたりします。このプロパティは <table> の行や列を隠すこともできます。
要素を不可視にしてレイアウトから除去するには、 visibility を使用する代わりに display プロパティを none に設定してください。
引用:visibility - CSS: カスケーディングスタイルシート | MDN
レイアウトから除去してしまうdisplay: none;
を使うとinput
が存在しないことになり、エラーが表示されなくなります。
それではまずいので、不可視になるvisibility: hidden;
を使います。
これで解決するかと思いきや…なかなかうまくいかない。
レイアウトから除去してしまうdisplay: none;
とは違い、visibility: hidden;
は要素が見えないだけでしっかり存在します。
存在するということは、その要素の大きさ分の空白ができるということです。
レイアウトのくびきから解き放つ positionプロパティ
さて参ったね。
エラーを表示するにはvisibility: hidden;
しか使えない。けれどこれを使ったらレイアウトが崩れる。
レイアウトを直すにはdisplay: none;
を使うのが一番だけど、これを使うとエラーが出なくなる。
どちらの要件も維持するには、本来のラジオボタンをレイアウトのくびきから解き放つしかない。
それがposition: absolute;
という設定。
HTMLの場合、レイアウトは相対で作られます。同じレイヤー上に存在するので、要素同士は隣り合っています。
しかし、position: absolute;
を設定した要素は、他の要素とは異なるレイヤーに配置され、ブラウザの左上を原点とした絶対値でレイアウトを制御することが可能になります。
イメージとしては、相対でレイアウトされている他要素の上に浮かんでいる感じです。
ただ、フリーダムすぎると制御不能になるので、従うべき親分を教えます。
毎回ブラウザの左上を親分にされると制御が難しいので、親要素にposition: relative;
を与えて「ワタシが親分です」と教えてあげると、素直に従ってくれます。
この場合、position: relative;
を持つ親要素の左上を原点として絶対値でレイアウトを作るので、制御は非常に簡単になります。
今回のレイアウトの都合上、上位の要素<article class="contents">
にtext-align: center;
があるため、何もせずにいるとチェックボックスは画面の中央に配置されます。
jQuery Validation Engineはinput
要素にエラーを表示します。
そのため、position: absolute;
だけでなく、left
とtop
に数値を入れて、擬似的に作り上げたラジオボタンの近くにレイアウトされるようにし、あたかもエラーが疑似ラジオボタンの上に出ている風を装います。
参考:position - CSS: カスケーディングスタイルシート | MDN
レイアウトもどうにかなったし、デフォルトの機能だけならこれでいいんです。バリデーションも走るし。エラーテキストも変更できるし。
でも、まだ足りない。ちょっと仕様が足りないから、このままじゃダメ。
…自ら面倒くさい地獄を呼び込むことに。
ラジオボタンと入力フォームを連動させたい問題
連絡方法の項目に関しては、以下の事がしたいなーって思います。
- 項目を選択したら入力フォームに入力させたい(項目未選択の場合は入力フォームもDisabled)
- 別の項目を選択したら入力した内容を消したい(見えなくしたい)
HTMLは手っ取り早いですな。初期値はdisabled
にしておきます。
<div class="contactForm_desc_comments">
<input type="radio" name="otherContact" value="電話番号" id="approach02"><label for="approach02">電話番号</label>
<div class="contactForm_desc_comments_input">
<input type="tel" name="otherContactComent" class="validate[condRequired[approach02]]" disabled="disabled">
</div>
</div><!-- /.contactForm_desc_comments -->
このままだと永遠にdisabled
なので、jQueryの力を借ります。
//ラジオボタンと入力エリア連動
$("[name='otherContact']").change(function(){
if ($("input:radio[id='approach02']:checked").val() == "電話番号") {
$("#approach02").nextAll(".contactForm_desc_comments_input").children("input[type=tel]").removeAttr("disabled");
} else {
$("#approach02").nextAll(".contactForm_desc_comments_input").children("input[type=tel]").attr("disabled", "disabled");
}
if ($("input:radio[id='approach03']:checked").val() == "その他") {
$("#approach03").nextAll(".contactForm_desc_comments_input").children("input[type=text]").removeAttr("disabled");
} else {
$("#approach03").nextAll(".contactForm_desc_comments_input").children("input[type=text]").attr("disabled", "disabled");
}
}).trigger('change');
チェックボックスが対象の場合は、こんな書き方で動くはず(今回のソースコードでは検証していない)。
$(".contactForm_desc_comments").change(function(){
$("input[type=text]").attr("disabled", "disabled");
$(":checked").nextAll(".contactForm_desc_comments_input").children("input[type=text]").removeAttr("disabled");
}).trigger("change");
これをやらず、わざわざval() == "電話番号"
みたいな書き方をしてるのは、ラジオボタンの場合、他の項目を執拗にタップすると関係ない入力フォームがActiveになるからw
テスト中は、執拗にタップし続けるのは大事ですねー
ここまででもいいんですが、入力フォームに記入した後に別の項目を選択してDisabledになっても、入力された文字が見えちゃいますw
ここはCSSで簡単に消します。
.contactForm_desc input[disabled="disabled"] {
color: transparent;
}
透明にしただけなので、再び項目を選択すると入力内容が復活します
jQueryで消しちゃってもいいんですが、この時はテストする時間が少な過ぎたので「見えなくする(透明にする)」だけにしました。
項目がActiveにならない限り、入力フォームはdisabled="disabled"
なので、内容が送信されることはありません。
#連打禁止
イタズラされるのが嫌なので、できる限り連打できないようにしたい。
「送信」ボタンをタップした後に、ボタンカラーを透過して「押しましたよ」という状況変化をつけるのもいいし、pointer-events
プロパティ使って、1度タップしたらポインターイベントを無効にしちゃってもいい。
ただ、pointer-events
は注意が必要。
バリデーションが走ってエラーになった後、なにかのタイミングで復帰させないと、必須項目を全て入力しても内容を送れないwwww
//連打緩和
$("#submit").on("click", function(){
$(this).css({"opacity":" .5" ,"pointer-events": "none"});
});
$("form").change(function(){
//フォーム内の要素に変更があると適用
$("#submit").css({"opacity":" 1" ,"pointer-events": "auto"});
});
バリデーションが走った後は、未入力を無くすためにタップなり記入なりするはずなので、フォーム内の要素に変更があったら、ボタンの透過をやめて、ポインターイベントを有効にします。
#一応動くなにか
jQuery Validation Engineは非常に有名なバリデーションプラグインで、Qiita上でも様々な人が記事を書いています。
しかし、フォーム部品に装飾を施した場合の事例は少ないように思います。
装飾を施していないフォームで利用する場合は、そう大きな問題はないと思いますが、色々装飾をした瞬間、ドツボにハマる可能性は極めて高いです。
そして、だいたいが「しょうもない理由」でハマってしまうので、判明した時の落ち込み方はひとしおですw
CodePenで検索すると古いバージョンしか出てこず、jQuery Validation EngineのCDNを探すのにちょっと苦労しましたorz
CDNはjsDelivrにあったので助かった
See the Pen Contact Form HTML by Mari Takahashi (@mrd-takahashi) on CodePen.
-
やっと解放されるね⭐️やっほーい!参考:Microsoft 社 Internet Explorer のサポート終了について:IPA 独立行政法人 情報処理推進機構 ↩
-
若い世代が知らない2000年代のHTMLコーディングの地獄…これをお読みいただければ、地獄加減はわかっていただけるであろう。バグやハックで朝までコースはザラ。 ↩