Skip to content

Commit

Permalink
Merge pull request #171 from 10up/feature/reorganize-patterns
Browse files Browse the repository at this point in the history
Feature: Synced Patterns, Overrides, and Block Bindings API pages
  • Loading branch information
fabiankaegy authored Oct 1, 2024
2 parents 6b081d1 + b3b9943 commit afa6178
Show file tree
Hide file tree
Showing 15 changed files with 477 additions and 8 deletions.
2 changes: 1 addition & 1 deletion guides/choose-your-adventure.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Group
You can easily enhance the editor experience by also adding a block pattern for this arrangement of core blocks. This allows editors to quickly and easily create this section whilst leaving them the flexibility to change everything to their liking.

:::caution
As mentioned in the [block pattern reference](../reference/Blocks/block-patterns) patterns have no connection to what created them. So any updates you make to the pattern in the future will not update instances that were created before you updated it.
As mentioned in the [block pattern reference](../reference/Patterns/overview) patterns have no connection to what created them. So any updates you make to the pattern in the future will not update instances that were created before you updated it.
:::

### 2.b Using Core Blocks + a Block Pattern with Restrictions
Expand Down
2 changes: 1 addition & 1 deletion reference/03-Blocks/block-locking.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,4 @@ add_filter(

## Links

- [Locking in Custom Post Types](../04-custom-post-types.md)
- [Locking in Custom Post Types](../05-custom-post-types.md)
258 changes: 258 additions & 0 deletions reference/04-Patterns/block-bindings-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
---
sidebar_position: 4
---

# Block Bindings API

In WordPress 6.6 the [Block Bindings API](https://make.wordpress.org/core/2024/03/06/new-feature-the-block-bindings-api/) was introduced. What this does is allow our blocks to read data from a variety of sources such as custom fields (post meta) or other logic created in php. Under the hood, it is this feature that allows us to override our synced patterns.

Again, it should be noted that only a handful of core blocks allow for this, and custom block support does not currently exist. As of WordPress 6.6, the following blocks are enabled with the supported attributes listed after each one.

| Supported Blocks | Supported Attributes |
| ---------------- | ---------------------|
| Image | url, alt, title |
| Paragraph | content |
| Heading | content |
| Button | url, text, linkTarget, rel |

While the editor does not currently have an interface for applying these features, we can manually apply attributes to our block markup using the Code Editor view. Let's run through an example using our Call to Action block from the previous entries on Synced Patterns.

## How Synced Patterns leverage the API

If we open up the **Edit Original** screen of our Synced Pattern and choose the code editor view **(⇧⌥⌘M
)**, we can get a little more insight into what's happening to make this work.

Let's take a closer look at our Heading block within our pattern and format it a bit for readability.

```html
<!--
wp:heading
{
"metadata": {
"bindings": {
"__default": {
"source": "core/pattern-overrides"
}
},
"name": "Call to Action Title"
},
"fontSize":"x-large"
}
-->
<h2 class="wp-block-heading has-x-large-font-size">Want to get in touch?</h2>
<!-- /wp:heading -->
```

Here we can see our new attribute added to the block for **"binding"** that controls the synchronization. In a Synced Pattern, the binded attribute is set as **"__default"**, which essentially means it's bound to all default supported attributes we initially set in our pattern. In our Heading's case, that would mean the *content* attribute but for a Button it would mean *url*, *text*, *linkTarget*, and *rel*. We can also see it has a source set to **"core/pattern-overrides"**. It is these attributes that we can alter using the Block Bindings API to create our own logic for managing our patterns content.

## Creating our own Bindings

Let's say we are using our pattern on a post type about products where the individual items data is stored in various post meta fields. Using the Block Bindings API, we can set defaults and use those meta fields to dynamically alter our patterns content.

Let's quickly look at the structure of the attributes we will need to use on our blocks. It's very similar to the one used on a Synced Pattern, with a notable difference in using our custom source (which we will create below) and then adding a key within the **args** object which allows for dynamic content.

```json
{
"metadata": {
"bindings": {
"content": {
"source": "tenup/cta-binding",
"args": {
"key": "cta_heading"
}
}
}
}
}
```

Here, we're using a Heading block as an example where we are binding its **content** attribute to our custom source.

:::caution
Much like Synced Pattern Overrides, when using the Block Bindings API it is very important to know that our blocks are not optional, and we cannot display them conditionally.

For example, if we bind our Heading's `content` attribute to post meta, but that post meta does not return a value, an empty `<h2>` tag will display on the frontend. Similarly, we cannot use multiple Buttons in our pattern and have the ability to sometimes display just a single one. Whatever blocks are given bindings, their markup will **always** be returned.

The lack of being able to conditionally include content can also result in orphaned labels. For example, if you were creating an info box for a product, you might have a list of features like so:

**Product:** *Anvil*<br/>
**Price:** *$99.99*<br/>
**Manufacturer:** *Acme Co.*

Typically in php you might be able to first check if a piece of post meta exists before rendering the full list item. Here, we must always return our markup and so if you are not careful, you may end up with orphaned labels such as this:

**Product:** *Anvil*<br/>
**Price:**<br/>
**Manufacturer:** *Acme Co.*

Because of this, when creating your pattern and setting up your custom binding source, it is recommended to always set default values, for example:

**Price:** *n/a*

More on how to do that below.
:::

### 1. Register our Post Meta

First things first, lets register our meta fields. We'll want the name of our product, it's price, and a link to purchase.

```php
add_action( 'init', 'tenup_register_post_meta', 99, 0 );

function tenup_register_post_meta() {

register_post_meta(
'post',
'product_name',
array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
)
);

register_post_meta(
'post',
'product_price',
array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
)
);

register_post_meta(
'post',
'product_url',
array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
)
);
}
```

### 2. Register our own Block Bindings source

As noted above, a Synced Pattern uses the **"core/pattern-overrides"** for it's source. Here we can register our own to use in a similar way. It's a fairly simple function, we just need to give a slug, a label, and then a callback function we can use to dynamically alter our content.

```php
add_action( 'init', 'tenup_register_block_bindings', 99, 0 );

function tenup_register_block_bindings() {

register_block_bindings_source(
'tenup/cta-binding',
array(
'label' => __( 'Call to Action Binding', 'tenup-theme' ),
'get_value_callback' => 'tenup_bindings_callback',
)
);
}
```

### 3. Create the callback function

This is where the fun begins. Take a look at the complete picture and then let's walk through each step below to create dynamic content in our pattern.

```php
function tenup_bindings_callback( $source_args ) {

// Return null if no key is set.
if ( ! isset( $source_args['key'] ) ) {
return null;
}

// Create defaults for our bindings.
$cta_heading = __( 'Want to get in touch?', 'tenup-theme' );
$cta_paragraph = __( 'We would love to hear from you!', 'tenup-theme' );
$cta_button_text = __( 'Contact Us', 'tenup-theme' );
$cta_button_url = '/contact-us';

// Get the data from the post meta.
$product_name = get_post_meta( get_the_ID(), 'product_name', true ) ?? false;
$product_price = get_post_meta( get_the_ID(), 'product_price', true ) ?? false;
$product_url = get_post_meta( get_the_ID(), 'product_url', true ) ?? false;

// If we have all of the data we want, change our default values to use it.
if ( $product_name && $product_price && $product_url ) {
$cta_heading = __( 'Like ', 'tenup-theme' ) . $product_name . __( '?', 'tenup-theme' );
$cta_paragraph = __( 'Visit the link below to buy ', 'tenup-theme' ) . $product_name . __( '.', 'tenup-theme' );
$cta_button_text = __( 'Buy Now ', 'tenup-theme' ) . $product_price;
$cta_button_url = $product_url;
}

// Return the data based on the key argument set in the block attributes.
switch ( $source_args['key'] ) {
case 'cta_heading':
return $cta_heading;
case 'cta_paragraph':
return $cta_paragraph;
case 'cta_button_text':
return $cta_button_text;
case 'cta_button_url':
return $cta_button_url;
default:
return null;
}
}
```

1. First we are looking for our keys. If no key is set telling us how to map our dynamic data, we will return null in our callback. The pattern will then use the defaults set when the pattern is created.
2. Next we create defaults for our keys. These can be the same items set when creating our pattern. Here for example, we will create our pattern with generic messaging leading to a Contact page and set our defaults to the same.
3. We then get our data from our post meta fields. If we have all desired data, we can then remap our bindings to dynmaic text to include our product's info. If any if the data is missing, we have our defaults to fall back on.
4. Finally, we return each key in a switch statement.

All that's it! Similar to the steps outlined in [Synced Patterns](../../reference/Patterns/synced-patterns#creating-synced-patterns-for-theme-authors), we can then create a pattern in our theme and manually set our blocks attributes as outlined above using our custom source's slug and our callback functions keys to sync it in our environments.

Below is the full code from our example Call to Action.

```html
<!-- wp:group {"metadata":{"name":"Call to Action"},"style":{"spacing":{"blockGap":"var:preset|spacing|10","padding":{"top":"var:preset|spacing|20","bottom":"var:preset|spacing|20","left":"var:preset|spacing|20","right":"var:preset|spacing|20"}}},"backgroundColor":"accent-4","layout":{"type":"flex","orientation":"vertical","justifyContent":"center"}} -->
<div class="wp-block-group has-accent-4-background-color has-background" style="padding-top:var(--wp--preset--spacing--20);padding-right:var(--wp--preset--spacing--20);padding-bottom:var(--wp--preset--spacing--20);padding-left:var(--wp--preset--spacing--20)">

<!-- wp:heading {"metadata":{"bindings":{"content":{"source":"tenup/cta-binding","args":{"key":"cta_heading"}}}},"fontSize":"x-large"} -->
<h2 class="wp-block-heading has-x-large-font-size">Want to get in touch?</h2>
<!-- /wp:heading -->

<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"tenup/cta-binding","args":{"key":"cta_paragraph"}}}},"fontSize":"medium"} -->
<p class="has-medium-font-size">We would love to hear from you!</p>
<!-- /wp:paragraph -->

<!-- wp:buttons -->
<div class="wp-block-buttons">

<!-- wp:button {"metadata":{"bindings":{"url":{"source":"tenup/cta-binding","args":{"key":"cta_button_url"}},"text":{"source":"tenup/cta-binding","args":{"key":"cta_button_text"}}}}} -->
<div class="wp-block-button">
<a class="wp-block-button__link wp-element-button" href="/contact-us">Contact Us</a>
</div>
<!-- /wp:button -->

</div>
<!-- /wp:buttons -->

</div>
<!-- /wp:group -->
```

Here we can see that our Call to Action will display a nice fallback should our post meta fields be missing.

![Call to action with default settings applied](../../static/img/block-pattern-call-to-action-example.jpg)
<br/>
*Call to action with default settings applied*

If we have our all of our desired data, we will display our custom content.

![An example of our populated post meta fields](../../static/img/block-pattern-bindings-post-meta-example.jpg)
<br/>
*An example of our populated post meta fields*

![Our dynamic Call to Action populated with our post meta fields](../../static/img/block-pattern-bindings-finished-example.jpg)
<br/>
*Our dynamic Call to Action populated with our post meta fields*

## Further reading

- [Introducing Block Bindings, part 1: connecting custom fields](https://developer.wordpress.org/news/2024/02/20/introducing-block-bindings-part-1-connecting-custom-fields/)
- [Introducing Block Bindings, part 2: Working with custom binding sources](https://developer.wordpress.org/news/2024/03/06/introducing-block-bindings-part-2-working-with-custom-binding-sources/)
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
sidebar_position: 1
sidebar_label: Overview
---

# Block Patterns
Expand Down Expand Up @@ -69,7 +70,7 @@ register_block_pattern(
'description' => _x( 'Two horizontal buttons, the left button is filled in, and the right button is outlined.', 'Block pattern description', 'my-plugin' ),
'content' => "<!-- wp:buttons {\"align\":\"center\"} -->\n<div class=\"wp-block-buttons aligncenter\"><!-- wp:button {\"backgroundColor\":\"very-dark-gray\",\"borderRadius\":0} -->\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link has-background has-very-dark-gray-background-color no-border-radius\">" . esc_html__( 'Button One', 'my-plugin' ) . "</a></div>\n<!-- /wp:button -->\n\n<!-- wp:button {\"textColor\":\"very-dark-gray\",\"borderRadius\":0,\"className\":\"is-style-outline\"} -->\n<div class=\"wp-block-button is-style-outline\"><a class=\"wp-block-button__link has-text-color has-very-dark-gray-color no-border-radius\">" . esc_html__( 'Button Two', 'my-plugin' ) . "</a></div>\n<!-- /wp:button --></div>\n<!-- /wp:buttons -->",
)
);
);
```

To improve the developer experience it makes sense to move the actual `content` out into a separate php file that we include here. This makes it much more maintainable.
Expand Down Expand Up @@ -119,7 +120,7 @@ register_block_pattern(
'description' => _x( 'Two horizontal buttons, the left button is filled in, and the right button is outlined.', 'Block pattern description', 'my-plugin' ),
'content' => get_pattern_content( 'buttons' );
)
);
);
```

</details>
Expand Down Expand Up @@ -277,16 +278,19 @@ This is achieved by adding `"__experimentalRole": "content"` to the attribute de

:::caution
There is one item that you need to be aware about in regards to Block Patterns. Once they are inserted they have no link to the original block pattern that they were created by. On insertion they become regular blocks. Therefore it is not possible to adjust all occurrences of a block pattern after it has been used.

WordPress does provide a way of handling this however by using the power of Synced Patterns. [For more info on Synced Patterns, please see the next lesson](../Patterns/synced-patterns).

:::

If you find an issue with the markup of a pattern that you want to fix it is only going to impact new instances of the pattern that are created after you updated it. And you will have to manually go into every instance that was created using the pattern and make the update manually, or create an update script to update it in the database directly.

If you want to get around this limitation you can of course also build block patterns made up of [custom-blocks](./custom-blocks) that don't actually store their markup in the database. That way you can get the benefits of both worlds.
If you want to get around this limitation you can of course also build block patterns made up of [custom-blocks](../Blocks/custom-blocks) that don't actually store their markup in the database. That way you can get the benefits of both worlds.

## Links

- [New features for working with patterns and themes in WordPress 6.0](https://make.wordpress.org/core/2022/05/02/new-features-for-working-with-patterns-and-themes-in-wordpress-6-0/)
- [Block Locking - 10up Gutenberg Reference](./block-locking.md)
- [Block Locking - 10up Gutenberg Reference](../03-Blocks/block-locking.md)
- [Patterns - Block Editor Handbook](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-patterns/)
- [New features for working with patterns and themes in WordPress 6.0](https://make.wordpress.org/core/2022/05/02/new-features-for-working-with-patterns-and-themes-in-wordpress-6-0/)
- [What are Contextual Patterns in WordPress?](https://wpengine.com/builders/wordpress-contextual-patterns/)
Expand Down
53 changes: 53 additions & 0 deletions reference/04-Patterns/synced-pattern-overrides.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
sidebar_position: 3
---

# Synced Pattern Overrides

Editors now have the capability to maintain a Synced Pattern while being able to edit the content of the block on an individual basis. In other words, we can keep the look and feel of our component in sync across the site, while being able to alter the text, links, or even images used within on a per use basis.

It should be noted that only a handful of core blocks allow for this, and custom block support does not currently exist. As of WordPress 6.6, the following blocks are enabled with the supported attributes listed after each one.

| Supported Blocks | Supported Attributes |
| ---------------- | ---------------------|
| Image | url, alt, title |
| Paragraph | content |
| Heading | content |
| Button | url, text, linkTarget, rel |

In the case of our Call to Action pattern, we can override the Heading, Paragraph, and Button used *within*, but not overide the settings as a whole on the parent Group for example.

:::caution
When using Synced Pattern Overrides, it is very important to know that our blocks are not optional, and we cannot display them conditionally.

For example, we cannot use multiple Buttons in our pattern and have the ability to sometimes display just a single one. Every block used in the original pattern will *always* render its markup, even if we leave its text content empty.

Another example of this limitation is the lack of support on the List block. As currently implemented, you would only ever be able to use the exact same amount of List Item blocks and they would not be optional.

[Follow along on GitHub here to see the progress of inserting additional innerblocks within a `contentOnly` locked pattern](https://github.com/WordPress/gutenberg/issues/52018).
:::

## How to enable Overrides

After enabling a Synced Pattern as outlined in the previous lesson, to allow Overrides you would do the following.

1. Select the pattern in the editor window and choose **Edit Original** in the block toolbar.
2. Select the supported block you wish to enable Overrides on. For now, lets choose the Heading block.
3. In the Advanced panel of the block's setting, choose **Enable Overrides**.
4. Give your Heading a title in the pop up window, we will call ours *"Call to Action Title"*. Then choose **Enable**.
5. Repeat the steps for any other supported blocks you wish to enable and hit **Save** in the upper right.
6. You can return to the page you were editing by using the **Back** button located near the Patterns title in the upper center of the screen.

Upon inserting our pattern into a new post, we can now edit our Heading.

## Settings for Override enabled blocks

When an Override enabled block is selected, after editing a **Reset** control is availble in the blocks toolbar.

When the pattern itself *or* any of its Override enabled innerblocks are selected, the usual block Inspector Control "Settings" panel is replaced by a "Content" panel where we can select the blocks that allow for editing.

![The updated block settings for an override enabled Synced Pattern](../../static/img/block-pattern-synced-override-enabled.jpg)
<br/>
*The updated block settings for an override enabled Synced Pattern*

On the next page, we'll take a look at what's going on under the hood of a Synced Pattern and learn how we can apply our own logic to manage the content of our Patterns using the Block Bindings API.
Loading

0 comments on commit afa6178

Please sign in to comment.