Experience Sitecore ! | Creating a custom rendering variant section to render element with background image

Experience Sitecore !

More than 200 articles about the best DXP by Martin Miles

Creating a custom rendering variant section to render element with background image

Note! The code used in this post can be cloned from GitHib repository: SXA.Foundation.Variants

I am working on a rendering variant, and was looking on getting an element with background image from Sitecore, something similar to this:

<div style="background-image: url(/-/media/Project/Tenant/Platform/Some-image-from-Sitecore.jpg)">
    <div>... some rendering variant fields here</div>
</div>
There is way to achieve that by placing container rendering, as it has an option of picking up a background image:


However, even it may bring desired result, having container it is not the best and simplest from structuring point of view as it creates plenty of nesting element and image comes not at the top of them, not to say that it is not editors friendly and have to mess with placeholder settings. ideally, I wanted something like Section which in fact is just a div container to store other variant fields, but with style attribute stating background image. Okay, since I found nothing, I can implement that with a new variant field.

Since Section seems to be the best option, let's just clone and extend it. To start with, I copy VariantSection template into my tenant templates folder under foundation, and name it Image Section. I add just one ImageFieldName field of type image to the newly created template, that will be a reference to background image. So far, so good.

Then I run /sitecore/admin/showconfig.aspx in order to investigate what logic is involved into processing sections and find these 2 processors, one for parser:

<parseVariantFields patch:source="Sitecore.XA.Foundation.RenderingVariants.config">
  <processor type="Sitecore.XA.Foundation.RenderingVariants.Pipelines.ParseVariantFields.ParseSection, Sitecore.XA.Foundation.RenderingVariants" resolve="true"/>
</parseVariantFields>
and another - for renderer:
<renderVariantField patch:source="Sitecore.XA.Foundation.RenderingVariants.config">
  <processor type="Sitecore.XA.Foundation.RenderingVariants.Pipelines.RenderVariantField.RenderSection, Sitecore.XA.Foundation.RenderingVariants" resolve="true"/>
</renderVariantField>

With JetBrains dotPeek, it becomes fairly easy to find the code and extend it. Based on what I found, I create 3 new classes to implement parser, renderer and also the model itself:


I extend section variant field with one extra Single-Line Text field, stating which field from the context item should contain image. Also, I am referencing my new template created earlier. Here's the code:

using System.Collections.Generic;
using Sitecore.Data.Items;
using Sitecore.XA.Foundation.Variants.Abstractions.Fields;
using Sitecore.XA.Foundation.RenderingVariants.Fields;

namespace Platform.Foundation.Variants.Pipelines.VariantFields
{
    public class VariantImageSection : RenderingVariantFieldBase
    {
        public VariantImageSection(Item variantItem) : base(variantItem)
        {
        }

        public static string DisplayName => "Image Section";

        public IEnumerable<BaseVariantField> SectionFields { get; set; }

        public string ImageFieldName { get; set; }

        public string LinkField { get; set; }

        public bool IsLink { get; set; }
    }
}

and now implement parser. Please pay attention that since we duplicated the template, fields for Image section will have new IDs, so we will also re-reference Tag, CssClass and IsLink fields from our static constants class, where we keep all the IDs:

using System.Collections.Generic;
using Sitecore.Data;
using Sitecore.DependencyInjection;
using Sitecore.XA.Foundation.SitecoreExtensions.Extensions;
using Sitecore.XA.Foundation.Variants.Abstractions.Pipelines.ParseVariantFields;
using Sitecore.XA.Foundation.Variants.Abstractions.Services;

namespace Platform.Foundation.Variants.Pipelines.VariantFields
{
    public class ParseImageSection : ParseVariantFieldProcessor
    {
        public override ID SupportedTemplateId => Constants.RenderingVariants.Fields.VariantImageSection;

        public override void TranslateField(ParseVariantFieldArgs args)
        {
            ParseVariantFieldArgs variantFieldArgs = args;

            var variantSection = new VariantImageSection(args.VariantItem);
            variantSection.ItemName = args.VariantItem.Name;
            variantSection.Tag = args.VariantItem.Fields[Constants.RenderingVariants.Fields.Tag].GetEnumValue();
            variantSection.CssClass = args.VariantItem[Constants.RenderingVariants.Fields.CssClass];
            variantSection.ImageFieldName = args.VariantItem[Constants.RenderingVariants.Fields.ImageField];
            variantSection.LinkField = args.VariantRootItem[Sitecore.XA.Foundation.Variants.Abstractions.Templates.IVariantDefinition.Fields.LinkField];
            variantSection.IsLink = args.VariantItem[Constants.RenderingVariants.Fields.IsLink] == "1";

            variantSection.SectionFields = args.VariantItem.Children.Count > 0 
                ? ((IVariantFieldParser)ServiceLocator.ServiceProvider.GetService(typeof(IVariantFieldParser))).ParseVariantFields(args.VariantItem, args.VariantRootItem, false) 
                : new List<Sitecore.XA.Foundation.Variants.Abstractions.Fields.BaseVariantField>();

            variantFieldArgs.TranslatedField = variantSection;
        }
    }
}

Now we implement renderer logic. What I am doing below, is getting a field name of a context item, where image supposed to be, and then generate a URL for that media item (in case field is valid and media item indeed presents there), further down this URL is being appended our section element (remember, you may select any tag, not just div) as a background image style attribute:

using System;
using System.Web.UI.HtmlControls;
using Sitecore.Data.Fields;
using Sitecore.Pipelines;
using Sitecore.Resources.Media;
using Sitecore.XA.Foundation.RenderingVariants.Pipelines.RenderVariantField;
using Sitecore.XA.Foundation.Variants.Abstractions.Fields;
using Sitecore.XA.Foundation.Variants.Abstractions.Models;
using Sitecore.XA.Foundation.Variants.Abstractions.Pipelines.RenderVariantField;

namespace Platform.Foundation.Variants.Pipelines.VariantFields
{
    public class RenderImageSection : RenderRenderingVariantFieldProcessor
    {
        public override Type SupportedType => typeof(VariantImageSection);

        public override RendererMode RendererMode => RendererMode.Html;

        public override void RenderField(RenderVariantFieldArgs args)
        {
            string styleInlineValue = String.Empty;
            var variantField = args.VariantField as VariantImageSection;

            if (!string.IsNullOrWhiteSpace(variantField?.ImageFieldName) && args.Item != null)
            {
                ImageField imgField = args.Item.Fields[variantField.ImageFieldName];
                if (imgField != null)
                {
                    string url = MediaManager.GetMediaUrl(imgField.MediaItem);
                    styleInlineValue = $"background-image: url({url})";
                }
            }

            var tag = new HtmlGenericControl(string.IsNullOrWhiteSpace(variantField.Tag) ? "div" : variantField.Tag);

            if (styleInlineValue.Length > 0)
            {
                tag.Attributes.Add("style", styleInlineValue);
            }

            AddClass(tag, variantField.CssClass);
            AddWrapperDataAttributes(variantField, args, tag);

            foreach (BaseVariantField sectionField in variantField.SectionFields)
            {
                var variantFieldArgs = new RenderVariantFieldArgs
                {
                    VariantField = sectionField,
                    Item = args.Item,
                    HtmlHelper = args.HtmlHelper,
                    IsControlEditable = args.IsControlEditable,
                    IsFromComposite = args.IsFromComposite,
                    RendererMode = args.RendererMode,
                    Model = args.Model
                };

                CorePipeline.Run("renderVariantField", variantFieldArgs);

                if (variantFieldArgs.ResultControl != null)
                {
                    tag.Controls.Add(variantFieldArgs.ResultControl);
                }
            }

            args.ResultControl = variantField.IsLink ? InsertHyperLink(tag, args.Item, variantField.LinkAttributes, variantField.LinkField, false, args.HrefOverrideFunc) : tag;
            args.Result = RenderControl(args.ResultControl);
        }
    }
}

Once done, create a patch configuration file and reference new processors: parser and renderer:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <parseVariantFields>
        <processor type="Platform.Foundation.Variants.Pipelines.VariantFields.ParseImageSection, Platform.Foundation.Variants" resolve="true"/>
      </parseVariantFields>
      <renderVariantField>
        <processor type="Platform.Foundation.Variants.Pipelines.VariantFields.RenderImageSection, Platform.Foundation.Variants" resolve="true"/>
      </renderVariantField>
    </pipelines>
  </sitecore>
</configuration>

The code above requires you to reference Sitecore.XA.Foundation.RenderingVariants, Sitecore.XA.Foundation.SitecoreExtensions and Sitecore.XA.Foundation.Variants.Abstractions libraries.

Another thing you will need to provide is adding insert options. I do not cover it as it is quite obvious. Finally, you'll be able to use your new rendering variant field:


with


which renders the following output and allows child / nested elements, as normal section would do:


This explains how one can easily extend existing rendering variant fields and provide any custom output without lengthy process of creating a custom component in SXA.

Comments are closed