Tags:

Episerver help texts, improved

Life as a web editor can be difficult. Some larger Episerver solutions may have more than 100 different page types, each of which has many tabs with tens of properties. Each property has a label, which is a short text that tells editors what the property are for, for example «Headline», «Main Image» or similar.

Sometimes, the label does not have room for all the information that editors should be presented with, and therefore, Episerver has added the option to display a tooltip, an additional description that appears at mouseover on the label. There are a couple of drawbacks to this solution. There is no visual marking that informs the editors of which properties have such tooltip text and which ones do not have. And since often very few properties have tooltip texts, it’s quite likely that the editors don’t see the ones that are added.

If Episerver can’t fix this for the better of their ediors, other developers will find ways.

The fact that all these solutions exist, might indicate that Episerver could have done a better job theirself.

Sometimes we could benefit from a help text in edit mode, that is neither tied directly to a property nor placed next to a property. I came up with a quick solution, that shows a helptext like in the screenshot below. I’m not using the tooltip to a property the editors can edit, but simply change the apperance of a property whose only purpose is to display the help text. A kind of custom property. The property’s label are shown as a header, and the tooltip is shown in regular font below.

The user interface with the help text

EditorDescriptor

EditorDescriptors is a concept introduced by Episerver in CMS7, and is a class defining how the client will display a property or a group of properties. With an EditorDescriptor we can choose what Dojo/Dijit widget (javascript file) that is responsible for rendering the property in edit mode.

using System;
using System.Collections.Generic;
using EPiServer.Shell.ObjectEditing.EditorDescriptors;
using EPiServer.Shell.ObjectEditing;

namespace Alloy.Business.EditorDescriptors
{
   [EditorDescriptorRegistration(TargetType = typeof(string), 
      UIHint = "HelpText")]
   public class HelpTextEditorDescriptor : EditorDescriptor
   {
      public override void ModifyMetadata(ExtendedMetadata metadata, 
         IEnumerable<Attribute> attributes)
      {
         ClientEditingClass = "alloy/editors/HelpText";
         metadata.EditorConfiguration.Add("title", metadata.DisplayName);
         metadata.DisplayName = "";
         base.ModifyMetadata(metadata, attributes);
      }
   }
}

The important thing here is TargetType that specifies which property type the EditorDescriptor applies to, UIHint which becomes the tag we can put as an attribute on selected properties to get the new rendering, and ClientEditingClass that specifies which javascript file defines how the property will be displayed in edit mode. In addition, we create a new title in EditorConfiguration so that the property’s display name becomes available to us from the javascript. Finally, we reset the built-in property for display name (metadata.DisplayName), to avoid displaying the default label with mouseover text. We want to display it (like title) so we have full control over markup.

On the code line defining ClientEditingClass, the segment alloy/ points to the folder /ClientResources/Scripts. To check what to use in your project, look in module.config for this definition.

<dojo>
   <paths>
      <add name="alloy" path="Scripts" />
   </paths>
</dojo>

Javascript

To specify exactly how the help text (property) are shown to the editor, we will have to add some javascript. In this case the javascript also includes some html. Here we position the value title (that we prepared in the EditorDescriptor) and the tooltip in markup, and also make use of a couple of CSS classes we’ll define later.

define([
        "dojo/_base/array",
        "dojo/_base/connect",
        "dojo/_base/declare",
        "dojo/_base/lang",

        "dijit/_CssStateMixin",
        "dijit/_Widget",
        "dijit/_TemplatedMixin",
        "dijit/_WidgetsInTemplateMixin",

        "epi/epi"
    ],
    function (
        array,
        connect,
        declare,
        lang,

        _CssStateMixin,
        _Widget,
        _TemplatedMixin,
        _WidgetsInTemplateMixin,

        epi
    ) {

        return declare("alloy.editors.HelpText", 
                [_Widget, 
                _TemplatedMixin, 
                _WidgetsInTemplateMixin, 
                _CssStateMixin], {

            templateString: 
                "<div class=\"helptext shadow\">\
                         <strong data-dojo-attach-point=\"heading\"></strong>\
                         <div data-dojo-attach-point=\"description\"></div>\
                 </div>",

            postCreate: function () {
                this.inherited(arguments);
                this.heading.innerHTML = this.title;
                this.description.innerHTML = this.tooltip;                
            }
        });
    }); 

CSS

To spice things up, we define the two CSS classes that we used in the Javascript file.

.helptext {
    width: 725px;
    border-radius: 20px;
    border: 2px solid #73AD21;
    padding: 10px;
    background-color: #f1f1f1;
    margin-bottom: 10px;
}

.shadow {
    -moz-box-shadow: 3px 3px 5px 6px #ccc;
    -webkit-box-shadow: 3px 3px 5px 6px #ccc;
    box-shadow: 3px 3px 5px 6px #ccc;
}

The CSS should be placed in a separate file, but in order to have Episerver load the CSS file in edit mode, we will have to register it in module.config.

<module>
   <clientResources>
      <add name="epi-cms.widgets.base" 
         path="Styles/HelpText.css" 
         resourceType="Style" />
   </clientResources>
</module>

Add the help text

All is set, now we just have to add the help text to the page types were we want them. The text on the screenshot in the top of this blog post were added like this. The heading was added as Name, and the more verbose text as Description. And of cource, we must remember the [UIHint("HelpText")].

public class ArticlePage : StandardPage
{
    [Display(Name = "Redaktørene har en vanskelig jobb!",
        Description = "Men jobben kan gjøres enklere hvis...")]
    [UIHint("HelpText")]
    public virtual string ExtraInformationAboutEditors { get; set; }
}

Support for language files

To add support for multiple languages, and enable updating of texts without compile and deploy, the texts could be added to a language xml file like this.

<standardpage>
        <properties>
            <extrainformationabouteditors>
                <caption>Translated caption</caption>
                <help>Translated description...</help>
            </extrainformationabouteditors>
        </properties>
</standardpage>

With the texts safely placed in the language file, we can omit them from the propery definition, and then this is all that is left.

public class ArticlePage : StandardPage
{
   [UIHint("HelpText")]
   public virtual string ExtraInformationAboutEditors { get; set; }
}

Finishing touches

The problem now is that the label gets its text from the language file at a later time than when the text is defined on the property definition. Because of this the label will be shown, even though we try to reset it in the EditorDescriptoren.

The help text with unwanted tool tip

No matter how the texts are defined, in language file or property definition, we can hide the label with some CSS. Simply add this definition to the CSS file we created earlier.

label[for^="alloy_editors_HelpText_"] {
    display: none !important;
}

The CSS selector hides all labels that are label for anything that starts with (^=alloy_editors_HelpText_, that must match ClientEditingClass defined in the EditorDescriptor. Just replace slash (/) with underscore(_).

The result

Now, we are finally able to add a heading + description at the top of tabs or between other properties, and the texts can be defined in either language files or directly in the property definition.

The user interface with the help text

Found this post helpful? Help keep this blog ad-free by buying me a coffee! ☕