-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Block API: Add Block Context support #21467
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
Merged
Merged
Changes from all commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
ef69571
Framework: Add render_block initial implementation
aduth ecb311f
Framework: Assign to and consume from block context
aduth d325f90
Framework: Assign default post context
aduth bdd8d28
Block Editor: Implement BlockContext component
aduth c2d9b26
Block Editor: Provide and consume block context
aduth efdf341
Editor: Assign default post context
aduth 49de552
Block Library: Post Title: Reimplement using block context
aduth 909e637
Framework: Rename block context global to clarify internal
aduth 00a0be5
Documentation: Add Block Context documentation
aduth 13a843c
Docs: Update Block Context text to refer to Context.Provider
aduth ccb86f5
Block Library: Post Title: Include dependencies for useSelect hook
aduth 0767f60
Block Editor: Use optional chaining for providesContext property access
aduth c4038c0
Block Editor: Docs: Clarify context consumed in editing interface
aduth 5c8d5ee
Docs: Block Context: Clarify language of providing block context
aduth 8c527aa
Framework: Remove redundant block context assignment
aduth 7a52d0a
Framework: Defer to highest priority block render filtering
aduth 3203233
Block Editor: Memoize block context provider value
aduth 4fa6a5c
Block Editor: Cache block context from InnerBlocks
aduth c406d4c
Block Library: Post Title: Ensure post entity is fetched
aduth 81c83c4
Docs: Block Context: Grammar and readability
aduth 10125d3
Block API: Add automatic block context namespacing
aduth d2b5624
Revert "Block API: Add automatic block context namespacing"
aduth 784a057
Block API: Change providesContext from array to object format
aduth a2dafcf
Framework: Disable PHPCS rule for snake case property
aduth 4f25668
Core Data: Add resolvers for getRawEntityRecord, getEditedEntityRecord
aduth e773ef6
Block Library: Post Title: Remove redundant entity resolver call
aduth 6b33df2
Block Editor: Reference postType, postId dependency variables
aduth 5005983
Block Editor: Export BlockContextProvider component for native
aduth c36fd65
Editor: EditorProvider: Cache default block context
aduth 2ebe9f3
Framework: Block Context: Restore block global after inner render
aduth 0594ce7
Framework: Block Context: Rename function to reflect implementation
aduth fc4c513
Block Editor: InnerBlocks: Fix block context provide attribute picking
aduth d7eb57c
Docs: Block Context: Document namespace recommendation
aduth fe97dd8
Framework: Block Context: Prepare attributes for context assignment
aduth 2d06181
E2E Tests: Add Block Context tests
aduth 54f183a
PHPUnit: Add block context PHP unit tests
aduth 293487c
Core Data: Add ifNotResolved internal utility for shared resolvers
aduth c54c862
Framework: Introduce WP_Block class for block rendering
aduth 0cafbe0
Framework: Assign prepared attributes using original reference
aduth c98a406
Framework: Accept context, registry arguments to WP_Block
aduth b0636f8
Docs: Update block context docs for render_callback argument
aduth 728d35d
Docs: Update render_callback references to prefer $block argument
aduth d36ad8c
Block Editor: Add BlockContext component to type-checking
aduth 207a770
Core Data: Improve ifNotResolved typeability
aduth 06c169a
Revert "Block Editor: Add BlockContext component to type-checking"
aduth ad677ec
Framework: Block Context: Use verbose array syntax
aduth 262b2a4
Framework: Block Context: Rename WP_Block constructor agument availab…
aduth f11c515
Framework: Block Context: Support null context overrides
aduth 8665824
E2E Tests: Block Context: Clarify waitForSelector comment expectation
aduth f8beb9e
Framework: Block Context: Update ArrayAccess to behave exactly as array
aduth b908cea
Block Library: Post Title: Update render callback to use block argument
aduth 22c699f
Block Library: Post Title: Add missing PHPDoc
aduth cb4a324
Block Editor: BlockEdit: Provide context to light block wrapper
aduth 2aa56ca
Revert "Block Editor: Use optional chaining for providesContext prope…
aduth File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
76 changes: 76 additions & 0 deletions
76
docs/designers-developers/developers/block-api/block-context.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| # Block Context | ||
|
|
||
| Block context is a feature which enables ancestor blocks to provide values which can be consumed by descendent blocks within its own hierarchy. Those descendent blocks can inherit these values without resorting to hard-coded values and without an explicit awareness of the block which provides those values. | ||
|
|
||
| This is especially useful in full-site editing where, for example, the contents of a block may depend on the context of the post in which it is displayed. A blogroll template may show excerpts of many different posts. Using block context, there can still be one single "Post Excerpt" block which displays the contents of the post based on an inherited post ID. | ||
|
|
||
| If you are familiar with [React Context](https://reactjs.org/docs/context.html), block context adopts many of the same ideas. In fact, the client-side block editor implementation of block context is a very simple application of React Context. Block context is also supported in server-side `render_callback` implementations, demonstrated in the examples below. | ||
|
|
||
| ## Defining Block Context | ||
|
|
||
| Block context is defined in the registered settings of a block. A block can provide a context value, or consume a value it seeks to inherit. | ||
|
|
||
| ### Providing Block Context | ||
|
|
||
| A block can provide a context value by assigning a `providesContext` property in its registered settings. This is an object which maps a context name to one of the block's own attribute. The value corresponding to that attribute value is made available to descendent blocks and can be referenced by the same context name. Currently, block context only supports values derived from the block's own attributes. This could be enhanced in the future to support additional sources of context values. | ||
|
|
||
| `record/block.json` | ||
|
|
||
| ```json | ||
| { | ||
| "name": "my-plugin/record", | ||
| "attributes": { | ||
| "recordId": { | ||
| "type": "number" | ||
| } | ||
| }, | ||
| "providesContext": { | ||
| "my-plugin/recordId": "recordId" | ||
| } | ||
aduth marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| ``` | ||
|
|
||
| As seen in the above example, it is recommended that you include a namespace as part of the name of the context key so as to avoid potential conflicts with other plugins or default context values provided by WordPress. The context namespace should be specific to your plugin, and in most cases can be the same as used in the name of the block itself. | ||
|
|
||
| ### Consuming Block Context | ||
|
|
||
| A block can inherit a context value from an ancestor provider by assigning a `context` property in its registered settings. This should be assigned as an array of the context names the block seeks to inherit. | ||
|
|
||
| `record-title/block.json` | ||
|
|
||
| ```json | ||
| { | ||
| "name": "my-plugin/record-title", | ||
| "context": [ "my-plugin/recordId" ] | ||
| } | ||
| ``` | ||
|
|
||
| ## Using Block Context | ||
|
|
||
| Once a block has defined the context it seeks to inherit, this can be accessed in the implementation of `edit` (JavaScript) and `render_callback` (PHP). It is provided as an object (JavaScript) or associative array (PHP) of the context values which have been defined for the block. Note that a context value will only be made available if the block explicitly defines a desire to inherit that value. | ||
|
|
||
| ### JavaScript | ||
|
|
||
| `record-title/index.js` | ||
|
|
||
| ```js | ||
| registerBlockType( 'my-plugin/record-title', { | ||
| edit( { context } ) { | ||
| return 'The current record ID is: ' + context[ 'my-plugin/recordId' ]; | ||
| }, | ||
| } ); | ||
| ``` | ||
|
|
||
| ### PHP | ||
|
|
||
| A block's context values are available from the `context` property of the `$block` argument passed to the `render_callback` function. | ||
|
|
||
| `record-title/index.php` | ||
|
|
||
| ```php | ||
| register_block_type( 'my-plugin/record-title', [ | ||
| 'render_callback' => function( $block ) { | ||
| return 'The current record ID is: ' . $block->context['my-plugin/recordId']; | ||
| }, | ||
| ] ); | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,240 @@ | ||
| <?php | ||
| /** | ||
| * Blocks API: WP_Block class | ||
| * | ||
| * @package Gutenberg | ||
| */ | ||
|
|
||
| /** | ||
| * Class representing a parsed instance of a block. | ||
| */ | ||
| class WP_Block implements ArrayAccess { | ||
|
|
||
| /** | ||
| * Name of block. | ||
| * | ||
| * @example "core/paragraph" | ||
| * | ||
| * @var string | ||
| */ | ||
| public $name; | ||
|
|
||
| /** | ||
| * Block type associated with the instance. | ||
| * | ||
| * @var WP_Block_Type | ||
| */ | ||
| public $block_type; | ||
|
|
||
| /** | ||
| * Block context values. | ||
| * | ||
| * @var array | ||
| */ | ||
| public $context = array(); | ||
|
|
||
| /** | ||
| * All available context of the current hierarchy. | ||
| * | ||
| * @var array | ||
| * @access protected | ||
| */ | ||
| protected $available_context; | ||
|
|
||
| /** | ||
| * Block attribute values. | ||
| * | ||
| * @var array | ||
| */ | ||
| public $attributes = array(); | ||
|
|
||
| /** | ||
| * List of inner blocks (of this same class) | ||
| * | ||
| * @var WP_Block[] | ||
| */ | ||
| public $inner_blocks = array(); | ||
|
|
||
| /** | ||
| * Resultant HTML from inside block comment delimiters after removing inner | ||
| * blocks. | ||
| * | ||
| * @example "...Just <!-- wp:test /--> testing..." -> "Just testing..." | ||
| * | ||
| * @var string | ||
| */ | ||
| public $inner_html = ''; | ||
|
|
||
| /** | ||
| * List of string fragments and null markers where inner blocks were found | ||
| * | ||
| * @example array( | ||
| * 'inner_html' => 'BeforeInnerAfter', | ||
| * 'inner_blocks' => array( block, block ), | ||
| * 'inner_content' => array( 'Before', null, 'Inner', null, 'After' ), | ||
| * ) | ||
| * | ||
| * @var array | ||
| */ | ||
| public $inner_content = array(); | ||
|
|
||
| /** | ||
| * Constructor. | ||
| * | ||
| * Populates object properties from the provided block instance argument. | ||
| * | ||
| * The given array of context values will not necessarily be available on | ||
| * the instance itself, but is treated as the full set of values provided by | ||
| * the block's ancestry. This is assigned to the private `available_context` | ||
| * property. Only values which are configured to consumed by the block via | ||
| * its registered type will be assigned to the block's `context` property. | ||
| * | ||
| * @param array $block Array of parsed block properties. | ||
| * @param array $available_context Optional array of ancestry context values. | ||
| * @param WP_Block_Type_Registry $registry Optional block type registry. | ||
| */ | ||
| public function __construct( $block, $available_context = array(), $registry = null ) { | ||
| $this->name = $block['blockName']; | ||
|
|
||
| if ( is_null( $registry ) ) { | ||
| $registry = WP_Block_Type_Registry::get_instance(); | ||
| } | ||
|
|
||
| $this->block_type = $registry->get_registered( $this->name ); | ||
|
|
||
| if ( ! empty( $block['attrs'] ) ) { | ||
| $this->attributes = $block['attrs']; | ||
| } | ||
|
|
||
| if ( ! is_null( $this->block_type ) ) { | ||
| $this->attributes = $this->block_type->prepare_attributes_for_render( $this->attributes ); | ||
| } | ||
|
|
||
| $this->available_context = $available_context; | ||
|
|
||
| if ( ! empty( $this->block_type->context ) ) { | ||
| foreach ( $this->block_type->context as $context_name ) { | ||
| if ( array_key_exists( $context_name, $this->available_context ) ) { | ||
| $this->context[ $context_name ] = $this->available_context[ $context_name ]; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if ( ! empty( $block['innerBlocks'] ) ) { | ||
| $child_context = $this->available_context; | ||
|
|
||
| /* phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase */ | ||
| if ( ! empty( $this->block_type->providesContext ) ) { | ||
| foreach ( $this->block_type->providesContext as $context_name => $attribute_name ) { | ||
| if ( array_key_exists( $attribute_name, $this->attributes ) ) { | ||
| $child_context[ $context_name ] = $this->attributes[ $attribute_name ]; | ||
| } | ||
| } | ||
| } | ||
| /* phpcs:enable */ | ||
|
|
||
| $this->inner_blocks = array_map( | ||
| function( $inner_block ) use ( $child_context, $registry ) { | ||
| return new WP_Block( $inner_block, $child_context, $registry ); | ||
| }, | ||
| $block['innerBlocks'] | ||
| ); | ||
| } | ||
|
|
||
| if ( ! empty( $block['innerHTML'] ) ) { | ||
| $this->inner_html = $block['innerHTML']; | ||
| } | ||
|
|
||
| if ( ! empty( $block['innerContent'] ) ) { | ||
| $this->inner_content = $block['innerContent']; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Generates the render output for the block. | ||
| * | ||
| * @return string Rendered block output. | ||
| */ | ||
| public function render() { | ||
| global $post; | ||
|
|
||
| $is_dynamic = $this->name && null !== $this->block_type && $this->block_type->is_dynamic(); | ||
| $block_content = ''; | ||
|
|
||
| $index = 0; | ||
| foreach ( $this->inner_content as $chunk ) { | ||
| $block_content .= is_string( $chunk ) ? | ||
| $chunk : | ||
| $this->inner_blocks[ $index++ ]->render(); | ||
| } | ||
|
|
||
| if ( $is_dynamic ) { | ||
| $global_post = $post; | ||
| $block_content = (string) call_user_func( $this->block_type->render_callback, $this, $block_content ); | ||
| $post = $global_post; | ||
| } | ||
|
|
||
| return $block_content; | ||
| } | ||
|
|
||
| /** | ||
| * Returns true if an attribute exists by the specified attribute name, or | ||
| * false otherwise. | ||
| * | ||
| * @link https://www.php.net/manual/en/arrayaccess.offsetexists.php | ||
| * | ||
| * @param string $attribute_name Name of attribute to check. | ||
| * | ||
| * @return bool Whether attribute exists. | ||
| */ | ||
| public function offsetExists( $attribute_name ) { | ||
| return isset( $this->attributes[ $attribute_name ] ); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the value by the specified attribute name. | ||
| * | ||
| * @link https://www.php.net/manual/en/arrayaccess.offsetget.php | ||
| * | ||
| * @param string $attribute_name Name of attribute value to retrieve. | ||
| * | ||
| * @return mixed|null Attribute value if exists, or null. | ||
| */ | ||
| public function offsetGet( $attribute_name ) { | ||
| // This may cause an "Undefined index" notice if the attribute name does | ||
| // not exist. This is expected, since the purpose of this implementation | ||
| // is to align exactly to the expectations of operating on an array. | ||
| return $this->attributes[ $attribute_name ]; | ||
| } | ||
|
|
||
| /** | ||
| * Assign an attribute value by the specified attribute name. | ||
| * | ||
| * @link https://www.php.net/manual/en/arrayaccess.offsetset.php | ||
| * | ||
| * @param string $attribute_name Name of attribute value to set. | ||
| * @param mixed $value Attribute value. | ||
| */ | ||
| public function offsetSet( $attribute_name, $value ) { | ||
| if ( is_null( $attribute_name ) ) { | ||
| // This is not technically a valid use-case for attributes. Since | ||
| // this implementation is expected to align to expectations of | ||
| // operating on an array, it is still supported. | ||
| $this->attributes[] = $value; | ||
| } else { | ||
| $this->attributes[ $attribute_name ] = $value; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Unset an attribute. | ||
| * | ||
| * @link https://www.php.net/manual/en/arrayaccess.offsetunset.php | ||
| * | ||
| * @param string $attribute_name Name of attribute value to unset. | ||
| */ | ||
| public function offsetUnset( $attribute_name ) { | ||
| unset( $this->attributes[ $attribute_name ] ); | ||
| } | ||
|
|
||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.