Configuring TinyMCE when it's used in an Episerver PropertyList

From version 2.0 of EPiServer.CMS.TinyMce onwards it has never been easier to customize and configure TinyMCE (in Episerver) from code. I won't get into the specifics here, the Episerver documentation is comprehensive and there is also a great introductory blog post here.

Despite the fact that I really appreciate how easy configuration now is, I recently ran into an issue whereby I wanted to configure TinyMCE when it was used for a ListProperty, I knew I couldn't be the only person who encountered this so wasn't surprised when I also saw a recent question in the forums regarding it. Anyway, I basically gave the answer there so if you can't be bothered to trawl through a blog post—you know where to go!

At first, I figured that it'd be easy to customize the TinyMCE editor even when it's used as part of a PropertyList (in fact, I was sure I was missing something in the documentation), however, after digging around a bit it seems there is no easy way to do it via the initialization module approach (it's worth nothing that it will always use the default which is probably fine 99.9% of the time).

Anyway, the reason is kind of two-fold, and I'll try and run through them quickly.

Firstly, when creating a TinyMCE configuration for a property you are expressly limited to IContentData by the generic type parameter on the TinyMceConfiguration.For method:

public TinyMceSettings For<T>(Expression<Func<T, object>> property, TinyMceSettings copyFrom = null)
where T : IContentData

This means that you can't create a configuration for your PropertyList class as it won't be implmeneting IContentData (at least it shouldn't be).

You can of course create one for the IList<T> property where you are using this class, however, that also won't do anything, which is because:

Secondly, the ExtendedMetadata for your PropertyList class has no context.

That is to say that the XhtmlStringEditorDescriptor (which associates the configurations you define with the property editor UI) has a private method which checks that the ExtendedMetadata for our PropertyList class is either ContentDataMetaData (it's not, because as mentioned above our class is not content) or has a Parent (it doesn't, so unfortunately we don't know where the class is being used). Because of this it can't resolve which content you're using the PropertyList class on and as such always resolves the default TinyMCE settings.

Combining these two points means, as far as I can see, you can't use the initialization module to create a configuration for TinyMCE when using it in a PropertyList, because:

  1. It's not IContentData
  2. On the EditorDescriptor we can't tell which content it belongs to
  3. The Episerver implementation relies on both these (at least for the methods to get and create the configurations)

To cut an already long story short, I played around with a couple of options—including trying to give it some context to where it was being used—but things began to feel pretty 'hacky'. In the end, I think the simplest approach is also the best (which was also my answer to the forum post). Given this is probably just an edge case let's not over complicate things:

[EditorDescriptorRegistration(TargetType = typeof(XhtmlString), EditorDescriptorBehavior = EditorDescriptorBehavior.OverrideDefault, UIHint = UIHint)]
public class PropertyListXhtmlStringEditorDescriptor : XhtmlStringEditorDescriptor
{
    public const string UIHint = "PropertyListXhtmlString";

    public PropertyListXhtmlStringEditorDescriptor(ServiceAccessor<TinyMceConfiguration> tinyMceConfiguration) : base(tinyMceConfiguration)
    {
    }

    public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable<Attribute> attributes)
    {
        base.ModifyMetadata(metadata, attributes);

        if (!metadata.EditorConfiguration.ContainsKey("settings"))
        {
            return;
        }

        var settings = (TinyMceSettings)metadata.EditorConfiguration["settings"];

        if (settings == null)
        {
            return;
        }

        // Update the settings for this usage
        settings.AddPlugin("print")
            .Toolbar("cut", "paste")
            .DisableMenubar();

        // Set the size as desired
        settings.Width(450);
        settings.Height(200);
        settings.Resize(TinyMceResize.Vertical);
    }
}

Here we create an EditorDescriptor which applies the default TinyMCE settings and then we make whatever changes we need. We can then add the prerequisite UIHint to our property.:

[UIHint(PropertyListXhtmlStringEditorDescriptor.UIHint)]
[Display(Name = "Text", Order = 10)]
public XhtmlString Text { get; set; }

There we go, that's all you needed! Honestly this blog post probably makes a bit of a mountain out of a molehill, but I wanted to explain why what seemed to be the obvious approach didn't necessarily work...and at least it included a solution! ✌️