Use blocks as legacy dynamic properties – POC

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

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

 

 

3 thoughts on “Use blocks as legacy dynamic properties – POC”

  1. I remember talk about a ContentType decoration for “treat as inline or block HTML element” when dropped into a TinyMCE instance. It was probably Linus Ekström and maybe it’s been in the icebox since he left Epi?

  2. I made a addition to the tinymce plugin and add a call to an api to check if the blocktype is the one(s) you want inline.
    There are not any draw backs anymore.

Leave a Reply to Kronbä Cancel reply

Your email address will not be published. Required fields are marked *