Take the community feedback survey now.

Praful Jangid
Jul 15, 2019
  91
(0 votes)

Adding icons/thumbnails to content tree items in Episerver CMS

Updates [2021/01/04]: Geta has updated the module to allow you to configure the tree icons. See below link

https://github.com/Geta/Epi.FontThumbnail#tree-icon-feature

You might not need this customization anymore.

.

.

In continuous efforts to make the content editor’s life easy, we will learn to add icons/thumbnails, to the items in our content tree. So that we can easily identify the type of an item.

In EpiServer, we have an attribute to add preview images to our custom types (Page/Block types). But that will only show while adding new item (as shown in below image). So, this is the default scenario. When we are using the Episerver’s ImageUrl attribute.

You can see the icons on the content types (on right side), but those are missing on the items in tree (in left panel).

So, I would recommend Geta module for thumbnails (Geta.Epi.FontThumbnail). That is very handy and easy to use. And, this (below image) is when using the Geta module.

The same situation we face here, icons are missing on content tree items.

Here, I will add some code along with Geta module to add icons to our items in tree.

Here we go.

Let’s start by adding an Initialization Module, named let's say TreeIconsInitialization inherited from IInitializableModule and dependencies as show below.

[InitializableModule]
[ModuleDependency(typeof(InitializableModule))]
[ModuleDependency(typeof(FontThumbnailInitialization))]
public class TreeIconsInitialization : IInitializableModule
{
}

Add the following code inside the Initialize function

public void Initialize(InitializationEngine context)
{
    if (context == null)
        return;

    var assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
    assemblies = assemblies.Where(x => x.GetName().Name.StartsWith("AlloyDemo")).ToList();

    var types = assemblies.SelectMany(x => x.GetTypes()).ToList();
    var typesWithIcon = types
        .Where(type => type.IsDefined(typeof(ThumbnailIcon), false))
        .ToList();

    var iconClasses = typesWithIcon
        .Select(type => new
        {
            Type = type,
            IconClass = this.ParseIconClass(type)
        })
        .ToDictionary(key => key.Type, value => value.IconClass);

    foreach (var uiDescriptor in context
        .Locate
        .Advanced
        .GetInstance<UIDescriptorRegistry>().UIDescriptors)
    {
        if (iconClasses.ContainsKey(uiDescriptor.ForType))
        {
            uiDescriptor.IconClass += iconClasses[uiDescriptor.ForType];
        }
    }
}

In above code, we are scanning the project assemblies to retrieve the types that are defined with the ThumbnailIcon attribute. Here, function ParseIconClass() is responsible for parsing the icon class (font awesome icon class).

Here is the code for parsing the icon class:

private string ParseIconClass(Type type)
{
    if (type == null)
        return null;

    var attribute = Attribute.GetCustomAttribute(type, typeof(ThumbnailIcon)) as ThumbnailIcon;

    string path = attribute?.Path;

    if (string.IsNullOrWhiteSpace(path))
        return null;

    Uri.TryCreate($"http://localhost{path}", UriKind.RelativeOrAbsolute, out Uri uri);
    var query = System.Web.HttpUtility.ParseQueryString(uri.Query);
    string characterCodeString = query["Character"];

    if (!int.TryParse(characterCodeString, out var characterCode))
        return null;

    FontAwesome fa = (FontAwesome)characterCode;
    string name = System.Enum.GetName(typeof(FontAwesome), fa);
    string kebab = FromPascalCaseToKebabCase(name);
    string iconClass = $"fa fa-{kebab}";

    return iconClass;
}

In this function, we are getting the path value of the ThumbnailIcon attribute, as this path contains the icon code in querystring param “Character”. Then converting that code into the font awesome icon class.

The method FromPascalCaseToKebabCase() is for converting the icon name into actual font-awesome icon class.

private static string FromPascalCaseToKebabCase(string input)
{
    if (string.IsNullOrWhiteSpace(input))
        return null;

    StringBuilder retVal = new StringBuilder(32);

    retVal.Append(char.ToLowerInvariant(input[0]));
    for (int i = 1; i < input.Length; i++)
    {
        if (char.IsLower(input[i]))
        {
            retVal.Append(input[i]);
        }
        else
        {
            retVal.Append("-");
            retVal.Append(char.ToLower(input[i], CultureInfo.InvariantCulture));
        }
    }

    return retVal.ToString();
}

After all these, of course we will need to add reference to font awesome css file. To do so, add (if, not already added) module.config and following code.

<?xml version="1.0" encoding="utf-8"?>
<module>
  <clientResources>
    <add name="epi-cms.widgets.base" path="/Static/css/font-awesome.min.css" resourceType="Style" />
  </clientResources>
</module>

And, here we have our icons/thumnails to content tree items

RnD credit goes to our EMVP Drew Null.

If anyone need a hand on any issue or understanding any code line, do let me know.

Happy Coding, thanks and regards,

Praful Jangid

Jul 15, 2019

Comments

Please login to comment.
Latest blogs
A day in the life of an Optimizely OMVP - Opticon London 2025

This installment of a day in the life of an Optimizely OMVP gives an in-depth coverage of my trip down to London to attend Opticon London 2025 held...

Graham Carr | Oct 2, 2025

Optimizely Web Experimentation Using Real-Time Segments: A Step-by-Step Guide

  Introduction Personalization has become de facto standard for any digital channel to improve the user's engagement KPI’s.  Personalization uses...

Ratish | Oct 1, 2025 |

Trigger DXP Warmup Locally to Catch Bugs & Performance Issues Early

Here’s our documentation on warmup in DXP : 🔗 https://docs.developers.optimizely.com/digital-experience-platform/docs/warming-up-sites What I didn...

dada | Sep 29, 2025

Creating Opal Tools for Stott Robots Handler

This summer, the Netcel Development team and I took part in Optimizely’s Opal Hackathon. The challenge from Optimizely was to extend Opal’s abiliti...

Mark Stott | Sep 28, 2025

Integrating Commerce Search v3 (Vertex AI) with Optimizely Configured Commerce

Introduction This blog provides a technical guide for integrating Commerce Search v3, which leverages Google Cloud's Vertex AI Search, into an...

Vaibhav | Sep 27, 2025

A day in the life of an Optimizely MVP - Opti Graph Extensions add-on v1.0.0 released

I am pleased to announce that the official v1.0.0 of the Opti Graph Extensions add-on has now been released and is generally available. Refer to my...

Graham Carr | Sep 25, 2025