Reusable blocks / global blocks
b
Currently Umbraco doesn't have the concept of resusable blocks / global blocks (yet). For now I just added a archive of content nodes, where each has a Block List to select 3 different blocks (text, image and accordion for now) to configure as global content. In the Block Grid there a reusable block to select of of these content nodes. However since the model if different, I can't directly pass the model into existing Block Grid block view. This works, but is there a simpler/better way to do this?
Copy code
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<Umbraco.Cms.Core.Models.Blocks.BlockGridItem<BlockReusableContent>>
@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;
@using Umbraco.Cms.Core
@using Umbraco.Cms.Core.Models.Blocks
@{
    var content = Model.Content;

    var data = content.Block as ReusableContent;

    if (data == null || !data.Block.HasAny())
        return;
}

@foreach (var block in data.Block)
{
    if (block?.ContentUdi == null) { continue; }

    var item = block.Content;

    var contentUdi = Udi.Create(Constants.UdiEntityType.Element, item.ContentType.Key);

    if (item is BlockImage image)
    {
        @await Html.PartialAsync("blockgrid/components/" + item.ContentType.Alias, new BlockGridItem<BlockImage>(contentUdi, image, null, null))
    }
    else if (item is BlockText text)
    {
        @await Html.PartialAsync("blockgrid/components/" + item.ContentType.Alias, new BlockGridItem<BlockText>(contentUdi, text, null, null))
    }
    else if (item is BlockAccordion accordion)
    {
        @await Html.PartialAsync("blockgrid/components/" + item.ContentType.Alias, new BlockGridItem<BlockAccordion>(contentUdi, accordion, null, null))
    }
}
m
I have done this in two ways 1) Use your block as a compostition for the global element you can then use the interface 2) have a single Reusable type with a blockgrid/list on it for storing the reusable elements
l
Surely you could replace the if / concrete generic stuff with something using reflection to support "all" cases in one block. Otherwise seems like a viable way to go about it.
b
@Matt Wise with 1) it then seems we would need multiple document types for each block (used as composition). I see the benefits of this, but also requires a new document type each time a new block should be used as global block. On the other side the properties are directly on the content node. with 2) it may be easier just to enable a new block to use in the "Reusable Block List" datatype instance and allow block settings to be used as well. In this case I was using Block List to keep it single, but I guess Block Grid could have been used as well and easier to re-use existing views then. The benefits with Block Grid would bee, that one could have a "Image + Text" reusable block, but can also be used as "Text + Image".
E.g.for the accordion block we have a few settings on the current block, "expand all", "show expand button" .. when using block list/grid when same settings block can be used on block list as well. When thinking about it may works with 1) as well using the settings block as composition too..
b
I done similar thing, I used block editor to render other reusable block grid list, so I handled reusable content in same way as I did block grid 🤔
b
I ended up using Block Grid insted.
Copy code
@using Microsoft.AspNetCore.Mvc.ViewComponents;
@using Umbraco.Cms.Core
@using Umbraco.Cms.Core.Models.Blocks
@{
    var content = Model.Content;

    var data = content.Block as ReusableContent;

    if (data == null || !data.Block.HasAny())
        return;
}

@inject IViewComponentSelector _selector;

@foreach (var item in data.Block)
{
    if (_selector.SelectComponent(item.Content.ContentType.Alias) is not null)
    {
        @await Component.InvokeAsync(item.Content.ContentType.Alias, item)
    }
    else
    {
        var partialViewName = "blockgrid/Components/" + item.Content.ContentType.Alias;

        try
        {
            @await Html.PartialAsync(partialViewName, item)
        }
        catch (InvalidOperationException)
        {
            <p>
                <strong>Could not render component of type: @(item.Content.ContentType.Alias)</strong>
                <br />
                This likely happened because the partial view <em>@partialViewName</em> could not be found.
            </p>
        }
    }
}
The normal Accordion Block has as setting block, the global Accordion hasn't.. This caused an error in binding/mapping. https://cdn.discordapp.com/attachments/1213032727117439006/1213126981558739065/image.png?ex=65f4578b&is=65e1e28b&hm=116fe5264c56eadae15896d479c9aa1695b7bc16e961863f433c0f00ceeadc33&
In accordion block I have this:
Copy code
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<Umbraco.Cms.Core.Models.Blocks.BlockGridItem<BlockAccordion, BlockSettingsAccordion>>
if I change this to:
Copy code
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<Umbraco.Cms.Core.Models.Blocks.BlockGridItem<BlockAccordion>>
then it works... but the settings isn't available to use then 🤔
I could make it work in a hacky way: while accordion block always has this:
Copy code
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<Umbraco.Cms.Core.Models.Blocks.BlockGridItem<BlockAccordion, BlockSettingsAccordion>>
and in reusable block:
Copy code
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<Umbraco.Cms.Core.Models.Blocks.BlockGridItem<BlockReusableContent>>
@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;
@using Microsoft.AspNetCore.Mvc.ViewComponents;
@using Umbraco.Cms.Core
@using Umbraco.Cms.Core.Models.Blocks
@{
    var content = Model.Content;

    var data = content.Block as ReusableContent;

    if (data == null || !data.Block.HasAny())
        return;
}

@inject IViewComponentSelector _selector;

@foreach (var item in data.Block)
{
    if (_selector.SelectComponent(item.Content.ContentType.Alias) is not null)
    {
        @await Component.InvokeAsync(item.Content.ContentType.Alias, item)
    }
    else
    {
        var partialViewName = "blockgrid/Components/" + item.Content.ContentType.Alias;

        try
        {
            if (item.Settings is null)
            {
                if (item.Content is BlockAccordion accordion)
                {
                    var blockItem = new BlockGridItem<BlockAccordion, BlockSettingsAccordion>(item.ContentUdi, accordion, item.SettingsUdi, (BlockSettingsAccordion)item?.Settings!);

                    @await Html.PartialAsync(partialViewName, blockItem)
                }
            }
            else
            {
                @await Html.PartialAsync(partialViewName, item)
            }
        }
        catch (InvalidOperationException)
        {
            <p>
                <strong>Could not render component of type: @(item.Content.ContentType.Alias)</strong>
                <br />
                This likely happened because the partial view <em>@partialViewName</em> could not be found.
            </p>
        }
    }
}
Actually I think it was just an issue with memory cache, because after applying a settings block to the resuable accordion block, the setting didn't output any settings udi .. even after a save & publish. but after reloading memory cache it showed up and also works without this part:
Copy code
if (item.Settings is null)
{
     if (item.Content is BlockAccordion accordion)
     {
          var blockItem = new BlockGridItem<BlockAccordion, BlockSettingsAccordion>(item.ContentUdi, accordion, item.SettingsUdi, (BlockSettingsAccordion)item?.Settings!);

          @await Html.PartialAsync(partialViewName, blockItem)
      }
}
I often come back to this old issue: https://github.com/umbraco/Umbraco-CMS/issues/10245
176 Views