Coding with Bob: On Page Editing Meta Properties

While on-page editing within Optimizely + Episerver is a powerful tool, by default it continues to hide some important metadata. Blend's Director of Development Bob Davidson highlights how to programmatically surface meta properties within on-page editing in this Coding with Bob video.

  • Bob Davidson
  • Jun. 08 2021

On-page-editing is, in my opinion, one Episerver's more distinctive features. This is a bit of a controversial opinion. A common complaint is that on-page-editing doesn't allow easy access to non-visual elements, such as page metadata and settings. If one has to switch to all-properties to edit those, why bother switching back to on-page-editing view?

It's a valid concern, but there is a relatively simple way to make at least some of these otherwise hidden attributes available from on-page-editing. By building a simple block type and adding some support to the UI, we can create a toolbar of "Property Blocks," each of which can clicked to show a modal of properties without leaving on-page-editing.

example2.gif

The process is a little involved, but this video walks through all the steps:

A summary of the steps, and some code snippets follow:

1. Create the base block type, which will be used as a marker for rendering purposes:

public abstract class PropertyBlock : BlockData
{
}

2. Create a bundle of metadata properties as a PropertyBlock:

[ContentType(GUID = "20b74707-145f-4523-be9d-2520ea2221b5",
    DisplayName = "Page Meta Data",
    AvailableInEditMode = false)] // It's important this is *not* available in edit mode
public class PageMetaDataPropertyBlock : PropertyBlock
{
    [Display(
        GroupName = Global.GroupNames.MetaData,
        Order = 100)]
    [CultureSpecific]
    public virtual string MetaTitle
    {
        get; set;
    }

    [Display(
        GroupName = Global.GroupNames.MetaData,
        Order = 200)]
    [CultureSpecific]
    [BackingType(typeof(PropertyStringList))]
    public virtual string[] MetaKeywords { get; set; }

    [Display(
        GroupName = Global.GroupNames.MetaData,
        Order = 300)]
    [CultureSpecific]
    [UIHint(UIHint.Textarea)]
    public virtual string MetaDescription { get; set; }
}

3. Create a controller to handle rendering all property blocks:

[TemplateDescriptor(Inherited = true)]
public class PropertyBlockController : BlockController<PropertyBlock>
{
    public override ActionResult Index(PropertyBlock currentContent)
    {
        var typeName = currentContent.GetOriginalType().Name;
        // Adjust the view paths for what makes sense for your team's standards
        return PartialView($"~/Views/PropertyBlocks/{typeName}.cshtml", currentContent);
    }
}

4. Add the property block as a property to your page model:

[Display(
    Name = "Page Meta Data",
    GroupName = Global.GroupNames.MetaData,
    Order = 300)]
public virtual PageMetaDataPropertyBlock PageMetaData { get; set; }

Note: Because this is a local block object, you cannot set [CultureSpecific] on the PageMetaData property, but rather on the properties of the PageMetaDataPropertyBlock.

5. Reference the new properties in the view. For example:

<title>@Model.CurrentPage.PageMetaData.MetaTitle</title>
@if (Model.CurrentPage.PageMetaData.MetaKeywords != null && Model.CurrentPage.PageMetaData.MetaKeywords.Length > 0)
{
    <meta name="keywords" content="@string.Join(",", Model.CurrentPage.PageMetaData.MetaKeywords)" />
}
@if (!string.IsNullOrWhiteSpace(Model.CurrentPage.PageMetaData.MetaDescription))
{
    <meta name="description" content="@Model.CurrentPage.PageMetaData.MetaDescription" />
}

6. Create space for the Property Block buttons in edit mode. In your template, add something like:

@if (EPiServer.Editor.PageEditing.PageIsInEditMode)
{
    <div class="edit-tool-bar">
        <div style="display: inline-block; width: 200px">
            @Html.PropertyFor(x => x.CurrentPage.PageMetaData)
        </div>
        @RenderSection("editbar", false)
    </div>
}

7. The @RenderSection("editbar", false) gives you a section in which you can add page-specific property blocks. For example:

@if (EPiServer.Editor.PageEditing.PageIsInEditMode)
{
    @section editbar {
        <div style="display: inline-block; width: 200px">
            @Html.PropertyFor(x => x.CurrentPage.ProductPageSettings)
        </div>
    }
}

8. If you have nested templates, in the outer templates, you'll need to "punch a whole" to pass through the editbar section. For example, in Alloy, in the _TwoPlusOne.cshtml file, you would need:

@if (EPiServer.Editor.PageEditing.PageIsInEditMode)
{
    @section editbar {
        @RenderSection("editbar", false)
    }
}

9. Create a view for each Property Block to render the "button" in the editbar space. For example:

@* ~/Views/PropertyBlocks/PageMetaDataPropertyBlock.cshtml *@
@model PageMetaDataPropertyBlock

@{ 
    var hasPageTitle = !string.IsNullOrEmpty(Model.MetaTitle);
}
<b>PAGE META DATA @(hasPageTitle ? "" : "[WARNING]")</b>

You can use this technique for other items as well. For example, I've done some curated navigation as a property block that renders the navigation (and is not in the editbar section, but rather where the navigation needs to render). Footers often work well this way as well.

On-page-editing is often neglected, but with this and similar techniques, there is quite a bit we can do to make the experience better and more usable--all without writing a single line of Dojo.