はじめてのプラグイン


管理画面に処理を差し込むプラグインを、1つ作ってみます。作業時間は 30 分ほどです。

実験的機能

管理画面の JavaScript 拡張は、Ver. 3.2 の間は実験的機能として提供し、Ver. 3.3 で正式に提供する予定です。管理画面に追加した独自の JavaScript・CSS は、バージョンアップ後の動作を保証しません。これらを原因とする不具合は、サポートの対象外です。変更される範囲と公開 API の扱いは、管理画面の JavaScript 拡張 の注記を確認してください。

このページで作るもの

  1. 画像をアップロードする前に確認ダイアログを表示する
  2. メディア詳細画面の alt 入力欄の下に、独自のボタンを追加する

ビルド環境は必要ありません。テキストエディタと、a-blog cms が動く環境だけ用意してください。

1. JavaScript ファイルを作る

a-blog cms を設置したディレクトリに extension/acms/assets/ フォルダを作り、その中に my-plugin.js を作成します。

/extension/acms/assets/my-plugin.js

中身は次のとおりです。

ACMS.adminReady(() => {
  ACMS.plugins.register('my-plugin', {
    version: '1.0.0',
    setup(ctx) {
      // アップロードする直前に確認ダイアログを出す
      ctx.events.on('media.upload.before', async (payload, gate) => {
        const ok = await ACMS.Library.dialog.confirm(`${payload.files.length} 件の画像をアップロードします。よろしいですか?`);
        if (!ok) {
          gate.cancel(); // 呼ぶとアップロードを中止する
        }
      });
    },
  });
});

ACMS.plugins.register() にプラグイン名と設定を渡すだけです。setup(ctx) の中に、やりたいことを書いていきます。ACMS.adminReady() は、管理画面の JavaScript の準備が終わってから中の処理を呼びます。すでに終わっていれば、すぐに呼びます。

2. 管理画面に読み込む

extension/acms/Hook.php を開き、afterBuild() メソッドに次の内容を追加します。Hook.php は a-blog cms に最初から入っています。

public function afterBuild(&$res)
{
    \Acms\Services\Facades\Asset::script('my-plugin', '/' . DIR_OFFSET . 'extension/acms/assets/my-plugin.js', [
        'position' => 'body_end',
        'attrs' => ['type' => 'module'],
    ]);
}
  • 第1引数の 'my-plugin' は、この JS を識別する名前(ハンドル)です。他と重複しない名前にします。
  • 第2引数は JS ファイルのパスです。DIR_OFFSET は a-blog cms を設置したディレクトリを表す定数で、ドキュメントルート直下に設置している場合は空文字になります。
  • position は読み込み位置です。body_end を指定します。
  • 'attrs' => ['type' => 'module'] を指定します。type="module" のスクリプトは、ページの解析が終わってから記述順に実行されるため、プラグインを読み込む順序が決まります。
  • 管理画面の JavaScript は非同期で読み込まれるため、プラグインのほうが先に実行されることがあります。ACMS.adminReady() で包むのはそのためです。 包まずに ACMS.plugins を呼ぶと、ACMS.plugins is undefined のエラーになることがあります。

読み込み方法の詳細は アセット API(JS / CSS の登録) を参照してください。

3. 動作を確認する

  1. 管理画面にログインします。
  2. 「メディア」を開き、画像をアップロードします。
  3. ファイルを選んだ直後に確認ダイアログが表示されます。「キャンセル」を選ぶとアップロードされません。

うまく動かないときは

ブラウザの開発者ツールを開き、次の3点を確認してください。

  • コンソールにエラーが出ていないか。 ACMS.plugins is undefined と出る場合は、ACMS.adminReady() で包まれていません。
  • JS ファイルが読み込まれているか。 ネットワークタブに my-plugin.js が出ているか確認します。404 の場合はパスを見直してください。
  • 管理画面で確認しているか。 この拡張は管理画面(ログイン後の画面)で動作します。サイトの表示側では動きません。

4. コードの読み方

ここまでで使った4つの要素を確認します。

ACMS.adminReady(() => {                                // (1) 管理画面の準備が終わるのを待つ
  ACMS.plugins.register('my-plugin', {                 // (2) プラグインを登録する
    setup(ctx) {                                       // (3) 登録時に一度だけ呼ばれる
      ctx.events.on('media.upload.before', handler);   // (4) イベントを購読する
    },
  });
});
  1. ACMS.adminReady(関数) — 管理画面の JavaScript の準備が終わってから、関数を呼びます。
  2. ACMS.plugins.register(名前, 設定) — プラグインを登録します。名前は管理画面全体で一意にします。
  3. setup(ctx) — 登録した直後に呼ばれます。イベントの購読や UI の差し込みは、すべてこの中で行います。
  4. ctx.events.on(イベント名, 処理) — イベントを購読します。処理は (payload, gate) を受け取ります。操作の直前に呼ばれるイベント(ゲート)では、gate.cancel() を呼ぶと操作を中止できます。return false では中止できません。

イベント名は {機能}.{操作}.{タイミング} の形です。media.upload.before は「メディアのアップロードの直前」という意味になります。ゲートかどうかはイベントごとに決まっていて、イベント一覧 の種別で確認できます。

5. ボタンを追加する

もう1つ、UI の差し込みを試します。my-plugin.js の setup(ctx) の中に追加してください。

      // メディア詳細画面の alt 入力欄の下にボタンを追加する
      ctx.ui.fill('media.edit-modal.field.alt', (el) => {
        const button = document.createElement('button');
        button.type = 'button';
        button.className = 'acms-admin-btn';
        button.textContent = 'ファイル名から入れる';
        el.append(button);
        return () => button.remove();
      });

管理画面のメディア一覧から画像をクリックし、alt 入力欄の下にボタンが表示されることを確認してください。

  • ctx.ui.fill(差し込み位置の名前, 処理) で UI を追加します。
  • 関数に渡される el は、差し込み位置に用意された空の要素です。この中に自由に DOM を追加できます。
  • 戻り値の関数は、画面が閉じられたときに呼ばれます(後片付け用)。
  • js/config.js の mediaShowAltAndCaptionOnModal を false にしている場合は、alt 入力欄ごと非表示になり、ボタンも表示されません。

差し込める位置の一覧は UI の差し込み(Slot / Fill) にあります。

完成したコード

ACMS.adminReady(() => {
  ACMS.plugins.register('my-plugin', {
    version: '1.0.0',
    setup(ctx) {
      ctx.events.on('media.upload.before', async (payload, gate) => {
        const ok = await ACMS.Library.dialog.confirm(`${payload.files.length} 件の画像をアップロードします。よろしいですか?`);
        if (!ok) {
          gate.cancel();
        }
      });

      ctx.ui.fill('media.edit-modal.field.alt', (el) => {
        const button = document.createElement('button');
        button.type = 'button';
        button.className = 'acms-admin-btn';
        button.textContent = 'ファイル名から入れる';
        el.append(button);
        return () => button.remove();
      });
    },
  });
});

次のステップ