question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Ability to arrange StructBlock sub-blocks in rows or groups, with control over ordering

See original GitHub issue

Is your proposal related to a problem?

With panels/edit handlers, we have FieldRowPanel and MultiFieldPanel to help add some useful structure to complicated models, but there is no equivalent for blocks, which means StructBlock rendering is syntactically and visually flat by default.

It would be great if Wagtail had a baked-in way to add more visual and syntactical structure to the UI presented by a StructBlock.

Describe the solution you’d like

You could add a StuctBlockLayout to a block’s Meta class to define the layout you like. You could either use block name strings to represent each block, like so:

class ExampleBlock(block.StructBlock):
   ... 
   class Meta:
        layout = StructBlockLayout("block_one", "block_two", "block_three") 

OR, provide BlockPlaceholder() instances when more control is needed:

class ExampleBlock(block.StructBlock):
   ... 
   class Meta:
        layout = StructBlockLayout(
            BlockPlaceholder("block_one", classname="foo"), 
            BlockPlaceholder("block_two", classname="bar"),
            "block_three", 
            "block_four", 
        )

Using BlockRow to add a row of blocks

If you wanted to group blocks into a row/grid layout, you would add a BlockRow instance to your layout. For MVP, you would simply provide the block placeholders in the order you wanted them to appear, and classes would be automatically applied to display them in a grid of equal-sized columns, that would break down sensibly at different screen sizes.

StructBlockLayout(
    BlockRow("block_one", "block_two", "block_three"),
)

Using BlockGroup to group blocks under a heading

You could group block fields into a fieldset like so:

BlockGroup(
    "block_one", 
    "block_two", 
    "block_three", 
    "block_four" , 
    heading="Advanced options",
    icon="fa-cog", 
    help_text="An optional description that will be displayed before the sub-blocks", 
    classname="group__advanced", 
) 

Alternatively, you could pass a list of sub-block placeholders/rows as a list, using the children keyword argument (like you can for MultiFieldPanel), e.g:

BlockGroup(
    heading="Advanced options",
    icon="fa-cog", 
    help_text="An optional description that will be displayed before the sub-blocks", 
    classname="group__advanced", 
    children=[
        BlockRow("block_one", "block_two", "block_three"), 
        BlockPlaceholder("block_four"), 
    ]
) 

What about nesting?

A BlockRow could be nested within a BlockGroup, but not vice versa.

Attempting to nest a BlockRow or BlockGroup within another would raise an exception.

What about blocks not included in the layout?

We always want to show all sub-blocks defined for a StructBlock (if they aren’t needed, they should be ‘nulled out’ using block_name = None), so if any blocks are ommited from a layout, they would automatically be output at the end of the block’s form.

What if a named block doesn’t exist?

I think the most sensible thing to do here is silently ignore these cases. With this level of control over layout available, I would imagine it would be more common to subclass blocks for reuse (the current lack of control over block order makes this more hassle than it is worth). This, combined with the fact that layouts would be inherited, would make it painful should you need to ‘null out’ blocks in a sub-block, but would otherwise like the layout to remain the same. If it raised an error, that would mean having to completely redefine a StructBlockLayout in these cases, which I feel is unhelpful.

One drawback of this approach is: There would be no protection against spelling errors / accidental mismatches at the Python level. Developers would have to load the UI and look at the output to confirm all of the fields were appearing as expected. Any mismatched blocks would appear at the bottom, so should hopefully be easy to spot.

How would this actually work?

Like blocks in a Streamfield definition, the actual BlockPlaceholder, BlockRow, BlockGroup instances used in the definition exist for as long as the app instance is running. So, to guarantee thread-safety, should not be bound to request or value-specific values. However, they should be able to implement a render method that accepts all of the necessary values, does any necessary processing, and renders the values a template.

Describe alternatives you’ve considered

At first, I thought we might be able to achieve this with new block types, (with signatures similar that of StreamBlock), that would become part of the block definition. However, I think this is a bad idea because:

  1. It’s mixing data and presentation, which is icky.
  2. It complicates inheritance. If you want the same block types in a sub-class, but want to present them in a different way, there’s no easy way to do that.
  3. It would lead to even bigger data migrations than are generated already, for data that is irrelevant for migrations.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:2
  • Comments:13 (7 by maintainers)

github_iconTop GitHub Comments

5reactions
thibaudcolascommented, Aug 17, 2022

With the new page editor designs / #8983, I believe displaying blocks in rows should be easier now that StreamField blocks’ usage of space is much more efficient (blocks are indented to the left only).

Here is a quick prototype, just applying display: grid; grid-auto-flow: column; to the blocks’ parent element: block-row

The main issue will be with the placement of the “Add” buttons in-between blocks. We’ve made them much smaller, and will soon™ also completely change the “add” UI when opened – so this shouldn’t be an issue for much longer.

Note from an accessibility standpoint I would recommend to keep usage of this row layout in forms to a minimum, as it can be problematic for users of magnification software, who might not realise they have to go through the page both top to bottom and left to right.

2reactions
vsalvinocommented, Aug 4, 2021

@awhileback be careful what you call “clever” — our dynamic use of generating streamfield dropdown values has created some major problems with Django migrations because it is too “clever” (or something like that) 🤣

Read more comments on GitHub >

github_iconTop Results From Across the Web

Release 1.5.3 Torchbox - Wagtail Documentation
StructBlock wagtail.wagtailcore.blocks.StructBlock. A block consisting of a fixed group of sub-blocks to be displayed together.
Read more >
StreamField block reference - Wagtail's documentation
StreamField block reference¶. This document details the block types provided by Wagtail for use in StreamField, and how they can be combined into...
Read more >
Building with Columns, Groups, Rows and Stacks - YouTube
In this Online Workshop, we'll explore how to design sophisticated layouts using Columns, Group, Row and Stack blocks.
Read more >
Freeform page content using StreamField
A control for selecting a page object, using Wagtail's page browser. ... A block consisting of a fixed group of sub-blocks to be...
Read more >
wagtail/CHANGELOG.txt at main - GitHub
Add ability to include `form_fields` as an APIField on `FormPage` (Sævar Öfjörð ... Fix: Group permission rows with custom permissions no longer have...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found