Skip to content

Instantly share code, notes, and snippets.

@folbert
Last active June 17, 2022 21:33
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save folbert/b04834fb98707e3901390bd4b4bcb07b to your computer and use it in GitHub Desktop.
Save folbert/b04834fb98707e3901390bd4b4bcb07b to your computer and use it in GitHub Desktop.

This is an attempt to describe how to pass data to Gutenberg InnerBlocks when using ACF and ACF Composer. I can not share complete code examples ATM since due to reasons that I can not affect.

We will be using Gutenbergs Context. It is not 100% straight forward or easy to explain but in short, a block can provide context to its inner blocks and use context that is passed down to it.

Disclaimer: I am writing this while focusing on other things so please forgive (and tell) me if anything is unclear. It probably helps if you have some deeper understanding of how ACF and ACF Composer works.

In order to do this you need to use ACF 6 which is in alpha 2 at the time of writing this.

ACF composer needs to be updated with the changes in this commit. Please note that the changes should also be considered alpha since I have only tested it on my setup but I can not see why it shouldn't work for everyone. Feel free to refer to my repo in your Composer file. I will maybe add a pull request with this to Log1x but since it is a breaking change, it wil probably require to be released as a new major version.

I am not sure if you need to tell ACF that it should use api_version 2 and acf_block_version 2 (read about it in the GH-issue linked above). But that is what I have done since i needed some of the other things that those values provides. If you want/need to do it too, it can be done by putting this in for example functions.php (code is simplified, I do not use anonymous functions in my real implementation):

add_filter('acf/register_block_type_args', function($args) {

  // New ACF args added in ACF 6.0.0-alpha1
  // @link https://github.com/AdvancedCustomFields/acf/issues/654
  $args['api_version'] = 2;
  $args['acf_block_version'] = 2;
  
  // Explained in the next section
  return apply_filters('myprojectnamespace/acf/register_block_type_args/block=' . $args['name'], $args);

});

I then have an abstract class that extends \Log1x\AcfComposer\Block and which all of my block classes extends. In the constructor of that class i have

add_filter('myprojectnamespace/acf/register_block_type_args/block=' . $this->get_namespace(), [$this, 'apply_block_type_args'])

This will make sure that each block class' apply_block_type_args-function is triggered only when an instance of that specific block is created. $this->get_namespace() returns the same namespace that \Log1x\AcfComposer\Block computes in compose() but since AcfComposer does it "too late" and keeps the value isolated, I have added a function in the abstract block class to be able to do it whenever needed:

public function get_namespace(): string
{

	if (empty($this->namespace)) {
		$this->namespace = Str::start($this->slug, $this->prefix);
	}

	return $this->namespace;

}

I have the rest of my setup a bit more automated and different than described below but let's keep this guide as simple as possible.

Each block class can have a function which could look something like this:

public function apply_block_type_args($args)
{

	// This is the data that should be passed down to inner blocks
	// The key is the name that the data will be sent down to the inner blocks as and the
	// value is the name of the block attribute that holds the value (more on this later)
	$args['provides_context'] = [
		'column_width' => 'column_width_attribute_name', // This will require more code as described later on.
		'block_name' => 'name', // "name" is already a top level attribute in the array that makes up a block so no further code needed for this
	];

	// This is data that the block expects from ancestors
	// Each item in the array is the context name from an ancestors 'provide_context'.
	$args['uses_context] = [
		'block_name',
	];

	return $args;

}

The last step is to add the attributes that a block provides and which holds the data. The attributes need to be at the top level in the array that makes up the block. For example, if you want to use data from ACF fields as context, you need to copy the values from the block array value 'data' to the top of the array. So the structure of the array will be

[
  'name' => 'Block name',
  ...
  'data' => [
    ...
  ],
  'custom_attribute_1_for_context' => 'Value',
  'custom_attribute_2_for_context' => 'Another value',
]

Once again, a simplified version of my code that goes in for example functions.php.

add_filter('acf/pre_save_block', function($attrs) {
  return apply_filters('myprojectnamespace/acf/custom_attributes/block=' . $attrs['name'], $attrs);
});

In the abstract block class constructor:

add_filter('myprojectnamespace/acf/custom_attributes/block=' . $this->get_namespace(), [$this, 'apply_block_attributes']);

And in the block class we have a function that sets the attributes that holds the data that the block provides in its context.

public function apply_block_attributes($attributes)
{

  // The value can of course be hard coded if needed but get_field() should work as well.
  // Or you could fetch the value from $attributes.
  $attributes['column_width_attribute_name'] = get_field('column_width');

  return $attributes;

}

If everything is correctly setup, block objects will now have the property $context which is an array that will hold values with the keys you specified in "uses_context" in apply_block_type_args().

This can maybe be done more efficient but it fits my workflow and how the rest of my ACF Blocks are set up with for example a dedicated model for each block that handles sending data to the Blade file.

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