Search code examples
wagtailwagtail-streamfieldwagtail-streamblock

Splitting StreamBlocks nested inside a StreamField in Wagtail


Background

I'm trying to determine whether this functionality is possible in Wagtail as things stand, or whether I am going to need to add a form widget adapter to handle this.

I have a StreamField on my page model which looks like this:

    body = StreamField(
        [
            ("introduction", content_blocks.IntroductionBlock()),
            ("benefits", content_blocks.BenefitsBlock()),
            ("alternatives", content_blocks.AlternativesBlock()),
            ("detail", content_blocks.DetailBlock()),
            ("success", content_blocks.SuccessBlock()),
            ("complications", content_blocks.ComplicationsBlock()),
            ("medication", content_blocks.MedicationBlock()),
            ("recovery", content_blocks.RecoveryBlock()),
            ("summary", content_blocks.SummaryBlock()),
        ],
        use_json_field=True,
    )

And each of those blocks are defined like so:

class PanelBlock(blocks.StreamBlock):
    header = HeaderBlock()
    content = BaseContentBlock()
    snippet = ReusableSnippetBlock()
    choice = LibraryChoiceBlock()
    embed = VideoBlock()
    figure = FigureBlock()


class IntroductionBlock(PanelBlock):
    class Meta:
        icon = "help"
        label = _("Introduction")
        label_format = "Introduction"


class BenefitsBlock(PanelBlock):
    class Meta:
        icon = "circle-check"
        label = _("Benefits")
        label_format = "Benefits"

# etc...

My template logic uses those "PanelBlocks" to chunk content into divs, and often editors want to ensure these divs are of an approximately similar size/length to keep the content visually balanced from an end users perspective (i.e. so that one panel is not significantly longer or shorter than its siblings).

Problem

When checking the "balance" of these panels, if a particular block is much longer than it's siblings, it would be useful for editors to be able to split the StreamBlock at a given point using the action menu (as works for RichTextBlocks nested in a StreamBlock):

Action menu entry for RichTextBlocks

But the "split" action is missing for the StreamBlocks, even though the parent is a StreamField:

No "Actions" submenu for StreamBlocks

From looking at the Wagtail codebase, this does appear to already be a feature from what I can tell (although I may be misreading), so my question is:

Is this a feature which already exists and there is just some issue with my implementation which is causing the split/actions menu to be disabled in error, or is this something which will require a custom solution?

So far, I have just been reading through the Wagtail docs, codebase, and related GH issues to try and find details about what specifically is supported by the existing "split" functionality, and whether the combobox can be easily extended.

Edit: Resolved

I ended up resolving this using client-side adapters for both PanelBlock and a newly defined BodyBlock as per the accepted response:

class PanelBlock(blocks.StreamBlock):
    header = HeaderBlock()
    content = BaseContentBlock()
    snippet = ReusableSnippetBlock()
    choice = LibraryChoiceBlock()
    embed = VideoBlock()
    figure = FigureBlock()


class IntroductionBlock(PanelBlock):
    class Meta:
        icon = "help"
        label = _("Introduction")
        label_format = "Introduction"


class BenefitsBlock(PanelBlock):
    class Meta:
        icon = "circle-check"
        label = _("Benefits")
        label_format = "Benefits"

# etc...

class BodyBlock(blocks.StreamBlock):
    introduction = IntroductionBlock()
    benefits = BenefitsBlock()
    alternatives = AlternativesBlock()
    complications = ComplicationsBlock()
    success = SuccessBlock()
    detail = DetailBlock()
    recovery = RecoveryBlock()
    medication = MedicationBlock()
    summary = SummaryBlock()

And the page model updated like so:

    body = StreamField(
        content_blocks.BodyBlock(),
        use_json_field=True,
    )

The BodyBlock can then be extended using a client-side adapter to add the splitting mechanism to that "parent" block, and store the block config as a window-level variable to allow the child-blocks to call that split method on the parent.


Solution

  • No, there is currently no support for splitting a StreamBlock in this way. The splitBlock method you're looking at is in fact where RichTextBlock splitting is implemented: the method is called when a RichTextBlock child block of the stream wishes to split itself up. Since this causes a change to the block sequence, the splitting has to be performed by the containing block.

    Currently, RichTextBlock is the only block type that makes use of this; however, it would be possible in principle to use this as a basis for splitting StreamBlocks within StreamBlocks, where the inner StreamBlock calls splitBlock on the parent. This hasn't been done yet, though (probably because StreamBlock within StreamBlock is quite an unusual configuration).