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 · 29 comments

Comments

Projects
None yet
@fabianpimminger
Copy link
Contributor

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link
Contributor Author

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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 wpmark referenced this issue Aug 23, 2018

Closed

Docs: Add user input dynamic block info #8765

0 of 4 tasks complete
@wpmark

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link
Contributor Author

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link
Contributor Author

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link
Contributor Author

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link

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.

@raffjones

This comment has been minimized.

Copy link

raffjones 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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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?

@raffjones

This comment has been minimized.

Copy link

raffjones 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.

@studionenontwerp

This comment has been minimized.

Copy link

studionenontwerp commented Mar 29, 2019

When creating a block in js it looks like this. The 'edit:' renders editing/display stuff, the 'save:' renders html for front-end that is saved inside post_content/database.

In my opinion saving styling-html into the database should be avoided if possible. Once saved you can never change it unless each post is edited individualy. I prefer working with custom post types where each post type gets its own front-end layout. Detailed post type information is saved as postmeta using AdvancedCustomField. On Front-end this postmeta is displayed inside its own layout, separated from the main content. Saving this layout inside each post is not my way of building webpages. I want to be able to add or change elements. So the gutenberg option for templates per custom post type (predefined set of blocks) is not what i should use, unless the 'save:' returns null :-)

( function( data, blocks, editor, i18n, element , components ) 
{
        var el                  = element.createElement;
	var __                  = i18n.__;
    
	blocks.registerBlockType( 'nen/button', 
        {
                title: __( 'Button', 'nen' ),
		icon: 'sos',
		category: 'nen',
                
                attributes: {
                        myRange:            { type: 'number',   default: null, },
			myCheckbox:         { type: 'boolean',  default: false, },						
		},
                
		edit: function( props ) 
                {
                        // set variables
                        var attr            = props.attributes;

                        // set onChange functions
                        function onChangeMyRange( value ) { 
                            props.setAttributes( { myRange: value, } ); }
                        function onChangeMyCheckbox( value ) { 
                            props.setAttributes( { myCheckbox: value, } ); }
                        
                        // building editor (example)
                        return  [
                                // inspector stuff 
                                el( editor.InspectorControls, { key: 'inspector' },
                                    el( components.PanelBody, { className: 'nen-panel', title: 'Title' , initialOpen: true },
                                        el( components.RangeControl, {   
                                                className: 'nen-range',
                                                help: __('Range', 'nen' ),
                                                value: attr.myRange,
                                                onChange: onChangeMyRange, }
                                        ),
                                        el( components.CheckboxControl, {
                                                className: 'nen-checkbox',
                                                label: __('Checkbox','nen'),
                                                checked: attr.myCheckbox,
                                                onChange: onChangeMyCheckbox,
                                            }
                                        ),
                                    ),
                                ),
                                // build editor html
                                el( 'div', { className: 'nen-button', },
                                    el( 'div' , { className: 'nen-button-inner' }, 
                                        el( editor.InnerBlocks, {} ),
                                    ),
                                ),
                        ]
                },
                
                save: function( props ) 
                {
                        // to build front-end html use wp-filter 'render_block' to create html
                        // best advantage: 
                        //  + no html inside post_content / database
                        //  + editable if needed
                        //  + no gutenberg error and breaking of blocks
                        
                        return null;
                }
        });
}(
        window.wp.data,
        window.wp.blocks,
        window.wp.editor,
        window.wp.i18n,
        window.wp.element,
        window.wp.components,
) );

Note that if your block has innerBlocks (children) you need to save them:

save: function( props ) 
{        
            return el( editor.InnerBlocks.Content , {} ) ;
}
@raffjones

This comment has been minimized.

Copy link

raffjones 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

This comment has been minimized.

Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.