Gutenberg: Allow block to be constrained to only be used in root (when it has no parent)

Created on 10 Jul 2018  路  20Comments  路  Source: WordPress/gutenberg

I've been looking for a way to force a block to only be used at the root level. I'm trying to create a block taxonomy where only one block type (A) is allowed in the root, and it only allows another block type (B) as its child, and then B allows a restricted set of blocks, but not A or B, for example:

A > B > !(A|B)

The context here is implementing AMP Stories in Gutenberg. The structure of an AMP Story is as follows:

image

So I have an amp-story post type which should only take 1 or more amp-story-page blocks, and the amp-story-page blocks should only allow one or more amp-story-grid-layer blocks, and then lastly amp-story-grid-layer allows a subset of the core blocks (e.g. paragraph, video, image) but not any amp-story-* blocks.

At the moment, the amp-story-page block is offered in the inserter to be added to the root and to any of the other nesting blocks because there is nothing constraining it otherwise.

I tried a top-down and bottom-up approach where I explicitly set the allowedBlocks _and_ the parent block property but thus resulted in some infinite recursion error.

I also tried to register a block with parent: [] but this just results in the inserter being hidden entirely, since all of the blocks other than amp-story-page have a parent set to be amp-story-page or amp-story-grid-layer.

Discussed in Slack at https://wordpress.slack.com/archives/C02QB2JS7/p1531158200000319

/cc @noisysocks

Needs Dev Needs Technical Feedback [Feature] Block API [Feature] Nested / Inner Blocks

Most helpful comment

If it helps, here is another use case: custom "slides" post type with "slide" blocks. No other block should be inserted at the root.

All 20 comments

This definitely sounds like a use case we should support. I have a few ideas on how to enable it.

1) Using parent

My first thought is that we should support this via parent which is similar to what @mtias first suggested in https://github.com/WordPress/gutenberg/issues/5540.

registerBlockType( 'amp-story', {
    parent: [ 'post', 'page' ],
} );
registerBlockType( 'amp-story-page', {
    parent: [ 'amp-story' ],
} );
registerBlockType( 'amp-story-grid', {
    parent: [ 'amp-story-page' ],
} );

We've gone back-and-forth on whether or not to include post types in parent鈥攕ee discussion in https://github.com/WordPress/gutenberg/pull/6753. I think it's a clean API, though, and makes sense to allow a block to only be inserted into certain post types, just as we allow a block to only be inserted into certain block types.

2) Using InnerBlocks

This came up in https://github.com/WordPress/gutenberg/issues/5540 and https://github.com/WordPress/gutenberg/pull/6753 as well. The basic idea is to allow you to specify a blacklist of blocks that cannot go inside a <InnerBlocks>, rather than just the whitelist which is what allowedBlocks currently acts as.

registerBlockType( 'amp-story', {
    edit() {
        return <InnerBlocks disallowedBlocks={ [ 'amp-story' ] } />;
    },
} );
registerBlockType( 'amp-story-page', {
    parent: [ 'amp-story' ],
    edit() {
        return <InnerBlocks disallowedBlocks={ [ 'amp-story' ] } />;
    },
} );
registerBlockType( 'amp-story-grid', {
    parent: [ 'amp-story-page' ],
    edit() {
        return <InnerBlocks disallowedBlocks={ [ 'amp-story' ] } />;
    },
} );

It's not clear what would happen if one specified both allowedBlocks and disallowedBlocks. We'd likely have to raise an error.

We can, alternatively, combine allowedBlocks and disallowedBlocks, which I experimented with in https://github.com/WordPress/gutenberg/pull/6753, e.g. allowedBlocks={ { 'amp-story': false, '*': true } } would allow all blocks except for amp-story. I sensed that this is too complex of an API, though.


Thoughts? cc. @WordPress/gutenberg-core

The use of parent in option 1 certainly seems simpler. For option 2 using disallowedBlocks it seems not-ideal to have to copy the root block for every nested block type.

I'm looking for something similar. I want to restrict the insertion of all block but one at the root level. So you can only insert one type of block at the root level. The rest can be inserted inside this block.

+1

The disallowedBlocks option for <InnerBlocks> is enough to doesn't let users repeat the same block inside itself, causing some visual error, sometimes difficult to see and revert. I'm thinking about a section block to wrap contents as an example.

+1

I experince most clients insisting to the classical editor and those few changing to GB tend to screw up their own websites because they are overwhelmed and even the UX is not intuitive enough.
I like the idea of disallowedBlocks for a beginning, however we build websites based on graphical layouts.

Those layouts have blocks but those blocks do not have content types (as they are all just graphical) but the designers give them aliases (like team member that contains a thumbnail and text for example).
What if later we could give our blocks such aliases and setup restriction rules based on those aliases rather than rules based on block types?

I mean most probably i do not want to make such a restriction that the client may not place an image block inside a text block... but for sure i do not want to allow them to place any text block in a block that has the alias "portrait gallery".

This way we would have an abstraction layer allowing us to group the very same block types together -but as they may have different aliases- we could set up different rules what content they are intended to contain based on the layout we got from the designer.

I couldn't make disallowedBlocks works with me. Is it still active and implemented? Or removed from the current version of Gutenberg?

Also, this one couldn't work with me:

registerBlockType( 'amp-story', {
    parent: [ 'post', 'page' ],
} );

I would like section block I made just visible on root of the blocks...any ideas?

If it helps, here is another use case: custom "slides" post type with "slide" blocks. No other block should be inserted at the root.

@swissspidy Is this the issue we discussed at WCEU? :)

@ellatrix Yes I believe so!

Having a similar need to what a few have described: Limit root-level blocks to one type. Allow multiple blocks within that block.

The post type template_locks or allowed_block_types filter (root level) override the individual block templateLock and allowedBlocks.

Is it a possibility that block level templateLock and allowedBlocks can override these root-level settings (for their level), or does it need to stay that way by design? Considering the example, child block settings would override parent blocks for their level, and the next child would for theirs, and so on. It seems like there is already a WP precedence for children to override parents.

There's many use cases where users should have the flexibility of multiple blocks, but certain layout parts/levels are locked/limited (event/product/realty listings).

I face the same problem. For now I just hide unnecesary blocks depending where inserter is in DOM structure, but it's neither clean nor working 100%. If only we could set 'root' blocks. Have anyone managed to solve the problem?

+1

We need this as well, but with the added constraint of only allowing 2 blocks for a specific page template in the root.

For example, we have a landing-page template and a home-page template, and on those two templates, we need the only option for editors to be inserting a section block in the root. That section block in turn would allow for about 10 or so blocks in itself.

Would be super helpful for us since Gutenberg is inherently a little confusing to a lot of our editors (most of them aren't WordPress editors by trade, its just part of their job to update their sites) and even more confusing is that there are different rules for different page templates (sections can go in these two templates but not these. Your page looks bad here because you picked the landing-page template but didn't use a section...etc.) Really would save our support team a lot of hours of explaining that over and over again if we could just automate which blocks are available given certain conditions.

+1

The need for block restriction is very real.

Is it possible to de-couple the definition modules made in allowedBlocks prop (js) and the 'allowed_block_types' filter (php) so that the InnerBlocks module can grant provision of the allowedBlocks independently? This would provide a way of explicity defining sets of allowed blocks in both the Root and the InnerBlock.

The next step here is for a developer who's interested in helping out to explore one of the approaches (probably (1)) described in https://github.com/WordPress/gutenberg/issues/7845#issuecomment-404019138. I've added the Needs Dev label to signify this.

Hello, I have registered a custom block as parent to all the default Gutenberg blocks. Also, inside the <InnerBlocks>, I have specified the allowedBlocks attribute to all the default Gutenberg blocks since the parent block was displaying in the inserter of InnerBlocks. Now the problem I am facing is, when click on the plus icon of the column block, it directly appends the custom block without opening the inserter.
To solve this, I added the column block also as the parent for all the default blocks so the inserter opens when clicked on the plus icon of the column block. This created another issue that is the custom block is now visible inside the inserter which opened on click of the plus icon of column block.

My question:

  1. Is the approach correct
  2. If yes, how can I make sure that the custom block is not visible inside the inserter of the column block?
  3. If no, what's the best approach?

My code
`
const parent = ['pykih/byo', 'core/column', 'core/group', 'core/media-text'];

addFilter('blocks.registerBlockType', 'assignParentBlock', (pSettings, pName) => {
    return Object.assign({}, pSettings, {
        parent: parent
    })
});

`

It would seem to me that limiting by post type on parent could be limiting. Just specifying parent: 'root' aught to be enough to limit the block to only be posted in a top level.

I would also suggest this as a "Why not both" scenario. It makes sense to add dissallowedBlocks to the InnerBlock api, but I don't see how that would solve the need to limit blocks to the page/post root.

+1 to dissallowedBlocks

so... still no way to prevent editors to add blocks in root level, besides the ones we supply in the template?

  • allow only a set of blocks in root level
  • make these blocks only insertable at root level ( this ticket )
  • block template in root level ( more in #5208, we don't care how many paragraphs editors add inside our blocks )
  • allow a set blocks inside our custom blocks ( :heavy_check_mark: done with allowedBlocks={ ... }

Can we make the definition of parent such that "no parent" is a valid test, and in the case above, if it is the only value then a block can only be used in the root? My use case is requiring a container for all WYSIWYG-like blocks since they are simple elements like <ul />, but not for layout blocks which are already wrapped in div containers which can be used for positioning.

I think that adding "root" here is strange because root is not a "parent block", nor is "post" or "page" a parent block. Instead, there is "no" parent block. I think an empty array for parent should also mean "no parents", as in it is only allowed in the root. But that could be a breaking change.

For the same reason, I disagree with adding "post type" to the parent definition. I think if we want to add post type restrictions to the block definition it should be as its own property postTypes. I think it would be weird to express "allow on page post type, with no parent or as a child of 'core/columns'" in a single array.

const Tabs = {
  // only allow this block in root, don't need tabbed columns
  parent: [], // or [ '' ]
}

const Accordion = {
  // this can be in a root, a column, a tab
  parent: ['', 'core/columns', 'myplugin/tabs'],
}

Another complementary idea which expresses the opposite of this would be to set the parent property to 'any' or ['any'] (or core/any to prevent collisions with custom blocks?) -- allowing a block to be inserted into any InnerBlocks, but NOT the root of Gutenberg. That way, the definition of parent doesn't have to include every possible, constantly evolving set of blocks that can be parents. The default for this property would now be equivalent to parent: [ '', 'any' ].

If we can agree this is a good way forward, I'll try and take a look and open a PR.

Here's a workaround that could work:

  • Define a custom block with InnerBlocks, which will be used as a parent (see docu)

    • This is also the place where you can restrict which types of blocks can be added by whitelisting (see docu)

    • Don't register this block to any category to avoid that it's added accidentally while editing, since there are no restrictions to this block (not sure if it'll pop up, but perhaps this is sufficient to hide it in the backend).

  • The blocks that are only allowed for the root level should define this root block as their parent, so they can only be added on this level (see docu)
  • Define a template for pages/posts and set it to this parent block (see docu)

    • Also lock the template, so the parent block is the only top level block (see docu)

Not the cleanest approach to nest the whole content in another div to mimic the root, but could work at least.

I didn't test this yet, but decided to write this idea down, since perhaps it could be useful for someone looking for a quick hack that makes this feature working now.

For now I just hide unnecesary blocks depending where inserter is in DOM structure,

@krywa5 can you show an example of how you do it?
I am looking for a way to do it but I have no clever idea

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mhenrylucero picture mhenrylucero  路  3Comments

cr101 picture cr101  路  3Comments

davidsword picture davidsword  路  3Comments

spocke picture spocke  路  3Comments

ellatrix picture ellatrix  路  3Comments