Take the community feedback survey now.

Mark Stott
Jul 7, 2023
  50
(0 votes)

Making Content Recommendations easy for content editors

The Problem

One of our clients had purchased the Content Recommendations module for use within their newly build CMS 12 corporate site.  Our challenge was that the main content team lacked development expertese and would outsource functionality like embedded content to external parties.  On top of this, the client had a desire for consistency in design across the system.  In it's default implementation, the addition of content recommendations across the system would require content editors to make changes to the handlebars scripts for every instance of the block.

The Solution

Our solution for this was to create a Custom Content Recommendations Block that obfuscates a number of the options on the default Content Recommendations Block and added additional properties that would allow them to render Content Recommendations as if it were the same design as another block within the site.  The key properties that I hid in this case were the Number of Recommendations and Recommendations Template properties.

// Display and ContentType attributes omitted for brevity.
public class CustomContentRecommendationsBlock : ContentRecommendationsBlock
{
    public virtual string? Title { get; set; }

    public virtual string? Subtitle { get; set; }

    public virtual bool OmitCardDescriptions { get; set; }

    public virtual string? ReadMoreText { get; set; }

    public virtual ContentReference? FallBackImage { get; set; }

    [ScaffoldColumn(false)]
    public override int NumberOfRecommendations
    {
        get { return 4;}
        set { _ = value; }
    }

    [ScaffoldColumn(false)]
    public override string? RecommendationsTemplate
    {
        get { return "Template is in the razor file."; }
        set { _ = value; }
    }
}

The razor file itself was then created to conditionally render elements within the Content Recommendations script tag on the server side in accordance with the designs by our creative team. To handle recommendations which lacked a main image, a fallback image was rendered server side within the {{^main_image_url}} handlebars tag.  Markup with {{^main_image_url}} is then only rendered by the content recommendations code when the main_image_url is null.

@model CustomContentRecommendationsBlock

<script class="idio-recommendations" type="text/x-mustache" data-api-key="@Model.DeliveryApiKey" data-rpp="@Model.NumberOfRecommendations">
<div class="container">
    <div class="cards-container">

        @if (!string.IsNullOrWhiteSpace(Model.Title) || !string.IsNullOrWhiteSpace(Model.Subtitle))
        {
            <div class="block__intro">
                @if (!string.IsNullOrWhiteSpace(Model.Title))
                {
                    <h2>@Html.PropertyFor(x => x.Title)</h2>
                }
                @if (!string.IsNullOrWhiteSpace(Model.Subtitle))
                {
                    <p>@Html.PropertyFor(x => x.Subtitle)</p>
                }
            </div>
        }

        <div class="cards-rows">
            <div class="cards" data-component="Cards">
                {{#content}}
                <div class="card" data-item>
                    <!-- Teaser Card -->
                    <!-- Image -->
                    {{#main_image_url}}
                        <div class="card__image">
                            <img src="{{main_image_url}}?width=560&height=299&quality=90&rmode=crop" alt="{{title}}">
                        </div>
                    {{/main_image_url}}
                    {{^main_image_url}}
                        @if (!Model.FallbackImage.IsNullOrEmpty())
                        {
                            <div class="card__image">
                                <img src="@Url.ImageUrl(Model.FallBackImage, 560, 299)" alt="@Model.FallBackImage.GetImageAltText()">
                            </div>
                        }
                    {{/main_image_url}}
                    <!-- Category -->
                    {{#topics}}
                        <div class="card__category">{{title}}</div>
                    {{/topics}}

                    <!-- Content -->
                    <div class="card__content">
                        <h4>{{title}}</h4>
                        @if (!Model.OmitCardDescriptions)
                        {
                            <p>{{abstract}}</p>
                        }
                        <a href="{{link_url}}" class="solid-arrow-link" title="{{title}}" data-label_1="{{title}}">@Model.ReadMoreText</a>
                    </div>
                </div>
                {{/content}}
            </div>
        </div>
    </div>
</div>
</script>

There were additional concerns that had to be addressed, the first being that the design of the block only allowed for a single category (Topic) to be rendered for each content card.  In this case we addressed this by using CSS styles to render hide off all but the first category.

Alternative Solution

Another approach to the solution would have been for us to look at overriding the default value assigned to the RecommendationsTemplate property so that when new Content Recommendations block was created, it would retain the ability for the content editor to customize the layout of the content recommendations.  This was an approach we chose not to go with in this situation as it still presented non-technical content editors with a technical property that they were not comfortable using.  Our chosen approach also allowed for functionality such as the fall back image to be updated centrally and immediately affect all custom content recommendation blocks within the site.

Client Expectations vs Reality

Within the CMS, the editor has the ability to prioritise categories that are displayed on cards shown on other blocks and they have the ability to provide an optional Teaser Title and Teaser Description against a page that is used across the site for that page that is different to standard meta data.  The client's expectation was that all of the content recommendations would render the exact same content and categories as if the teaser content had been curated by themselves on a regular block within the site. We had to explain how Content Recommendations worked:

  • That Content Recommendations is based on a scan of the fully rendered page
  • That Content Recommendations does not have direct access to the raw CMS data.
  • That it would not be a live replication of changes within the CMS.
  • That Content Recommendations come from the cloud service and not directly from the CMS.

Ultimately the client understood this and is now using the custom content recommendations block across their live site. In our next phase of Content Recommendations, we want to look at pushing additional meta data into Content Recommendations that matches the teaser data defined by the content editor and then hopefully retrieve and display this data within the results from Content Recommendations.

Jul 07, 2023

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