In Litium 7, blocks that are built in the platform replace the sections used in previous versions of Accelerator. For an introduction to blocks, please look here.
Litium Accelerator is shipped with standard blocks that can be used for reference. The following components are required to create a custom block:
- a block template: to define the block type, what fields it has and how to display it in the website.
- a display template: an MVC action to render the block in the website.
- a view to render the block.
Let's create a simple custom block that only renders a text.
Create a block template
A block template can be created via the UI and from code (under Litium.Accelerator\Definitions\). For the sake of simplicity, let's do it via the UI: click on the cogwheel in back office and select Blocks > Field Templates in the left menu. When a block is created, a block Id is mandatory. Let's call it Text block. We will configure it later when we have created a controller. Since Litium 7.1, we can configure the icon for the block template, so when editing page in block mode, a block template's icon will be shown in the Add Block panel. The icon value should be the class name of the Font Awesome icon, for example: fa-file-alt. The list of icons can be found here. The system supports Font Awesome 5.6.3.
Text block should have a text field so that the editor can enter text. Let's create one with the Id Text and add it under the Fields tab:
Create a display template
We need an MVC controller to render Text block, configured as a display template.
Create a model
Create TextBlockViewModel under Litium.Accelerator\ViewModels\Block:
using AutoMapper;
using JetBrains.Annotations;
using Litium.Accelerator.Builders;
using Litium.Accelerator.Extensions;
using Litium.Runtime.AutoMapper;
using Litium.Web.Models.Blocks;
namespace Litium.Accelerator.ViewModels.Block
{
public class TextBlockViewModel : IViewModel, IAutoMapperConfiguration
{
public string Text { get; set; }
[UsedImplicitly]
void IAutoMapperConfiguration.Configure(IMapperConfigurationExpression cfg)
{
cfg.CreateMap<BlockModel, TextBlockViewModel>()
.ForMember(x => x.Text, m => m.MapFromField("Text"));
}
}
}
Create a builder
The builder is a component of the Business Logic Layer (BLL), to create a common layer that can be consumed by the MVC or Web API. We will create TextBlockController and use the builder in the next step, but this is not mandatory. We can also choose to use only the controller. The builder, however, will help us if we want to use another approach than MVC to render a block, since we have a common layer to reuse. For more information about the BLL and the architecture of Litium Accelerator, please check here.
Let's create a builder under Litium.Accelerator\Builders\Block:
using Litium.Accelerator.ViewModels.Block;
using Litium.Runtime.AutoMapper;
using Litium.Runtime.DependencyInjection;
using Litium.Web.Models.Blocks;
namespace Litium.Accelerator.Builders.Block
{
[Service(ServiceType = typeof(TextBlockViewModelBuilder))]
public class TextBlockViewModelBuilder : IViewModelBuilder<TextBlockViewModel>
{
public virtual TextBlockViewModel Build(BlockModel blockModel)
{
return blockModel.MapTo<TextBlockViewModel>();
}
}
}
In this builder we use AutoMapper to map the text field from the field we named Text in our Text block template. So when blockModel.MapTo<TextBlockViewModel>() is executed, the value of the Text field in the Text block template is set to the text field of the TextBlockViewModel.
We created the model and builder in the Litium.Accelerator project, which is a common one. Now let's move to the Litium.Accelerator.Mvc project to create a controller and a view that use the model and builder we just created.
Create a controller
Create TextBlockController under Litium.Accelerator.Mvc\Controllers\Blocks:
using System.Web.Mvc;
using Litium.Accelerator.Builders.Block;
using Litium.Web.Models.Blocks;
namespace Litium.Accelerator.Mvc.Controllers.Blocks
{
public class TextBlockController : ControllerBase
{
private readonly TextBlockViewModelBuilder _builder;
public TextBlockController(TextBlockViewModelBuilder builder)
{
_builder = builder;
}
[HttpGet]
public ActionResult Index(BlockModel currentBlockModel)
{
var model = _builder.Build(currentBlockModel);
return PartialView("~/Views/Block/Text.cshtml", model);
}
}
}
The controller does not do much work. It just asks TextBlockViewModelBuilder to build the model, and render a partial view using Text.cshtml. Now let's create the view under Litium.Accelerator.Mvc\Views\Block:
@model Litium.Accelerator.ViewModels.Block.TextBlockViewModel
<p>@Model.Text</p>
Configure a display template
We have already created a display template. Let's build the solution and configure one for Text block:
Text block is now ready to use and you can edit a page, for example an article page, in blocks mode and add the block to a page container.
How it works
We created a block template, added the block to an article page where Text block is rendered properly. Now let's see how the rendering is done.
The article page is a page template configured to use ArticleController as display template. When we take a look at the Index action of ArticleController, we can see that it calls ModelBuilder to build the model and pass it on to the Article\Index.cshtml view to render the article page. ArticleViewModel has a block field that serves as a dictionary between the Id of a block container and the list of blocks which should be there.
Block containers are configured in the page template. It defines how many containers we have for the article page. In the article view we can display different containers in different location, as we wish.
Here is how a container is rendered in the article page (Views\Article\Index.cshtml) In this example, Article has only one container called Header.
When adding a container to a page template, remember to add the following snippet to the Razor view, in order to render the container:
@Html.BlockContainer(Model.Blocks, BlockContainerNameConstant.Header)
@Html.BlockContainer is an extension method for HtmlHelper, which is defined in Litium.Accelerator.Mvc\BlockContainerHelperExtension.cs. This extension method makes sure that we render the container's blocks with Views\Shared\Framework\_BlockContainer.cshtml.
@model List<Litium.Web.Models.Blocks.BlockModel>
@foreach (var item in Model)
{
<section data-litium-block-id="@item.SystemId">
@Html.Block(item)
</section>
}
_BlockContainer.cshtml renders all blocks, and every block should be rendered inside a section tag with the data-litium-block-id attribute. This attribute is a special one, and must be added to every block. The page edit engine uses the attribute to find a block, in order generate a thumbnail for it. Without this attribute, the system cannot find the block in the HTML code, so it cannot extract the thumbnail. Note that this only impacts the page edit feature in back office, not how the block is rendered in the website. Without that attribute, the block will still be rendered correctly.
BlockContainer extension method and _BlockContainer.cshtml view are there to help us render a block easily. When we create a custom block, we don't have to worry about that. The implementation can be done in another way, but make sure that the data-litium-block-id attribute is there for every block. Its value must be the system Id of the block.
With this data-litium-block-id attribute, the website or the actual block component can be implemented in any technology and any framework. For example, we have the slider block, which is rendered as a React component. The page edit can still capture the thumbnail of the block, as long as it finds the data-litium-block-id attribute. It can be an MVC website, a hybrid website with MVC and some React components, or we can go all the way to single-page application, implemented in other UI frameworks too. We are not locked to React.