管理メニュー

JavaScriptPHP

【WordPress】動的に項目を追加・削除できる管理画面を作成する方法

更新日:2023/01/23

WordPressで行単位で追加や削除・移動等を行える管理画面を作成してみます。

 

やりたいこと

次の図のようなイメージで、項目追加ボタンで行を追加、削除ボタンで行を削除できるような管理画面を作成します。

動的に項目を追加・削除できる管理画面

ついでに、行の移動ボタンも組み込んでみました。

入力されたデータは、optionsテーブルに次のような形式で登録します。

option_nameフィールド : 'my_test_item'
option_valueフィールド : {
  0 : { item1: 値 , item2: 値}
  1 : { item1: 値 , item2: 値}
}

各インデックスに、一行のデータが格納されるイメージです。

 

管理画面の作成

まずは、管理画面を作成するコードです。
WodPressのfunction.php等に記述してください。

add_action('admin_menu', function() {
      // メニュー登録
    add_menu_page('設定テスト', '設定テスト', 'manage_options' , 'my_test_option'
      , 'echo_my_test_option','',3);

    register_setting( 'test-settings-group', 'my_test_item' );

      // スクリプト登録
    add_action('admin_enqueue_scripts', function ($hook) {
      if ('my_test_option' !== substr($hook, -strlen('my_test_option'))) return;
      wp_enqueue_script('my_test_option', get_template_directory_uri() . '/my_test_option.js');
    });
});

  // 管理画面出力
function echo_my_test_option() {
  $datas = get_option('my_test_item',array());
  
?>
  <div class="wrap">
    <h2>設定テスト</h2>
    <form method="post" action="options.php">
      <?php 
        settings_fields( 'test-settings-group' );
        do_settings_sections( 'test-settings-group' );
      ?>
      <p><button id="my-test-add" type="button">項目追加</button></p>
      <table id="my-test-table" class="widefat striped">
        <thead><tr>&nbsp;<th></th><th>項目1</th><th>項目2</th><th>&nbsp;</th></tr></thead>
        <tbody id="my-test-tbody">
        <?php
            foreach( $datas as $v ){
                  echo_my_test_option_item($v['item1'],$v['item2']);
            }
        ?>
        </tbody>
      </table>
      <?php submit_button(); ?>
    </form>
    <?php
    echo '<template id="my-test-template">';
    echo_my_test_option_item( '' , '' );
    echo '</template>';
    ?>
  </div>
<?php
}
  // tr要素の生成
function echo_my_test_option_item($value1,$value2) {

  $tag = '<tr><th scope="row" class="my-test-number"></th>'
  .'<td><input type="text" class="my-test-item" data-itemname="[item1]" value="%s"></td>'
  .'<td>'
  .'<textarea class="my-test-item" data-itemname="[item2]" rows="7">%s</textarea>'
  .'</td>'
  .'<td>'
  .'<button class="my-test-del" type="button">削除</button>&nbsp;'
  .'<button class="my-test-up" type="button">▲</button>&nbsp;'
  .'<button class="my-test-down" type="button">▼</button>'
  .'</td>'
  .'</tr>';
  
  echo sprintf( $tag , esc_attr($value1) , esc_textarea($value2) );
}

最初のadmin_menuアクションフックで、独自管理画面の登録をしています。

echo_my_test_option()関数は、管理画面を生成しています。
その際にデータベースからオプション値を取得して、データがあったらテーブルの行を作成しています。
この時キーが存在しているかなどのチェックをしていません。
プラグインなどで配布等を行う時は、チェックしたほうがいいかもしれません。

また、inputなどの入力タグのname属性に値をセットしていません。
この処理は、js側に丸投げしています。

最後にtemplateタグを出力していますが、これはjs側でtemplateタグの内容を複製してテーブルに追加するために使用されます。

管理画面の作成方法については、次の記事で紹介しているので読んでみてください。

ボタンが押された時の処理は、ブラウザ上のJSで行います。
今回は my_test_option.jsファイルを読み込んでいます。

※重要

buttonタグは、

type="button"

が必須です。

これが無いと、ボタンを押したときにsubmitされて、画面が再読み込みされます。

 

jsの作成

管理画面で読み込むjsファイルは、次のようなコードです。

my_test_option.js

document.addEventListener("DOMContentLoaded",()=>{
    const tbody = document.getElementById("my-test-tbody");
    const template = document.getElementById("my-test-template");

        // 追加ボタン押下処理
    document.getElementById("my-test-add")
        .addEventListener("click", () => {addNode();renumber();});
    // TRの新規追加
    const addNode = () => {
        // テンプレートから複製を作成
        const node = template.content.firstElementChild.cloneNode(true);
        // テーブルに追加
        tbody.appendChild(node);
    };
    
        // テーブル内ボタン押下の処理
    tbody.addEventListener('click', event => event.target.eventFunc?.());
        // 削除処理・上移動処理・下移動処理
    const deleteTr = function () {this.remove(); renumber();};
    const upTr = function () { this.myTestData?.prevTr.before(this); renumber(); };
    const downTr = function () { this.myTestData?.nextTr.after(this); renumber(); };

        // ボタンと呼び出される関数の定義
    const btmEvent = [
        { class:"my-test-del",func:deleteTr},
        { class: "my-test-up", func: upTr },
        { class: "my-test-down", func: downTr }
    ];

    // name属性の更新
    const renumber =()=>{
        const trNode = tbody.getElementsByTagName("tr");
            // テーブルにtrが無い=>追加して再実行
        if (trNode.length === 0) { addNode(); renumber();return}

        for (let i = 0; i < trNode.length; i++) {
            const node = trNode[i];
                
            if(node.myTestData === undefined ){
                  // ボタンにイベント関数をセット
                btmEvent.forEach(e => node.getElementsByClassName(e.class)[0]
                        .eventFunc = e.func.bind(node));
                  // 入力項目を抽出
                node.myTestData = { items:Array.from(node.getElementsByClassName("my-test-item")) };
            }
                // 行番号のセット
            node.getElementsByClassName("my-test-number")[0].innerText
                = (i+1).toString();
                // name属性のセット
            node.myTestData.items.forEach(
                e=>e.setAttribute('name', `my_test_item[${i}]${e.dataset.itemname}`));
                // 前後のTRへの参照セット
            node.myTestData.prevTr = i === 0 ? null : trNode[i-1];
            node.myTestData.nextTr = i === trNode.length-1 ? null : trNode[i + 1];
        };
    };

    renumber();
});

やっていることは単純です。

追加ボタンが押されたら、テンプレートを複製して tbody要素に追加します。
削除ボタンが押されたら、tbody要素から削除します。
上下ボタンが押されたら、前後の要素と入れ替えます。

この処理を単純化するために、ボタンにeventFuncプロパティを作成して、押されたときに呼び出す関数をセットしています。
さらに呼び出す関数には、行(TR)要素をthis値としてbindしてあります。
さらに、行(TR)要素には前後の行要素をセットしています。

これにより、削除や移動を行う各関数(deleteTr()・upTr()・downTr())は非常に簡潔な処理になっています。

行操作が行われたら、renumber()で各行の番号やname属性を再設定します。
name属性は、WordPress側に送信するときに一つの連想配列にまとまるように設定しています。

方法については、次のページを読んでみてください。
【WordPress】管理画面の複数の項目値を連想配列で一つにまとめて登録する方法

意外と便利だったのがjsのオプショナルチェーン演算子( ?. )です。
次の記述です。

テーブル内ボタン押下の処理

event.target.eventFunc?.()

ここでは表のクリックイベントをtbody要素のみで受け付けています。
そのため、ボタン以外をクリックしたときもイベントが発生します。

event.targetにはクリックされた要素です。
しかし、eventFuncメソッドが定義されているのは各ボタンだけです。
普通ならボタン以外をクリックすると、エラーになる可能性があります。

しかしオプショナルチェーン演算子により、メソッドが存在しないときはそれ以降の評価が止まります。
そのため、エラーになりません。

コードの意図を伝えにくくなりますが、掲載を考えてできるかぎりコード量を減らしている立場からすると、大変重宝する演算子です。

JSのオプショナルチェーン演算子については、次のページを読んでみてください。
【JavaScript】 Nullishな値に関する演算 Null合体演算子とオプショナルチェーン演算子

WordPressはHTMやCSSの知識も必要。総合的な知識を身につけよう。

更新日:2023/01/23

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。