Help! What does this template do again?

One of the challenges of building sites with Sitecore can be making sure that your content editors understand the purpose of each template and what data they’re supposed to be filling in for each field to build up their site. This can be a challenge for complex sites – the data model required for building a complex and feature packed site can sometimes be difficult to explain to non-technical editors.

Historically I’ve usually tackled this problem though documentation and training sessions – but it struck me the other day that Sitecore itself should be able to help too.

So I’ve been experimenting with the basics of a really simple prototype for explaining templates. I’ll explain a what I was thinking about for Content Editor this week, and hopefully offer some ideas for adding similar behaviour to Page Editor next week.

Sitecore has some fields that can help

If you turn on “Standard Fields” via the “View” tab in the Content Editor ribbon, you can find a “Help” section appears:

Help field

So out of the box we have a set of fields available that could be used for helping editors:

  • Name / DisplayName
  • Title
  • Help link
  • Long description
  • Short description

Generally developers will fill in (Display)Name and Title whenever they’re creating fields. But if we can get the extra description fields filled in, we should be able to make use of these by displaying them to editors.

But where can we display them?

Adding a help page

Commonly desktop applications have a “help” button, which opens up a window showing context-sensitive help. We can mimic this behaviour in Sitecore with a custom button on the ribbon, and by adding a custom editor tab. You commonly see editor tabs when you’re looking at template definitions:

Custom editor tabs

The “Builder” and “Inheritence” tabs here are custom editors.

To make your own custom tab, you need to build fairly standard ASP.Net page that will receive some context data via its querystring. I’ll come to the detail of that in the minute. Then you need a way to trigger its display – and a ribbon icon seems like the right approach here.

Switching to the Core database, we can add ribbon item in the usual way, by inserting a new LargeButton item. I’ve chosen to stick it next to the Save icon:

New icon

Other than picking a sensible icon, the key thing to set here is to set the Click to point at a new Command entry:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <commands>
      <command name="testing:help" type="Testing.HelpCommand,Testing" />
    </commands>
  </sitecore>
</configuration>

This wires up the button to a C# class that triggers our editor:

public class HelpCommand : Command
{
    public override void Execute(CommandContext context)
    {
        Assert.ArgumentNotNull(context, "context");

        if (context.Items.Length != 1)
        {
            return;
        }

        UrlString urlString = new UrlString("/Testing/Editors/FormFieldHelp.aspx");
        context.Items[0].Uri.AddToUrlString(urlString);
        UIUtil.AddContentDatabaseParameter(urlString);

        ShowEditorTab showEditorTab = new ShowEditorTab
        {
            Command = "contenteditor:preview",
            Header = "Help",
            Icon = Images.GetThemedImageSource("Core2/32x32/whats_this_h.png"),
            Url = urlString.ToString(),
            Id = "Help",
            Closeable = true,
            Activate = true,
        };

        SheerResponse.Eval(showEditorTab.ToString());
    }
}

When this command is executed it sets the url of our custom ASP.Net page, and makes sure that we pass through two parameters: The context item (which is what we’ll display help for) and the content database being used.

Then it uses the Sheer APIs to show a new tab, with the right icon.

Once we’re compiled that and deployed the placeholder custom page, we get our new icon, and clicking it will open our new tab:

New help tab

Rendering Sitecore’s help text

So now we want to fill in the custom tab with some useful data. Based on the standard Sitecore fields above, we can build some simple repeaters to show each section, field and its description and link:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <h1 runat="server" id="heading"/>

        <p><asp:Literal runat="server" ID="sysFields" Visible="false">Showing system fields</asp:Literal></p>

        <asp:PlaceHolder runat="server" ID="noFields" Visible="false">
            <div>This template has no fields to display help for</div>
        </asp:PlaceHolder>

        <asp:PlaceHolder runat="server" ID="hasFields" Visible="false">
            <asp:Repeater runat="server" ID="sectionRepeater">
                <ItemTemplate>
                    <p>
                        <div><asp:Image runat="server" id="sectionImage" /> <b>Section:</b> <asp:Literal runat="server" ID="sectionName" /></div>
                    
                        <asp:Repeater runat="server" ID="fieldRepeater">
                            <ItemTemplate>
                                <div><b>Field:</b> <asp:Literal runat="server" ID="fieldName" /></div>
                                <div><asp:Literal runat="server" ID="fieldDescription" /></div>
                                <asp:HyperLink runat="server" ID="fieldLink" />
                            </ItemTemplate>
                        </asp:Repeater>
                    </p>
                </ItemTemplate>
            </asp:Repeater>
        </asp:PlaceHolder>

    </form>
</body>
</html>

With that markup, we need to fetch the data to bind to the repeaters:

protected void Page_Load(object sender, EventArgs e)
{
    var prm = Sitecore.Web.WebUtil.ParseQueryString(HttpUtility.UrlDecode(Request.Url.Query));
            
    string database = prm["db"];
    string itemID = prm["id"];

    var db = Sitecore.Data.Database.GetDatabase(database);
    var item = db.GetItem(itemID);

    this.Title = "Dynamic help for '" + item.Template.Name + "' template";
    heading.InnerText = this.Title;

    var baseTemplate = Sitecore.Data.Managers.TemplateManager.GetTemplate(Sitecore.Configuration.Settings.DefaultBaseTemplate, db);
    bool showSystem = Sitecore.Web.UI.HtmlControls.Registry.GetBool("/Current_User/UserOptions.ContentEditor.ShowSystemFields", false);

    sysFields.Visible = showSystem;

    fieldSet = showSystem ? item.Template.Fields.AsQueryable() : item.Template.OwnFields.AsQueryable();
    int fieldCount = fieldSet.Count();

    if (fieldCount == 0)
    {
        noFields.Visible = true;
    }
    else
    {
        hasFields.Visible = true;

        sectionRepeater.DataSource = fieldSet
            .Select(f => f.Section)
            .DistinctBy(f => f.ID)
            .OrderBy(s => s.Sortorder)
            .ThenBy(s => s.Name);

        sectionRepeater.ItemDataBound += sectionRepeater_ItemDataBound;
        sectionRepeater.DataBind();
    }
}

The code starts off by parsing the querystring that has been constructed by the Command we defined above. It extracts the name of the database and the ID of the context item, and then uses the standard Sitecore API to fetch the database and load the item. And it sets the title and heading for the page.

Next, the code uses the API to fetch the standard base template, and a flag to find out whether the user has selected the “Show standard fields” option.

The fields collection in the current item are then turned into an IQueryable. If we’re not showing the standard fields then we filter these out, so they won’t get displayed, and then count the result.

If the remaining field set is empty, then we display an error. Otherwise we find the set of sections being used and bind them to the repeater, having sorted them appropriately.

For the sections, the repeater binding code can then render:

void sectionRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    if (e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item)
    {
        var section = e.Item.DataItem as TemplateSectionItem;

        Literal sectionName = e.Item.FindControl("sectionName") as Literal;
        Image sectionImage = e.Item.FindControl("sectionImage") as Image;
        Repeater fieldRepeater = e.Item.FindControl("fieldRepeater") as Repeater;
                
        sectionName.Text = section.Name;
        sectionImage.ImageUrl = Sitecore.Resources.Images.GetThemedImageSource(section.Icon, Sitecore.Web.UI.ImageDimension.id16x16);

        fieldRepeater.DataSource = fieldSet
            .Where(f => f.Section.ID == section.ID)
            .OrderBy(f => f.Sortorder);
        fieldRepeater.ItemDataBound += fieldRepeater_ItemDataBound;
        fieldRepeater.DataBind();
    }
}

Nothing special here – it fills in the markup for the heading of the section, and then extracts all the fields in the section for the second repeater.

void fieldRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    if (e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item)
    {
        var field = e.Item.DataItem as TemplateFieldItem;

        Literal fieldName = e.Item.FindControl("fieldName") as Literal;
        Literal fieldTitle = e.Item.FindControl("fieldTitle") as Literal;
        Literal fieldDescription = e.Item.FindControl("fieldDescription") as Literal;
        HyperLink fieldLink = e.Item.FindControl("fieldLink") as HyperLink;

        if (string.IsNullOrWhiteSpace(field.Title))
        {
            fieldName.Text = field.Name;
        }
        else
        {
            fieldName.Text = field.Title + " [" + field.Name + "]";
        }
        fieldDescription.Text = field.Description;

        if (!string.IsNullOrWhiteSpace(field.HelpLink.Value) && field.HelpLink.Value != "<link/>")
        {
            fieldLink.SetWithLinkField(field.HelpLink);
            fieldLink.Text = "More details";
        }
    }
}

Similarly, this just sticks the data from the field into the fields in the markup.

And with that in place, we can re-display the help page and see:

Basic Help

It’s in need of some better formatting, but it can show all the basic help for our fields. And it will refresh itself with the appropriate data as you navigate around the content tree.

Extending the help information

So other than some prettier CSS, what can we do to improve this more and help users?

The obvious thing to me is that we’re providing help here just for the fields. It would probably be helpful to add some help for the overall template. The same help fields are available here, and they can be extracted from the underlying item for the context item’s template.

If we add the following markup to be beginning of the asp:PlaceHolder markup:

<div>Desc: <asp:Literal runat="server" ID="templateDescription" /></div>
<div>Lnk: <asp:HyperLink runat="server" ID="templateLink"/></div>

Then we can populate that by fetching the data from the template:

templateDescription.Text = item.Template.InnerItem.Fields[Sitecore.TemplateFieldIDs.Description].Value;
var lnk = item.Template.InnerItem.Fields[Sitecore.TemplateFieldIDs.HelpLink];
if (!string.IsNullOrWhiteSpace(lnk.Value) && lnk.Value != "<link/>")
{
    templateLink.SetWithLinkField(lnk);
}

Another possibility for extending this might be to add some custom fields. If you had specific data you wanted to show that wouldn’t conveniently fit into the pre-existing Sitecore fields, then you could create your own “help template” and make the Sitecore Standard temple inherit it. This might be helpful for adding data for how one template relates to another, for example.

So some CSS and maybe a bit of Javascript could extend this further to make it fit in with the style of the Sitecore UI.

And (with luck) next week I’ll explain an approach to doing something similar in Page Editor…

Advertisements

One thought on “Help! What does this template do again?

  1. Pingback: Templates and help, part two | Jeremy Davis

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s