Skip to content

Commit

Permalink
Add "Show label" control (#22)
Browse files Browse the repository at this point in the history
* Rename ContextControls to BlockBindingControls

* Add "Show label" control
  • Loading branch information
chriszarate authored Aug 30, 2024
1 parent f6133bd commit c20f178
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 112 deletions.
13 changes: 12 additions & 1 deletion inc/editor/block-bindings/block-bindings.php
Original file line number Diff line number Diff line change
Expand Up @@ -214,14 +214,25 @@ public static function get_remote_value( array $block_context, array $source_arg
$field_name = $source_args['field'];
$index = $source_args['index'] ?? 0; // Index is only set for loop queries.

if ( isset( $source_args['name'] ) && $source_args['name'] !== $block_name ) {
self::log_error( 'Block binding belongs to a different remote data block', $block_name, $field_name );
return null;
}

$query_results = self::execute_query( $block_context, $field_name );

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

return $query_results['results'][ $index ]['result'][ $field_name ]['value'];
// Prepend label to value if provided.
$value = $query_results['results'][ $index ]['result'][ $field_name ]['value'];
if ( ! empty( $source_args['label'] ?? '' ) ) {
return sprintf( '%s: %s', $source_args['label'], $value );
}

return $value;
}

public static function loop_block_render_callback( array $attributes, string $content, WP_Block $block ): string {
Expand Down
117 changes: 117 additions & 0 deletions src/blocks/remote-data-container/components/block-binding-controls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { CheckboxControl, SelectControl } from '@wordpress/components';

import { TEXT_FIELD_TYPES } from '@/blocks/remote-data-container/config/constants';

interface BlockBindingFieldControlProps {
availableBindings: AvailableBindings;
fieldTypes: string[];
label: string;
target: string;
updateFieldBinding: ( target: string, field: string ) => void;
value: string;
}

export function BlockBindingFieldControl( props: BlockBindingFieldControlProps ) {
const { availableBindings, fieldTypes, label, target, updateFieldBinding, value } = props;
const options = Object.entries( availableBindings )
.filter( ( [ _key, mapping ] ) => fieldTypes.includes( mapping.type ) )
.map( ( [ key, mapping ] ) => {
return { label: mapping.name, value: key };
} );

return (
<SelectControl
label={ label }
name={ target }
options={ [ { label: 'Select a field', value: '' }, ...options ] }
onChange={ ( field: string ) => updateFieldBinding( target, field ) }
value={ value }
/>
);
}

interface BlockBindingControlsProps {
attributes: ContextInnerBlockAttributes;
availableBindings: AvailableBindings;
blockName: string;
removeBinding: ( target: string ) => void;
updateBinding: ( target: string, args: Omit< RemoteDataBlockBindingArgs, 'name' > ) => void;
}

export function BlockBindingControls( props: BlockBindingControlsProps ) {
const { attributes, availableBindings, blockName, removeBinding, updateBinding } = props;
const contentArgs = attributes.metadata?.bindings?.content?.args;
const contentField = contentArgs?.field ?? '';
const imageAltField = attributes.metadata?.bindings?.image_alt?.args?.field ?? '';
const imageUrlField = attributes.metadata?.bindings?.image_url?.args?.field ?? '';

function updateFieldBinding( target: string, field: string ): void {
if ( ! field ) {
removeBinding( target );
return;
}

const args = attributes.metadata?.bindings?.[ target ]?.args ?? {};
updateBinding( target, { ...args, field } );
}

function updateFieldLabel( showLabel: boolean ): void {
if ( ! contentField ) {
// Form input should be disabled in this state, but check anyway.
return;
}

const label = showLabel
? Object.entries( availableBindings ).find( ( [ key ] ) => key === contentField )?.[ 1 ]?.name
: undefined;
updateBinding( 'content', { ...contentArgs, field: contentField, label } );
}

switch ( blockName ) {
case 'core/heading':
case 'core/paragraph':
return (
<>
<BlockBindingFieldControl
availableBindings={ availableBindings }
fieldTypes={ TEXT_FIELD_TYPES }
label="Content"
target="content"
updateFieldBinding={ updateFieldBinding }
value={ contentField }
/>
<CheckboxControl
checked={ Boolean( contentArgs?.label ) }
disabled={ ! contentField }
label="Show label"
name="show_label"
onChange={ updateFieldLabel }
/>
</>
);

case 'core/image':
return (
<>
<BlockBindingFieldControl
availableBindings={ availableBindings }
fieldTypes={ [ 'image_url' ] }
label="Image URL"
target="image_url"
updateFieldBinding={ updateFieldBinding }
value={ imageUrlField }
/>
<BlockBindingFieldControl
availableBindings={ availableBindings }
fieldTypes={ [ 'image_alt' ] }
label="Image alt text"
target="image_alt"
updateFieldBinding={ updateFieldBinding }
value={ imageAltField }
/>
</>
);
}

return null;
}
65 changes: 0 additions & 65 deletions src/blocks/remote-data-container/components/context-controls.tsx

This file was deleted.

31 changes: 11 additions & 20 deletions src/blocks/remote-data-container/hooks/with-block-binding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { createHigherOrderComponent } from '@wordpress/compose';
import { useContext, useMemo } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

import { ContextControls } from '@/blocks/remote-data-container/components/context-controls';
import { BlockBindingControls } from '@/blocks/remote-data-container/components/block-binding-controls';
import { REMOTE_DATA_CONTEXT_KEY } from '@/blocks/remote-data-container/config/constants';
import { LoopIndexContext } from '@/blocks/remote-data-container/context/loop-index-context';
import {
Expand All @@ -21,13 +21,12 @@ interface BoundBlockEditProps {
availableBindings: AvailableBindings;
blockName: string;
children: JSX.Element;
loopIndex: number;
remoteData: RemoteData;
remoteDataName: string;
setAttributes: ( attributes: ContextInnerBlockAttributes ) => void;
}

function BoundBlockEdit( props: BoundBlockEditProps ) {
const { attributes, availableBindings, blockName, loopIndex, remoteData, setAttributes } = props;
const { attributes, availableBindings, blockName, remoteDataName, setAttributes } = props;
const existingBindings = attributes.metadata?.bindings ?? {};

function removeBinding( target: string ) {
Expand All @@ -41,29 +40,21 @@ function BoundBlockEdit( props: BoundBlockEditProps ) {
} );
}

function updateBinding( target: string, field?: string ) {
// Remove binding if it was unselected.
if ( ! field ) {
removeBinding( target );
return;
}

const fieldValue = remoteData.results?.[ loopIndex ]?.[ field ] ?? '';
function updateBinding( target: string, args: Omit< RemoteDataBlockBindingArgs, 'name' > ) {
setAttributes( {
[ target ]: fieldValue,
metadata: {
...attributes.metadata,
bindings: {
...attributes.metadata?.bindings,
[ target ]: {
source: BLOCK_BINDING_SOURCE,
args: {
blockName,
field,
...args,
name: remoteDataName, // Remote Data Block name
},
},
},
name: availableBindings[ field ]?.name, // Changes block name in list view.
name: availableBindings[ args.field ]?.name, // Changes block name in list view.
},
} );
}
Expand All @@ -72,10 +63,11 @@ function BoundBlockEdit( props: BoundBlockEditProps ) {
<>
<InspectorControls>
<PanelBody title={ __( 'Remote Data', 'remote-data-blocks' ) }>
<ContextControls
<BlockBindingControls
attributes={ attributes }
availableBindings={ availableBindings }
blockName={ blockName }
removeBinding={ removeBinding }
updateBinding={ updateBinding }
/>
</PanelBody>
Expand Down Expand Up @@ -115,7 +107,7 @@ export const withBlockBinding = createHigherOrderComponent( BlockEdit => {
const patternOverrides = context[ PATTERN_OVERRIDES_CONTEXT_KEY ] as string[] | undefined;
const { index } = useContext( LoopIndexContext );
const isInSyncedPattern = Boolean( patternOverrides );
const hasEnabledOverrides = Object.values( props.attributes.metadata?.bindings ?? {} ).some(
const hasEnabledOverrides = Object.values( attributes.metadata?.bindings ?? {} ).some(
binding => binding.source === PATTERN_OVERRIDES_BINDING_SOURCE
);

Expand All @@ -138,8 +130,7 @@ export const withBlockBinding = createHigherOrderComponent( BlockEdit => {
attributes={ mergedAttributes }
availableBindings={ availableBindings }
blockName={ name }
loopIndex={ index }
remoteData={ remoteData }
remoteDataName={ remoteData?.blockName ?? '' }
setAttributes={ setAttributes }
>
<BlockEdit { ...props } attributes={ mergedAttributes } />
Expand Down
20 changes: 18 additions & 2 deletions src/utils/block-binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,25 @@ function getAttributeValue( attributes: unknown, key: string | undefined | null
return attributes[ key ]?.toString() ?? '';
}

function getExpectedAttributeValue(
result?: Record< string, string >,
args?: RemoteDataBlockBindingArgs
): string | null {
if ( ! args?.field || ! result?.[ args.field ] ) {
return null;
}

let expectedValue = result[ args.field ];
if ( args.label ) {
expectedValue = `${ args.label }: ${ expectedValue }`;
}

return expectedValue ?? null;
}

export function getBoundAttributeEntries(
attributes: ContextInnerBlockAttributes
): [ string, ContextBinding ][] {
): [ string, RemoteDataBlockBinding ][] {
return Object.entries( attributes.metadata?.bindings ?? {} ).filter(
( [ _target, binding ] ) => binding.source === BLOCK_BINDING_SOURCE
);
Expand All @@ -31,7 +47,7 @@ export function getMismatchedAttributes(
getBoundAttributeEntries( attributes )
.map( ( [ target, binding ] ) => [
target,
results[ index ]?.[ binding.args.field ] ?? null, // null signals a bad binding, ignore
getExpectedAttributeValue( results[ index ], binding.args ),
] )
.filter(
( [ target, value ] ) => null !== value && value !== getAttributeValue( attributes, target )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ describe( 'withBlockBinding', () => {
bindings: {
content: {
source: BLOCK_BINDING_SOURCE,
args: { blockName: 'test/block', field: 'title' },
args: { name: 'test/block', field: 'title' },
},
},
},
Expand Down Expand Up @@ -164,7 +164,7 @@ describe( 'withBlockBinding', () => {
bindings: {
content: {
source: BLOCK_BINDING_SOURCE,
args: { blockName: 'test/block', field: 'title' },
args: { name: 'test/block', field: 'title' },
},
},
},
Expand Down
Loading

0 comments on commit c20f178

Please sign in to comment.