WordPress / gutenberg

The Block Editor project for WordPress and beyond. Plugin is available from the official repository.
https://wordpress.org/gutenberg/
Other
10.3k stars 4.11k forks source link

Programmatically Initialize Block Variation on Editor Load #63563

Open jethin opened 1 month ago

jethin commented 1 month ago

Hi. I'm trying to programmatically insert a block variation on editor load and am getting a 'block type not registered' error. I believe this might be related to lazy initialization of block variations discussed here and here. Not sure if there are any existing methods to accomplish this, but I can't find them. Ideally I'd like to include my block variation in a custom post type template attribute, but I could also initialize/insert it along with registerBlockVariation (JavaScipt) if that's the preferred method.

I'm not sure exactly what category this issue fall under; apologies, and please re-classify it if I've got it wrong. Thanks.

What problem does this address?

Block editor does not initialize block variations on load; no known programmatic method to initialize specific block variations.

What is your proposed solution?

Add method to programmatically initialize block variations if it doesn't exist.

talldan commented 1 month ago

@jethin Would you be able to share some approximate code of what you're trying to achieve?

I might be wrong, but I don't think you can refer to your block variation in templates via its name, you'd need to instead need to add the base block type with all the preset attributes for the variation.

For example, for core blocks, grid is a variation of group with the layout type attribute set to 'grid'. If I want to insert one programmatically I'd have to do this:

var block = wp.blocks.createBlock('core/group', {
  layout: { type: 'grid' }
} );
wp.data.dispatch('core/block-editor').insertBlock( block )
jethin commented 1 month ago

Hi @talldan . I had actually implemented your suggestion, but my code is pretty ugly. Three issues:

  1. I was unable to set some of the (protected?) attributes using createBlock() (title, allowedControls). So I had to register a block variation and copy its attributes individually to the core block.

  2. In order to access the block variation's attributes I needed to wait for the REST api to bootstrap -- wp.dom fires too early. So sadly, I had to set a timeout routine to check when the block variations were registered.

  3. Ideally I'd like to include my block variation in the 'template' blocks attribute when registering my custom post type. But I can't do this if it isn't registered/initialized.

I guess ultimately I think it would be nice to have a wp.blocks.createBlockVariation() or wp.blocks.initializeBlockVariation() method (or php versions.) But I'm not sure such things exists, or why they can't/shouldn't. Thanks.

talldan commented 1 month ago

@jethin Ah, I'm not saying you should use insertBlock, it's just a simple test you can run in your console that shows how variations are presets of attributes, the same will work using a block template. Lots of the templates provided by themes include variations like 'row' and 'stack' in the same way.

Similarly, when you insert something like a youtube block (variation of an embed) there's nothing special that tells the editor it's a youtube block other than the block attributes (providerNameSlug is a normal attribute set to the value youtube, which the variation's isActive callback uses to determine the currently active variation):

<!-- wp:embed {"providerNameSlug":"youtube","responsive":true} /-->

I was unable to set some of the (protected?) attributes using createBlock() (title, allowedControls). So I had to register a block variation and copy its attributes individually to the core block.

I'm not really sure what these things (title, allowedControls) are, but I don't think they're block attributes. I'd need more information about what type of variation you're trying to insert to be able to help more.

In order to access the block variation's attributes I needed to wait for the REST api to bootstrap -- wp.dom fires too early. So sadly, I had to set a timeout routine to check when the block variations were registered.

I don't completely follow what you're saying here. It'd be good if you could provide more information about the type of embed you're implementing (including code snippets).

Ideally I'd like to include my block variation in the 'template' blocks attribute when registering my custom post type. But I can't do this if it isn't registered/initialized.

From my testing you can register variations at any point, and they'll be applied to existing blocks. Here's an example of me creating a (contrived) variation for a paragraph block that has a anchor attribute of 'test' for an existing block after the editor is loaded:

https://github.com/user-attachments/assets/c3f9638e-355b-4dcf-af01-0c3007efd1e4

The block immediately updates to reflect it's a variation. (Unfortunately in the video I was scrolled down, but the block label did say 'Paragraph' prior to me registering the variation).

wp.blocks.createBlockVariation() or wp.blocks.initializeBlockVariation()

How would this differ from wp.blocks.registerBlockVariation?

jethin commented 1 month ago

Gotcha @talldan . Basically the issue is that registered block variations aren't initialized on editor load, and thus not available for programmatic insertion.

My ultimate goal is to programmatically insert a new query block variation with highly customized attributes into the editor when a new custom post type post is created. When attempting to do this via JS loaded in 'enqueue_block_editor_assets' I'm running into the issues I mentioned above. Below is my JS code, which is working but also kind of a mess. It would be great if I could programmatically register, initialize and insert the highly-customized block variation into the editor via simple methods that 'just work' within the WP/Gutenberg ecosystem.

const tagPageQueryAttributes = {
        namespace: 'sotp/tag-page-query',
        query: {
                postType: 'post',
                inherit: false,
                tag_page_query: true
        }
};

const tagPageQueryInnerBlocksTemplate = [
        [ 'core/post-template', {"className":"tag-page-posts"},
                [
                        [ 'core/post-featured-image', { "isLink":true, "sizeSlug":"medium" } ],
                        [ 'core/post-title', { "isLink":true } ],
                        [ 'core/post-date', {} ],
                        [ 'core/post-excerpt', {} ]
                ]
        ]
];

wp.blocks.registerBlockVariation(
        'core/query', {
                name: 'tag-page-query',
                title: 'Tag Page Query',
                description: 'Displays posts tagged with value in tag_slug custom field. Displays recent posts if tag_slug is not set.',
                isActive: ['namespace'],
                allowedControls: [],
                attributes: tagPageQueryAttributes,
                innerBlocks: tagPageQueryInnerBlocksTemplate
        }
);

wp.domReady(function () {

    const queryVariations = wp.blocks.getBlockVariations('core/query') ? wp.blocks.getBlockVariations('core/query') : null;
    function addTagPageQueryToEditor(){
        if( queryVariations.length ){
            clearInterval(checkForVariations);

            const getTagPageQueryBlock = wp.data.select( 'core/block-editor' ).getBlocks().find(({ attributes }) => attributes.namespace === "sotp/tag-page-query");
            const tagPageQueryExists = getTagPageQueryBlock ? true : false;

            if( !tagPageQueryExists ) {

                const tagPageQueryVariation = queryVariations.find(({ name }) => name === "tag-page-query");
                if( tagPageQueryVariation ){

                    const newTagPageQuery = wp.blocks.createBlock('core/query');
                    newTagPageQuery.attributes = tagPageQueryVariation.attributes;
                    // newTagPageQuery.name = tagPageQueryVariation.name; // not editable
                    newTagPageQuery.title = tagPageQueryVariation.title;
                    newTagPageQuery.description = tagPageQueryVariation.description;
                    newTagPageQuery.isActive = tagPageQueryVariation.isActive;
                    newTagPageQuery.innerBlocks = wp.blocks.createBlocksFromInnerBlocksTemplate(tagPageQueryInnerBlocksTemplate);

                    const tagPageParagraph = wp.data.select( 'core/block-editor' ).getBlocks().find(({ attributes }) => attributes.placeholder !== undefined && attributes.placeholder.includes("Tag Page Query") );

                    if(tagPageParagraph){
                        wp.data.dispatch('core/block-editor').replaceBlock(tagPageParagraph.clientId, newTagPageQuery);
                    }
                    else{
                        wp.data.dispatch('core/block-editor').insertBlocks(newTagPageQuery);
                    }
                }

            }
        }
    }

    const checkForVariations = setInterval(addTagPageQueryToEditor, 1000);

});
talldan commented 1 month ago

Basically the issue is that registered block variations aren't initialized on editor load, and thus not available for programmatic insertion.

I don't think you need to wait for the variation to be initialized, you can insert the query loop block with the correct attributes first (e.g. in a template) and initialize the variation separately in a later hook. The variation will retroactively apply to the already present block. Your code already stores the variations attributes, so you shouldn't have to look it up using getBlockVariations.

I think the only time this wouldn't work is if your variation itself is only registered conditionally.

Some core blocks have conditionally registered post types and the problem is solved by adding variations server-side - here's the template part block, which adds a variation for each template part instance (e.g. header, footer): https://github.com/WordPress/gutenberg/blob/88dffa048232c538ffdcaff6cd6a5ece2454706f/packages/block-library/src/template-part/index.php#L297

It might be possible to replicate that using the get_block_type_variations filter, though I haven't tried it.

Those server registered variations are enhanced client-side to add JS-only functionality like isActive and icon: https://github.com/WordPress/gutenberg/blob/88dffa048232c538ffdcaff6cd6a5ece2454706f/packages/block-library/src/template-part/variations.js#L24

I agree though there could be some improvements around the API.