I've got a WP Block with a <ServerSideRender />
component in the backend. It works fine with the attributes. But it does not return the $content
used in the php callback for the frontend.
<ServerSideRender
block="mydomain/my-block"
attributes={ attributes }
/>
And I can't find in the documentation if there is a way to dynamicly render the block on the backend while using the $content.
The $content
is a nested block, so the save()
is used like:
save() {
return <InnerBlocks.Content />;
},
Does anyone know how to accomplice this?
--EDIT--
I've added my current edit()
code. Please note that not all is relevant.
The focus lays on:
- the use of
InnerBlocks
- the use of a preview button. one state is to edit the content and one state it to render a preview with the php callback.
- the use of
ServerSideRender
/**
* WordPress dependencies
*/
const { __ } = wp.i18n;
const {
InnerBlocks,
InspectorControls,
MediaUpload,
MediaUploadCheck,
BlockControls,
} = wp.blockEditor || wp.editor; // Fallback to 'wp.editor' for backwards compatibility
const {
PanelBody,
RangeControl,
Button,
SelectControl,
ToolbarGroup,
ToolbarButton,
ServerSideRender,
} = wp.components;
const { Component, Fragment } = wp.element;
const { withSelect, select } = wp.data;
const { compose } = wp.compose;
class BlockImageOverlayEdit extends Component {
render() {
const {
clientId,
attributes,
className,
setAttributes,
hasChildBlocks,
} = this.props;
const {
id,
preview,
editorSize,
bgColor,
mediaId,
mediaUrl,
centerContent,
mediaSpanCols,
mediaSpanOffset,
overlaySpanCols,
overlaySpanOffset,
alignX,
alignY,
} = attributes;
if ( id !== clientId ) {
setAttributes( { id: clientId } );
}
const colMaxAmount = 12;
const sizes = [ 'xs', 'sm', 'md', 'lg', 'xl', 'xxl' ];
const removeMedia = () => {
this.props.setAttributes( {
mediaId: 0,
mediaUrl: '',
} );
};
const onSelectMedia = ( media ) => {
setAttributes( {
mediaId: media.id,
mediaUrl: media.url,
} );
};
const onChangeColumns = ( value ) => {
const newMediaSpanOffset = (value + mediaSpanOffset) > colMaxAmount ? colMaxAmount - value : mediaSpanOffset;
setAttributes(
{
mediaSpanCols: value,
mediaSpanOffset: newMediaSpanOffset,
} );
};
const onChangeColumnsOverlay = ( value ) => {
const newOverlaySpanOffset = (value + overlaySpanOffset) > colMaxAmount ? colMaxAmount - value : overlaySpanOffset;
setAttributes(
{
overlaySpanCols: value,
overlaySpanOffset: newOverlaySpanOffset,
} );
};
const MyServerSideRender = () => {
// thought. maybe I can fetch, and prerender the inner blocks here, serialize them and set them as an attribute. Right before this return. (and at save() set it back to null)
return (
<ServerSideRender
block="mydomain/block-wp-block-image-overlay-01"
attributes={ attributes }
/>
);
};
const PreviewButton = () => (
<BlockControls>
<ToolbarGroup>
<ToolbarButton
icon={ preview ? 'hidden' : 'visibility' }
label="Preview"
onClick={ () => setAttributes( { preview: ! preview } ) }
/>
</ToolbarGroup>
</BlockControls>
);
const BreakpointButtons = () => (
<ToolbarGroup>
{ sizes.map( ( size, index ) => {
return (
<ToolbarButton
key={ index }
style={ { padding: 0.5 + 'em' } } //TODO: fix spacing
isPrimary={ editorSize === size }
isSecondary={ editorSize !== size }
// icon={ edit }
label="Edit"
onClick={ () => setAttributes( { editorSize: size } ) }
>{ size }</ToolbarButton>
);
} ) }
</ToolbarGroup>
);
const AlignPanel = () => (
<PanelBody
title={ __( 'Column size', 'mydomain' ) }
initialOpen={ false }
>
<SelectControl
label={ __( 'Horizontal align', 'mydomain' ) }
value={ alignX }
options={ [
{ value: 'start', label: __( 'Image on the right', 'mydomain' ) },
{ value: 'end', label: __( 'Image on the left', 'mydomain' ) },
] }
onChange={ ( value ) => setAttributes( { alignX: value } ) }
/>
<SelectControl
label={ __( 'Vertical align', 'mydomain' ) }
value={ alignY }
options={ [
{ value: 'start', label: __( 'Image on the top', 'mydomain' ) },
{ value: 'end', label: __( 'Image on the bottom', 'mydomain' ) },
] }
onChange={ ( value ) => setAttributes( { alignY: value } ) }
/>
</PanelBody>
);
const ColumnPanel = () => (
<PanelBody
title={ __( 'Column size', 'mydomain' ) }
initialOpen={ false }
>
<BreakpointButtons/>
<RangeControl
label={ __( 'Image columns', 'mydomain' ) }
value={ mediaSpanCols }
onChange={ onChangeColumns }
min={ 1 }
max={ colMaxAmount }
/>
<RangeControl
label={ __( 'Overlay columns', 'mydomain' ) }
value={ overlaySpanCols }
onChange={ onChangeColumnsOverlay }
min={ 1 }
max={ colMaxAmount }
/>
</PanelBody>
);
const Media = () => (
<Fragment>
<MediaUploadCheck>
<MediaUpload
onSelect={ onSelectMedia }
value={ mediaId }
allowedTypes={ [ 'image' ] }
render={ ( { open } ) => (
<Button
className={ mediaId === 0 ? 'editor-post-featured-image__toggle' : 'editor-post-featured-image__preview' }
onClick={ open }
>
{ mediaId === 0 && __( 'Choose an image', 'mydomain' ) }
{ mediaUrl !== undefined &&
<img alt={ '' } src={ mediaUrl }/>
}
</Button>
) }
/>
</MediaUploadCheck>
{
mediaId !== 0 &&
<MediaUploadCheck>
<MediaUpload
title={ __( 'Replace image', 'mydomain' ) }
value={ mediaId }
onSelect={ onSelectMedia }
allowedTypes={ [ 'image' ] }
render={ ( { open } ) => (
<Button
onClick={ open }
isDefault
isLarge>{ __( 'Replace image', 'mydomain' ) }
</Button>
) }
/>
</MediaUploadCheck>
}
{
mediaId !== 0 &&
<MediaUploadCheck>
<Button
onClick={ removeMedia }
isLink
isDestructive>{ __( 'Remove image', 'mydomain' ) }
</Button>
</MediaUploadCheck>
}
</Fragment>
);
const Overlay = () => (
<InnerBlocks
templateLock={ false }
renderAppender={
hasChildBlocks ?
undefined :
() => <InnerBlocks.ButtonBlockAppender/>
}
/>
)
return (
<Fragment>
<InspectorControls>
<AlignPanel/>
<ColumnPanel/>
</InspectorControls>
<PreviewButton/>
{ ! preview &&
<div className={ `${ className }` } style={ { display: 'flex' } }>
<div style={ { width: 50 + '%' } }>
<Media />
</div>
<div style={ { width: 50 + '%' } }>
<Overlay />
</div>
</div> }
{ preview &&
<MyServerSideRender /> }
</Fragment>
);
}
}
export default compose(
withSelect
( (
select
,
ownProps
) => {
const {
clientId
}
= ownProps;
const { getBlockOrder } =
select( 'core/block-editor' ) || select( 'core/editor' ); // Fallback to 'core/editor' for backwards compatibility
return {
hasChildBlocks: getBlockOrder( clientId ).length > 0,
};
} )
)
( BlockImageOverlayEdit );
ServerSideRender
uses the Block Renderer API endpoint which does not parse inner blocks, hence the$content
would be empty. But why are you usingServerSideRender
? Can we see youredit()
function?edit()
as a string/html ? Than I could set it as a block attribute which can be send to theServerSideRender
and use it there. Afterwards I could reset the attribute to empty atsave()
. Maybe a hacky workaround : ) Yes, in theory I could do all inedit()
But I now have two columns in the edit, one for an image and one for innerblocks. They calculate an overlay based on 5 attibutes and breakpoints. It would seem overkill to programme it twice. (frontend/backend)