Loading...
Skip to Content

How to open rich text links in a new tab in Contentful.net

Using an IContentRenderer

Now don't get me wrong, I think Contentful is a great tool, but it definitely has gaps in its features. One big one for me is not being able to specify a target _blank, etc. on a link in the rich text field type. So until this functionality is implemented I use a custom IContentRenderer to check the start of the URL and decide how to render it.

Below is my LinkRenderer, which is pretty basic, but does the job for what I need.

using Contentful.Core.Models;

public class LinkRenderer : IContentRenderer
{
    private readonly ContentRendererCollection _rendererCollection;

    public LinkRenderer(ContentRendererCollection rendererCollection)
    {
        _rendererCollection = rendererCollection;
    }

    public bool SupportsContent(IContent content)
    {
        return content is Hyperlink;
    }

    public string Render(IContent content)
    {
        var link = (content as Hyperlink);

        var sb = new StringBuilder();

        if (link != null && (link.Data.Uri.StartsWith("https://") || link.Data.Uri.StartsWith("http://")))
        {
            sb.Append(
                $"<a target=\"_blank\" rel=\"noopener\" href=\"{link?.Data?.Uri}\" title=\"{link?.Data?.Title}\">");
        }
        else
        {
            sb.Append(
                $"<a href=\"{link?.Data?.Uri}\" title=\"{link?.Data?.Title}\">");
        }

        foreach (var subContent in link.Content)
        {
            var renderer = _rendererCollection.GetRendererForContent(subContent);
            sb.Append(renderer.Render(subContent));
        }

        sb.Append("</a>");

        return sb.ToString();
    }

    public Task<string> RenderAsync(IContent content)
    {
        return Task.FromResult(Render(content));
    }

    public int Order { get; set; }
}

N.B. There is a limitation with Contentful.net before 6.0.9. If you are using an earlier version, use the class below instead of the HtmlRenderer, which has the ContentRendererCollection exposed so you can use _rendererCollection.GetRendererForContent(subContent); in the LinkRenderer.

using Contentful.Core.Models;

public class CustomHtmlRenderer
{
    private readonly ContentRendererCollection _contentRendererCollection;

    //Added so I can use the ContentRendererCollection in my LinkRenderer
    public ContentRendererCollection Renderers
    {
        get { return _contentRendererCollection; }
    }

    /// <summary>
    /// Initializes a new instance of HtmlRenderer.
    /// </summary>
    public CustomHtmlRenderer() : this(new HtmlRendererOptions())
    {
    }

    public CustomHtmlRenderer(HtmlRendererOptions options)
    {
        options ??= new HtmlRendererOptions();
        _contentRendererCollection = new ContentRendererCollection();
        _contentRendererCollection.AddRenderers(new List<IContentRenderer> {
            new ParagraphRenderer(_contentRendererCollection),
            new HyperlinkContentRenderer(_contentRendererCollection),
            new TextRenderer(),
            new HorizontalRulerContentRenderer(),
            new HeadingRenderer(_contentRendererCollection),
            new ListContentRenderer(_contentRendererCollection),
            new ListItemContentRenderer(_contentRendererCollection, options.ListItemOptions),
            new QuoteContentRenderer(_contentRendererCollection),
            new AssetRenderer(_contentRendererCollection),
            new NullContentRenderer()
        });
    }

    /// <summary>
    /// Renders a document to HTML.
    /// </summary>
    /// <param name="doc">The document to turn into HTML.</param>
    /// <returns>An HTML string.</returns>
    public async Task<string> ToHtml(Document doc)
    {
        if (!(doc?.Content?.Any() ?? false))
            return await Task.Run(() => "");

        var sb = new StringBuilder();
        foreach (var content in doc.Content)
        {
            var renderer = _contentRendererCollection.GetRendererForContent(content);
            sb.Append(await renderer.RenderAsync(content));
        }

        return sb.ToString();
    }

    /// <summary>
    /// Adds a contentrenderer to the rendering pipeline.
    /// </summary>
    /// <param name="renderer"></param>
    public void AddRenderer(IContentRenderer renderer)
    {
        _contentRendererCollection.AddRenderer(renderer);
    }
}

Andy Blyth

Andy Blyth, an Optimizely MVP (OMVP) and Technical Architect at 26 DX with a keen interest in martial arts, occasionally ventures into blogging when memory serves.

 
Andy Blyth