diff --git a/example/rest-api/art-institute/art-institute.php b/example/rest-api/art-institute/art-institute.php
index c8f3202c..217d9be7 100644
--- a/example/rest-api/art-institute/art-institute.php
+++ b/example/rest-api/art-institute/art-institute.php
@@ -32,18 +32,18 @@ function register_aic_block(): void {
'data_source' => $aic_data_source,
'endpoint' => function ( array $input_variables ) use ( $aic_data_source ): string {
$endpoint = $aic_data_source->get_endpoint();
-
+
// Get and clean IDs from comma-separated string
$ids = array_filter(
array_map( 'trim', explode( ',', (string) $input_variables['id'] ) ),
'strlen'
);
-
- if ( !empty( $ids ) ) {
+
+ if ( ! empty( $ids ) ) {
return add_query_arg([
'ids' => implode( ',', $ids ),
'fields' => 'id,title,image_id,artist_title',
- ], $endpoint);
+ ], $endpoint );
}
return $endpoint;
@@ -53,6 +53,7 @@ function register_aic_block(): void {
'name' => 'Art ID',
'type' => 'id',
'supports_bulk' => true,
+ 'required' => false,
],
],
'output_schema' => [
@@ -157,11 +158,56 @@ function register_aic_block(): void {
],
]);
+ $collection_query = HttpQuery::from_array([
+ 'data_source' => $aic_data_source,
+ 'endpoint' => function ( array $input_variables ) use ( $aic_data_source ): string {
+ $endpoint = $aic_data_source->get_endpoint();
+ return add_query_arg( [
+ 'limit' => 10,
+ 'fields' => 'id,title,image_id,artist_title',
+ ], $endpoint );
+ },
+ 'output_schema' => [
+ 'is_collection' => true,
+ 'path' => '$.data[*]',
+ 'type' => [
+ 'id' => [
+ 'name' => 'Art ID',
+ 'type' => 'id',
+ ],
+ 'artist_title' => [
+ 'name' => 'Artist Title',
+ 'type' => 'string',
+ 'path' => '$.artist_title',
+ ],
+ 'title' => [
+ 'name' => 'Title',
+ 'type' => 'string',
+ 'path' => '$.title',
+ ],
+ 'image_url' => [
+ 'name' => 'Image URL',
+ 'generate' => function ( $data ): string {
+ return 'https://www.artic.edu/iiif/2/' . $data['image_id'] . '/full/843,/0/default.jpg';
+ },
+ 'type' => 'image_url',
+ ],
+ ],
+ ],
+ ]);
+
register_remote_data_block([
'title' => 'Art Institute of Chicago',
'icon' => 'art',
'render_query' => [
'query' => $get_art_query,
+ 'additional_queries' => [
+ [
+ 'query' => $collection_query,
+ 'type' => 'collection',
+ 'display_name' => 'Collection',
+ ],
+ ],
],
'selection_queries' => [
[
diff --git a/inc/Editor/BlockManagement/ConfigRegistry.php b/inc/Editor/BlockManagement/ConfigRegistry.php
index 312234dd..86880cf8 100644
--- a/inc/Editor/BlockManagement/ConfigRegistry.php
+++ b/inc/Editor/BlockManagement/ConfigRegistry.php
@@ -25,6 +25,7 @@ class ConfigRegistry {
public const DISPLAY_QUERY_KEY = 'display';
public const LIST_QUERY_KEY = 'list';
public const SEARCH_QUERY_KEY = 'search';
+ public const COLLECTION_QUERY_KEY = 'collection';
public static function init( ?LoggerInterface $logger = null ): void {
self::$logger = $logger ?? LoggerManager::instance();
@@ -48,7 +49,21 @@ public static function register_block( array $user_config = [] ): bool|WP_Error
return self::create_error( $block_title, sprintf( 'Block %s has already been registered', $block_name ) );
}
+ // Process default render query first
$display_query = self::inflate_query( $user_config[ self::RENDER_QUERY_KEY ]['query'] );
+
+ // Initialize queries array with display query as default
+ $queries = [
+ self::DISPLAY_QUERY_KEY => $display_query,
+ ];
+
+ // Process additional render queries if present
+ if ( isset( $user_config[ self::RENDER_QUERY_KEY ]['additional_queries'] ) ) {
+ foreach ( $user_config[ self::RENDER_QUERY_KEY ]['additional_queries'] as $query_config ) {
+ $queries[ $query_config['type'] ] = self::inflate_query( $query_config['query'] );
+ }
+ }
+
$input_schema = $display_query->get_input_schema();
// Check if render query has any bulk-supporting inputs
@@ -81,29 +96,35 @@ function ( $input ) {
'loop' => $user_config[ self::RENDER_QUERY_KEY ]['loop'] ?? false,
'overrides' => $user_config['overrides'] ?? [],
'patterns' => [],
- 'queries' => [
- self::DISPLAY_QUERY_KEY => $display_query,
- ],
+ 'queries' => $queries,
'selectors' => [
- [
- 'image_url' => $display_query->get_image_url(),
- 'inputs' => array_map( function ( $slug, $input_var ) {
- return [
- 'name' => $input_var['name'] ?? $slug,
- 'required' => $input_var['required'] ?? true,
- 'slug' => $slug,
- 'type' => $input_var['type'] ?? 'string',
- 'supports_bulk' => $input_var['supports_bulk'] ?? false,
- ];
- }, array_keys( $input_schema ), array_values( $input_schema ) ),
- 'name' => 'Manual input',
- 'query_key' => self::DISPLAY_QUERY_KEY,
- 'type' => 'input',
- ],
+ self::create_selector(
+ $display_query,
+ self::DISPLAY_QUERY_KEY,
+ 'input',
+ 'Manual input',
+ $has_bulk_support
+ ),
],
'title' => $block_title,
];
+ // Add collection queries to selectors if present
+ if ( isset( $user_config[ self::RENDER_QUERY_KEY ]['additional_queries'] ) ) {
+ foreach ( $user_config[ self::RENDER_QUERY_KEY ]['additional_queries'] as $query_config ) {
+ array_unshift(
+ $config['selectors'],
+ self::create_selector(
+ $queries[ $query_config['type'] ],
+ $query_config['type'],
+ $query_config['type'],
+ $query_config['display_name'] ?? null,
+ $has_bulk_support
+ )
+ );
+ }
+ }
+
// Register "selectors" which allow the user to use a query to assist in
// selecting data for display by the block.
foreach ( $user_config[ self::SELECTION_QUERIES_KEY ] ?? [] as $selection_query ) {
@@ -135,21 +156,13 @@ function ( $input ) {
// Add the selector to the configuration.
array_unshift(
$config['selectors'],
- [
- 'image_url' => $from_query->get_image_url(),
- 'inputs' => array_map( function ( $slug, $input_var ) {
- return [
- 'name' => $input_var['name'] ?? $slug,
- 'required' => $input_var['required'] ?? false,
- 'slug' => $slug,
- 'type' => $input_var['type'] ?? 'string',
- ];
- }, array_keys( $from_input_schema ), array_values( $from_input_schema ) ),
- 'name' => $selection_query['display_name'] ?? ucfirst( $from_query_type ),
- 'query_key' => $from_query::class,
- 'supports_bulk' => $has_bulk_support,
- 'type' => $from_query_type,
- ]
+ self::create_selector(
+ $from_query,
+ $from_query::class,
+ $from_query_type,
+ $selection_query['display_name'] ?? null,
+ $has_bulk_support
+ )
);
}
@@ -206,4 +219,30 @@ private static function inflate_query( array|QueryInterface $config ): QueryInte
return $config;
}
+
+ // Create a selector for a query
+ private static function create_selector(
+ QueryInterface $query,
+ string $query_key,
+ string $type,
+ ?string $display_name = null,
+ bool $supports_bulk = false
+ ): array {
+ return [
+ 'image_url' => $query->get_image_url(),
+ 'inputs' => array_map( function ( $slug, $input_var ) {
+ return [
+ 'name' => $input_var['name'] ?? $slug,
+ 'required' => $input_var['required'] ?? false,
+ 'slug' => $slug,
+ 'type' => $input_var['type'] ?? 'string',
+ 'supports_bulk' => $input_var['supports_bulk'] ?? false,
+ ];
+ }, array_keys( $query->get_input_schema() ), array_values( $query->get_input_schema() ) ),
+ 'name' => $display_name ?? ucfirst( $type ),
+ 'query_key' => $query_key,
+ 'type' => $type,
+ 'supports_bulk' => $supports_bulk,
+ ];
+ }
}
diff --git a/inc/Validation/ConfigSchemas.php b/inc/Validation/ConfigSchemas.php
index cc8e3106..05898ab9 100644
--- a/inc/Validation/ConfigSchemas.php
+++ b/inc/Validation/ConfigSchemas.php
@@ -80,6 +80,20 @@ private static function generate_remote_data_block_config_schema(): array {
Types::instance_of( QueryInterface::class ),
Types::serialized_config_for( HttpQueryInterface::class ),
),
+ 'additional_queries' => Types::nullable(
+ Types::list_of(
+ Types::object( [
+ 'display_name' => Types::nullable( Types::string() ),
+ 'query' => Types::one_of(
+ Types::instance_of( QueryInterface::class ),
+ Types::serialized_config_for( HttpQueryInterface::class ),
+ ),
+ 'type' => Types::enum(
+ ConfigRegistry::COLLECTION_QUERY_KEY,
+ ),
+ ] )
+ )
+ ),
'loop' => Types::nullable( Types::boolean() ),
] ),
'selection_queries' => Types::nullable(
diff --git a/src/blocks/remote-data-container/components/InnerBlocks.tsx b/src/blocks/remote-data-container/components/InnerBlocks.tsx
index 03acaaf7..62f30077 100644
--- a/src/blocks/remote-data-container/components/InnerBlocks.tsx
+++ b/src/blocks/remote-data-container/components/InnerBlocks.tsx
@@ -5,18 +5,25 @@ import { LoopTemplate } from '@/blocks/remote-data-container/components/loop-tem
interface InnerBlocksProps {
blockConfig: BlockConfig;
- getInnerBlocks: ( result: RemoteDataResult ) => BlockInstance< RemoteDataInnerBlockAttributes >[];
+ getInnerBlocks: (
+ result: RemoteDataApiResult
+ ) => BlockInstance< RemoteDataInnerBlockAttributes >[];
remoteData: RemoteData;
}
export function InnerBlocks( props: InnerBlocksProps ) {
const {
- blockConfig: { loop },
+ blockConfig: { loop, selectors },
getInnerBlocks,
remoteData,
} = props;
- if ( loop || remoteData.results.length > 1 ) {
+ // Use loop template for both loop blocks, multi-selection, or collection queries
+ if (
+ loop ||
+ remoteData.results.length > 1 ||
+ selectors.some( selector => selector.type === 'collection' )
+ ) {
return ;
}
diff --git a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx
index 898f1bec..b1e77add 100644
--- a/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx
+++ b/src/blocks/remote-data-container/components/placeholders/ItemSelectQueryType.tsx
@@ -1,4 +1,4 @@
-import { ButtonGroup } from '@wordpress/components';
+import { Button, ButtonGroup } from '@wordpress/components';
import { InputModal } from '../modals/InputModal';
import { InputPopover } from '../popovers/InputPopover';
@@ -38,6 +38,21 @@ export function ItemSelectQueryType( props: ItemSelectQueryTypeProps ) {
{ ...selectorProps }
/>
);
+ case 'collection':
+ return (
+
+ );
case 'input':
return selector.inputs.length === 1 && selector.inputs[ 0 ] ? (