Module: Page Builder
7 of 15 Pages
Finish the structured data template
Comparing the current template to the mockup from the start of this series, you’ll notice that the Product features table is missing.
Its data is already in the template model, so it should be fairly straightforward to render to the page.
However, if you inspect the ProductFeature content type, you may notice that its value can come from multiple different places depending on the selected ProductFeatureValueType.
Let’s make one last tag helper, so that we don’t need to clutter the view with conditionals.
Add a product feature value tag helper
Since this one is specifically related to Product features, and not general styling, we can put it in the ~/Features/Products/TagHelpers folder.
- Create a new self-closing tag helper called
ProductFeatureValueTagHelper
, with the tag nametg-product-feature-value
. - Add a
ProductFeatureViewModel
property and a constant for thespan
tag. - In the
Process
method, determine the formated value depending on the providedProductFeatureValueType
from the view model and render it.
using System.Globalization;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Razor.TagHelpers;
using TrainingGuides.Web.Features.Products.Models;
namespace TrainingGuides.Web.Features.Products.TagHelpers;
/// <summary>
/// Formats Product feature value based on its type.
/// </summary>
[HtmlTargetElement("tg-product-feature-value", TagStructure = TagStructure.WithoutEndTag)] //This TagStructure allows the helper to be called with a single self-closing tag in razor views.
public class ProductFeatureValueTagHelper : TagHelper
{
public ProductFeatureViewModel? Feature { get; set; }
private const string SPAN_TAG = "span";
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = SPAN_TAG;
// Make sure to set the output's TagMode to StartTagAndEndTag.
// This ensures that even though the tag helper is called with a single tag, the rendered output closes the `span` tag that wraps the value.
output.TagMode = TagMode.StartTagAndEndTag;
string? formattedValue = Feature?.ValueType switch
{
ProductFeatureValueType.Text => Feature.ValueHtml.Value,
ProductFeatureValueType.Number => string.Format(CultureInfo.CurrentUICulture, "{0:0.00}", Feature.Price),
ProductFeatureValueType.Boolean => Feature.FeatureIncluded ? "✔" : "-",
_ => string.Empty
};
output.Content.SetHtmlContent(new HtmlString(formattedValue));
}
}
Add the product features list to the template view
- In the ~/Features/Products/ProductPagePageTemplate.cshtml folder, add a
foreach
loop to cycle through the product features. - Retrieve each feature’s
LabelHtml
directly, and use the new tag helper to display its value - Wrap the whole thing in the
tg-component-style
tag helper, settingcolor-scheme
directly andcorner-style
based on the template properties.
...
<tg-component-style color-scheme="@ColorSchemeOption.Light1" corner-style="@Model.Properties.CornerStyle" class="c-table">
@foreach (var feature in templateModel.Features)
{
<div>
<div>@feature.LabelHtml</div>
<div>
<tg-product-feature-value feature="@feature"/>
</div>
</div>
}
</tg-component-style>
...
Finish styling the template.
Now if you run the site, you’ll see all the data, but it’s still not formatted in a way that matches the mockup.
Add CSS styles and any necessary elements to make the template more visually appealing.
@using TrainingGuides.Web.Features.Products.Models
@using TrainingGuides.Web.Features.Products
@using TrainingGuides.Web.Features.Shared.OptionProviders.ColorScheme
@model TemplateViewModel<ProductPagePageTemplateProperties>
@{
var templateModel = Model.GetTemplateModel<ProductPageViewModel>();
}
<tg-component-style color-scheme="@Model.Properties.ColorScheme" corner-style="@Model.Properties.CornerStyle">
<div class="tg-padding-big">
<div class="row">
<div class="col align-self-center">
<h3>@templateModel.NameHtml</h3>
<p>@templateModel.ShortDescriptionHtml</p>
</div>
<tg-styled-image src="@templateModel.Media.FirstOrDefault()?.FilePath" alt="@templateModel.Media.FirstOrDefault()?.Description" corner-style="@Model.Properties.CornerStyle" class="col c-product-img object-fit-cover" />
</div>
<div class="row">
<div class="c-pricelist">
<div class="col">
<tg-component-style color-scheme="@ColorSchemeOption.Light1" corner-style="@Model.Properties.CornerStyle" class="c-table">
@foreach (var feature in templateModel.Features)
{
<div class="c-table_row">
<div class="c-table_cell"><div class="c-table_cell">@feature.LabelHtml</div></div>
<div class="c-table_cell text-end">
<tg-product-feature-value feature="@feature"/>
</div>
</div>
}
</tg-component-style>
</div>
</div>
</div>
</div>
</tg-component-style>
At the end of it all, you should be able to achieve this by configuring your template.