-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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 docs for extending the Query Loop block via variations #44137
Changes from 2 commits
f0a62b9
57c509a
23ecd7a
6c51dfd
89d88bf
8439831
c327579
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,195 @@ | ||
# Extending the Query Loop block | ||
|
||
The Query Loop block is a powerful tool that allows users to cycle through a determined list of posts and display a certain set of blocks that will inherit the context of each of the posts in the list. For example, it can be set to cycle through all the posts of a certain category and for each of those posts display their featured image. And much more, of course! | ||
|
||
But precisely because the Query Loop block is so powerful and allows for great customization, it can also be daunting. Most users wouldn't want to be presented with the full capabilities of the Query Loop block, as most users wouldn't be familiar with the concept of a “query” and its associated technical terms. Instead, most users will likely appreciate a pre-set version of the block, with fewer settings to adjust and clearer naming. The Post List variation offered by default is a good example of this practice: the user will be using the Query Loop block without being exposed to its technicalities, and will also be more likely to discover and understand the purpose of the block. | ||
|
||
In the same manner, a lot of extenders might need a way to present bespoke versions of the block, with their own presets, additional settings and without customization options which are irrelevant to their use-case (often, for example, their custom post type). The Query Loop block offers very powerful ways to create such variations. | ||
|
||
## Extending the block with variations | ||
|
||
By registering your own block variation with some specific Query Loop block settings, you can have finer control over how it is presented, while still being able to use the full capabilities which the Query Loop block offers underneath. If you are not familiar with block variations, learn more about them [here](/docs/reference-guides/block-api/block-variations.md). | ||
|
||
With the block variations API you can provide the default settings that make the most sense for your use-case. | ||
|
||
Let's go on a journey, for example, of setting up a variation that could fit a plugin which registers a `book` [custom post type](https://developer.wordpress.org/plugins/post-types/). | ||
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. Nit: I think we can do some rewording here and maybe have a heading for this section as an example use case. 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. The thing is that the example is spanning through the entire document, so I don't feel like it makes sense to scope it under a section heading. As for the rewording, do you have suggestions? Or, what's the feeling that you get from this paragraph that puts you off? Is it the phrasing, or the syntax? Let me know so I can provide a suggestion for the rewording. Or feel free to change it! 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. No strong opinion on the heading.. Regarding the rewording the only thing that felt 'weird' to me was: Maybe it can be as simple as changing 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. You're right. Maybe "suit" was a better choice than "fit". But I have changed it with "for" to keep it simple |
||
|
||
### Offer sensible defaults | ||
|
||
Your first step would be to create a variation which will be set up in such a way to provide a block variation which will display by default a list of books instead of blog posts. You would start with something like this: | ||
|
||
```js | ||
registerBlockVariation( 'core/query', { | ||
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 it would be better to add the full block variation here, and then explain the specific parts. It's also important to note that due to the Query Loop setup with patterns and To elaborate on this, if for example we have set
In addition to the above, in the case of having declared I hope I'll be able to fix this for 6.1 to handle specific Query Loop variation patterns, so the extenders could offer specific patterns. 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 this PR is going to land now, it would be better to add info about it and we could probably skip some of the details I mention above about the |
||
name: 'my-plugin/books-list', | ||
attributes: { | ||
query: { | ||
/** ...more query settings if needed */ | ||
postType: 'book', | ||
}, | ||
}, | ||
} ); | ||
``` | ||
|
||
In this way, the users won't have to choose the custom `postType` from the dropdown, and be already presented with the correct configuration. However, you might ask, how is a user going to find and insert this variation? Good question! To enable this, you should add: | ||
|
||
```js | ||
{ | ||
/** ...variation properties */ | ||
scope: [ 'inserter' ], | ||
} | ||
``` | ||
|
||
In this way, your block will show up just like any other block while the user is in the editor and searching for it. At this point you might also want to add a custom icon, title and description to your variation, just like so: | ||
|
||
```js | ||
{ | ||
/** ...variation properties */ | ||
title: 'Books List', | ||
description: 'Displays a list of books', | ||
icon: /* Your svg icon here */, | ||
} | ||
``` | ||
|
||
Also note that the Query Loop block supports `'block'` as a string in the `scope` property, and it will allow the variation to be picked up after inserting the block itself. Read more about the Block Variation Picker [here](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/block-variation-picker/README.md). | ||
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 we can skip this 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. And don't we want to inform users that this is possible? I find that, especially with #44197, it could be really helpful. At least, that's something we will definitely use in Woo Blocks. 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. Yeah, I think we should skip it. With #44197 a plugin should either register their own patterns for best user experience or just add The |
||
|
||
At this point, your custom variation will be virtually indistinguishable from a stand-alone block. Completely branded to your plugin, easy to discover and directly available to the user as a drop in. | ||
|
||
### Making Gutenberg recognize your variation | ||
|
||
There is one slight problem you might have realized after implementing this variation: while it is transparent to the user as they are inserting it, Gutenberg will still recognize the variation as a Query Loop block at its core and so, after its insertion, it will show up as a Query Loop block in the tree view of the editor, for instance. | ||
|
||
We need a way to tell the editor that this block is indeed your specific variation. This is what the `isActive` property is made for: it's a way to determine whether a certain variation is active based on the block's attributes. You might be tempted to then do something like this: | ||
|
||
```js | ||
{ | ||
/** ...variation properties */ | ||
isActive: [ 'postType' ], | ||
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. Just occurred to me that the shorthand version of 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. Oh, excellent point! Do you think we could recommend to only 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 think your example is good with a function because you explain very well below what the pitfall could be. Maybe we could just add a sentence in the end that most(if all times) the |
||
} | ||
``` | ||
|
||
In this way, Gutenberg will recognize the block as your variation any time the `postType` matches `book`! That's awesome, but the problem is that now Gutenberg will recognize the block as your specific variation any time the `postType` is set to `book`, which is not what we want: other plugins might want to publish variations based on the `book` post type, or we might just not want the variation to be recognized every time the user sets the type to `book` manually through the editor settings. | ||
|
||
That's why the Query Loop block exposes a special attribute called `namespace`: it really doesn't do anything inside the block implementation, and it's used as an easy way for extenders to recognize and scope their own variation. So you would use it like so: | ||
|
||
```js | ||
{ | ||
/** ...variation properties */ | ||
attributes: { | ||
/** ...variation attributes */ | ||
namespace: 'my-plugin/books-list', | ||
}, | ||
isActive: [ 'postType', 'namespace' ], | ||
ntsekouras marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
``` | ||
|
||
Like so, Gutenberg will know that it is your specific variation only in the case it matches your custom namespace! So convenient! | ||
|
||
## Extending the query | ||
|
||
Even with all of this, your custom post type might have unique requirements: it might support certain custom attributes that you might want to filter and query for, or some other query parameters might be irrelevant or even completely unsupported! We have build the Query Loop block with such use-cases in mind, so let's see how you can solve this problem. | ||
|
||
### Disabling irrelevant or unsupported query controls | ||
|
||
Let's say you don't use at all the `sticky` attribute in your books, so that would be totally irrelevant to the customization of your block. In order to not confuse the users as to what a setting might do, and only exposing a clear UX to them, we want this control to be unavailable. Furthermore, let's say that you don't use the `author` field at all, which generally indicates the person who has added that post to the database, instead you use a custom `bookAuthor` field. As such, not only keeping the `author` filter would be confusing, it would outright break your query. | ||
|
||
For this reason, the Query Loop block supports a property called `allowedControls` which accepts an array of keys of the controls we want to whitelist. By default, we accept all the controls, but as soon as we provide an array to this property, we want to be specific and whitelist only the controls which are going to be relevant for us! | ||
ntsekouras marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
As of version 13.9, the following controls are available: | ||
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 we add the GB version or the WP one here ? 🤔 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. That's a good question, I went with GB as my first draft, but it depends on what's the usual convention for the docs and if the context in which they appear makes it clear they are for GB or WP. Just let me know and I'll act accordingly. 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 can leave this discussion open for documentation folks to chime in. In general we will need some other small changes in some other files for the documentation to work, but we can add them in the end, when the copy is finalized. |
||
|
||
- `inherit` - Shows the toggle switch for allowing the query to be inherited directly from the template. | ||
- `postType` - Shows a dropdown of available post types. | ||
- `order` - Shows a dropdown to select the order of the query. | ||
- `sticky` - Shows a checkbox to only display sticky posts. | ||
- `taxQuery` - Shows available taxonomies filters for the currently selected post type. | ||
- `author` - Shows an input field to filter the query by author. | ||
- `search` - Shows an input filed to filter the query by keywords. | ||
|
||
In our case, the property would look like this: | ||
|
||
```js | ||
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. It is not clear where this needs to be added. I am working through this with GB 14.1 and cannot get this part to affect the variation in anyway. 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. Just to follow up, the key was incorrect. |
||
{ | ||
/** ...variation properties */ | ||
allowedControls: [ 'inherit', 'order', 'taxQuery', 'search' ], | ||
ryanwelcher marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
``` | ||
|
||
Notice that we have also disabled the `postType` control: when the user selects our variation, why show them a confusing dropdown to change the post type? On top of that it might break the block as we can implement custom controls, as we'll see shortly. | ||
|
||
### Adding additional controls | ||
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. Up to this point here this guide is super clear and easy to understand. I personally find this final section to be a little confusing. The example code uses 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. Hey @fabiankaegy, thanks for the feedback. Is the In my latest commit I changed the comment above the code to this:
Does it sound more appropriate? I mean, I can also provide the full code for |
||
|
||
Because our plugin uses custom attributes that we need to query, we want the users of our block to be able to select those instead of the ones we have just disabled from the core inspector controls. We can do this via a [React HOC](https://reactjs.org/docs/higher-order-components.html), like so: | ||
ntsekouras marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```jsx | ||
export const withBookQueryControls = ( BlockEdit ) => ( props ) => { | ||
// We only want to add these controls if it is our variation, | ||
// so here we can implement a logic to check for that, similiar | ||
// to the `isActive` function described above. | ||
return isMyBooksVariation( props ) ? ( | ||
<> | ||
<BlockEdit { ...props } /> | ||
<InspectorControls> | ||
<BookAuthorSelector /> { /** Our custom component */ } | ||
</InspectorControls> | ||
</> | ||
) : ( | ||
<BlockEdit { ...props } /> | ||
); | ||
}; | ||
|
||
addFilter( 'editor.BlockEdit', 'core/query', withBookQueryControls ); | ||
``` | ||
|
||
Of course, you'll be responsible for implementing the logic of your control (you might want to take a look at [`@wordpress/components`](https://www.npmjs.com/package/@wordpress/components) to make your controls fit seamlessly within the Gutenberg UI). Any extra parameter you assign within the `query` object inside the blocks attributes can be used to create a custom query according to your needs, with a little extra effort. | ||
|
||
Currently, you'll likely have to implement slightly different paths to make the query behave correctly both on the front-end side (i.e. on the end user's side) and to show the correct preview on the editor side. | ||
|
||
```js | ||
{ | ||
/** ...variation properties */ | ||
attributes: { | ||
/** ...variation attributes */ | ||
query: { | ||
/** ...more query settings if needed */ | ||
postType: 'book', | ||
/** Our custom query parameter */ | ||
bookAuthor: 'J. R. R. Tolkien' | ||
} | ||
} | ||
} | ||
``` | ||
|
||
### Making your custom query work on the front-end side | ||
|
||
The Query Loop block functions mainly through the Post Template block which receives the attributes and builds the query from there. Other first-class children of the Query Loop block (such as the Pagination block) behave in the same way. They build their query and then expose the result via the filter [`query_loop_block_query_vars`](https://developer.wordpress.org/reference/hooks/query_loop_block_query_vars/). | ||
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. The link 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'm actually unsure of how it works, that's something I needed to clarify with you folks. I added the URL using the convention I saw, assuming that some sort of script reads the JSDoc of a hook and publishes the updated references. Would that be correct? Or who would I have to ask? |
||
|
||
You can hook into that filter and modify your query accordingly. Just make sure you don't cause side-effects to other Query Loop blocks by at least checking that you apply the filter only to your variation! | ||
|
||
```php | ||
if( 'my-plugin/books-list' === $block[ 'attrs' ][ 'namespace' ] ) { | ||
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. Where would one be calling this hook from so that they have access to the attributes of the 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. So, this would be a bit tricky. Because I don't want to get too prescriptive in this guide and people can take their own route as preferred to access the block. We do it inside a pre-render filter. I will add some info about that, however, I understand this is a bit confusing. |
||
add_filter( | ||
'query_loop_block_query_vars', | ||
function( $query ) { | ||
/** You can read your block custom query parameters here and build your query */ | ||
}, | ||
); | ||
} | ||
``` | ||
|
||
### Making your custom query work on the editor side | ||
|
||
To finish up our custom variation, we might want the editor to react to changes in our custom query and display an appropriate preview accordingly. This is not required for a functioning block, but it enables a fully integrated user experience for the consumers of your block. | ||
|
||
The Query Loop block fetches its posts to show the preview using the [WordPress REST API](https://developer.wordpress.org/rest-api/). Any extra parameter added to the `query` object will be passed as a query argument to the API. This means that these extra parameters should be either supported by the REST API, or be handled by custom filters such as the [`rest_{$this->post_type}_query`](https://developer.wordpress.org/reference/hooks/rest_this-post_type_query/) filter which allows you to hook into any API request for your custom post type. Like so: | ||
|
||
```php | ||
add_filter( | ||
'rest_book_query', | ||
function( $args, $request ) { | ||
/** We can access our custom parameters from here */ | ||
$book_author = $request->get_param( 'bookAuthor' ); | ||
ntsekouras marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/** ...your custom query logic */ | ||
} | ||
); | ||
``` | ||
|
||
And, just like that, you'll have created a fully functional variation of the Query Loop block! |
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.
If you are not familiar, learn more about [block variations](/docs/reference-guides/block-api/block-variations.md).