Introduction:
Content editors often face a tricky challenge when dealing with images within TinyMCE in Optimizley CMS. In this post, we'll explore the default options, the hurdles, and a straightforward solution to make our content more inclusive for everyone.
The Challenge:
Content editors often find themselves at a crossroads when integrating images into TinyMCE. Default options include the convenient drag-and-drop of media assets or the selection of images through specific TinyMCE controls. However, a notable issue surfaces when Optimizley CMS defaults to placing an empty alt tag or, in the case of previous version, utilizes the image name (derived from the file name at upload time) as the alternative text.
The Markup Dilemma:
The resulting markup, exemplified by
1 2 3 | < img src = "/globalassets/someimagefile.png" alt = "" /> or < img src = "/globalassets/someimagefile.png" alt = "someimagefile.png" /> |
The Solution
At render time, we can intercept XhtmlString and modify what is rendered.
Step 1: Extend ImageFile.cs with a new property AltText
[ContentType(GUID = "0A89E464-56D4-449F-AEA8-2BF774AB8730")] | |
[MediaDescriptor(ExtensionString = "jpg,jpeg,jpe,ico,gif,bmp,png")] | |
public class ImageFile : ImageData | |
{ | |
public virtual string Copyright { get; set; } | |
public virtual string AltText { get; set; } | |
} |
Step 2: Create a XhtmlString.cshtml Display Template
To take control of the rendering process for XhtmlString properties and ensure you set it up properly in your solution.
XhtmlString.cshtml
1 2 3 4 | @using EPiServer.Core @model XhtmlString @Html.Raw(Html.XhtmlString(Model.AdjustAltText())) |
Step 3: Develop the Extension Method - AdjustAltText()
Create an extension method AdjustAltText()
to instruct Optimizely to render the content for modification. Optimizely utilizes its mechanisms to personalize and render blocks within XhtmlString fields. After obtaining the HTML result, apply the necessary modifications using HtmlAgilityPack
.
using Alloy.Models.Media; | |
using EPiServer.Core; | |
using EPiServer.Core.Html.StringParsing; | |
using EPiServer.ServiceLocation; | |
using EPiServer.Web; | |
using HtmlAgilityPack; | |
public static class XhtmlExtensions | |
{ | |
public static XhtmlString AdjustAltText(this XhtmlString xhtmlString) | |
{ | |
if (xhtmlString == null) return new XhtmlString(string.Empty); | |
var contextModeResolver = ServiceLocator.Current.GetInstance<IContextModeResolver>(); | |
if (contextModeResolver.CurrentMode == ContextMode.Edit) | |
{ | |
return xhtmlString; | |
} | |
var htmlString = xhtmlString.ToHtmlString(); | |
var doc = new HtmlDocument(); | |
doc.LoadHtml(htmlString); | |
// find all image tags | |
var images = doc.DocumentNode.SelectNodes("//img"); | |
if (images == null) | |
return xhtmlString; | |
var altTextDictionary = GenerateAltTextDictionary(xhtmlString.Fragments); | |
// Iterate through each image and replace alt="" or alt="name" with alt="alt text" to enhance accessibility. | |
foreach (var image in images) | |
{ | |
var src = image.Attributes["src"].Value; | |
if (string.IsNullOrWhiteSpace(src)) | |
continue; | |
if (src.StartsWith("~")) | |
{ | |
src = src[1..]; | |
} | |
var altText = image.Attributes["alt"].Value; | |
if (!string.IsNullOrWhiteSpace(altText)) | |
continue; | |
// If we have the alt text associated with a key, substitute it with the corresponding value from the dictionary. | |
// Keep in mind that the dictionary stores pairs of <Url, AltText>. | |
if (altTextDictionary.ContainsKey(src)) | |
{ | |
image.SetAttributeValue("alt", altTextDictionary[src]); | |
} | |
} | |
var outerHtml = doc.DocumentNode.OuterHtml; | |
return new XhtmlString(outerHtml); | |
} | |
private static Dictionary<string, string> GenerateAltTextDictionary(StringFragmentCollection htmlFragments) | |
{ | |
var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>(); | |
var altTextDictionary = new Dictionary<string, string>(); | |
foreach (var urlFragment in htmlFragments.Where(x => x is UrlFragment)) | |
{ | |
foreach (var guid in urlFragment.ReferencedPermanentLinkIds) | |
{ | |
if (!contentLoader.TryGet(guid, out ImageFile image) || string.IsNullOrEmpty(image.AltText)) | |
continue; | |
var key = $"/link/{image.ContentGuid:N}.aspx"; | |
if (!altTextDictionary.ContainsKey(key)) | |
{ | |
altTextDictionary.Add(key, image.AltText); | |
} | |
} | |
} | |
return altTextDictionary; | |
} | |
} |
After deploying this change-up, any time an XhtmlString is rendered, we are now intercepting it and adjusting its alternate text accordingly.
Troubleshooting: In the unlikely event of the AdjustAltText()
extension method not triggering, add the UIHintAttribute [UIHint("XhtmlString")]
to the XhtmlString datatype property.
1 2 3 4 5 6 | [Display( GroupName = SystemTabNames.Content, Order = 310)] [CultureSpecific] [UIHint( "XhtmlString" )] public virtual XhtmlString MainBody { get ; set ; } |
*Drawing Inspiration from Dylan McCurry's Episerver and Alternate Text for Images in the TinyMCE Rich Text Editor