イベント(ACMS.events)


管理画面の操作に処理を割り込ませる仕組みです。購読できるイベントの一覧は イベント一覧 にあります。

実験的機能

この機能は Ver. 3.2 の間は実験的機能として提供し、Ver. 3.3 で正式に提供する予定です。

  • 変わりうる範囲: イベント名と、イベントが受け取る値の構造は、マイナーバージョンアップで変更される可能性があります。
  • 変わらない範囲: 従来の DOM イベント(acms.media-updated など)と ACMS.addListener は今後も動作します。既存のコードを書き換える必要はありません。
  • 公開 API の線引き: イベント一覧 に掲載しているイベント名を公開 API として扱います。掲載外のものも利用できますが、告知なく変更されることがあります。
  • サポート対象外: 管理画面に追加した独自の JavaScript・CSS は、バージョンアップ後の動作を保証しません。これらを原因とする不具合は、サポートの対象外です。

基本の使い方

ACMS.plugins.register('my-plugin', {
  setup(ctx) {
    ctx.events.on('media.upload.success', (event) => {
      console.log(`${event.items.length} 件アップロードされました`);
    });
  },
});

プラグインを作らずに購読することもできます。

ACMS.events.on('media.upload.success', (event) => { /* ... */ });

ただし ctx.events.on() を使うと、プラグインの登録解除時に自動で購読も解除されるため、通常はプラグインの中で書くことを推奨します。

API説明
ACMS.events.on(type, handler)購読する。type は配列で複数指定できます。戻り値は購読解除用の関数。
ACMS.events.off(type, handler)購読を解除する。
ACMS.events.notify(type, payload)通知イベントを発火する(Promise を返します)。
ACMS.events.gate(type, payload)中止・書き換えができるイベントを発火する(Promise を返します)。

イベント名の読み方

{機能}.{操作}.{タイミング}     例: media.upload.before

タイミングは6種類あります。

タイミング意味処理を止められるか
.before操作の直前多くは止められる(中止・値の書き換え・非同期の待機)。一部は通知のみ(下記)
.success失敗しうる処理(保存・通信など)が成功した後通知のみ
.failure処理が失敗した後通知のみ
.change値や選択状態が変わった後通知のみ
.after操作が完了した直後(成功・失敗の区別がない操作)通知のみ
.ready初期化が一度だけ完了したとき通知のみ(後述の注意あり)

イベントには、処理を止められるゲートと、知らせるだけの通知の2種類があります。どちらかは名前では決まらず、イベント一覧 の「種別」列(中止可 / 通知)で確認してください。たとえば unit-editor.command.before は名前が .before で終わりますが、通知のみのイベントです。

処理を止めたり値を書き換えたりできるのは、種別が「中止可」のイベントだけです。 通知イベントのハンドラには中止のための引数が渡されず、戻り値も無視されます。

ゲートでできること

種別が「中止可」のイベントのハンドラは、第2引数に中止用のオブジェクト(以降 gateCtx)を受け取ります。できることは4つです。

やりたいこと書き方
何もしない(見るだけ)何もしない
値を書き換える受け取った event のプロパティを直接書き換える
操作を中止するgateCtx.cancel() または gateCtx.cancel('理由') を呼ぶ
非同期の処理を待たせるasync 関数にする(Promise を返すと a-blog cms が待ちます)
ctx.events.on('media.upload.before', async (event, gateCtx) => {
  // 非同期のチェック
  if (!(await isAllowed(event.files))) {
    gateCtx.cancel('この操作は許可されていません');
    return;
  }

  // 中止(理由なし)
  if (event.files.length === 0) {
    gateCtx.cancel();
    return;
  }

  // 値の書き換え(大きすぎるファイルを除く)
  event.files = event.files.filter((file) => file.size < 5_000_000);
});

中止のしかた

  • gateCtx.cancel() — 理由を表示せずに中止します。
  • gateCtx.cancel('理由') — 理由を添えて中止します。この文字列はそのまま利用者に表示されます(画面によってはダイアログや通知として表示されます)。ユーザーの操作を止める場合は、理由を添える方を推奨します。

cancel() は呼んだ時点で中止が確定します。return false や { cancel: true } のような戻り値では中止できません(jQuery の return false など、別の目的で書いた戻り値で意図せず中止されるのを避けるためです)。

値の書き換えについて

書き換えられる範囲はイベントごとに違います(イベント一覧 の「変更できるもの」列を参照)。たとえば、

  • media.update.before — 送信内容(formData)を書き換えられます。
  • media.upload.before — すでにあるファイルを除外することはできますが、新しいファイルに差し替えることはできません。
  • media.delete.before — 対象の絞り込みはできません。除外したい場合は全体を中止してください。

実行順とルール

  • ハンドラは登録順に1つずつ実行されます。並列実行はされません。
  • ゲートのイベントでは、前のハンドラが書き換えた値が次のハンドラに渡ります。
  • どれか1つのハンドラが gateCtx.cancel() を呼んだ場合、そのハンドラの処理が終わった後、残りのハンドラは実行されません。
  • ハンドラの中で例外が発生した場合も中止として扱われ、コンソールにエラーが記録されます。
  • 通知イベントでは、あるハンドラが例外を投げても他のハンドラと管理画面の動作は継続します。
  • 優先度の指定はありません。 実行順は登録順(=JS の読み込み順)だけで決まります。

過去のイベントは届きません

ACMS.events は、購読した時点より後に発生したイベントにだけ反応します。すでに発生済みのイベントは再生されません。

そのため、購読はスクリプトの読み込み時にトップレベルで行ってください(プラグイン API の「登録するタイミング」参照)。

起動時の処理を書く

「管理画面の準備ができたら実行したい」処理は、購読より先にすでに完了しているかどうかを確認してください。準備完了は一度しか発生しないため、購読が間に合わないことがあります。

function onAdminReady() {
  // 実行したい処理
}

if (ACMS.readyStates.adminReady === 'complete') {
  onAdminReady();
} else {
  ACMS.events.on('acms.state.admin-ready', onAdminReady);
}
イベント確認するプロパティ意味
acms.state.readyACMS.readyStates.readya-blog cms の JavaScript の初期化が完了した(サイト表示側・管理画面の両方)
acms.state.admin-readyACMS.readyStates.adminReady管理画面の初期化が完了した
acms.pending.readyACMS.pendingReadyState読み込み中表示の準備が完了した

ACMS.Ready() は従来どおり使えます(内部でこの仕組みを使っています)。

従来のイベントとの関係

a-blog cms はこれまで、DOM の CustomEvent(acms.media-updated など)と ACMS.dispatchEvent / ACMS.addListener でイベントを提供してきました。

  • 従来のイベントは今後も発火します。 既存のテーマ・プラグインの購読を書き換える必要はありません。
  • 新しく書く場合は ACMS.events を使ってください。中止・値の書き換え・非同期の待機は ACMS.events のゲートにしかありません。
  • 対応表は イベント一覧 の「従来のイベントとの対応」にあります。

独自のイベントを発火する

ACMS.events.gate() と ACMS.events.notify() は主に a-blog cms 本体が使う API ですが、プラグインが自分のイベントを発火することもできます。一覧に載っていない名前も使えます。

// 発火する側(中止できるイベント)
const result = await ACMS.events.gate('my-plugin.sync.before', { items });
if (result.canceled) {
  if (result.reason) {
    ACMS.Library.dialog.alert(result.reason);
  }
  return;
}

// 発火する側(知らせるだけのイベント)
await ACMS.events.notify('my-plugin.sync.success', { items: result.payload.items });

// 購読する側
ctx.events.on('my-plugin.sync.before', (event, gateCtx) => { /* ... */ });

gate() の戻り値は次の形です。

プロパティ説明
canceled中止されたかどうか
reasoncancel('理由') で中止された場合の理由
errorハンドラが例外を投げた場合の例外(この場合も canceled は true)
payload最終的な(書き換え後の)値

notify() は戻り値を返しません。

イベント名は {機能}.{操作}.{タイミング} の規約に合わせ、機能名にはプラグイン名を使うと衝突を避けられます。中止できるようにしたい場合は gate() で発火し、名前を .before で終わらせてください。

変更履歴

バージョン変更内容
Ver. 3.2.29新規追加(実験的機能)
Ver. 3.2.33発火とキャンセルの API を変更。ACMS.events.emit() と ACMS.events.cancel() を廃止し、gate() / notify() と gateCtx.cancel() に変更