Skip to content

Commit c20f178

Browse files
authored
Add "Show label" control (#22)
* Rename ContextControls to BlockBindingControls * Add "Show label" control
1 parent f6133bd commit c20f178

File tree

8 files changed

+211
-112
lines changed

8 files changed

+211
-112
lines changed

inc/editor/block-bindings/block-bindings.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,14 +214,25 @@ public static function get_remote_value( array $block_context, array $source_arg
214214
$field_name = $source_args['field'];
215215
$index = $source_args['index'] ?? 0; // Index is only set for loop queries.
216216

217+
if ( isset( $source_args['name'] ) && $source_args['name'] !== $block_name ) {
218+
self::log_error( 'Block binding belongs to a different remote data block', $block_name, $field_name );
219+
return null;
220+
}
221+
217222
$query_results = self::execute_query( $block_context, $field_name );
218223

219224
if ( ! isset( $query_results['results'][ $index ]['result'][ $field_name ]['value'] ) ) {
220225
self::log_error( 'Cannot resolve field for block binding', $block_name, $field_name );
221226
return null;
222227
}
223228

224-
return $query_results['results'][ $index ]['result'][ $field_name ]['value'];
229+
// Prepend label to value if provided.
230+
$value = $query_results['results'][ $index ]['result'][ $field_name ]['value'];
231+
if ( ! empty( $source_args['label'] ?? '' ) ) {
232+
return sprintf( '%s: %s', $source_args['label'], $value );
233+
}
234+
235+
return $value;
225236
}
226237

227238
public static function loop_block_render_callback( array $attributes, string $content, WP_Block $block ): string {
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { CheckboxControl, SelectControl } from '@wordpress/components';
2+
3+
import { TEXT_FIELD_TYPES } from '@/blocks/remote-data-container/config/constants';
4+
5+
interface BlockBindingFieldControlProps {
6+
availableBindings: AvailableBindings;
7+
fieldTypes: string[];
8+
label: string;
9+
target: string;
10+
updateFieldBinding: ( target: string, field: string ) => void;
11+
value: string;
12+
}
13+
14+
export function BlockBindingFieldControl( props: BlockBindingFieldControlProps ) {
15+
const { availableBindings, fieldTypes, label, target, updateFieldBinding, value } = props;
16+
const options = Object.entries( availableBindings )
17+
.filter( ( [ _key, mapping ] ) => fieldTypes.includes( mapping.type ) )
18+
.map( ( [ key, mapping ] ) => {
19+
return { label: mapping.name, value: key };
20+
} );
21+
22+
return (
23+
<SelectControl
24+
label={ label }
25+
name={ target }
26+
options={ [ { label: 'Select a field', value: '' }, ...options ] }
27+
onChange={ ( field: string ) => updateFieldBinding( target, field ) }
28+
value={ value }
29+
/>
30+
);
31+
}
32+
33+
interface BlockBindingControlsProps {
34+
attributes: ContextInnerBlockAttributes;
35+
availableBindings: AvailableBindings;
36+
blockName: string;
37+
removeBinding: ( target: string ) => void;
38+
updateBinding: ( target: string, args: Omit< RemoteDataBlockBindingArgs, 'name' > ) => void;
39+
}
40+
41+
export function BlockBindingControls( props: BlockBindingControlsProps ) {
42+
const { attributes, availableBindings, blockName, removeBinding, updateBinding } = props;
43+
const contentArgs = attributes.metadata?.bindings?.content?.args;
44+
const contentField = contentArgs?.field ?? '';
45+
const imageAltField = attributes.metadata?.bindings?.image_alt?.args?.field ?? '';
46+
const imageUrlField = attributes.metadata?.bindings?.image_url?.args?.field ?? '';
47+
48+
function updateFieldBinding( target: string, field: string ): void {
49+
if ( ! field ) {
50+
removeBinding( target );
51+
return;
52+
}
53+
54+
const args = attributes.metadata?.bindings?.[ target ]?.args ?? {};
55+
updateBinding( target, { ...args, field } );
56+
}
57+
58+
function updateFieldLabel( showLabel: boolean ): void {
59+
if ( ! contentField ) {
60+
// Form input should be disabled in this state, but check anyway.
61+
return;
62+
}
63+
64+
const label = showLabel
65+
? Object.entries( availableBindings ).find( ( [ key ] ) => key === contentField )?.[ 1 ]?.name
66+
: undefined;
67+
updateBinding( 'content', { ...contentArgs, field: contentField, label } );
68+
}
69+
70+
switch ( blockName ) {
71+
case 'core/heading':
72+
case 'core/paragraph':
73+
return (
74+
<>
75+
<BlockBindingFieldControl
76+
availableBindings={ availableBindings }
77+
fieldTypes={ TEXT_FIELD_TYPES }
78+
label="Content"
79+
target="content"
80+
updateFieldBinding={ updateFieldBinding }
81+
value={ contentField }
82+
/>
83+
<CheckboxControl
84+
checked={ Boolean( contentArgs?.label ) }
85+
disabled={ ! contentField }
86+
label="Show label"
87+
name="show_label"
88+
onChange={ updateFieldLabel }
89+
/>
90+
</>
91+
);
92+
93+
case 'core/image':
94+
return (
95+
<>
96+
<BlockBindingFieldControl
97+
availableBindings={ availableBindings }
98+
fieldTypes={ [ 'image_url' ] }
99+
label="Image URL"
100+
target="image_url"
101+
updateFieldBinding={ updateFieldBinding }
102+
value={ imageUrlField }
103+
/>
104+
<BlockBindingFieldControl
105+
availableBindings={ availableBindings }
106+
fieldTypes={ [ 'image_alt' ] }
107+
label="Image alt text"
108+
target="image_alt"
109+
updateFieldBinding={ updateFieldBinding }
110+
value={ imageAltField }
111+
/>
112+
</>
113+
);
114+
}
115+
116+
return null;
117+
}

src/blocks/remote-data-container/components/context-controls.tsx

Lines changed: 0 additions & 65 deletions
This file was deleted.

src/blocks/remote-data-container/hooks/with-block-binding.tsx

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { createHigherOrderComponent } from '@wordpress/compose';
55
import { useContext, useMemo } from '@wordpress/element';
66
import { __ } from '@wordpress/i18n';
77

8-
import { ContextControls } from '@/blocks/remote-data-container/components/context-controls';
8+
import { BlockBindingControls } from '@/blocks/remote-data-container/components/block-binding-controls';
99
import { REMOTE_DATA_CONTEXT_KEY } from '@/blocks/remote-data-container/config/constants';
1010
import { LoopIndexContext } from '@/blocks/remote-data-container/context/loop-index-context';
1111
import {
@@ -21,13 +21,12 @@ interface BoundBlockEditProps {
2121
availableBindings: AvailableBindings;
2222
blockName: string;
2323
children: JSX.Element;
24-
loopIndex: number;
25-
remoteData: RemoteData;
24+
remoteDataName: string;
2625
setAttributes: ( attributes: ContextInnerBlockAttributes ) => void;
2726
}
2827

2928
function BoundBlockEdit( props: BoundBlockEditProps ) {
30-
const { attributes, availableBindings, blockName, loopIndex, remoteData, setAttributes } = props;
29+
const { attributes, availableBindings, blockName, remoteDataName, setAttributes } = props;
3130
const existingBindings = attributes.metadata?.bindings ?? {};
3231

3332
function removeBinding( target: string ) {
@@ -41,29 +40,21 @@ function BoundBlockEdit( props: BoundBlockEditProps ) {
4140
} );
4241
}
4342

44-
function updateBinding( target: string, field?: string ) {
45-
// Remove binding if it was unselected.
46-
if ( ! field ) {
47-
removeBinding( target );
48-
return;
49-
}
50-
51-
const fieldValue = remoteData.results?.[ loopIndex ]?.[ field ] ?? '';
43+
function updateBinding( target: string, args: Omit< RemoteDataBlockBindingArgs, 'name' > ) {
5244
setAttributes( {
53-
[ target ]: fieldValue,
5445
metadata: {
5546
...attributes.metadata,
5647
bindings: {
5748
...attributes.metadata?.bindings,
5849
[ target ]: {
5950
source: BLOCK_BINDING_SOURCE,
6051
args: {
61-
blockName,
62-
field,
52+
...args,
53+
name: remoteDataName, // Remote Data Block name
6354
},
6455
},
6556
},
66-
name: availableBindings[ field ]?.name, // Changes block name in list view.
57+
name: availableBindings[ args.field ]?.name, // Changes block name in list view.
6758
},
6859
} );
6960
}
@@ -72,10 +63,11 @@ function BoundBlockEdit( props: BoundBlockEditProps ) {
7263
<>
7364
<InspectorControls>
7465
<PanelBody title={ __( 'Remote Data', 'remote-data-blocks' ) }>
75-
<ContextControls
66+
<BlockBindingControls
7667
attributes={ attributes }
7768
availableBindings={ availableBindings }
7869
blockName={ blockName }
70+
removeBinding={ removeBinding }
7971
updateBinding={ updateBinding }
8072
/>
8173
</PanelBody>
@@ -115,7 +107,7 @@ export const withBlockBinding = createHigherOrderComponent( BlockEdit => {
115107
const patternOverrides = context[ PATTERN_OVERRIDES_CONTEXT_KEY ] as string[] | undefined;
116108
const { index } = useContext( LoopIndexContext );
117109
const isInSyncedPattern = Boolean( patternOverrides );
118-
const hasEnabledOverrides = Object.values( props.attributes.metadata?.bindings ?? {} ).some(
110+
const hasEnabledOverrides = Object.values( attributes.metadata?.bindings ?? {} ).some(
119111
binding => binding.source === PATTERN_OVERRIDES_BINDING_SOURCE
120112
);
121113

@@ -138,8 +130,7 @@ export const withBlockBinding = createHigherOrderComponent( BlockEdit => {
138130
attributes={ mergedAttributes }
139131
availableBindings={ availableBindings }
140132
blockName={ name }
141-
loopIndex={ index }
142-
remoteData={ remoteData }
133+
remoteDataName={ remoteData?.blockName ?? '' }
143134
setAttributes={ setAttributes }
144135
>
145136
<BlockEdit { ...props } attributes={ mergedAttributes } />

src/utils/block-binding.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,25 @@ function getAttributeValue( attributes: unknown, key: string | undefined | null
1414
return attributes[ key ]?.toString() ?? '';
1515
}
1616

17+
function getExpectedAttributeValue(
18+
result?: Record< string, string >,
19+
args?: RemoteDataBlockBindingArgs
20+
): string | null {
21+
if ( ! args?.field || ! result?.[ args.field ] ) {
22+
return null;
23+
}
24+
25+
let expectedValue = result[ args.field ];
26+
if ( args.label ) {
27+
expectedValue = `${ args.label }: ${ expectedValue }`;
28+
}
29+
30+
return expectedValue ?? null;
31+
}
32+
1733
export function getBoundAttributeEntries(
1834
attributes: ContextInnerBlockAttributes
19-
): [ string, ContextBinding ][] {
35+
): [ string, RemoteDataBlockBinding ][] {
2036
return Object.entries( attributes.metadata?.bindings ?? {} ).filter(
2137
( [ _target, binding ] ) => binding.source === BLOCK_BINDING_SOURCE
2238
);
@@ -31,7 +47,7 @@ export function getMismatchedAttributes(
3147
getBoundAttributeEntries( attributes )
3248
.map( ( [ target, binding ] ) => [
3349
target,
34-
results[ index ]?.[ binding.args.field ] ?? null, // null signals a bad binding, ignore
50+
getExpectedAttributeValue( results[ index ], binding.args ),
3551
] )
3652
.filter(
3753
( [ target, value ] ) => null !== value && value !== getAttributeValue( attributes, target )

tests/src/blocks/remote-data-container/hooks/with-block-bindings.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ describe( 'withBlockBinding', () => {
124124
bindings: {
125125
content: {
126126
source: BLOCK_BINDING_SOURCE,
127-
args: { blockName: 'test/block', field: 'title' },
127+
args: { name: 'test/block', field: 'title' },
128128
},
129129
},
130130
},
@@ -164,7 +164,7 @@ describe( 'withBlockBinding', () => {
164164
bindings: {
165165
content: {
166166
source: BLOCK_BINDING_SOURCE,
167-
args: { blockName: 'test/block', field: 'title' },
167+
args: { name: 'test/block', field: 'title' },
168168
},
169169
},
170170
},

0 commit comments

Comments
 (0)