Vue.jsとPowerCMSのスニペットフィールドを使ってリンク入力欄(複数)を作成する

公開

PowerCMSのスニペットフィールドを利用して複数の入力欄を作成する必要が出たので「スニペットフィールドを使ってリンク入力欄(複数)を作成する | PowerCMS ブログ」を読みました。ただ、「ステップ 2. 入力画面用のテンプレートモジュールを作成」で「うっ…」となるのが正直な所ではないでしょうか。

ゆっくり落ち着いて読めば理解できるのですが、入力欄が増えたり複数のスニペットフィールドでこのコードを利用しはじめたりすると、なんだか作成・メンテナンスが大変そうです。

そこで、気軽に利用できるVue.jsで同じことを書いてみました。メリットは以下の2点だと考えます。まだ軽く動作確認をした段階ですが、PowerCMSブログのコードと同じ動作になっています。

  • 同じような記述を繰り返す必要がない
  • ライブラリに頼ることでコード量が減って見通しが良くなる
<mt:LocalVars>
    <mt:Ignore>入力値を変数にセットする</mt:Ignore>
    <mt:SetVar name="titles" />
    <mt:SetVar name="urls" />

    <mt:Loop name="customfield_entry_outerlink_title_loop">
        <mt:SetVarBlock name="titles" function="push"><mt:Var name="snippet_option"></mt:SetVarBlock>
    </mt:Loop>
    <mt:Loop name="customfield_entry_outerlink_url_loop">
        <mt:SetVarBlock name="urls" function="push"><mt:Var name="snippet_option"></mt:SetVarBlock>
    </mt:Loop>

    <mt:Ignore>入力フィールドの定義</mt:Ignore>
    <div id="customfield_entry_outerlink">
        <div v-for="(item, index) in items" :key="item.title" style="margin-block-start: 10px; padding: 10px; border: 1px solid #c0c6c9; background-color: #fff;">
            <div><button type="button" @click="deleteItem(index)">削除</button></div>
            <div>
                <label :for="'customfield_entry_outerlink_title_' + index">リンクテキスト</label><br>
                <input type="text" :id="'customfield_entry_outerlink_title_' + index" name="customfield_entry_outerlink_title" v-model="item.title" class="text full">
            </div>
            <div>
                <label :for="'customfield_entry_outerlink_url_' + index">URL</label><br>
                <input type="url" :id="'customfield_entry_outerlink_url_' + index" name="customfield_entry_outerlink_url" v-model="item.url" class="text full">
            </div>
        </div>
        <div style="margin-block-start: 20px;"><button type="button" @click="addItem">入力欄を追加</button></div>
    </div>

    <mt:Ignore>入力フィールドのアプリケーション</mt:Ignore>
    <script>
        const { createApp } = Vue;
        const defaultItem = {
            // キーと初期値を列挙しておく
            // 例)customfield_entry_outerlink_title だったら title にする。これが v-model="item.title" のような記述につながる。
            title: '',
            url: '',
        };

        createApp({
            data() {
                return {
                    // 入力値をJavaScriptのオブジェクトで出力する
                    // キーはdefaultItemの所に書いたものと同じにする
                    items: [<mt:If name="titles">
                        <mt:Loop name="titles">
                            <mt:Var name="__counter__" op="--" setvar="index" />
                            {
                                title: '<mt:Var name="titles" index="$index" />',
                                url: '<mt:Var name="urls" index="$index" />',
                            }<mt:unless name="__last__">,</mt:unless>
                        </mt:Loop>
                    <mt:Else>[{...defaultItem}]</mt:If>],
                }
            },

            methods: {
                deleteItem(index) {
                    if (window.confirm('削除しますか?')) {
                        this.items.splice(index, 1);
                    }
                },

                addItem() {
                    this.items.push(defaultItem);
                },
            }
        }).mount('#customfield_entry_outerlink'); // 入力フィールドの定義に記述したID属性値をセット
    </script>
</mt:LocalVars>

Vue.jsの読み込みが必要なので、PowerCMS設定内にある「<head> への埋め込み」欄に下記を記述しました。ローカルに設置してパスを書いてもOKです。また、フィールドのスタイルもCSSファイルにまとめ、パスをheadに追加するのが良いと思います。

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

さらに改良するとすれば

Vue.jsのミックスインを使えばmethods(フィールドの追加・削除処理)を1つにまとめることができるようです。ただ、Vue 3ではミックスインが非推奨になったようで、Composition APIを使用したコンポーザブル関数を使うのが良いそうです。(ひとまずミックスインでもいいかなぁ…)