Use blocks as legacy dynamic properties – POC

POC how to tweak blocks behavior in XhtmlString Property and TinyMce.

icon of user profile

Published 20th of May 2019
Episerver.CMS.UI 11.15
Episerver.CMS.TinyMce >2

Last day I found an interesting question at episerver.world (yes it links to world.episerver.com). The user wanting to use blocks inline in P-tags in TinyMce. That does’nt work since:
1. content is inserted as div
2. tinymce does auto correct divs inside p, to p, div, p.

So i suggested to

1. override insert event, make the div to span instead. (use the BeforeSetContent event to intercept and change it
2. span is not rendering content, so you need to fix that too.

Default behavior:

Wanted behavior:

Proof of concept

PoC Block model: (with one property)

namespace EpiserverTestSite.Models.Blocks
{
    [SiteContentType(GUID = "996CF12F-1F01-4EA0-922F-0778314DDA99")]
    public class DynamicPropertyBlock : BlockData
    {
        [Display(Order = 1, GroupName = SystemTabNames.Content)]
        [Required]
        public virtual string PropertyName { get; set; }

    }
}

PoC Block view (Get the current page model and a specific property on that current page)

@model DynamicPropertyBlock @Html.Raw(EPiServer.ServiceLocation.ServiceLocator.Current.GetInstance<EPiServer.Web.Routing.IPageRouteHelper>().Page.Property[Model.PropertyName])

Change the rendering of XhtmlString by replacing the default view in Views/shared/blocks/XhtmlString.cshtml

@using System.Text.RegularExpressions
@using EPiServer.Editor
@using EPiServer.Web.Mvc.Html
@model EPiServer.Core.XhtmlString
@{

    if (PageEditing.PageIsInEditMode)
    {
        Html.RenderXhtmlString(Model);
    }
    else
    {
        Html.RenderXhtmlString(ReplaceDynamicContent(Model));
    }
}

@functions
{

    private static readonly Regex MyRegex =
        new Regex("<span[^>]*class=\"epi-contentfragment\"[^>]*>([^<]*)</span>",
            RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline);


    public EPiServer.Core.XhtmlString ReplaceDynamicContent(EPiServer.Core.XhtmlString xhtmlString)
    {
        string internalString = xhtmlString.ToInternalString();

        // find all instanses
        var matchCollection = MyRegex.Matches(internalString);
        if (matchCollection.Count > 0)
        {
            foreach (Match match in matchCollection)
            {
                //revert so episerver default rendering kicks in
                var newhtml = match.Value.Replace("<span", "<div").Replace("</span", "</div");
                internalString = internalString.Replace(match.Value, newhtml);
            }
            return new EPiServer.Core.XhtmlString(internalString);
        }
        return xhtmlString;
    }    

}

Override the inserted html when block dropped

JS file:

"use strict";
var tinymce = tinymce || {};
tinymce.PluginManager.add('insertchange', function (editor, url) {

    editor.on('BeforeSetContent', function (e) {
        if (e.content.indexOf('<div data-contentlink') > -1) {
            e.content = e.content.replace("<div ", "<span ").replace("</div ", "</span ");
        }
    });

    return {
        getMetadata: function () {
            return {
                name: 'Insert block templating', url : 'https://devblog.gosso.se'
            };
        }
    };
});

Register the js plugin in TinyMceInitialization

 [ModuleDependency(typeof(TinyMceInitialization))]
    public class CustomizedTinyMceInitialization : IConfigurableModule
    {
        public void Initialize(InitializationEngine context)
        {
        }

        public void Uninitialize(InitializationEngine context)
        {
        }

        public void ConfigureContainer(ServiceConfigurationContext context)
        {
            context.Services.Configure<TinyMceConfiguration>(config =>
            {
                //you can set this plugin on only specific page types of course
                config.Default()
                    .AddExternalPlugin("insertchange", "/ClientResources/Scripts/tinymce/plugins/insert_hack/insertchange.js");
            });

        }
    }

Disable block types to be dropped into XhtmlString

Since AllowedTypes is not applicable to XhtmlString, you need to implement an editordescriptor like this:

[EditorDescriptorRegistration(
    TargetType = typeof(XhtmlString),
    EditorDescriptorBehavior = EditorDescriptorBehavior.ExtendBase,
    UIHint = "OnlyAllowSomeBlock")]
public class OnlyAllowSomeBlock : EditorDescriptor
{
    public OnlyAllowSomeBlock()
    {
        AllowedTypes = new[] { typeof(DynamicPropertyBlock) };
    }
}

Put Attribute on Property:

[UIHint("OnlyAllowSomeBlock")]
public virtual XhtmlString MainBody { get; set; }

 

Conclusion

There are obvious drawbacks with this solution.
1. You don’t get the default behavior “Go To Block” (because episerver has scoped it to “div.epi-contentfragment:not([mceItem])” in the “epi-block-tools” widget, but you can implement your own, check source code epi-block-tools.js.uncompressed.js)
2. It doesnt trace the block and warn when “moved to trash”
3. It doesnt update the name if you change the Block Name

Would I recommend this?

No, I would rather see a solution with some markup instead, and replaced when rendered with output process action filter. Something like this: https://gist.github.com/davidknipe/4dc89826ea00d4fc65df60965526b69d
https://www.david-tec.com/2017/11/tokenised-content-in-episerver/

More reading:

SEO TERMS

  • Disable dragging of blocks in XHTML properities
  • Blocks in tinymce

About the author

Luc Gosso
– Independent Senior Web Developer
working with Azure and Episerver

Twitter: @LucGosso
LinkedIn: linkedin.com/in/luc-gosso/
Github: github.com/lucgosso