Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add block bindings API basis and metadata source #56867

Closed

Conversation

SantosGuillamot
Copy link
Contributor

@SantosGuillamot SantosGuillamot commented Dec 7, 2023

What?

Related issues:

In this pull request, I'm creating a first prototype of the Block Bindings API and the metadata source. Mainly covering:

  • Create the basis of the block bindings API and register new sources.
  • Based on the block bindings API, create the metadata source, used for accessing custom fields.
  • Adapt the current experiment for partially synced patterns to use the block bindings API basis.

I believe we can start with this prototype, after polishing and reviewing it, as an experiment so users can test it if wanted, and work on the following changes/features in other pull requests on top of it.

Why?

I prepared this demo to showcase what I tried to achieve and how the current prototype works:

Block.bindings.demo.mp4

In the video demo, after creating a "Movies" CPT and adding some custom fields like "poster URL", "release date", "runtime"..., I cover basically the UX:

  • What we want to achieve:
    • be able to create a movie template, using custom fields to show a different image, release date, runtime, etc, per movie page.
    • be able to create a movie query loop that shows a different image, release data, runtime, etc for each movie in the loop.
  • A "bindings" button added to the paragraph, image, heading, and button blocks that when clicked, shows the block attributes that users can connect. You can use that to add the bindings property to the metadata attribute.
  • Show how it works for the movie template, although there is an issue with the current implementation because we don't have a movie id there. It works fine in the frontend.
  • Show how it works with the movie query loop, although there is an issue because in the editor it shows the same content for all the movies. Although it works fine in the frontend.
  • Show an example of how we could group the fields in groups like post meta, post data, site data, ACF...

How?

I prepared this video to explain how I did it:

https://www.loom.com/share/24ed54eaf5844db590fbe0a7d9ee324c

Video explanation

First, I'd like to recap how the block bindings API works. It basically:

  1. Add the "bindings" property to the block attributes in the editor.
  2. In the server, read the "bindings" property and get the value from the source specified there.
  3. Replace the HTML with the value obtained.

1 . Add the "bindings" property to the block attributes in the editor.

This is probably the most challenging part and where more issues remain. What I did so far is:

  • Using the blocks.registerBlockType filter, add a "bindings" button to the blocks included in the whitelist and add the context needed for the metadata source.
  • Create a updateBlockBindingsAttribute util that can be used by the different source to update the "bindings" property.
  • Add the UI for the Block Bindings logic, that includes the button shown in the demo and the list of attributes available.
  • Add the UI for the metadata source, currently hardcoded in the Block Bindings component. In that part, using getEntityRecord, I'm fetching the available fields and showing them in the Block Bindings popover.
  • I modified the partially synced patterns logic to adapt to the new syntax and use the helper created.

2 . In the server, read the "bindings" property and get the value from the source specified there.

As part of this:

  • I created a mechanism so sources can be created. Each source must provide a name/id and a callback function that receives the source params, and $block, $block_content, and $block_instance in case they need that. For example, the "metadata" source will call the get_metadata function, while a hypothetical "shortcode" source would use do_shortcode.
  • Using the render_block_ filter, whenever a block has a bindings property, it processes the object to get the value from the proper source and replace the proper HTML (next step).
  • Removed the old implementation.

3 . Replace the HTML with the value obtained.

I created a first version of a function named block_bindings_replace_html to modify the HTML with the value obtained from the source. Depending on the block attribute source, the HTML to replace might be different. For example, for the paragraph content, with source "html", we want to replace the inner HTML. While with the image URL, with source "attribute" and "src", we want to replace the src attribute.

List of remaining decision/issues/features after this PR

After working on this property, it raised some decisions we have to take, some issues we have to solve, and some functionalities we might consider. This is a non-exhaustive list of what I believe could be the next steps.

As I mentioned, I believe we could work on them after merging this PR, and make changes on top of it.

I'll try to open new issues for the most relevant ones in the meantime I created this video explaining them

List.of.decisions_fixes.mp4
  • Decide the format of the bindings property.
  • How should the Block Bindings sources extend the editor?
  • Make the function to register new sources private for this initial version.
  • Make it work with the Query Loop in the editor.
  • Make it work with the templates in the editor.
  • Ensure it works with taxonomies.
  • Fix checkbox in partially synced patterns.
  • Refresh bindings when loading the editor in case the custom field value has changed.
  • Modify the HTML replacement for buttons and headings.
  • Change experimental flag naming and description.
  • Improve the HTML processing.
    • Make it work with CSS selectors.
    • Add some validation.
    • Explore the possibility of creating a private version of set_inner_markup until that's ready.
  • Improve the metadata UI.
    • Add UI to the sidebars.
    • Make content not editable when a binding is created until we are able to edit the value of the custom fields.
    • More questions remaining.
  • Fix Eslint issues.
  • Should we include all the data (post meta, post data, site data, ACF...) in the same "metadata" source? Or should each one be a different source?
  • How should validation work? So far, it is only working with text.
  • Is it okay to replace the original attribute in the editor or should we just make the editor understand the bindings property included in blocks?
  • Are we using the correct filters in PHP and the editor? Should we change the APIs or the packages used?
  • Add more checks to ensure there are no errors.
  • Mechanism to opt-in for the block bindings API.
  • Add support for more core blocks.
  • Add tests and docs.

Testing Instructions

  1. Go to Gutenberg -> Experiments and enable the Connections.
  2. Add some custom fields to your posts/pages/custom post type.
  3. Edit a paragraph, image, heading, or button to connect to a custom field using the button in the toolbar.
  4. Check if it works as expected in the frontend.

@gziolo gziolo added [Type] Technical Prototype Offers a technical exploration into an idea as an example of what's possible [Feature] Block API API that allows to express the block paradigm. [Type] New API New API to be used by plugin developers or package users. labels Dec 7, 2023
Comment on lines 39 to 45
$p2 = new WP_HTML_Tag_Processor( $markup );
$p2->next_tag();
$names = $p->get_attribute_names_with_prefix( '' );
foreach ( $names as $name ) {
$p2->set_attribute( $name, $p->get_attribute( $name ) );
}
return $p2->get_updated_html();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mentioned in #56704 (comment) that this unfortunately disregards any markup before or after the found tag, so it won't work for button which is a <button> inside a <div> wrapper. It strips the <div> wrapper.

I realize we need $p->set_inner_html to support it properly, but in the meantime it might be worth removing button block support.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're totally right. I just made a commit that tries to make it work with heading and button structures: link. Whenever the set_inner_html function is ready we can change the logic. But as of right now, we only plan to support a few core blocks. I don't see a problem having it hardcoded because we know their HTML structure.

Let me know if that works for you 🙂

if ( ! $p->next_tag(
array(
// TODO: build the query from CSS selector.
'tag_name' => $block_type->attributes[ $block_attr ]['selector'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on #56704, this might not work for the heading block, but you could borrow from @kevin940726's approach in that PR of exploding the selector, at least until the HTML tag processor supports CSS selectors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for sharing! As part of this commit, I'm using that approach for supporting the different selectors we can have for the heading and the button.

return settings;
}

// TODO: Review the implications of this and the code.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what this code is doing? If the idea is to expose the context to all blocks that allow block bindings, then maybe it would work best to make these context entries global, or on the server they should be automatically injected during the block registration when the block allows binding.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two parts:

  • Exposing the context to all blocks.
  • Injecting the <BlockBindingsUI /> component.

Regarding adding the context to all blocks I am not 100% sure if it is needed. Maybe sources like this one, can get the postType and postID without needed to add it to the block.json. Any ideas if that's possible?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the implementation with this idea in this commit of another pull request (I've split this prototype in smaller PRs): link.

In the post meta source, I'm getting the postType and the postId using select( editorStore ).getCurrentPostId() and select( editorStore ).getCurrentPostType() when they are not defined. That way, we can decide to not show that source if we are in a taxonomy for example. And we don't have to register the context as we were doing.

Let me know what you think and I can reuse it in the rest of the sources 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, I believe in that approach, we're not getting the context properly when we are in a query loop, for example.

@SantosGuillamot SantosGuillamot added the [Feature] Custom Fields Anything related to the custom fields project - connecting block attributes and dynamic values label Dec 22, 2023
@SantosGuillamot
Copy link
Contributor Author

As explained in this update, I've decided to split this prototype into smaller pull requests so they are easier to review and we can keep the conversations separately. Please feel free to jump there if you have any feedback 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Block API API that allows to express the block paradigm. [Feature] Custom Fields Anything related to the custom fields project - connecting block attributes and dynamic values [Type] New API New API to be used by plugin developers or package users. [Type] Technical Prototype Offers a technical exploration into an idea as an example of what's possible
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants