inputタグなどのフォーム項目(フォームコントロール)には、inputmode
とか autocomplete
とか、required
のように大変便利な属性値が存在します。
しかし、利用しているCMSやCMSのプラグインによってはフォームコントロールそのもののHTMLに制約がある場合もあります。
その場合でも、もしフォームコントロールを囲む要素のHTMLを任意のHTMLに設定できるなら、JavaScriptでフォームコントロールを変更できるのではという備忘録です。
目次
実際のコード
まず、HTML構造が以下のようになっていると想定します。
<div class="js-form-attr" data-input-type="date">
<input type="text" name="date" id="date-field" size="60" value="">
</div>
<div class="js-form-attr" data-input-inputmode="url">
<input type="text" name="url" id="url-field" size="60" value="">
</div>
.js-form-attrクラスを持つ要素を取得し、それぞれのデータ属性(data-input-*)を解析。
データ属性の値が存在する場合は、それを新たな属性として子要素に追加します。
function appendFormAttr(){
const domElements = document.querySelectorAll(".js-form-attr")
domElements.forEach((element) => {
// 各要素のデータ属性(data-*)を取得
const dataSet = element.dataset;
// 各要素の子要素(input, select, textarea)を取得
const childElements = element.querySelectorAll("input:not([type='hidden']), select, textarea");
// 取得したデータ属性の集まり(dataSet)から、1つずつデータ属性を取り出す
for(const property in dataSet) {
// データ属性名が 'input' を含む場合のみ、処理を続行する
if(!property.includes('input')) continue;
// データ属性の接頭詞 'input' を取り除き、実際の属性名を取得
let attrName = property.split("input")[1]?.toLowerCase();
//属性名が有効かどうかをチェック
if(!attrName) continue;
childElements.forEach((childElement)=>{
if(dataSet[property]) {
childElement.setAttribute(attrName, dataSet[property]);
} else { // データ属性が設定されているが、値が空の場合、新しい属性は空の値を持つ
childElement.setAttribute(attrName, "");
}
})
}
})
}
appendFormAttr();
結果
<div class="js-form-attr" data-input-type="date">
<input type="date" name="date" id="date-field" size="60" value="">
</div>
<div class="js-form-attr" data-input-inputmode="url">
<input type="text" name="url" id="url-field" size="60" value="" inputmode="url">
</div>
おまけ:エラー表示用の要素があるとき、inputにariaをつける
以下のようにエラーを表す要素が表示される場合に、エラーメッセージをinputに紐づけたり、エラーが出ていることをinput要素で示したりする。
<div class="js-form-attr" data-input-inputmode="url">
<input type="text" name="url" id="url-field" size="60" value="">
<span class="error">未入力です。</span>
</div>
function appendFormError(){
const domElements = document.querySelectorAll(".js-form-attr")
domElements.forEach((element) => {
// 各要素の子要素(input, select, textarea)を取得
const childElement = element.querySelector("input:not([type='hidden']), select, textarea");
const errorElement = element.querySelector('span.error');
//errorElementが無ければスキップ
if (!errorElement) return;
const errorElementId = `${childElement.id}-error`;
childElement.setAttribute('aria-describedby', errorElementId);
childElement.setAttribute('aria-invalid', 'true');
errorElement.id = errorElementId;
})
}
appendFormError();
結果
<div class="js-form-attr" data-input-inputmode="url">
<input type="text" name="url" id="url-field" size="60" value="" aria-describedby="url-field-error" aria-invalid="true">
<span class="error" id="url-field-error">未入力です。</span>
</div>
おまけ:リアルタイム検証をしてみる
参考1:HTML5におけるinput要素のpattern、type属性のおさらい
参考2:[JavaScript]HTML5 Form Validationの制御と注意事項
<div class="js-form-validation" data-val-url="">
<input type="text" name="url" id="url-field" size="60" value="">
</div>
function appendFormValidation(){
const domElements = document.querySelectorAll(".js-form-validation")
//定義リスト
const patterns={
zip:{
pattern:"\\d{3}-?\\d{4}",
message:"郵便番号を数字7桁で入力してください"
},
tel:{
pattern: "^(+|)\\d{2,4}-?\\d{3,4}-?\\d{3,4}",
message: "電話番号の形式で入力してください"
},
url:{
pattern:"https?://.+",
message: "httpまたはhttpsからはじまるURLを入力してください"
}
};
domElements.forEach((element) => {
// 各要素のデータ属性(data-*)を取得
const dataSet = element.dataset;
// 各要素の子要素(input, select, textarea)を取得
const childElements = element.querySelector("input:not([type='hidden']), select, textarea");
// 取得したデータ属性の集まり(dataSet)から、1つずつデータ属性を取り出す
for(const property in dataSet) {
// データ属性名が 'val' を含む場合のみ、処理を続行する
if(!property.includes('val')) continue;
// データ属性の接頭詞 'val' を取り除き、実際の属性名を取得 (e.g., 'zip')
let attrName = property.split("val")[1]?.toLowerCase();
// patternsに定義が存在したら
if (patterns[attrName]) {
const regularExpression = new RegExp("^" + patterns[attrName].pattern + "$");
const validateInput = function() {
console.log(this);
// input要素の値がパターンにマッチすればエラーメッセージをクリア。そうでない場合はエラーメッセージを設定
if (regularExpression.test(this.value)) {
this.setCustomValidity("");
} else {
this.setCustomValidity(patterns[attrName].message);
}
}
childElements.forEach((childElement)=>{
// oninput イベントにリスナを追加
childElement.oninput = validateInput;
// ロード時にもバリデーションを実行
validateInput.call(childElement);
// リアルタイム検証メッセージをtitle属性にセットし読み上げ可能にする
childElement.setAttribute('title', patterns[attrName].message);
element.setAttribute('data-validity-title', patterns[attrName].message);
})
}
}
});
}
window.addEventListener('pageshow', () => {
//ページをキャッシュから読み込んだ時も正常に動作させる
appendFormValidation();
});
:is(input,select,textarea):user-invalid{
border: solid 2px red!important;
}
.js-form-validation:has(:user-invalid)::after{
content:attr(data-validity-title);
color: red;
}
※user-invalid
はiOS16.5からのCSSです。
結果
<div class="js-form-validation" data-val-url="" data-validity-title="httpまたはhttpsからはじまるURLを入力してください">
<input type="text" name="url" id="url-field" size="60" value="" title="httpまたはhttpsからはじまるURLを入力してください">
</div>
コメント