Skip to content

Instantly share code, notes, and snippets.

@gaambo
Last active December 29, 2021 18:11
  • Star 15 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save gaambo/633bcd83a9596762218ffa65d0cfe22a to your computer and use it in GitHub Desktop.
ACF Block with Innerblocks
import { Fragment } from "@wordpress/element";
import { InnerBlocks } from "@wordpress/editor";
/**
* Changes the edit function of an ACF-block to allow InnerBlocks
* Should be called like this on `editor.BlockEdit` hook:
* ` addFilter("editor.BlockEdit", "namespace/block", editWithInnerBlocks("acf/block-name"));`
*
* @param {string} blockName the name of the block to wrap
* @param {object} innerBlockParams params to be passed to the InnerBlocks component (like allowedChildren)
*/
const editWithInnerBlocks = (
blockName,
innerBlockParams,
append = true,
hideBlockEdit = false
) => BlockEdit => props => {
if (props.name !== blockName) {
return <BlockEdit {...props} />;
}
if (append) {
return (
<Fragment>
{!hideBlockEdit && <BlockEdit {...props} />}
<InnerBlocks {...innerBlockParams} />
</Fragment>
);
}
// put before block edit
return (
<Fragment>
<InnerBlocks {...innerBlockParams} />
{!hideBlockEdit && <BlockEdit {...props} />}
</Fragment>
);
};
/**
* Changes the save function of an ACF-block to allow InnerBlocks
* Should be called like this on `blocks.getSaveElement` hook:
* `addFilter("blocks.getSaveElement", "namespace/block", saveWithInnerBlocks("acf/block-name"));`
*
* @param {string} blockName the name of the block to wrap
*/
const saveWithInnerBlocks = blockName => (BlockSave, block) => {
if (typeof block === "undefined") {
return BlockSave;
}
if (block.name !== blockName) {
return BlockSave || block.save;
}
return (
<div>
<InnerBlocks.Content />
</div>
);
};
export { editWithInnerBlocks, saveWithInnerBlocks };
@terence1990
Copy link

terence1990 commented Feb 19, 2020

Hey guys, this I is a standardisation I am using to move Inner Blocks in the editor:

import editWithInnerBlocks from './editWithInnerBlocks'
import saveWithInnerBlocks from './saveWithInnerBlocks'
import moveInnerBlocks from './moveInnerBlocks';

const { addFilter } = wp.hooks

const blocks = acf.data.blockTypes.filter(block => block.has_inner_blocks) // this property explained further down

blocks.forEach(block => {
	addFilter("editor.BlockEdit", `with-inner-blocks/${block.name}`, editWithInnerBlocks(block.name))
	addFilter("blocks.getSaveElement",  `with-inner-blocks/${block.name}`, saveWithInnerBlocks(block.name))
	acf.addAction( `render_block_preview/type=${block.name.replace('acf/', '')}`, (preview) => moveInnerBlocks(preview, block) )
})
// moveInnerBlocks
export default ($preview, block) => {
	
	const preview = $preview[0]	
	const target = preview.querySelector('.js-inner-blocks') // this className explained further down
	
	if( target ) {
		
		// check cached innerBlocks first otherwise we lose them every time we make change to ACF field for the block
		if( block.innerBlocks ) {
			
			target.appendChild(block.innerBlocks)
			
		} else {
			
			const innerBlocks = preview.closest('.wp-block').querySelector('.editor-inner-blocks')
			
			// cache the innerBlocks for later otherwise we lose them every time we make change to ACF field for the block
			block.innerBlocks = innerBlocks 
			
			target.appendChild(innerBlocks)
			
		}
		
	}
	
}

These are my args for these kind of Blocks:

[
    'name' => 'example-block',
    'title' => 'Example Block',
    'category' => 'wp-kit-example-blocks',
    'icon' => 'welcome-widgets-menus',
    'description' => 'An example block',
    'has_inner_blocks' => true,
    'render_callback' => function($block, $inner_blocks) {	
		include(locate_template('views/example.block.php'));
	}
]

By having has_inner_blocks set to true the iteration is handled in Javascript above

Here's my HTML for the block:

// views/example.block.php
<div class="example">
	
	<h1>Hello <?php the_field('text'); ?>!</h1>
	
	<div class="js-inner-blocks">
	
		<?= $inner_blocks; ?>
		
	</div>
	
</div>

I always have a node with js-inner-blocks className wrapping where I want my inner blocks so Javascript above can target it in block editor. For the frontend the $inner_blocks is coming in from second argument of render_callback. Everything looks nice in the backend and the frontend.

I think these kind of standardisation could be worked into ACF directly, the code is not too opinionated.

@gaambo
Copy link
Author

gaambo commented Feb 20, 2020

@terence1990 that looks great. I also thought about having a supports flag innerBlocks and den do everything automatically (also the supported innerblocks etc.). Thanks for your snippet :)

@CreativeDive
Copy link

@terence1990 really cool and thank you for your work. It works like a charm :-)

Please note, since the latest Gutenberg version the inner blocks container selector was changed from .editor-inner-blocks to .block-editor-inner-blocks.

@CreativeDive
Copy link

@gaambo and @terence1990 any idea how we can solve it with multiple inner blocks like different columns inside an ACF block and each column can include different inner blocks? :-)

@CreativeDive
Copy link

CreativeDive commented Mar 30, 2020

@gaambo and @terence1990 an other issue is, if you use the same block multiple times the selector class "js-inner-blocks" works only for on block, but not for multiple blocks. The selector class needs a unique identifier e.g. the ACF block id, but I don't know how I can get the ACF block id inside the react code. Is there a filter of ACF which provides the block id?

@gaambo
Copy link
Author

gaambo commented Apr 1, 2020

Thanks for your inputs - I'll have to test & play with WordPress 5.4 in the following days and hopefully I can come up with an solution or at least an idea - I'll let you know :)

@gaambo
Copy link
Author

gaambo commented Apr 1, 2020

@CreativeDive Regarding multiple inner blocks: AFAIK there's still no way to include multiple innerBlocks in a block (even via React) - so we'd have to gez creative here and a solution should be future-compatible.
Right now the only thing that comes into my mind is building multiple blocks:

  • "Container"/"Wrapper" block which allows only the following "Slots" block
  • "Slots" block which can only be inserted to certain parents and only allows single blocks
  • Single block which can only be inserted in the slots block.

Depending on the use case that's not really easiert then using the core group + columbs blocks.

For accordions I solved it like this:
Accordion-ACF-Block which only allows Accordion-Iten Blocks as innerBlocks.

@gaambo
Copy link
Author

gaambo commented May 29, 2020

@CreativeDive
Copy link

@gaambo: Very exciting ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment