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

Proposal for Block Templates #3588

Closed
mtias opened this Issue Nov 21, 2017 · 15 comments

Comments

@mtias
Contributor

mtias commented Nov 21, 2017

Seems we didn't have an overview issue for block templates even though we have discussed them repeatedly.

Definition: In its most general sense, a block template is a definition that includes a list of block items. Those blocks can have predefined attributes, placeholder content, be static or dynamic.

Uses

Not to mistake with global blocks and nested blocks, even though they can be related — the list of blocks a template provides could potentially include a global block and it can have nested blocks in the specification. The use case of "reusing a group of blocks" is not necessarily a template task, but a global blocks one paired with nesting.

What block templates allow is to specify a default state for an editor session and a mechanism for rendering a page-route's corresponding PHP theme file. This can be done in multiple ways:

  • Setting a default through code dynamically. (defaultBlock API should support option of supplying a list of blocks, not just a single block.)
  • Instantiated as a default for a given post type, post format, etc. (When registering a post type you can specify a default block or template to use for any new post of that type.)
  • Saved and assigned to pages as "page templates". They can be defined in a template.php file or pulled from a custom post type (wp_templates) that is site specific.
  • As the equivalent of the theme hierarchy.

Example with pseudo code below of a template.php:

<body>

<?php
    do_blocks( array(
        // Theme header: "blocks/header/index.php"
        do_block( 'theme/header' );
        // A core text block
        do_block( 'core/text', array( 'placeholder' => 'Fill some text here...' ) );
        do_block( 'core/post-title', array( 'id' => get_post_id() ) );
        // A post block: renders the_content
        do_block( 'core/post', array( 'id' => get_post_id() ) );
        // Theme footer: "my-theme/blocks/footer/index.php"
        do_block( 'theme/footer' );
    ) );
?>

</body>

Details

Templates can:

  • Be defined in JS (state tree) or PHP.
  • Include blocks with children.
  • Be locked (no movers or ability to delete its components).
  • Be set as defaults for post types.
  • Be saved and edited by the user.
  • Be used as the contents of a page-template.php for initializing an editor provided by a theme.
  • Include static blocks as de facto placeholders in themselves since they don't have a save callback on the server.
  • Be inherently portable across themes.

Implementation Steps

(From 3 onwards we are in customization territory.)

  1. Add support for specifying an array of blocks to the editor as initial state.
  2. Add ways of defining array of blocks in both JS and PHP.
  3. Allow rendering a .php file that includes a list of blocks and relevant functions. These are transformed to an editor state tree when loaded in an editing context.
  4. Allow saving templates by the user. These are shown when creating a new page alongside theme provided defaults.
  5. Build theme with nothing but block definitions in the template hierarchy.
  6. Allow editing regular theme hierarchy templates (index.php, archive.php, etc) through the editor, saving changes in a revisioned CPT that can rule over the theme supplied ones as needed.

Tasks

  • Define array of blocks in the editor as initial state.
  • Allow custom post types to define a template.
  • Allow block templates to be locked down.
  • Allow templates to be saved in a wp_templates hidden post type.
@domtra

This comment has been minimized.

domtra commented Dec 14, 2017

It took me a while to find this issue, but this is exactly what I have been looking for. Great, @mtias, that this already exists and work is actually already being done.

I just want to add a couple of thoughts regarding your proposal:

With a system like this, and nested blocks via BlockList or some other component that should have the same option to add a template like in your proposal, quite a few issues could be solved.
Especially when looking at #3330 and in particular meta boxes support, this could be another way on how to migrate legacy meta boxes to Gutenberg. Having a template that you can hook into and customize from a theme, for example, would also mean that even if the editor (the_content) is not available in a custom post type, you could still use Gutenberg for a better experience by integrating all meta boxes as fixed blocks defined in the template.

At my company we use ACF Pro quite extensively. One of the great features is to be able to define static fields, but also (with the pro version) flexible content and repeater fields. The same thing could be achieved by defining a template that is locked and including a BlockList component to which you can add dynamic blocks. You would need to specify what childblocks are allowed in this BlockList, but I think someone is already on that.

One other thing that comes to my mind is that besides Gutenberg showing a locked template, there should probably also be some server side validation. I do not know if this is a priority atm though.

@youknowriad

This comment has been minimized.

Contributor

youknowriad commented Dec 22, 2017

I'm going to close this as the initial version of this is shipped. I'll leave #1684 open to track "nested locking"

@mtias

This comment has been minimized.

Contributor

mtias commented Jan 3, 2018

Reopening so we can still access the remaining points about saved templates.

@mtias mtias reopened this Jan 3, 2018

@greatislander

This comment has been minimized.

Contributor

greatislander commented Jan 3, 2018

I'm wondering if the code that has been merged for Block Template support exposes any API that would allow the template to be switched based on taxonomy selection? Say, for example, you have a category that you use for all image posts. Upon selecting that category, could you switch to a Block Template with an image block and nothing else?

I guess this also leads to a broader question. If a post is begun without an assigned Block Template and a change is made (e.g. selecting a specific post format) that assigns a Block Template, what happens to the content? The block paradigm changes the current behaviour, where switching post formats has no impact on the editor.

@Soean Soean added the Templates API label Jan 15, 2018

@melchoyce melchoyce added this to In progress in Phase 2 Jan 16, 2018

@westonruter

This comment has been minimized.

Member

westonruter commented Jan 16, 2018

Example with pseudo code below of a template.php:

I think that such a blockified PHP template should really be returning the data structure. For example:

<?php
return do_blocks( array(
    // Theme header: "blocks/header/index.php"
    do_block( 'theme/header' );
    // A core text block
    do_block( 'core/text', array( 'placeholder' => 'Fill some text here...' ) );
    do_block( 'core/post-title', array( 'id' => get_post_id() ) );
    // A post block: renders the_content
    do_block( 'core/post', array( 'id' => get_post_id() ) );
    // Theme footer: "my-theme/blocks/footer/index.php"
    do_block( 'theme/footer' );
) );

This would then allow for the template's block configuration to be read out as data and it wouldn't have to be executed. This would make it easier for a PHP-coded template to be copied into the database for modification, and it would also allow a theme to introspect its templates to find where a given block is used. This would solve the problem currently faced in core with regard to widget sidebars, where we don't know in the theme where a given sidebar is used. If all of the templates were data, then it would be possible to parse them to find out which blocks are used in which templates, and then be able to come up with a URL for a request that would result in a template being chosen that would contain the desired block to view.

@aduth aduth referenced this issue Jan 24, 2018

Closed

Framework: Add support for dynamic templates #4659

0 of 3 tasks complete

@mtias mtias added this to the Feature Complete milestone Jan 26, 2018

@gziolo gziolo added this to Discussions in Extensibility Jan 31, 2018

@ZebulanStanphill

This comment has been minimized.

Contributor

ZebulanStanphill commented Mar 14, 2018

Forgive me if this has already been asked/answered, and I'm not even sure this is the right place to ask it, but I haven't been able to find any specific info relating to how the following situation would be handled:

I create a template that I use on every instance of a certain kind of post, whether it be a custom post type or a post about a certain thing or whatever - it doesn't matter what post it is, just that it is using the template.

One day, I decide to change the layout of the blocks for those kinds of posts. I change the template... and what happens to the existing posts? Do they automatically change to match the new template? If I have a website with, for example, hundreds of products, and I decide one day that I want to move the gallery block from the left side to the right side of the page, do I have to go in and change every single product?

I guess what I'm asking for is global layouts, without making the specific content (product title, description, the image used, etc.) global. This is kind of similar to the "Selective Sync" feature that Divi Builder modules have, which allows you to set which settings of a module are global and which are specific to each individual instance of the module.

https://www.elegantthemes.com/documentation/divi/divi-library/
https://www.elegantthemes.com/documentation/divi/global-modules/

In the context of Gutenberg, where blocks are basically the equivalent of modules, the closest existing equivalent to the "Selective Sync" feature is making a plugin with a custom block made from one of the existing ones, but with certain settings turned into some kind of global field could work, but that's rather cumbersome and far from user-friendly.

But in this case, what I'm asking for isn't limited to the scope of a single block, but rather the entire post... or if you want to allow for even more freedom (like a section that only exists on a single product), just a section of the post.

If a "Selective Sync" kind of feature was added to global blocks, then the making-changes-to-templates issue could be resolved by having every block of a post nested in one parent block that uses global sync for the order of its children blocks, but not the content of the blocks themselves. But I'm not really sure as to whether or not this is the right way to handle this.

What do you think... is this something that should be implemented in templates? Global blocks? Or something else entirely?

Pre-Gutenberg, you would separate the content/data from the layout by using custom fields to store stuff like the price/description/title/whatever and page template .php files to store the layout of the page and how the content is presented. of course you could still do this in Gutenberg by using blocks that are basically just custom field holders that don't actually render on the page, like what GCF does:
https://wordpress.org/plugins/gutenberg-custom-fields/

However, since Gutenberg is intended to make editing posts more visual, it would be really unfortunate if people using custom post types with a need for standardized layouts (WooCommerce products, for example) to settle for just throwing custom-field-input blocks onto the Gutenberg editor with no relation to the actual visual layout... or else make the editing experience visual but have layouts be stuck to individual posts with no easy way to apply changes to all of them at once.

@jasonbahl

This comment has been minimized.

jasonbahl commented Apr 9, 2018

Is it possible to have block templates for Page Templates?

I have some pages that have the need to have "fixed" sections of content, and some flexible "freeform" sections of content.

I would like for a user to select a "Page Template" that hydrates the block UI with the appropriate mix of locked and free-form blocks.

From what I can tell, that's not yet possible, but perhaps I'm missing something?

@wpscholar

This comment has been minimized.

Contributor

wpscholar commented Apr 12, 2018

I agree wholeheartedly with @jasonbahl. This is a must-have in my opinion.

There is a ticket to this effect here: #3835

Initially, I was thinking it would make sense to add a filter to the gutenberg_editor_scripts_and_styles() function for the $editor_settings variable right before it is passed to JS. This would allow for empty pages with a specific page template to have the block template and locking set as desired. However, it doesn't provide a clean experience when switching templates within (and without leaving) the editor. An optimal solution will ensure that all available templates and their associated block templates and locking settings are all registered and available in the Gutenberg editor so changes can take place immediately after the user changes the page template.

Currently, users are able to switch page templates within the Gutenberg editor, which triggers an EDIT_POST action. When that occurs, we would need to check if the page template was changed. Then, we could update the blocks on the page (assuming we have a good way of registering the block templates and making them accessible to JS). This would be perfect if there are no blocks on the page. However, the problem is when there are pre-existing blocks on the page. What happens to the user's content? What happens if they switch back to the previous template?

We could temporarily store a copy of the blocks in local storage for each page template; that way when the user switches the template back and forth without leaving the page it would allow us to change all the content and blocks without losing their data. I think it is safe to assume that after saving the post and leaving the page the old content can be forgotten, right?

I think it makes sense to store the page template with revisions so that restoring a page will not break the block structure. We don't want a restored revision to return content from a previous page template when the current page template doesn't match and doesn't support that block structure.

At the moment, I see a way to subscribe to the core data store via wp.coreData.default.subscribe() but not to the core/editor data store (which I can observe via the Redux DevTools extension). Is that by design, or am I just missing something? For maximum interoperability between Gutenberg (itself) and third-party Gutenberg blocks/extensions, I am thinking I should be able to subscribe to and dispatch actions to any registered data store.

Allowing this type of access to the data stores would make it possible to implement a custom solution like @greatislander mentioned where the block template could be changed based on the taxonomy term selected (granted you still have the issue of deciding what block template to use when a user selects multiple taxonomy terms). Or it could allow you to change the block template based on the post format, etc.

@westonruter

This comment has been minimized.

Member

westonruter commented Apr 12, 2018

@wpscholar

At the moment, I see a way to subscribe to the core data store via wp.coreData.default.subscribe() but not to the core/editor data store (which I can observe via the Redux DevTools extension). Is that by design, or am I just missing something?

According to the data docs, there is an example with a comment:

const { subscribe } = wp.data;
const unsubscribe = subscribe( () => {
	// You could use this opportunity to test whether the derived result of a
	// selector has subsequently changed as the result of a state update.
} );

I've taken this to mean that you have to subscribe to changes to the entire state tree for everything and then detect if a part of it has changed. That doesn't seem ideal, but it's what I've done in a recent PR to look at changes to a given post field in the core/editor store, specifically via a handleValidationErrorsStateChange function which is used to monitor changes via wp.data.subscribe( module.handleValidationErrorsStateChange ).

@wpscholar

This comment has been minimized.

Contributor

wpscholar commented Apr 12, 2018

Thanks @westonruter! I'll take a closer look at this.

@jasonbahl

This comment has been minimized.

jasonbahl commented May 4, 2018

@wpscholar @westonruter I've got template switching for a project I'm working on. I'm using WPGraphQL, so what I have doesn't make sense for a core Gutenberg merge (unless we do a WPGraphQL core merge as well 😜). . .but might be helpful should Gutenberg core want to provide a way to swap templates as well.

gutenberg-switch-page-templates

The way I have it working:

  • Subscribe to the template change
  • On change, ask the server for the corresponding template
    • note: I'm using WPGraphQL here to get the template back, but could be REST if there was an endpoint registered that could return the template
  • When GraphQL returns the template, use it to replaceBlocks

Here's a Gist of the client side code: https://gist.github.com/jasonbahl/2af7959e5d10c7eb6781fb86c097786e

If it's helpful at all, here's the Logic used to hook into the WPGraphQL Schema to provide the necessary field/args for GraphQL to respond with the template(https://gist.github.com/jasonbahl/d2df4874a9e3c19fd05767c21fa800a1). . .I'll likely make some adjustments here.

Also, one thing I want to add is having the current state of the template saved to post_meta before making the switch. You can see the GraphQL resolver is already setup to return the persisted template, if one exists. Not sure if meta is ideal for this, but will work for my immediate needs.

@jasonbahl

This comment has been minimized.

jasonbahl commented May 7, 2018

^ Follow up to my last post. The replaceBlocks method doesn't appear to set attributes. So, if you pass:

<!-- wp:oshpd/my-block {"someAttribute":"someValue"} /-->

What you end up with after using that in replaceBlocks is <!-- wp:oshpd/my-block /-->

Not quite sure what I'm missing and how to replace blocks with attributes set. 🤔

@LABCAT

This comment has been minimized.

LABCAT commented Jun 23, 2018

Is there anyway we could make a user select the page template when a page is first created?

@0aveRyan

This comment has been minimized.

Member

0aveRyan commented Sep 4, 2018

While this isn't in scope for the Block Templates API, the final solution should be considerate to potential 3rd party (or Core) UIs for a captive template picker flow akin to Google Docs.

screen shot 2018-09-04 at 10 11 55 am

@mtias

This comment has been minimized.

Contributor

mtias commented Oct 7, 2018

Closing this as the extent in which support mattered for phase 1 is in place. Phase 2 will work further on the saving mechanism and the UI for selecting templates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment