How would you like to work with Tiny MCE settings and Page Type Builder?

I’m currently working with support for creating property settings for the Tiny MCE editor in Page Type Builder. The easy part, the coding, is pretty much done, but the hard part remains. How it should work. Let me begin by outlining the general idea.

There will be support for creating both global settings and settings for a specific property on a page type. For properties on a page type we’ll create the settings using a new attribute, called TinyMCESettingsAttribute. It will look something like this (details omitted for brevity):

public class TinyMceSettingsAttribute : PropertySettingsAttribute
{
    public string ContentCss { get; set; }

    public int Width { get; set; }

    public int Height { get; set; }

    public string[] FirstToolbarRow { get; set; }

    public string[] SecondToolbarRow { get; set; }

    public string[] ThirdToolbarRow { get; set; }

    public string[] FourthToolbarRow { get; set; }

    public string[] FifthToolbarRow { get; set; }

    public string[] NonVisualPlugins { get; set; }
}

As you can see it pretty much matches the TinyMCESettings class in EPiServer except that the attribute has a number of string arrays for toolbar rows while the settings class has a list of lists that specifies the buttons on each toolbar row.

Using the attribute could look something like this

[TinyMceSettings(Width = 600, Height = 400,
    FirstToolbarRow = new [] { "anchor", "epilink"}, 
    SecondToolbarRow = new [] { "unlink"})] 
[PageTypeProperty]
public virtual string MainBody { get; set; }

So what’s the problem?

My conundrum is what the default values should be. The TinyMCESettings class, like all property settings, has a method called GetDefaultValues which returns an instance of the class with the default settings. It’s these you see when choosing to create new settings in EPiServer’s admin mode.

If one wanted to use the attribute to specify the same settings as the default ones it would look like this:

[TinyMceSettings(Width = 600, Height = 400,
    FirstToolbarRow = new [] { 
    "epilink", "unlink", "anchor", "image", "media", 
    "epidynamiccontent", "epiquote", "separator", 
    "cut", "copy", "paste", "pastetext", "pasteword", 
    "separator", "table", "delete_table", "row_props", 
    "cell_props", "separator", "col_before", "col_after", 
    "delete_col", "separator", "row_before", "row_after", 
    "delete_row", "separator", "split_cells", 
    "merge_cells"},
    SecondToolbarRow = new [] { 
    "bold", "italic", "underline", "separator", 
    "justifyleft", "justifycenter", "justifyright", 
    "separator", "bullist", "numlist", "hr", 
    "formatselect", "styleselect", "undo", "redo", 
    "separator", "cleanup", "code", "visualaid", 
    "fullscreen", "search"},
    NonVisualPlugins = new [] {
    "advimage", "epifilebrowser", "epifilemanagerdrop"})] 
[PageTypeProperty]
public virtual string MainBody { get; set; }

Now that’s ugly! Of course if we just wanted the default settings we probably wouldn’t use the attribute in the first place. But what if we wanted to specify that we wanted the default values except for one button?

One option would be that the default values in the attribute would match what’s returned by TinyMCESettings.GetDefaultValues(). That way we would only have to specify the row that we would like to modify. Another is to say that the above examples are fine since the use case when wanting to specify settings for a property is often that we only want to allow a small set of buttons. In this case the attribute would default to empty rows for toolbars. But what about the non visual settings then? If the toolbars default to empty, then we’d probably want to do the same for non visual plugins (and width and height?) in order to be consistent. But on the other hand, we’d probably want the default settings there, since specifying them each time would be a pain.

Help me!

I’m really not sure what way to go here. Therefore, I could really use your help by thinking about how you would like to use the attribute and in what situations. Especially what use cases you can see for using the attribute is interesting.

But before you leave a comment, please read this post again and take 30 minutes to think about it, because I don’t think there’s a quick and simple solution and I really need well thought out input :-)

A few things to keep in mind:

  • There will also be support for global settings and specifying that they should be used for a specific property, which I think will be the most common way to specify settings for the Tiny MCE editor.
  • Removing buttons from a set of defaults could be handled by adding properties for specifying what buttons to remove from each row, but remember that there are also multiple separators in there and in order to remove one of those one would probably have to specify the index of it rather than the name.
  • Constants can’t be arrays as a constant initializer must be compile time constant, so providing a set of ready-to-use arrays with buttons won’t work.
  • It’s only possible to set attribute values of element types (byte, sbyte, int, uint, long, ulong, float, double, String, char, bool, enum and type) and single-dimension arrays of such types.

PS. For updates about new posts, sites I find useful and the occasional rant you can follow me on Twitter. You are also most welcome to subscribe to the RSS-feed.

Comments

  1. Jarle Friestad's avatar

    Jarle Friestad 1 years ago

    You could create constant values so you don't have to use those magic strings. They constants could also contain multiple values and then split the values in the property setter on FirstToolbarRow/SecondToolbarRow .

     public class TMButtons
     {
         const string Link = "epilink",
         const string UnLink = "unlink"
         const string LinkSet = "epilink,unlink"
     }
    


    Another thing i would change is to have the concept of a toolbar "table". That way you could have infinite number of toolbar rows (if possible)

    [TinyMceSettings(Width = 600, Height = 400,
        Toolbar= new Toolbar( 
                 new ToolbarRow(TMButtons.Links),
                 new ToolbarRow(TMButtons.Copy, TMButtons.Paste))]
    

  2. Joel Abrahamsson's avatar

    Joel Abrahamsson 1 years ago

    Hey Jarle, you can't instantiate objects in attribute annotations. In other words, there is no way to have an infinite number of toolbar rows using attributes without using really nasty string based solutions (see the "A few things to keep in mind" list) for further explanation.

    As for constants for buttons that's already done, problem is just that the code would have been even longer if I had used them in the example above :)

    Splitting strings when creating settings from the arrays in the attribute is an interesting idea, thanks.

  3. Magnus Paulsson's avatar

    Magnus Paulsson 1 years ago

    I agree with Jarle on the string constants.

    One alternative is to have just the settings differing from the defaults, like a FirstRowToolbarAdd (Extend?) and FirstRowToolbarRemove (Restrict?) property that modify the default collection. Leave FirstRowToolbar too, for those cases when you need to specify just a small set (in which case FirstRowToolbarRemove would be very long). The standard javascript settings for tinymce work something like that, at least when it comes to html filtering (allowed_elements vs extended_allowed_elements).

  4. Linus Ekström's avatar

    Linus Ekström 1 years ago

    Hi!

    I stumbled upon a bit of a similar problem in the EPiServer CMS 6 R2 development where I needed to be able to set tinymce init options in an attribute where the values would be dynamicly set from configuration. My solution was to add the ability to define a type that was responsible for settting the options. This class has to implement an interface.

    If you go for this solution you could have a base class in ptb that sets the "default" settings and a method that you can override in your custom class that can add/remove or simply define it's own settings. I guess it could look something like this:

    [TinyMceSettings(SettingsClass=typeof(MyTinyMceSettingsClass)]

    public class MyTinyMceSettingsClass : PtbTinyMceDefaultSettings
    {
    protected override SomeSettingsObject GetSettings()
    {
    SomeSettingsObject settings = base.GetSettings();
    settings.Add([some plug-in]);
    return settings;
    }
    }

  5. Johan Björnfot's avatar

    Johan Björnfot 1 years ago

    Could one option be to use a Flags declared enum instead of string[]? Like something below:
    [Flags]
    public enum ToolbarItem
    {
    None = 0,
    Anchor = 1,
    Image = 2,
    Copy = 4,
    ...
    Default = Anchor | Image | ...,
    All = Anchor | Image | Copy |...
    }

    Then you could create instances of the attribute like:
    [TestAtttribute(Items = ToolbarItem.Default ^ ToolbarItem.Image)]

    which would give the default except Image.

  6. Joel Abrahamsson's avatar

    Joel Abrahamsson 1 years ago

    Linus, I should have provided more context. The attribute it self actually provides such functionality (see https://github.com/joelabrahamsson/Page-Type-Builder/blob/master/PageTypeBuilder/PropertySettings/TinyMCESettingsAttribute.cs). So you can either provide your very own attribute or inherit from the default one in PTB and override things.
    In other words, what I'm going for here is more what PTB should provided out of the box.

    Any thoughts given this extra context (which I should have provided in the post, sorry)?

    Johan and Magnus, I thought of that as well, but will that work with separators? Let's say you want to remove a button and a separator? I'm not good at enums using flags so I very much value the input.
    When it comes to enums I guess it should also be possible to also add custom plugins, would that work with enums?

  7. Magnus Paulsson's avatar

    Magnus Paulsson 1 years ago

    A problem with flags/bitmasks is that the number of options has an upper limit? 31 for a standard enum and 63 for one based on long? I don't know about the custom values, it might be possible to use them in the bitmasks if you reserve some of the "address space", find a way to "resolve" the custom values and then treat the masks as ints/longs instead. But no, not a good way I think.

  8. Johan Björnfot's avatar

    Johan Björnfot 1 years ago

    Sorry, I did not see that the same value can occour several times on a row (like separator). To support that with enums a row would have to be an array of enum and in that case I there is probably no benefits of using an enum rather that a string....

  9. Jarle Friestad's avatar

    Jarle Friestad 1 years ago

    What about 2 dimensional arrays then? A bit ugly but still efficient =>

        [AttributeUsage(AttributeTargets.Class)]
        class TinyMceSettings : Attribute
        {
            public string[][] Toolbar { get; set; }
        }
       [TinyMceSettings(Toolbar = 
                    new [] 
                    { 
                        new[] { "link", "unlink" },
                        new[] { "copy", "paste", "cut" }
                    })]
    
    


  10. Johan Björnfot's avatar

    Johan Björnfot 1 years ago

    When I think of it a row could be an array of enum and treated in the way that a separator is actually put between two elements in the array. For example

    [TestAtttribute(FirstRow = new ToolBarItem[]{ ToolbarItem.LinkTools ^ ToolbarItem.Image, ToolBarItem.PasteTools)]

    where e.g. LinkTools is defined in enum as e.g.
    Anchor | Media | Image ....

    Would give a row where there first are all LinkTools items except Image then follows a separator and then comes all elements definied in PasteTools (e.g. Copy | Cut | Paste ...)

    Hope you can follow my thoughts...:-)

  11. Anders Hattestad's avatar

    Anders Hattestad 1 years ago

    I think that if we add a attributes that inherits from TinyMceSettings we could have some normal code as a method. Then PTB could provide 1 or 2 attributes that inherits from TinyMceSettings , and if we need to add some new function (button or alike) we could inherit from the attribute and add it.

    so the actully attribute on the property would be just one line

  12. Joel Abrahamsson's avatar

    Joel Abrahamsson 1 years ago

    Very interesting idea Johan! The only drawback I guess would be that there would need to be special handling for custom buttons, right?

  13. Linus Ekström's avatar

    Linus Ekström 1 years ago

    Joel: I think that the enum solution will be a hinder both for custom plug-ins but also when EPiServer releases with new tiny/EPi plug-ins. In both these cases special handling needs to be done to add these plug-ins.

  14. Johan Björnfot's avatar

    Johan Björnfot 1 years ago

    Yes custom plugins would need to be special handled. Perhaps the "user" could add his custom plugin to the enum and recompile PageTypeBuilder.
    New EPiServer releases are probably easier since I guess there will be a new version of PageTypeBuilder then as well.

  15. Lee Crowe's avatar

    Lee Crowe 1 years ago

    Hi Joel

    Is there any reason why we have to have a specific attribute for the property settings?

    One way of doing this could be to have an interface e.g. IPropertySettingsCreator.

    We could then have classes that implement the interface. The interface could like like the following:

    interface IPropertySettingsCreator
    {
        IPropertySettings CreatePropertySettings();
    }
    


    A class implementation of the interface could like like:

    class TinyMceSettingsCreator : IPropertySettingsCreator
    {
        public IPropertySettings CreatePropertySettings()
        {
            TinyMCESettings settings = new TinyMCESettings().GetDefaultValues() as TinyMCESettings;
            settings.ToolbarRows.Clear();
            return settings;
        }
    }
    


    Then you could add a new property named "PropertySettingsCreator" of type Type to your PageTypePropertyAttribute.


    Lee
  16. Anders Hattestad's avatar

    Anders Hattestad 1 years ago

    You could have that logic as an attribute that inherits from the TinyMceSettings. If Lee's method contains a input TinyMCESettings we could mark a property with more than one of those different attributes. One to clear all, one to add links, one to add format...

  17. Joel Abrahamsson's avatar

    Joel Abrahamsson 1 years ago

    Lee, interesting idea. Need to sleep on it though :)

    Anders, maybe I'm misunderstanding you, but attributes must inherit from Attribute so there's the issue of multiple inheritance.

  18. Anders Hattestad's avatar

    Anders Hattestad 1 years ago

    What I meen is that we could have one base class like this

    public class TinyMceAttribute : Attribute
    {
        public virtual TinyMCESettings ApplySettings(TinyMCESettings settings)
        {
            return settings;
        }
    }
    

    Then we could make new attributes like this, and add logic in ApplySettings.
    public class ClearAllAttribute : TinyMceAttribute { }
    public class AddTextFormatAttribute : TinyMceAttribute { }
    public class AddLinksAttribute : TinyMceAttribute { }

    then we could mark like this
    [ClearAll]
    [AddLinks]
    [AddTextFormat]
    public string Test { get; set; }
    

    Then we could have code that finds all the attributes that inherit from TinyMceAttributes
    Type type = this.GetType();
    TinyMCESettings settings = new TinyMCESettings().GetDefaultValues() as TinyMCESettings;
    object[] list = type.GetProperty("Test").GetCustomAttributes(typeof(TinyMceAttribute), true);
    string debug = "";
    foreach (TinyMceAttribute item in list)
    {
        debug += item.GetType().Name + "";
        item.ApplySettings(settings);
    }
    

    The output of this code will return
    AddLinksAttribute
    ClearAllAttribute
    AddTextFormatAttribute
    

    so the place will be random it can seem, but basicly that was my idea
  19. Lee Crowe's avatar

    Lee Crowe 1 years ago

    Hi Joel

    The way I see it is that you just need to provide the mechanismn within PTB for us to attach property settings (not just tiny mce) to a property.

    Providing a class specific to tiny seems out of scope of the project and maybe should be left to the developer.

    Although a helper library could be created for common settings.

    That's just my thoughts ;)

  20. Joel Abrahamsson's avatar

    Joel Abrahamsson 1 years ago

    Thanks for all your input! I'll play around with this a bit more and try to get a preview out there pretty soon.

Follow me on Twitter

  1. @tim_abell The gigantic ones are for customers who specifically ask for them only :) 1 months ago
  2. Looking to buy a gigantic easter egg filled with candy for delivery in Stockholm. Any recommendations? 1 months ago
  3. @strandberg_m Du måste skriva om resultatet efteråt! 1 months ago
follow me

Latest comments

  1. Joel Abrahamsson wrote "Hi Jonas! The fluent API is really geared towards working..." on Building a search page for an EPiServer site using Truffler
  2. Jonas wrote "Thank you for one more great write up! If you're not lucky ..." on Building a search page for an EPiServer site using Truffler
  3. David Knipe wrote "The CategoriesFacet method will save me a load of headaches ..." on Cool new features in the Truffler .NET API

About this site

This blog is built with EPiServer Community, EPiServer CMS, ASP.NET MVC and a bunch of other great products. The source code is available for download at the projects page, where you also can read more about this site and my other projects.

read more