Table of Content
これも相当苦労しましたが、一般的なニーズがあるものと思い、頑張って作りました。
是非お役に立てていただければありがたいです。
なぜ、ブロック監視が必要か
まず、ブロックの監視って何をするのということの説明をします。一言でいうと、あるブロックがページ内に存在する場合は、配置しようとしているブロックの配置ができないようにするという制御です。
何のために必要かというと、私が作っているオープニングブロックは現在3種類あって、それぞれ同じ名前のIDをもつDOM要素を内包しています。それによって共通するアニメーションの制御を一つの関数で実行できるようにしているのです。ですから、この3つのブロックがWebページに同居するとIDが重なることになるし、仮にそれが許されたとしても思わぬ動きになってしまうでしょう。オープニングは一つのWebページに一つで十分でしょうから、2つ以上配置できないようにしたいというわけです。
同じブロックが2つ以上にならないようにするのは簡単で、block.jsonに次のように書き込みます。
"supports": { "multiple": false, ・・・他の設定 },
これでそのブロックは同一ページに2つ存在することができなくなります。
問題は違うブロックだけれども、そのブロックがすでに存在するときは配置できないようにするという制御です。
先に完成したコードを示します。
//初めのブロックの状態を取得 let blocksInEditor = select('core/block-editor').getBlocks();・・・① let noticeCreated = false; //エラーメッセージ表示フラグ subscribe(() => { //状態変更時のブロックの状態を取得 const newBlocksInEditor = select('core/block-editor').getBlocks();・・・② if (newBlocksInEditor.length > blocksInEditor.length) {・・・③ //オープニングブロックがあるかの検証 const disabledBlocks = ['itmar/logo-anime', 'itmar/tea-time', 'itmar/welcome']; const isDisabledBlockPresent = blocksInEditor.some(block => disabledBlocks.includes(block.name));・・・④ // 追加しようとするブロックの取得 const newBlock = newBlocksInEditor.find( block => !blocksInEditor.some(existingBlock => existingBlock.clientId === block.clientId) );・・・⑤ const isNewBlockDisabled = disabledBlocks.includes(newBlock.name) //オープニングブロックがあり追加しようとするブロックもオープニングブロックの場合 if (isDisabledBlockPresent && isNewBlockDisabled) { //noticeCreatedがfalseならエラー通知 if (!noticeCreated) { noticeCreated = true; dispatch('core/notices').createNotice( 'error', 'オープニングブロックは一つしか配置できません。', { type: 'snackbar' } ); }・・・⑥ noticeCreated = false; // 追加されたブロックを削除する dispatch('core/block-editor').removeBlock(newBlock.clientId, false); }・・・⑦ } // ブロックリストを更新する blocksInEditor = newBlocksInEditor;・・・⑧ });
ここで主役になるのはsubscribeです。これは、WordPressが提供する関数で、データストア(編集画面に配置したブロック等の内容物)の状態が変わるたびにコールバック関数を呼び出すことで、状態の変更をサブスクライブ(購読)します。
さらに詳しく説明します。
①でこの関数が実行された最初の編集画面内の状態をblocksInEditorという変数にキープします。このステートメントはリロードされるまで実行されることはありません。
②でsubscribeで呼び出されたときの編集画面内の状態をnewBlocksInEditorという変数キープします。これと①でキープしたblocksInEditorを比べるわけです。
newBlocksInEditor.length、blocksInEditor.lengthはそれぞれブロックの数を取得します。③の比較はブロックの数がnewBlocksInEditor内の方が多いとき、つまり、ブロックが新たに挿入されたことを検出するための比較です。
④でdisabledBlocks配列に設定した名前をもつブロックが subscribeが呼び出される前からあったかどうかを調べて、isDisabledBlockPresentフラグにセットします。
⑤ではnewBlocksInEditor内でblocksInEditor内のブロックにないものを探します。つまり、それが追加しようとしているブロックということです。そして、そのブロックもdisabledBlocks配列に設定した名前をもつブロックであれば、そのブロックは追加させないようにする処理に回るわけです。
⑥はエラーメッセージを表示するステートメントです。ここで最大の落とし穴があります。このステートメントが実行されると次のようなティップスが表示されます。

これは「データストア(編集画面に配置したブロック等の内容物)の状態が変わる」ということになるようで、処理が中断されそれ、subscribeのコールバックに制御が戻ってしまいます。したがって、以降のdispatch(‘core/block-editor’).removeBlock処理は行われないようです。そこでdispatch(‘core/notices’).createNoticeの実行前にnoticeCreated = true;を実行してフラグを書き換え、次にsubscribeのコールバックでif (!noticeCreated)の条件が成就しないようにしています。その後、noticeCreated = false;でフラグを戻してやれば、もう一度同じ操作が行われてもdispatch(‘core/notices’).createNoticeは実行されるという仕組みです。
この動きを確認するのに何度も何度もデバックツールでトレースしました。
このdispatch(‘core/notices’).createNoticeの仕組みを知らないと無限ループに入ってしまします。このステートメントは様々な場面で使うことがあると思うので気をつけてください。
⑦にはdispatch(‘core/notices’).createNoticeが実行されないときにようやくたどり着けます。ここで追加したブロックを削除します
⑧の処理も重要です。ブロックの数の変化以外でもsubscribeのコールバック内で常に実行されるようにしました。これはWordpressがブロックがまったくなくなったとき自動的にcore/paragraphブロックを追加するような動きをすることから if (newBlocksInEditor.length > blocksInEditor.length)での状態管理が完全にならないことがあるためです。また、ブロックの追加操作以外でもブロックのclientIdが変化してしまい、newBlock に元あったブロックが設定されてしまうことがありました。
それほど難しいロジックではないと思ったんですが、dispatch(‘core/notices’).createNoticeの作用がネックでした。これで何度も無限ループに入ってしまって、それを制御するのが大変でした。
でも、一度作ってしまえばいろんなところで応用が効きそうなので是非ご利用ください。
ちなみにこのコードはブロックのコンポーネントに含めることは相当ではないと思います。それをするとブロックがマウントされないと監視が効かないからです。したがって、このスクリプトは@wordpress/create-blockのwebpackの設定にエントリポイントを加えてトランスパイルし、それで出来上がったjsファイルをブロックコンポーネントのエントリポイントであるPHPファイルでエンキューしてWebページ全体で働くようにする必要があります。
PHPファイルには
function itmar_opening_block_add_plugin() { //ブロックの2重登録の監視 wp_enqueue_script( 'itmar-check-script', plugins_url( 'build/check-blocks.js?'.date('YmdHis'), __FILE__ ), array( 'wp-blocks', 'wp-element', 'wp-data', 'wp-hooks' ), true ); //他のエンキューなど } add_action('enqueue_block_assets', 'itmar_opening_block_add_plugin');
というふうにenqueue_block_assetsフックでエンキューさせます。
ここで問題となるのはbuild/check-blocks.jsがトランスパイルされたファイルでなけれならないということです。それをWebpackのカスタマイズで実行する必要があるのですが、それはまた別のブログでお話しします。
コメントを残す