【備忘録】任意の属性値を付与するのが難しいCMSのフォーム項目にJavaScriptで属性値を付与する【JavaScript】

  • URLをコピーしました!

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>
リアルタイム検証が動作するようになる
エラー表示がJavaScriptで定義したものに変更される
よかったらシェアしてね!
  • URLをコピーしました!

コメント

コメントする

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

目次