Creating a Block
A Block
is used to quickly create content within the Page Builder. Smartstore has already implemented several blocks such as HTML output, images, iframes, product lists and many more. The possibilities for new blocks are endless.
Write a simple Block
First you create a directory called Blocks and add a new file called HelloWorldBlock.cs
.
Implement IBlock
Then you create a class that implements the IBlock
interface.
namespace MyOrg.HelloWorld.Blocks
{
public class HelloWorldBlock : IBlock
{
}
}
This is where you add block-settings and values you may need for model preparation. Treat it like a model.
[LocalizedDisplay("Plugins.MyOrg.HelloWorld.Name")]
public string Name { get; set; }
If you don't want a property to be stored in the database, use IgnoreDataMember
.
[IgnoreDataMember]
public Object NotForTheDatabase { get; set; }
Use BindNever
to manually bind the model.
[IgnoreDataMember, BindNever]
public MediaFileInfo mediaFile { get; set; }
Add a BlockHandler
Next, add the new HelloWorldBlockHandler
class to your file and inherit from the BlockHandlerBase<T>
base class.
public class HelloWorldBlockHandler : BlockHandlerBase<HelloWorldBlock>
{
// Doing nothing means default behavior.
}
Place the following attribute on top of your BlockHandler
definition.
[Block("helloworld", FriendlyName = "Hello World", Icon = "fa fa-eye")]
The input for this attribute represents the metadata of your block. Three things are accomplished here:
helloworld
: Defines the location of the views for yourBlock
. A lowercase value is recommended by our guidelines.Hello World
: Specifies the label text of your block as it will appear in Page Builder.fa fa-eye
: Specifies the Font Awesome icon that appears next to the label in the Page Builder.
You don't need to add any code to your BlockHandler
. It will have the default behavior you want at this point. See Advanced topics for other uses.
Add a validator
To ensure that all user input is correct, add a validator to your file that inherits from the AbstractValidator<T>
base class.
public partial class HelloWorldBlockValidator : AbstractValidator<HelloWorldBlock>
{
public HelloWorldBlockValidator()
{
}
}
Here you can define your rules using FluentValidation
. Add the following to ensure that Name
is never empty.
RuleFor(x => x.Name).NotEmpty();
If you have more properties you want to check, simply add more rules.
RuleFor(x => x.Name).NotEmpty();
RuleFor(x => x.ASmallValue).GreaterThan(0);
RuleFor(x => x.AShortText).MaximumLength(255);
Your final code should look something like this:
using FluentValidation;
using Smartstore.Core.Content.Blocks;
using Smartstore.Web.Modelling;
namespace MyOrg.HelloWorld.Blocks
{
[Block("helloworld", Icon = "fa fa-eye", FriendlyName = "Hello World")]
public class HelloWorldBlockHandler : BlockHandlerBase<HelloWorldBlock>
{
// Doing nothing means default behavior.
}
public class HelloWorldBlock : IBlock
{
[LocalizedDisplay("Plugins.MyOrg.HelloWorld.Name")]
public string Name { get; set; }
}
public partial class HelloWorldBlockValidator : AbstractValidator<HelloWorldBlock>
{
public HelloWorldBlockValidator()
{
RuleFor(x => x.Name).NotEmpty();
}
}
}
Add Views
Create the BlockTemplates/helloworld/ directory in the Views/Shared/ directory of your module.
The folder name helloworld corresponds to the attribute you set in your BlockHandler!
Create the two files Edit.cshtml and Public.cshtml for simple output.
Edit.cshtml
This is your configuration view. It appears when you are configuring the block. It works the same way as the configuration views of modules.
@model HelloWorldBlock
<div class="adminContent">
<div class="adminRow">
<div class="adminTitle">
<smart-label asp-for="Name" />
</div>
<div class="adminData">
<input asp-for="Name" />
<span asp-validation-for="Name"></span>
</div>
</div>
</div>
Public.cshtml
The HTML structure defined in this view is rendered publicly. You can also see this by clicking on Preview in the Page Builder.
@model HelloWorldBlock
<div>My block name: @Model.Name</div>
Now you can use your Block
in the Page Builder.
Advanced topics
Taking a closer look at the BlockHandler
, there is some more functionality you can add to your module.
Handling StoryViewMode
StoryViewMode
allows you to distinguish between the four different view modes of a Block
:
Edit
: Appears when editingBlock
properties.GridEdit
: Shown when arranging blocks on the grid.Preview
: Shown when you preview your story.Public
: Shown on the front end when your story is published.
Create a new block-setting MyLocalVar
in HelloWorldBlock
.
public string MyLocalVar { get; set; } = "Initialised in Block";
Add the Load
function to the HelloWorldBlockHandler
.
protected override HelloWorldBlock Load(
IBlockEntity entity,
StoryViewMode viewMode)
To access the model of the current block, use the following line.
var block = base.Load(entity, viewMode);
Now you can use viewMode
to get the current view mode for your block.
if (viewMode == StoryViewMode.Edit)
{
// This is called only in Edit mode.
block.MyLocalVar += " - Running in Edit-Mode";
}
else if (viewMode == StoryViewMode.Preview)
{
// This is called only in Preview-Mode
block.MyLocalVar += " - Running in Preview-Mode";
}
else if (viewMode == StoryViewMode.GridEdit)
{
// This is called only in Grid-Edit-Mode
block.MyLocalVar += " - Running in Grid-Edit-Mode";
}
else if (viewMode == StoryViewMode.Public)
{
// This is called only in Public-Mode
block.MyLocalVar += " - Running in Public-Mode";
}
return block;
Add the following to Edit.cshtml, Preview.cshtml and Public.cshtml.
<div>My local variable: @Model.MyLocalVar</div>
Run your code and see how MyLocalVar
responds to each view mode.
Using a widget as a view
If you want to display a widget instead of a view, you can use the following methods.
protected override Task RenderCoreAsync(
IBlockContainer element,
IEnumerable<string> templates,
IHtmlHelper htmlHelper,
TextWriter textWriter)
RenderCoreAsync
gives you the ability to change the rendering behavior. This means that you can display the HelloWorld widget instead of using your views.
Add these lines to the RenderCoreAsync
function in the HelloWorldBlockHandler
.
if (templates.First() == "Edit")
{
return base.RenderCoreAsync(element, templates, htmlHelper, textWriter);
}
else
{
return RenderByWidgetAsync(element, templates, htmlHelper, textWriter);
}
This will allow the GetWidget
method to be called so that you can render your widget.
protected override Widget GetWidget(
IBlockContainer element,
IHtmlHelper htmlHelper,
string template)
GetWidget
works much like GetDisplayWidget
in Module.cs. To call it, add these lines to the GetWidget
method in HelloWorldBlockHandler
:
return new ComponentWidget(typeof(HelloWorldViewComponent), new
{
widgetZone = "productdetails_pictures_top",
model = new ProductDetailsModel { Id = 1 }
});
This will display your widget, rendering the content for the product with the Id 1
.
Final code
Your complete code should look something like this:
namespace MyOrg.HelloWorld.Blocks
{
[Block("helloworld", Icon = "fa fa-eye", FriendlyName = "Hello World")]
public class HelloWorldBlockHandler : BlockHandlerBase<HelloWorldBlock>
{
protected override HelloWorldBlock Load(
IBlockEntity entity,
StoryViewMode viewMode)
{
var block = base.Load(entity, viewMode);
if (viewMode == StoryViewMode.Edit)
{
// This is called only in Edit mode.
block.MyLocalVar += " - Running in Edit-Mode";
}
else if (viewMode == StoryViewMode.Preview)
{
// This is called only in Preview-Mode
block.MyLocalVar += " - Running in Preview-Mode";
}
else if (viewMode == StoryViewMode.GridEdit)
{
// This is called only in Grid-Edit-Mode
block.MyLocalVar += " - Running in Grid-Edit-Mode";
}
else if (viewMode == StoryViewMode.Public)
{
// This is called only in Public-Mode
block.MyLocalVar += " - Running in Public-Mode";
}
return block;
}
protected override Task RenderCoreAsync(
IBlockContainer element,
IEnumerable<string> templates,
IHtmlHelper htmlHelper,
TextWriter textWriter)
{
if (templates.First() == "Edit")
{
return base.RenderCoreAsync(element, templates, htmlHelper, textWriter);
}
else
{
return RenderByWidgetAsync(element, templates, htmlHelper, textWriter);
}
}
protected override Widget GetWidget(
IBlockContainer element,
IHtmlHelper htmlHelper,
string template)
{
var block = (HelloWorldBlock)element.Block;
return new ComponentWidget(typeof(HelloWorldViewComponent), new
{
widgetZone = "productdetails_pictures_top",
model = new ProductDetailsModel { Id = 1 }
});
}
}
public class HelloWorldBlock : IBlock
{
[LocalizedDisplay("Plugins.MyOrg.HelloWorld.Name")]
public string Name { get; set; }
public string MyLocalVar { get; set; } = "Initialised in Block";
}
public partial class HelloWorldBlockValidator : AbstractValidator<HelloWorldBlock>
{
public HelloWorldBlockValidator()
{
RuleFor(x => x.Name).NotEmpty();
}
}
}
Conclusion
In this tutorial, you created your own Block
. You learned how to access the different view modes and how to use a widget as output for a block.
Last updated
Was this helpful?