Skip to content
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

What is the proposed way to update blocks? #4849

Closed
fabianpimminger opened this issue Feb 3, 2018 · 55 comments
Closed

What is the proposed way to update blocks? #4849

fabianpimminger opened this issue Feb 3, 2018 · 55 comments

Comments

@fabianpimminger
Copy link
Contributor

@fabianpimminger fabianpimminger commented Feb 3, 2018

I've been wondering how content should be updated on old posts after the block html-markup changes?

Without using dynamic blocks it is not possible to "re-render"/update all blocks, am I correct? What is the proposed way to update non-dynamic blocks when site owners want to change the markup of the content? Is there any way to do this without opening all old posts and re-saving them? Or should we just use dynamic blocks when markup could change in the future?

@youknowriad
Copy link
Contributor

@youknowriad youknowriad commented Feb 3, 2018

I'll reply with a question. Do you think it's ok to update old content on a website when a plugin gets updated without the user's consent? Don't you think if the content is published, the user doesn't want it to change unless he does explicitly?

I think most of the time, this is not an issue.

If you'd like to know more about deprecated blocks, see this page https://wordpress.org/gutenberg/handbook/block-api/deprecated-blocks/

And yes, in some small use-cases (remains to be seen for static blocks), you want to update the content in which case, it's probably better to use a dynamic block.

Thanks for opening the issue, I'm closing it right now. Let me know if I didn't answer your concerns.

@youknowriad youknowriad closed this Feb 3, 2018
@fabianpimminger
Copy link
Contributor Author

@fabianpimminger fabianpimminger commented Feb 3, 2018

Let me give a theoretical example. I'm developing a block for my own blog. I use divs and other current markup to achieve this. Then, a new html element emerges in a future standard and I want to update all instances of this block in all posts because it's a better fit. As I'm the developer of my own block, I know how it will affect my blog.

I think this will drive many developers to choose dynamic blocks and this won't be such a small use-case?

I just wanted to get a confirmation if dynamic blocks are the way to go if I want to have the possibility of updating all instances of one block. Thanks :)

@youknowriad
Copy link
Contributor

@youknowriad youknowriad commented Feb 3, 2018

Yes, dynamic blocks are the way to go in this case. But your use case is not so common. In general content creators are very different from block authors.

Enjoy Hacking

@katerlouis
Copy link

@katerlouis katerlouis commented May 30, 2018

I am facing the same issue and absolutely agree with Fabian on this.

What if the frontend switches to a different carousel plugin, which requires new HTML structure for the galleries. From an editors perspective, the content isn't changing. From a devs perspective, quite a lot has to change.

Or what if you simply made a simple mistake in the blocks output and need to update it?

I mean, there must be a plan to deal with updating blocks, right? I don't even remotely see how updating blocks is an edge case.

When even the slightest changes in my block.js break the block in the editor, I'd really like to have frontend output with PHP hooks back :'(

image

Maybe I'm completely missing something here; please elaborate on what that is, because falling back to RichText or the HTML alone effectively makes complex blocks uneditable once the js code is updated.

@wturnerharris
Copy link

@wturnerharris wturnerharris commented Aug 9, 2018

So I think we are encountering this very issue, and our only solve is to either have a stable block that will rarely change from a markup-perspective or build deprecations? As an agency we will be constantly iterating over blocks for clients as design needs change or as content structure evolves. To dismiss use-cases is to ignore the community.

I do agree that some way to hook into the output to upgrade a block, to make intelligent decisions on whether to output content based on emptiness would be useful, especially for rapid development and without destroying non-markup content.

There should be some way to manage the data layer. Furthermore, if there is a hook for the "Convert to Blocks" button, then we might be able to refactor on the spot without losing data.

@wpmark
Copy link

@wpmark wpmark commented Aug 23, 2018

I also agree that this is a massive problem is the markup of a block cannot be changed without re-saving the posts that use the block. I have tried to add some doc on the dynamic blocks doc page here to explain that using dynamic blocks is the way to go here.

#8765

However, @tofumatt indicates this is not the way to go - therefore what is?

@youknowriad you mention

Do you think it's ok to update old content on a website when a plugin gets updated without the user's consent?

We are not talking about updating content - just markup.

@fabianpimminger
Copy link
Contributor Author

@fabianpimminger fabianpimminger commented Aug 23, 2018

@wpmark Good point, because @youknowriad said it is the way to go? I think this should be mentioned in the docs because it is an important factor to decide which type of block developers should use.

@youknowriad
Copy link
Contributor

@youknowriad youknowriad commented Aug 23, 2018

We are not talking about updating content - just markup.

How is that different from the technical perspective, if we allow people to change markup, they would be able to change content without the user's consent.

There is definitely some improvements we can make to the way we deal with those deprecated versions but dynamic blocks have their drawback:

  • If you disable the plugin, there's no content anymore
  • If for some reason the PHP filter is not executed (think about plugins exploiting the database), the content is there and it's semantically valid.

While I agree that it seems better devX wise, but for the user I'd argue that dynamic blocks are worse and Gutenberg should definitely not emphasize on those. Content blocks are better defined as static blocks for the user.

@fabianpimminger
Copy link
Contributor Author

@fabianpimminger fabianpimminger commented Nov 28, 2018

I've revisited Gutenberg yesterday (as the 5.0 release is near) and I still think that this is a fundamental problem of Gutenberg.

With the 5.0 release, there are still many blocks that are not 100% ready. The gallery block for example still doesn't solve all issues related to image sizes/responsiveness. As mentioned in #1450, blocks related to image handling will change after 5.0.

So if I update my most important posts after the 5.0 release to include some Gutenberg features (probably 25-50 out of ~100), I will have to manually update all posts again when 5.1 is released to get better support for image handling? And probably again when 5.2 is released?

Contrary to previous Wordpress updates, when image responsiveness (srcset attributes) just got added to ALL posts, this process seems overly cumbersome.

@jSanchoDev
Copy link

@jSanchoDev jSanchoDev commented Dec 12, 2018

Correctly me if I'm wrong (b/c I'm new to Gutenberg), but if I develop a plugin with 20 or 30 blocks and release it, then rewrite blocks markup for version 1.0.1, then for 1.0.2, etc. - I'd have to copy/paste the save() / edit() methods code from each previous version? And keep them forever?

If this is the case, then what is the best strategy - to keep the markup always the same? Or to use dynamic blocks for all blocks? What if I have a large markup for block and have to remove some tiny part of it, or correct an error - do I have to keep all this in deprecation history? This may result in huge js bundle size for version 2.0.0 :)

Also, what is the proper way to handle this behavior while developing a block? Each markup change basically invalidates the saved version and you can only convert it to HTML.

@davidcrandall99
Copy link

@davidcrandall99 davidcrandall99 commented Dec 14, 2018

If the blocks don't update dynamically, it kind of defeats the purpose of using them. I.e, if I change my theme, I may need the blocks to use different html in order to work cohesively with my theme. Otherwise, I'm forced to manually go through all posts and update every block individually.

Marrying the content to the UI is just bad content management. There's no reason that divs, columns, rows, etc., should be part of the SQL markup for the actual content.

Gutenberg has a lot of promise, but it's not a scalable solution for developers.

@roryashfordbentley
Copy link

@roryashfordbentley roryashfordbentley commented Jan 9, 2019

Hypothetically, how would the following be possible:

I have a blog with 1000s of posts. Within most of these posts are multiple Button Blocks that look like this:

<div class="wp-block-button"><a class="wp-block-button__link" href="https://google.com">Link to Google</a></div>

The client has made a support request to add an icon to each button and we would like to change the button markup to the following:

<a class="my-button-class" href="https://google.com">Link to Google<img src="myicon.svg"</a>

Right now, how would it be possible to roll this update out to all existing posts? Would I need to update the Block component and then use something like WP-CLI to do a database find and replace for all existing instances of the button?

@fabianpimminger
Copy link
Contributor Author

@fabianpimminger fabianpimminger commented Jan 9, 2019

Would I need to update the Block component and then use something like WP-CLI to do a database find and replace for all existing instances of the button?

Yes, … or open each post where the block has been used and save it again using the editor.

(just for the sake of the argument obviously. This could've been done using css for all instances)

@roryashfordbentley
Copy link

@roryashfordbentley roryashfordbentley commented Jan 9, 2019

Yeah this particular example could certainly use CSS I appreciate that :)

I have just been testing out the an ACF (Advanced Custom Fields) acf_register_block() functionality and I really like the way they store and handle the data for blocks.

ACF Gutenberg Blocks are stored in the DB like this:

<!-- wp:acf/button {"id":"block_5c360c7625570","data":{"field_5c3604866626a":"Link to Google","field_5c3604996626b":"https://www.google.com"},"name":"acf/button","align":"","mode":"preview"} /-->

Presumably this would suffer from similar issues with updating old instances of components but I'm a big fan of Key/Value/JSON stores and this way completely seperates markup from content. I can now load a theme component passing it the field values and that component handles all of the presentational styles.

@wturnerharris
Copy link

@wturnerharris wturnerharris commented Jan 10, 2019

@youknowriad - can this be re-opened? I'm not sure why you closed it. There's a lot of activity on this issue, and it's more widespread than the limited view for the use-cases in the wild.

@youknowriad
Copy link
Contributor

@youknowriad youknowriad commented Jan 11, 2019

Form the repository management doc https://wordpress.org/gutenberg/handbook/contributors/repository-management/

A healthy issue backlog is one where issues are relevant and actionable. Relevant in the sense that they relate to the project’s current priorities. Actionable in the sense that it’s clear what action(s) need to be taken to resolve the issue.

Any issues that are irrelevant or not actionable should be closed, because they get in the way of making progress on the project. Imagine the issue backlog as a desk: the more clutter you have on it, the more difficult it is to use the space to get work done.

I'd be happy to reopen the issue again but at the moment this is issue is not actionable. I agree that there's a tradeoff here in the way Gutenberg works but sometimes you need to make choices for some users instead of developpers. There's nothing we can do from the Core's side to address the use-cases here at the moment. Do you have any suggestions? actionable items? I'd be happy to reopen and explore if that's the case.

Thanks

@katerlouis
Copy link

@katerlouis katerlouis commented Jan 11, 2019

sometimes you need to make choices for some users instead of developpers

@youknowriad
I still don't see how the user benefits from static blocks more than he suffers from them. It's not only in the developers interest to update block-instances through thousands of posts automatically.

For what I intend to do with blocks, there is no question I go for dynamic blocks all the time. I fully agree that the representation/interpretation of data has no place in the database.

@leph83
Copy link

@leph83 leph83 commented Jan 20, 2019

I really want to develop my own block. And of course I will have to make changes to those blocks after they are used on several pages. I've tried the deprecated blocks, to avoid breaking my old versioned blocks completely. But having to save all pages with the used blocks to see my changes is a no go. There's got to be a better solution to that.

@braco
Copy link

@braco braco commented Mar 6, 2019

@youknowriad

I'd be happy to reopen the issue again but at the moment this is issue is not actionable. I agree that there's a tradeoff here in the way Gutenberg works but sometimes you need to make choices for some users instead of developpers. There's nothing we can do from the Core's side to address the use-cases here at the moment. Do you have any suggestions? actionable items? I'd be happy to reopen and explore if that's the case.

There are many Github issues raising the same concern. Forcing deprecation for markup changes as simple as adding another CSS class is totally insane. Gutenberg is currently crippled as a corporate CMS because of this – deprecation is not a reasonable solution for sites with a huge amount of content that need even MINOR layout changes. Even the upgrade path to the new block is broken, it's just such an infuriating overwrought mess that I would strongly dissuade orgs from adopting Wordpress at this point.

As far as protecting users, there could, at the very least, be some kind of Wordpress setting that allowed for automatic upgrading of blocks. You can also assume that some users will be in a controlled environment where they shouldn't be making decisions about what to upgrade. I'm curious how many of your largest, paying customers fall under that umbrella.

edit: there are entire companies built around the premise of having structured input with dynamic output, like Contentful, and Wordpress is frustratingly close to being able to fulfill this goal, but some of the decisions you guys have made are crazy making. There are so many arguments for types of content being attribute-first instead of layout-first that it's hard to even understand how this decision was reached.

@youknowriad
Copy link
Contributor

@youknowriad youknowriad commented Mar 6, 2019

There's still interest in improving the deprecation/validation/upgrade experience. There's no clear path forward at the moment, if you have proposals please share them in #7604

@leph83
Copy link

@leph83 leph83 commented Mar 19, 2019

How are other developers updating their blocks? I mean there has to be a way since there are so many plugins out there.

@codewithfeeling
Copy link

@codewithfeeling codewithfeeling commented Mar 21, 2019

I am amazed that this is such a problem. Seems like complete insanity that if you need to make any kind of a change to the HTML output, your blocks just fall apart. Maddening.

@MonsterKing
Copy link

@MonsterKing MonsterKing commented Mar 27, 2019

Depending on your situation, there are ways (though they may not be very elegant) to update blocks without using deprecation. For instance, I built a cover block for a client. After it was complete and in use, they wanted the option of having the entire block be "clickable." I added the necessary attributes and changes to the edit section. In the "save" section, I simply added a condition (if a URL has been entered, return html set 1; if not, return the original html). By saving the portion of the html that would be repeated in each condition as a const, I don't have to retype that. Now the same client has asked to have an optional overlay added. Since adding/changing a class after-the-fact causes a rendering error, I can apply the same/similar solution. It make be cringe-worthy to some, but it's really only a few extra lines of code and no pain of breakage or deprecation.

@studionenontwerp
Copy link

@studionenontwerp studionenontwerp commented Mar 28, 2019

Just don't use the 'save-section'. I am using Gutenberg just for display in admin and to set the attributes. For display on front-end I am using the wp-filter 'render_block'. A bit like 'render_callback' but this is a more user friendly way of editing a post. Inside 'render_block' I'm creating the html and catch the content of the 'innerBlocks' (all wrapped in my custom class).

add_action( 'render_block' , array( $this ,'render_block_html') , 10 , 2 );

inside 'render_block_html' I am checking for my blockName and call a function by blockName and suffix.

function render_block_html( $content , $block )
{
        if( strpos($block['blockName'] , 'nen/') === 0 )
        {                        
                $name       = str_replace( 'nen/', 'nen_block_', $block['blockName'] );
                $function   = str_replace( '-' , '_' , $name) . '_front_html' ;

                if( is_callable( $function ) )
                {
                        $attrs      = $this->prepare_attributes( $block );
                        $content    = call_user_func( $function , $content , $attrs , $block );
                }
        }
        return $content;
}

To get all attributes I'm merging the default attributes for my block-type with the attributes set in admin. This is because Gutenberg returns only the edited attributes.

function prepare_attributes( $block )
{
        $obj            = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
        $cur_attrs      = $block['attrs'];
        $return_attrs   = array();

        foreach( $obj->attributes as $attr => $setup )
        {
                if( array_key_exists( $attr , $cur_attrs ) )
                {
                        # cur content
                        $return_attrs[$attr] = $cur_attrs[$attr];
                }
                else
                {
                        # default value
                        $return_attrs[$attr] = ( array_key_exists( 'default' , $setup ) ? $setup['default'] : null ) ;
                }
        }

        $return_attrs = apply_filters( 'nen_block_'.$block['blockName'].'_front_html_attributes' , $return_attrs , $block );

        return (object)$return_attrs;
}

So for any custom block I only need to create a new function like 'nen_block_' . $name . '_front_html' to output the html. Just dont forget to add the innerBlocks (it's children).

function nen_block_button_front_html( $innerblocks , $attr , $block )
{
    return '<div class="nen-block-button ' . $attr->attributeName . ' ">' . $innerblocks . '</div>';
}
@leph83
Copy link

@leph83 leph83 commented Mar 29, 2019

Just don't use the 'save-section'. I am using Gutenberg just for display in admin and to set the attributes. For display on front-end I am using the wp-filter 'render_block'. A bit like 'render_callback' but this is a more user friendly way of editing a post. Inside 'render_block' I'm creating the html and catch the content of the 'innerBlocks' (all wrapped in my custom class).

add_action( 'render_block' , array( $this ,'render_block_html') , 10 , 2 );

inside 'render_block_html' I am checking for my blockName and call a function by blockName and suffix.

function render_block_html( $content , $block )
{
        if( strpos($block['blockName'] , 'nen/') === 0 )
        {                        
                $name       = str_replace( 'nen/', 'nen_block_', $block['blockName'] );
                $function   = str_replace( '-' , '_' , $name) . '_front_html' ;

                if( is_callable( $function ) )
                {
                        $attrs      = $this->prepare_attributes( $block );
                        $content    = call_user_func( $function , $content , $attrs , $block );
                }
        }
        return $content;
}

To get all attributes I'm merging the default attributes for my block-type with the attributes set in admin. This is because Gutenberg returns only the edited attributes.

function prepare_attributes( $block )
{
        $obj            = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
        $cur_attrs      = $block['attrs'];
        $return_attrs   = array();

        foreach( $obj->attributes as $attr => $setup )
        {
                if( array_key_exists( $attr , $cur_attrs ) )
                {
                        # cur content
                        $return_attrs[$attr] = $cur_attrs[$attr];
                }
                else
                {
                        # default value
                        $return_attrs[$attr] = ( array_key_exists( 'default' , $setup ) ? $setup['default'] : null ) ;
                }
        }

        $return_attrs = apply_filters( 'nen_block_'.$block['blockName'].'_front_html_attributes' , $return_attrs , $block );

        return (object)$return_attrs;
}

So for any custom block I only need to create a new function like 'nen_block_' . $name . '_front_html' to output the html. Just dont forget to add the innerBlocks (it's children).

function nen_block_button_front_html( $innerblocks , $attr , $block )
{
    return '<div class="nen-block-button ' . $attr['attributeName'] . ' ">' . $innerblocks . '</div>';
}

Well this is an interesting approach. Do you have an existing plugin example that you could share?

@codewithfeeling
Copy link

@codewithfeeling codewithfeeling commented Mar 29, 2019

@studionenontwerp Nice. Sounds like an approach worth pursuing to me. I'm not sure what you are referring to here:

don't use the 'save-section'

But the rest of the code makes sense. I like how you are only really using blocks as an admin-facing tool to set the attributes. Thanks - I'll try something along those lines.

@codewithfeeling
Copy link

@codewithfeeling codewithfeeling commented Mar 29, 2019

@studionenontwerp Thanks. So you just meant just not to implement the save method in the React component. Thanks for the clarification. I'm going to take your method for a spin - definitely seems like the best approach for now. Hopefully there will soon be a way to flag that block output can be overwritten, as I would love to be managing the content completely within React.

@roryashfordbentley
Copy link

@roryashfordbentley roryashfordbentley commented Apr 10, 2019

As has been discussed I still firmly believe that we should only be storing data in the database, I recently came across editor.js, a block based editor much like gutenberg but they use json to store block data and it's clear from the example on their site (https://codex.so/editor) just how great their data structure is:

"blocks" : [
        {
            "type" : "paragraph",
            "data" : {
                "text" : "Hey. Meet the new Editor. On this page you can see it in action"
            }
        },
        {
            "type" : "list",
            "data" : {
                "style" : "unordered",
                "items" : [
                    "It is a block-styled editor",
                    "It returns clean data output in JSON",
                    "Designed to be extendable and pluggable with a simple API"
                ]
            }
        }
]

Its simple clean and flexible. It only stores data and not code. It would mean that you could write components to render this data easily and cleanly and if you want to change how a block looks across the site it will take minutes vs hours because you don't need to find and replace every single database instance across the entire wp_posts table.

@leph83
Copy link

@leph83 leph83 commented Apr 27, 2019

@studionenontwerp I've tried to create a block like you described but getting an error for $this here:
add_action( 'render_block' , array( $this ,'render_block_html') , 10 , 2 );

Could you post or create a repository with the full plugin, so that I can analyze this? I'm still having troubles to understand how to get this up and running

@studionenontwerp
Copy link

@studionenontwerp studionenontwerp commented Apr 27, 2019

I merged it in my theme so it's not a plugin. $this refers to the class thats managing my custom blocks. The 'add_action' has to be in the '__construct()' function. The function should be inside the class.

I'll see what i can do to create a plugin. May this can help:



class NEN_Gutenberg_Blocks_Example
{
        
        private static $_instance = null;



        /**
         * Singleton instance
         *
         */

        public static function instance() 
        {
                if ( is_null( self::$_instance ) ) {
                        self::$_instance = new self();
                }

                return self::$_instance;
        }

        

        private function __construct() 
        {            
                add_action( 'render_block' , array( $this ,'render_block_html') , 10 , 2 );
        }
        
                
        function render_block_html( $content , $block )
        {
                if( strpos($block['blockName'] , 'nen/') === 0 )
                {                        
                        $name       = str_replace( 'nen/', 'nen_block_', $block['blockName'] );
                        $function   = str_replace( '-' , '_' , $name) . '_front_html' ;

                        if( is_callable( $function ) )
                        {
                                $attrs      = $this->prepare_attributes( $block );
                                $content    = call_user_func( $function , $content , $attrs , $block );
                        }
                        
                        // or use
                        // $content = apply_filters( $function , $content , $attrs , $block );
                }
                return $content;
        }
        
        
        function prepare_attributes( $block )
        {
                $obj            = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
                $cur_attrs      = $block['attrs'];
                $return_attrs   = array();

                foreach( $obj->attributes as $attr => $setup )
                {
                        if( array_key_exists( $attr , $cur_attrs ) )
                        {
                                # cur content
                                $return_attrs[$attr] = $cur_attrs[$attr];
                        }
                        else
                        {
                                # default value
                                $return_attrs[$attr] = ( array_key_exists( 'default' , $setup ) ? $setup['default'] : null ) ;
                        }
                }

                $return_attrs = apply_filters( 'nen_block_'.$block['blockName'].'_front_html_attributes' , $return_attrs , $block );

                return (object)$return_attrs;
        }
        
      
}
NEN_Gutenberg_Blocks_Example::instance();


function nen_block_button_front_html( $innerblocks , $attr , $block )
{
        return '<div class="nen-block-button ' . $attr->attributeName . ' ">' . $innerblocks . '</div>';
}

@jodamo5
Copy link
Contributor

@jodamo5 jodamo5 commented Jun 17, 2019

This is crazy that the intended functionality is that updated blocks will not update!

Our specific scenario is that we have built custom Gutenberg components for our clients' websites. One of these components includes an image as well as a heading, text and a link. When we originally built the block the image was not linked. But after using this block on a number of pages, we're now adjusting the block to make the image be linked too, in order to improve the user experience. It is ridiculous that we need to open up every page this block has been used on and re-save the page just to get the updated code to apply.

I agree with @braco - "Gutenberg is currently crippled as a corporate CMS because of this".

@MonsterKing
Copy link

@MonsterKing MonsterKing commented Jun 17, 2019

@jodamo5
Copy link
Contributor

@jodamo5 jodamo5 commented Jun 18, 2019

@MonsterKing - sorry I think you misunderstood the example. We're fine with writing the conditioning logic in php for when to insert the URL link. The problem we have is that we originally didn't have this logic in the Gutenberg block. Then we decided to add the logic. That new logic works fine whenever we edit a page and re-save it (this causes Gutenberg to save it with the updated block configuration). However the big problem is that to apply this updated block to existing content, we have to open up every page on which this block has been used and then re-save that page, so that it gets the new updated Gutenberg block code. Without this process the updated Gutenberg block code does not get applied.

Thankfully the site we needed to do this on had less than 20 pages that needed to be updated. But we have a site we're working on shortly that will have over 4000 posts when it is launched. If we update the code for a block on that site, we definitely don't want to have to re-save 4000 posts one at a time in order for them to get the updated block layout!!

@MonsterKing
Copy link

@MonsterKing MonsterKing commented Jun 18, 2019

@jodamo5
Copy link
Contributor

@jodamo5 jodamo5 commented Jun 19, 2019

Thanks to the comments on this post, and some other posts, I can confirm that dynamic blocks are what should be used if you want updates to blocks to be reflected immediately on your site.

Since I had to read multiple posts to understand what was going on, here is a brief summary of key points that might help others:

Default Guternberg behaviour:

  • When creating Gutenberg blocks if you use the "Save" function, which is recommended in nearly all the tutorials, the exact HTML content of your block is saved into the database.
  • If you then update the code of your block at all (even by adding a new class to an element), the changes will not be seen on the front end - Wordpress keeps pulling in the HTML it has saved in the database.
  • When a user edits a page where the block has been updated, they'll see a warning on that block saying it has been externally modified. They need to choose whether to edit it as HTML or convert it to a block again. This is annoying (and concerning!) for users - not a good user experience at all. If they update the block and save the page, only then will the updated block format be seen on the front end of the site.

Using Dynamic Blocks Instead

  • Using dynamic blocks means that when you update the code of your block, the changes will be immediately seen on the front end and there are no verification issues in the editor.
  • To create a dynamic block you need to return null for the save function. This stops Gutenberg saving the HTML into the database. (As explained by @studionenontwerp above).
  • Attributes in your block are automatically saved to the database when the page is saved. (These are stored as HTML comments in the post entry in the database, which Gutenberg reads). So save all content of your block as attributes. In my example we save the Title, the image URL, the link text and the link URL as attributes. This means all these details are saved in the database and can be used when we render the block.
  • We then use render_callback (or @studionenontwerp used render_block) when displaying the block on the front end, so that our block layout code is pulled in dynamically. We can access the attributes and use these as the data when rendering the block on the front end. So the HTML is always built on the fly from our PHP code, which means that any update we make to the block will be immediately replicated across the site.

We are going to switch all of the blocks we have created over to dynamic blocks, as we always want to have changes immediately applied across all pages where the block has been used.

@maurisrx
Copy link

@maurisrx maurisrx commented Jun 20, 2019

I am facing the same issue and it's quite frustrating since I update my custom block HTML quite often. And I agree with @jodamo5 that we can use dynamic block for this issue. CMIIW, but I think if we ever update the block attributes, the block verification that will break the old block will still happen, right?

@leph83
Copy link

@leph83 leph83 commented Jun 20, 2019

So I was looking for a tutorial to build a dynamic block and stumbled upon this
But after installing the plugin the only block that was not working was the dynamic one. Already wrote an issue and hope there is a fix.

@leph83
Copy link

@leph83 leph83 commented Jun 21, 2019

@jodamo5 do you mind posting a working example? This is my 3rd attempt to build a block where I can add an image, title, subtitle, description and link. On the inspector I want to have a dropdown with different styles. Depending on the style I want to add a different markup. I would love to build my own gutenberg block with es5.

I've got following up and running. I can change color attributes and they are saved correctly. But if you type something in the block, that won't be saved.
https://github.com/leph83/dynamic_gutenberg_block

@vietvho
Copy link

@vietvho vietvho commented Jan 1, 2020

Thanks to the comments on this post, and some other posts, I can confirm that dynamic blocks are what should be used if you want updates to blocks to be reflected immediately on your site.

Since I had to read multiple posts to understand what was going on, here is a brief summary of key points that might help others:

Default Guternberg behaviour:

  • When creating Gutenberg blocks if you use the "Save" function, which is recommended in nearly all the tutorials, the exact HTML content of your block is saved into the database.
  • If you then update the code of your block at all (even by adding a new class to an element), the changes will not be seen on the front end - Wordpress keeps pulling in the HTML it has saved in the database.
  • When a user edits a page where the block has been updated, they'll see a warning on that block saying it has been externally modified. They need to choose whether to edit it as HTML or convert it to a block again. This is annoying (and concerning!) for users - not a good user experience at all. If they update the block and save the page, only then will the updated block format be seen on the front end of the site.

Using Dynamic Blocks Instead

  • Using dynamic blocks means that when you update the code of your block, the changes will be immediately seen on the front end and there are no verification issues in the editor.
  • To create a dynamic block you need to return null for the save function. This stops Gutenberg saving the HTML into the database. (As explained by @studionenontwerp above).
  • Attributes in your block are automatically saved to the database when the page is saved. (These are stored as HTML comments in the post entry in the database, which Gutenberg reads). So save all content of your block as attributes. In my example we save the Title, the image URL, the link text and the link URL as attributes. This means all these details are saved in the database and can be used when we render the block.
  • We then use render_callback (or @studionenontwerp used render_block) when displaying the block on the front end, so that our block layout code is pulled in dynamically. We can access the attributes and use these as the data when rendering the block on the front end. So the HTML is always built on the fly from our PHP code, which means that any update we make to the block will be immediately replicated across the site.

We are going to switch all of the blocks we have created over to dynamic blocks, as we always want to have changes immediately applied across all pages where the block has been used.

But Serversiderender is not useful in some case:
Example: When you build a plugin with many elements and attributes inside. After that, you want to update features that change much more attribute and belong attribute/elements, js action. The ServerSiderender will make your editing Block very slow and can not call new js inside new ServerSiderender.
Could we disable the Block Validations between too version update and just get the newest Block saving by JS?
Thank you

@neryams
Copy link

@neryams neryams commented Feb 14, 2020

To anyone looking for a way to get around this, you can tie into the the_post hook and update the post content using your plugin. Detect if a post has an older version of your block output somehow, then update it using string replaces or whever methods you have at your disposal.

This will also work in the admin edit systems - as long as the hook's output matches the output of your new block code, the admin won't complain.

@pratham2003
Copy link

@pratham2003 pratham2003 commented Feb 20, 2020

There is also a technical road block to this that no one has spoken of so far in this thread..

The block's structure is defined in a JavaScript file which generates the HTML (client side) when editing an article.

We would need to figure out a way for PHP to update multiple articles when a block's structure is updated in JavaScript.
Some kind of PHP/NodeJS based server-side rendering of JavaScript?

Can someone point me to a page that documents reasons for WordPress' decision to use JS for HTML template instead of PHP?

@neryams
Copy link

@neryams neryams commented Feb 20, 2020

There is also a technical road block to this that no one has spoken of so far in this thread..

The block's structure is defined in a JavaScript file which generates the HTML (client side) when editing an article.

We would need to figure out a way for PHP to update multiple articles when a block's structure is updated in JavaScript.
Some kind of PHP/NodeJS based server-side rendering of JavaScript?

Can someone point me to a page that documents reasons for WordPress' decision to use JS for HTML template instead of PHP?

I think the upgrade would be a manual process; i.e. an upgrade script that the author would have to write that would run to update the HTML properly from one version to another, if the output needs to be changed.

@ckmahoney
Copy link

@ckmahoney ckmahoney commented Mar 10, 2020

There is also a technical road block to this that no one has spoken of so far in this thread..

The block's structure is defined in a JavaScript file which generates the HTML (client side) when editing an article.

We would need to figure out a way for PHP to update multiple articles when a block's structure is updated in JavaScript.
Some kind of PHP/NodeJS based server-side rendering of JavaScript?

Can someone point me to a page that documents reasons for WordPress' decision to use JS for HTML template instead of PHP?

@pratham2003 If you want to edit the content of multiple posts at once, you can use some of the ideas in this StackOverflow post.

As far as a reason to use JS over PHP, Gutenberg has explained its intentions pretty thoroughly in the handbook. In short it is about the editing experience. If you want strong UX, you need JavaScript.

I'd support it further by saying that JavaScript is undeniably the immediate future of web development. Through popular frameworks, including React, we are able to easily create stunning UX. PHP does not provide stunning UX.

@MonsterKing
Copy link

@MonsterKing MonsterKing commented Mar 10, 2020

@ddluc
Copy link

@ddluc ddluc commented Mar 17, 2020

Do you think it's ok to update old content on a website when a plugin gets updated without the user's consent? Don't you think if the content is published, the user doesn't want it to change unless he does explicitly?

@youknowriad

Why not just let the user choose? I do agree that it would be bad if the plugin was updated and the markup (or worse, the content) was modified, resulting in unconsented updates on a user's website. But, I think that only applies to publicly released plugins. For me, I'm a known vendor for my clients, and any updates I'd make to a block have explicit constent—or have been requested—to be updated.

What would it take for there to be a setting that the user can set from the block sidebar, which allows static blocks to automatically apply any updates that have been made in the block's save method? I don't have experience contributing to WP, but this is such a headache for me that I'd be willing to devote some time to learning and implementing this feature.

It would be a much much better development experience if the front-end portion of the block could just be built using Javascript/JSX rather than having to revert to PHP just so I can have my edits automatically detected and applied.

@marvinpoo
Copy link

@marvinpoo marvinpoo commented Apr 7, 2020

Why not just let the user choose? I do agree that it would be bad if the plugin was updated and the markup (or worse, the content) was modified, resulting in unconsented updates on a user's website.

Exactly!

I have no idea how everyone on here works, but I am sure most, if not all of us are following the laws, because as far as I know altering code without the consent of the owner (aka Client) is considered illegal in most western countries anyway, so why use the excuse of "you could update the content without consent" if it would include a illegal action of the dev. Is this the strategy of community management of Wordpress since Gutenberg? It's time to admit there have been some major problems in handling the transition and was forced way to hard and way to fast.

@jodamo5
Copy link
Contributor

@jodamo5 jodamo5 commented Apr 7, 2020

I have no idea how everyone on here works, but I am sure most, if not all of us are following the laws, because as far as I know altering code without the consent of the owner (aka Client) is considered illegal in most western countries anyway,
@marvinpoo

I think you've got things confused. If the law worked the way you describe then all plugin updates that have been happening for many years would be illegal! Because plugin updates alter code used on sites. Instead, let's take a step back and look at the main issue being described here:

A developer creates a block. That block is used on hundreds of pages - or maybe even hundreds or thousands of sites. The developer then wants to make some improvements to the block - maybe because users have requested improved functionality, or there is a bug with recent browser updates, or the developer finds a problem that should be fixed. When the update is applied to the site, the fix should be applied immediately to all current instances of the block, so that the issue is fixed everywhere they block is used. However, with the current deprecation process, all of the existing blocks would keep their old buggy state and only new blocks would get the fixed version. Multiply this across hundreds or thousands of pages - that is why it is impractical to get the user to go to every page that the block is used and edit and resave to upgrade to the new block.

Our specific scenario that we personally had to deal with was that one of the custom Gutenberg blocks we built for a client's website included an image, a heading, text and a link. When we originally built the block the image was not linked only the heading was. But after using this block on a number of pages, we then wanted to adjust the block to make the image be linked too, in order to improve the user experience. It is ridiculous that we need to open up every page this block has been used on and re-save the page just to get the updated code to apply. When a client requests a change like this, Gutenberg blocks should all update automatically to reflect the new version of the block - which is exactly what used to happen with PHP blocks in Wordpress. For us to achieve this, we now build all blocks as Dynamic Blocks. But this is not exactly ideal as it has some limitations (e.g. no nested blocks) and isn't the intended use of Gutenberg.

So this is still definitely an area that needs improvement! The conversation around how to improve this is happening on #7604 but there doesn't seem to be much confirmed progress so far.

@marvinpoo
Copy link

@marvinpoo marvinpoo commented Apr 8, 2020

I think you've got things confused. If the law worked the way you describe then all plugin updates that have been happening for many years would be illegal! Because plugin updates alter code used on sites.

Thats technically something different. When you buy/download a plugin and enable auto updates/ updates, then you give consent to the altering or even agreed to the tos of the distributors shop and their conditions. If the dev changes something on the website without the consent of the client, then they would theoretically do a unauthorized modifiaction. Tho, as with everything in law it is, if no one sues, no one is sued. It's like with domains. If you would just move a domain from A to B without the consent of your client, you have breaken the law, but no (serious & integer) developer would move the domain of a client without their permission, same goes for altering the website/priject in general.

What I was trying to express was, that in the normal behaviour a developer has a contract or atleast the permission to change/edit/differ something in the code. If you as developer change something on a website of a (ex)client without their consent then you are theoretically breaking the law. That doesn't mean in most cases your (ex)client wouldn't be thankful for the change (as example security fix or similar). But no developer would just go to their clients website and edit something out of the blue.

But afterall, that was just a side node pointing to the excuse that this feature is not being aimed on because devs would alter the content, which puts the developer into a position of distrust by a thirdparty bystander. I am sure most developers have contracts or similar agreements with their clients.

Our specific scenario that we personally had to deal with was that one of the custom Gutenberg blocks we built for a client's website included an image, a heading, text and a link. When we originally built the block the image was not linked only the heading was. But after using this block on a number of pages, we then wanted to adjust the block to make the image be linked too, in order to improve the user experience. It is ridiculous that we need to open up every page this block has been used on and re-save the page just to get the updated code to apply. When a client requests a change like this, Gutenberg blocks should all update automatically to reflect the new version of the block - which is exactly what used to happen with PHP blocks in Wordpress. For us to achieve this, we now build all blocks as Dynamic Blocks. But this is not exactly ideal as it has some limitations (e.g. no nested blocks) and isn't the intended use of Gutenberg.

This is so true and basically exactly the same problem we have encountered recently (tho this wasn't the first time this annoyed us). Our example was different but very close. We've used infocards on some sites and wanted to add the ability for the client to choose their own icon at the infocard instead of a simple arrow icon. The old version and the new version are technically the same except for the fact that the content of the

<i class="material-icons">{ attributes.urlicon }</i>

is being altered by the input of it.

I think it is very unpractical for both sides, the developer and the client and needs some work. We have some clients that work on their own content and for some clients we edit the content. If only we would be changing the content, I could live with that, but when I have to tell a client that he has to edit all of his pages because we added a new icon feature, that raises some questions in our clients if we are even competent enough. We understand in the technical way why and how this needs to be done atm, but when I tell that a client who understands the basics of the web, they just plainly ask us, "why did you choose that cms in the first place then?" and honestly, the client is correct. Why would we keep using wordpress if the basics of contenting will always bring up problems like this.

For us to achieve this, we now build all blocks as Dynamic Blocks. But this is not exactly ideal as it has some limitations (e.g. no nested blocks) and isn't the intended use of Gutenberg.

Yes, that is my concern, too.
As @youknowriad stated in 2018

There is definitely some improvements we can make to the way we deal with those deprecated versions but dynamic blocks have their drawback:
If you disable the plugin, there's no content anymore
If for some reason the PHP filter is not executed (think about plugins exploiting the database), the content is there and it's semantically valid.

Thank you for linking to the correct issue, by the way. Let's hope there will be a acceptable solution for this topic at some point. Until then, we will keep the use of blocks at a minimum.

@pwkip
Copy link
Contributor

@pwkip pwkip commented Aug 8, 2020

@youknowriad concerning these quotes:

Do you think it's ok to update old content on a website when a plugin gets updated without the user's consent?

and

How is that different from the technical perspective, if we allow people to change markup, they would be able to change content without the user's consent.

Plugin authors already have the power to do this. Nothing (technical) is preventing them from running a DB query and modifying the code parts between <!-- org:block --> and <!-- /org:block -->

Now they would do it all in their own way, instead of doing it in a well documented manner. Having an approach well documented in one place, could also include a best practices note to encourage plugin developers to actually ask the user for consent before running the actual DB updates.

I am not saying that updating a block should automatically update all existing posts. What I'm (and I believe most people here are) asking is some kind of standardized way or documentation on how to bulk update the blocks in existing posts.

Do you have any suggestions? actionable items? I'd be happy to reopen and explore if that's the case.

Here's a suggestion:

  • add a function called update_block($block_name, $post_id) that will convert all the $block_name blocks in a post to their updated version.

Would this be sufficient to reopen the issue?

@pwkip
Copy link
Contributor

@pwkip pwkip commented Aug 16, 2020

Since this doesn't seem to be a priority for the core team, I went ahead and created a plugin that allows updating blocks. It is very raw, so I wouldn't recommend using it on production websites at this stage. There might be some useful code snippets for developers. https://github.com/pwkip/bulk-update-blocks

@cbratschi
Copy link

@cbratschi cbratschi commented Oct 30, 2020

In my opinion the main issue is that the whole architecture is markup oriented and not data oriented at the end. In order to support the deprecation handling path, the same old markup has to be generated forever or the block is broken. I would like to have a way to completely decouple blocks from the generated markup. This would allow developers to focus on the attributes and provide a more stable process to handle older block data. I am wondering why this does not exist and why there is no versioning available.

Simple example of a block registration:

{
    ...
    version: '1.0.1',
    migration: (attributes, from, to) => {
        switch (from) {
            case '1.0.0':
                //upgrade data
               break;

            //throw exception if migration not possible (broken block case; should be avoided at all costs)
        }
    }
}

The other issue is that rendering is done mostly on the client side and updates are only possible through a manual edit and save. Due to a strict versioning it should be possible to iterate through all posts and update the markup to the latest version (if not up-to-date). A lot of plugins have to scan through all posts, for instance to generate a database index, so this is quite familiar to most WordPress users.

Just my two cents.

@juandahveed
Copy link

@juandahveed juandahveed commented Nov 5, 2020

@studionenontwerp I know i'm a little late to the party, but I love the approach. Any idea why the prepare_attributes method throws an error with this line?

$obj = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );

it always returns NULL but I'm not sure why.

@studionenontwerp
Copy link

@studionenontwerp studionenontwerp commented Nov 5, 2020

@juandahveed , I think your block is not registered when you use this line of code. I register all my blocks in 'init' , priority 9.

add_action( 'init', 'register_my_blocks' , 9 );

@juandahveed
Copy link

@juandahveed juandahveed commented Nov 5, 2020

@studionenontwerp I'm just not understanding... In the example above, you used JavaScript to register the block.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet