クエリーループブロックの拡張

Topics

  • バリエーションを使用したブロックの拡張
    • 合理的なデフォルトの提供
    • バリエーションのレイアウトのカスタマイズ
    • Gutenberg のバリエーションの認識
  • クエリーの拡張
    • 関連性のない、またはサポートされていないクエリーコントロールの無効化
    • コントロールの追加
    • カスタムクエリをフロントエンド側で動かす
    • カスタムクエリーをエディタ側で動かす

クエリーループブロックは強力なツールです。ユーザーが決定した投稿のリストをループし、リスト内の各投稿のコンテキストを継承するブロックの特定セットを表示できます。例えば、あるカテゴリの投稿をすべてループするように設定し、それぞれの投稿のアイキャッチ画像を表示できます。もちろん、それ以外にもさまざまなことが可能です !

しかし、クエリーループブロックが非常に強力で自由にカスタマイズ可能な分、面倒な面もあります。ほとんどのユーザーは「クエリー」の概念や関連する専門用語に精通しておらず、クエリループブロックのすべての機能を見せられたくはありません。代わりに、調整する設定が少なく、名前もわかりやすく、あらかじめ設定されたバージョンのブロックの方が遥かに好みです。実際、デフォルトで提供されている、投稿リストのバリエーションはその良い例です。ユーザーは技術的な面に触れることなくクエリーループブロックを使用できるだけでなく、ブロックの目的は見つけやすく、理解しやすくなっています。

同じように多くの拡張コンポーネントでも、ブロックの特別なバージョンの表示方法が必要になるかもしれません。たとえば独自のプリセット、追加の設定、ユースケースに無関係なカスタマイズオプションの省略 (たとえば、多くの場合、カスタム投稿タイプ)など。クエリーループブロックにはこのようなバリエーションを作成する、非常に強力な方法があります。

バリエーションを使用したブロックの拡張

特定のクエリーループブロック設定を持つ、カスタムブロックバリエーションを登録することで、ベースのクエリーループブロックが提供する機能をすべてそのまま使用しながら、表示方法をより細かく制御できます。ブロックバリエーションの詳細については、こちらを参照してください。

ブロックバリエーション API を使用すると、ユースケースに最も適切なデフォルト設定を提供できます。

さっそくサンプルプログラムを試してみましょう。book カスタム投稿タイプ を登録するプラグイン用のバリエーションを設定します。

Top ↑

合理的なデフォルトの提供

最初のステップはバリエーションの作成です。バリエーションは設定により、デフォルトで投稿記事の代わりに書籍 (book) のリストを表示するブロックバリエーションを提供します。完全なバリエーションのコードは以下のようになります。

const MY_VARIATION_NAME = 'my-plugin/books-list';

registerBlockVariation( 'core/query', {
	name: MY_VARIATION_NAME,
	title: 'Books List',
	description: 'Displays a list of books',
	isActive: ( { namespace, query } ) => {
		return (
			namespace === MY_VARIATION_NAME
			&& query.postType === 'book'
		);
	},
	icon: /** SVG アイコンがここに来る */,
	attributes: {
		namespace: MY_VARIATION_NAME,
		query: {
			perPage: 6,
			pages: 0,
			offset: 0,
			postType: 'book',
			order: 'desc',
			orderBy: 'date',
			author: '',
			search: '',
			exclude: [],
			sticky: '',
			inherit: false,
		},
	},
	scope: [ 'inserter' ],
	}
);

大変に見えるかもしれませんが、心配は不要です。それぞれのプロパティについて、なぜ存在し、何をするのかを見ていきましょう。

基本的には、次のようなコードから始める事になります。

registerBlockVariation( 'core/query', {
	name: 'my-plugin/books-list',
	attributes: {
		query: {
			/** ...必要に応じて、さらにクエリー設定を追加する */
			postType: 'book',
		},
	},
} );

このコードによりユーザーはドロップダウンからカスタム postType を選択しなくても、正しい構成で表示できます。しかし、ユーザーはどうやってこのバリエーションを発見し、挿入するのでしょうか ? いい質問です ! これを有効にするには、以下を追加します。

{
	/** ... バリエーションのプロパティ */
	scope: [ 'inserter' ],
}

これによりユーザーがエディターを開き、検索すると、このブロックが他のブロックと同じように表示されます。この時点でバリエーションにカスタムアイコン、タイトル、説明も追加できます。

{
	/** ... バリエーションのプロパティ */
	title: 'Books List',
	description: 'Displays a list of books',
	icon: /* SVG アイコンがここに */,
}

これでカスタムバリエーションはスタンドアロンブロックとほとんど区別がつかなくなります。プラグインに完全に溶け込み、発見しやすく、ユーザーはドロップインで直接利用できます。

Top ↑

バリエーションのレイアウトのカスタマイズ

クエリーループブロックは scope プロパティとして、文字列 'block' をサポートすることに注意してください。理論的にはこれで、ブロック自体を挿入した後に、バリエーションをピックアップできるようになります。ブロックバリエーションピッカーについてはこちらを参照してください。

しかし現在、この使用は推奨できません。これはパターンと scope: [ 'block' ] バリエーションを使用したクエリーループの設定のためです。postType と inherit クエリープロパティを除く、選択されたパターンのすべての属性が使用され、矛盾した機能的でないバリエーションにつながる可能性があります。

これを回避するには、2つの方法があります。1つ目は、デフォルトの innerBlocks を追加する方法です。

innerBlocks: [
	[
		'core/post-template',
		{},
		[ [ 'core/post-title' ], [ 'core/post-excerpt' ] ],
	],
	[ 'core/query-pagination' ],
	[ 'core/query-no-results' ],
],

バリエーションに innerBlocks を含めることで、本質的にクエリーループブロックのセットアップフェーズを提案されたパターンでスキップし、最初のコンテンツとしてこのインナーブロックをブロックに挿入できます。

もう一つの方法では、セットアップで提案されるバリエーション固有のパターンを登録し、ブロックのフローを置き換えます。

クエリーループブロックは、自身のアクティブなバリエーションの有無、そしてこのバリエーションで利用可能な特定のパターンの有無を判断します。もしあれば、このパターンがユーザーに提案される唯一のパターンになります。オリジナルのクエリーループブロックのデフォルトパターンは含まれません。もしそのようなパターンがなければ、デフォルトのパターンが提案されます。

パターンをクエリーループのバリエーションと「接続」するには、パターンの blockTypes プロパティに、クエリーループの名前を接頭辞につけたバリエーション名 (例えば core/query/$variation_name) を追加する必要があります。パターンの登録についての詳細はこちらを参照してください。

もしバリエーションに innerBlocks を指定していなければ、セットアップの段階でユーザが「新規」を選択したときに、「接続」バリエーションを提案する方法もあります。これは「接続」パターンと同じ方法で処理されます。クエリーループのアクティブなバリエーションの有無、そして提案する接続されたバリエーションの有無がチェックされます。

バリエーションを別のクエリーループのバリエーションに接続するには、['block'] を値として scope 属性を定義し、namespace 属性を配列として定義する必要があります。この配列には、接続したいバリエーションの名前 (name プロパティ) を含める必要があります。

例えば、クエリーループのバリエーションをインサーター (scope: ['inserter']) に名前 products で公開している場合、namespace 属性を ['products'] に設定することで、指定した block バリエーションを接続できます。ユーザーが「新規」をクリックした後にこのバリエーションを選択すると、namespace 属性はメインのインサーターのバリエーションによって上書きされます。

Top ↑

Gutenberg のバリエーションの認識

このバリエーションを実装した後で気づかれたかもしれませんが、ちょっとした問題が1つあります。バリエーションを挿入している間、ユーザーには透過ですが、Gutenberg は内部でバリエーションをクエリーループブロックとして認識します。たとえばエディターのツリービューではクエリーループブロックとして表示されます。

このブロックが実際には特定のバリエーションであることをエディターに伝える方法が必要です。これが isActive プロパティが作られた目的です。ブロックの属性に基づいて、特定のバリエーションがアクティブかどうかを判断できます。以下のように使用します。

{
	/** ... バリエーションのプロパティ */
	isActive: ( { namespace, query } ) => {
		return (
			namespace === MY_VARIATION_NAME
			&& query.postType === 'book'
		);
	},
}

postType だけを比較すれば良いと思うかもしれません。postType が book と一致したときに Gutenberg がそのブロックをバリエーションと認識すれば良いと。しかし、これでは網の目が広すぎます。他のプラグインも投稿タイプ book のバリエーションを公開するかもしれませんし、ユーザーがエディター設定から手動でタイプを book に設定するたびにバリエーションを認識させたくもありません。

クエリーループブロックが特別な属性 namespace を公開する理由がこれです。この属性はブロックの実装の中では何もしませんが、拡張コンポーネントが自身のバリエーションを認識し、スコープを設定する際に、簡単で一貫性のある方法として使用されます。さらに isActive はまた、比較する属性を文字列の配列として受け取ります。多くの場合、namespace で十分なため、以下のように使用します。

{
	/** ... バリエーションのプロパティ */
	attributes: {
		/** ... バリエーションの属性 */
		namespace: 'my-plugin/books-list',
	},
	isActive: [ 'namespace' ],
}

このように、Gutenberg はカスタム名前空間と一致する場合のみ、それが特定のバリエーションであることを認識します ! これはとても便利です !

Top ↑

クエリーの拡張

こうした点をすべて踏まえても、カスタム投稿タイプには独自の要件があるかもしれません。フィルタリングしてクエリーしたい特定のカスタム属性をサポートするかもしれませんし、他のクエリーパラメータは無関係だったり、完全にサポートされていないかもしれません。クエリーループブロックはこのようなケースも想定して作成されています。問題の解決方法を見ていきましょう。

Top ↑

関連性のない、またはサポートされていないクエリーコントロールの無効化

カスタム投稿タイプ book では、sticky 属性をまったく使わず、したがってブロックのカスタマイズにはまったく関係ないとします。ユーザーに何を設定すれば良いかと悩ませず、明確な UX を提供するため、このコントロールを使用できないようにしたいところです。さらに、一般には投稿をデータベースに追加したユーザーを示す author フィールドをまったく使わず、代わりにカスタムフィールド bookAuthor を使うとします。このとき authorフィルタを保持することは混乱を招くだけでなく、クエリを完全に「破壊する」ことになります。

このためクエリーループブロックバリエーションは、インスペクターのサイドバーに表示するコントロールのキー配列を受け付けるプロパティ allowedControls をサポートします。デフォルトでは、すべてのコントロールを受け付けますが、このプロパティに配列を指定し始めるとすぐに、関連するコントロールだけを指定したくなるはずです。

Gutenberg バージョン14.2では、以下のコントロールを利用できます。

  • inherit – テンプレートから直接クエリを継承するためのトグルスイッチを表示する。
  • postType – 利用可能な投稿タイプのドロップダウンを表示する。
  • order – クエリの順番を選択するドロップダウンリストを表示する。
  • sticky – 先頭固定投稿の処理方法を選択するドロップダウンを表示する。
  • taxQuery – 現在選択されている投稿タイプで利用可能なタクソノミフィルタを表示する。
  • author – 作成者でクエリをフィルタリングするための入力フィールドを表示する。
  • search – キーワードでクエリをフィルタリングするための入力フィールドを表示する。

この例ではプロパティは以下のようになります。

{
	/** ...variation properties */
	allowedControls: [ 'inherit', 'order', 'taxQuery', 'search' ],
}

上の利用可能なコントロールをすべて非表示にしたければ、allowedControls の値として空の配列を設定できます。

postType コントロールも無効にしていることに注意してください。ユーザーがバリエーションを選択した際、紛らわしい投稿タイプ変更のドロップダウンを表示する理由はありません。その上、カスタムコントロールを実装できるため、ブロックを壊す可能性があります。これについてはすぐ後で見ます。

Top ↑

コントロールの追加

プラグインはクエリに必要なカスタム属性を使用するため、たった今無効にしたコアのインスペクタコントロールの代わりに、ユーザーがカスタム属性を選択できるような独自コントロールを追加します。これには、ブロックフィルターにフックされた React HOC 経由で行います。

import { InspectorControls } from '@wordpress/block-editor';

export const withBookQueryControls = ( BlockEdit ) => ( props ) => {
	// 私たちのバリエーションである場合にのみ、これらのコントロールを追加したいとします。
	// これには前述の `isActive` 関数と同様、それをチェックするカスタムロジックを
	// 実装できます。
	// 以下では、これを処理するカスタム関数 `isMyBooksVariation` を記述したとします。
	return isMyBooksVariation( props ) ? (
		<>
			<BlockEdit key="edit" { ...props } />
			<InspectorControls>
				<BookAuthorSelector /> { /** カスタムコンポーネント */ }
			</InspectorControls>
		</>
	) : (
		<BlockEdit key="edit" { ...props } />
	);
};

addFilter( 'editor.BlockEdit', 'core/query', withBookQueryControls );

もちろん、コントロールのロジックの実装はあなたの責任です (Gutenberg UI にシームレスにコントロールをフィットさせるには、@wordpress/components を参考にするとよいでしょう)。ブロック属性内の query オブジェクトに追加パラメータを割り当てると、わずかな追加の手間で、ニーズに応じたカスタムクエリを作成できます。

現在、フロントエンド側 (つまりエンドユーザー側) でクエリを正しく動かし、エディター側で正しいプレビューを表示するには、少し異なるパスを実装する必要があります。

{
	/** ... バリエーションのプロパティ */
	attributes: {
		/** ... バリエーションの属性 */
		query: {
			/** ... 必要であれば、もっとクエリー設定 */
			postType: 'book',
			/** カスタムクエリーパラメータ */
			bookAuthor: 'J. R. R. Tolkien'
		}
	}
}

Top ↑

カスタムクエリをフロントエンド側で動かす

クエリーループブロックは主に、属性を受け取ってそこからクエリーを構築する、投稿テンプレートブロックを通して機能します。クエリーループブロックの他の重要な子ブロック (ページネーションブロックなど) も同じように動作します。これらはクエリを構築し、フィルター query_loop_block_query_vars を介して、結果を公開します。

そのフィルターにフックして、それに応じてクエリを変更できます。ただし他のクエリーループブロックに副作用を引き起こさないようにしてください。少なくとも自分のバリエーションにのみフィルタを適用することを確認してください。

if( 'my-plugin/books-list' === $block[ 'attrs' ][ 'namespace' ] ) {
	add_filter(
		'query_loop_block_query_vars',
		function( $query ) {
			/** ここでブロックカスタムクエリパラメータを読み込み、クエリを構築できます。*/
		},
	);
}

(上のコードでは、ブロックにアクセスする何らかの方法があることを想定しています。たとえば pre_render_block フィルター内など。しかし、具体的な解決策はユースケースによって異なる可能性があるため、これは確実な推奨ではありません)。

Top ↑

カスタムクエリーをエディタ側で動かす

カスタムバリエーションの仕上げとして、カスタムクエリの変更にエディターが反応し、それに応じて適切なプレビューを表示します。これはブロックの機能に必須ではありませんが、ブロックの利用者に対して、完全に統合されたユーザーエクスペリエンスを実現します。

クエリーループブロックは、WordPress REST API を使用して、プレビューを表示する投稿を取得します。query オブジェクトに追加されたパラメータは、API のクエリー引数として渡されます。つまり、これらの追加パラメータは REST API でサポートされるか、カスタム投稿タイプの API リクエストにフックできる rest_{$this->post_type}_query フィルターのようなカスタムフィルターで処理する必要があります。

add_filter(
	'rest_book_query',
	function( $args, $request ) {
		/** ここからカスタムパラメータにアクセスできる */
		$book_author = $request->get_param( 'bookAuthor' );
		/** ... カスタムクエリーロジック */
	}
);

以上で、完全に機能するクエリーループブロックのバリエーションが完成しました !

原文

最終更新日: