-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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 Server Side Render component. #5602
Changes from 10 commits
cd58776
b0ede76
340136e
514dd4f
a90f40d
0c127e1
b0b977a
395a168
1d05c2d
c40dd25
f323651
c4abc69
6557877
228c756
b48ef81
e39332d
46506a9
ce1f7c6
e0fec83
17e404a
39b867a
4c3129b
3c60d4b
a10bfac
a166324
f0f4a77
6d1ee65
2bbbeb7
6f7d62e
67af6b7
007fb1d
1c9ef56
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
ServerSideRender | ||
======= | ||
|
||
ServerSideRender component is used for server-side-rendering preview in Gutenberg editor, specifically for dynamic blocks. | ||
|
||
|
||
## Usage | ||
|
||
Render core/latest-posts preview. | ||
```jsx | ||
<ServerSideRender | ||
block="core/latest-posts" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably register another block for something that makes more sense server-rendered (maybe tag cloud widget?) to use as the example. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mtias For usage example I merged the code from this PR to #5495 which adds So basically #5495 now includes adding Archives block + SSR component with the idea that SSR component could be merged separately first. Let me know if that's OK or if you'd prefer having the Archives block added here under this PR. cc @westonruter There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mtias Updated the description with added screenshots of Archives block based on SSR. Let me know if there's anything else you see in the PR that requires addressing. |
||
{ this.props.attributes } | ||
/> | ||
``` | ||
|
||
## Output | ||
|
||
Output is using the `render_callback` set when defining the block. For example if `block="core/latest-posts"` as in the example then the output will match `render_callback` output of that block. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/latest-posts/archives/ |
||
|
||
## API Endpoint | ||
API endpoint for getting the output for ServerSideRender is `/gutenberg/v1/blocks-renderer/:block`. It accepts any params which are used as `attributes` for the block's `render_callback` method. | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { | ||
Component, | ||
RawHTML, | ||
} from '@wordpress/element'; | ||
import { __ } from '@wordpress/i18n'; | ||
import { addQueryArgs } from '@wordpress/url'; | ||
|
||
export class ServerSideRender extends Component { | ||
constructor( props ) { | ||
super( props ); | ||
this.state = { | ||
response: {}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the empty object as a representation of missing state a pattern from somewhere else? I'd expect if ( response && response.output ) { Meaning that we can easily distinguish the three possible states, e.g.:
|
||
attributes: props, | ||
}; | ||
} | ||
|
||
componentDidMount() { | ||
this.getOutput(); | ||
} | ||
|
||
componentWillReceiveProps( nextProps ) { | ||
if ( JSON.stringify( nextProps ) !== JSON.stringify( this.props ) ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
componentWillReceiveProps( nextProps ) {
if ( ! isEqual( nextProps, this.props ) ) {
this.getOutput();
}
} |
||
this.setState( { attributes: nextProps }, this.getOutput ); | ||
} | ||
} | ||
|
||
getOutput() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is a name like |
||
this.setState( { response: {} } ); | ||
const { block } = this.props; | ||
const attributes = this.state.attributes; | ||
const apiURL = addQueryArgs( '/gutenberg/v1/blocks-renderer/' + block, { | ||
...attributes, | ||
_wpnonce: wpApiSettings.nonce, | ||
} ); | ||
|
||
return wp.apiRequest( { path: apiURL } ).then( response => { | ||
if ( response && response.output ) { | ||
this.setState( { response: response.output } ); | ||
} | ||
} ); | ||
} | ||
|
||
render() { | ||
const response = this.state.response; | ||
if ( ! response.length ) { | ||
return ( | ||
<div key="loading" className="wp-block-embed is-loading"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For what reason do we need a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replaced with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We shouldn't reuse classes but components. If there's something in the way the embed blocks handles the loading state that is useful, it should be extracted in a separate UI component. |
||
|
||
<p>{ __( 'Loading...' ) }</p> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need both a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replaced with |
||
</div> | ||
); | ||
} | ||
|
||
return ( | ||
<RawHTML key="html">{ response }</RawHTML> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess not because then styles won't be applied. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @westonruter Sandbox doesn't stop styles being applied (If I understand correctly it ensures no css / js bleeds into admin experience). It's used in #4710. Should work on such block-specifics defer to this once it's released? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean the CSS from the editor. Otherwise, the CSS has to be re-printed in the iframe. |
||
); | ||
} | ||
} | ||
|
||
export default ServerSideRender; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
<?php | ||
/** | ||
* Blocks Renderer REST API: WP_REST_Blocks_Renderer_Controller class | ||
* | ||
* @package gutenberg | ||
* @since ? | ||
*/ | ||
|
||
/** | ||
* Controller which provides REST endpoint for rendering blocks. | ||
* | ||
* @since ? | ||
* | ||
* @see WP_REST_Controller | ||
*/ | ||
class WP_REST_Blocks_Renderer_Controller extends WP_REST_Controller { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be singular? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yah, same with the endpoint route. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in c4abc69 |
||
|
||
/** | ||
* Constructs the controller. | ||
* | ||
* @access public | ||
*/ | ||
public function __construct() { | ||
|
||
// @codingStandardsIgnoreLine - PHPCS mistakes $this->namespace for the namespace keyword. | ||
$this->namespace = 'gutenberg/v1'; | ||
$this->rest_base = 'blocks-renderer'; | ||
} | ||
|
||
/** | ||
* Registers the necessary REST API routes. | ||
* | ||
* @access public | ||
*/ | ||
public function register_routes() { | ||
|
||
// @codingStandardsIgnoreLine - PHPCS mistakes $this->namespace for the namespace keyword. | ||
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<name>[\w-]+\/[\w-]+)', array( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One idea: instead of letting This would allow for a list of all the server-side renderable blocks to be discovered by looking at the REST API schema. Only the blocks returned by |
||
'args' => array( | ||
'name' => array( | ||
'description' => __( 'Unique registered name for the block.', 'gutenberg' ), | ||
'type' => 'string', | ||
), | ||
), | ||
array( | ||
'methods' => WP_REST_Server::READABLE, | ||
'callback' => array( $this, 'get_item_output' ), | ||
'permission_callback' => array( $this, 'get_item_output_permissions_check' ), | ||
'args' => array( | ||
'context' => $this->get_context_param( array( 'default' => 'view' ) ), | ||
), | ||
), | ||
'schema' => array( $this, 'get_public_item_schema' ), | ||
) ); | ||
} | ||
|
||
/** | ||
* Checks if a given request has access to read blocks. | ||
* | ||
* @since ? | ||
* @access public | ||
* | ||
* @return true|WP_Error True if the request has read access, WP_Error object otherwise. | ||
*/ | ||
public function get_item_output_permissions_check() { | ||
if ( ! current_user_can( 'edit_posts' ) ) { | ||
return new WP_Error( 'gutenberg_block_cannot_read', __( 'Sorry, you are not allowed to read Gutenberg blocks as this user.', 'gutenberg' ), array( | ||
'status' => rest_authorization_required_code(), | ||
) ); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Returns block output from block's registered render_callback. | ||
* | ||
* @since ? | ||
* @access public | ||
* | ||
* @param WP_REST_Request $request Full details about the request. | ||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. | ||
*/ | ||
public function get_item_output( $request ) { | ||
if ( ! isset( $request['name'] ) ) { | ||
return new WP_Error( 'rest_block_invalid_name', __( 'Invalid block name.', 'gutenberg' ), array( 'status' => 404 ) ); | ||
} | ||
|
||
$registry = WP_Block_Type_Registry::get_instance(); | ||
$block = $registry->get_registered( $request['name'] ); | ||
|
||
if ( ! $block || ! $block instanceof WP_Block_Type ) { | ||
return new WP_Error( 'rest_block_invalid_name', __( 'Invalid block name.', 'gutenberg' ), array( 'status' => 404 ) ); | ||
} | ||
|
||
$atts = $this->prepare_attributes( $request->get_params() ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we have $atts = array();
$schema = $block->attributes;
foreach ( $request->get_params() as $key => $value ) {
if ( ! isset( $schema[ $key ] ) ) {
return new WP_Error( 'rest_unrecognized_block_attribute' /* ... */ );
}
$validity = rest_validate_value_from_schema( $value, $schema[ $key ], $key );
if ( is_wp_error( $validity ) ) {
return $validity;
}
$atts[ $key ] = rest_sanitize_value_from_schema( $value, $schema[ $key ] );
} This should eliminate the need for |
||
|
||
$data = array( | ||
'output' => $block->render( $atts ), | ||
); | ||
return rest_ensure_response( $data ); | ||
} | ||
|
||
/** | ||
* Fix potential boolean value issues. The values come as strings and "false" and "true" might generate issues if left like this. | ||
* | ||
* @param array $attributes Attributes. | ||
* @return mixed Attributes. | ||
*/ | ||
public function prepare_attributes( $attributes ) { | ||
foreach ( $attributes as $key => $value ) { | ||
if ( 'false' === $value ) { | ||
$attributes[ $key ] = false; | ||
} elseif ( 'true' === $value ) { | ||
$attributes[ $key ] = true; | ||
} | ||
} | ||
|
||
return $attributes; | ||
} | ||
|
||
/** | ||
* Retrieves block's output schema, conforming to JSON Schema. | ||
* | ||
* @since ? | ||
* @access public | ||
* | ||
* @return array Item schema data. | ||
*/ | ||
public function get_item_schema() { | ||
return array( | ||
'$schema' => 'http://json-schema.org/schema#', | ||
'title' => 'blocks-renderer', | ||
'type' => 'object', | ||
'properties' => array( | ||
'output' => array( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in c4abc69 |
||
'description' => __( 'The block\'s output.', 'gutenberg' ), | ||
'type' => 'string', | ||
'required' => true, | ||
), | ||
), | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/latest-posts/archives/