What about Rendering Parameters?
I recently did a POC using rendering parameters in a component in XM Cloud. In this blog i will describe my approach, issues faced and how i overcame them.
Adding a rendering to a component, is relatively straight forward in Sitecore development, i.e.(https://doc.sitecore.com/xmc/en/developers/xm-cloud/walkthrough–building-a-simple-rendering.html).
However, there are some circumstances where things are a little bit more complex (than they need to be). The use-case for this particular POC is we want to add a background color to a Rich Text component. The aim is to apply this style as an extra parameter to the component in Sitecore and pass it along to the front-end.
Technologies:
Sitecore XM Cloud
Sitecore Pages Editor
Nextjs components
Create rendering parameters
For adding extra parameters to a component (e.g for things such as additional styles, data or anything custom), Sitecore provides a facility called “Standard Rendering Parameters”. This is a Sitecore OOTB template for this specific purpose. We would derive our custom template from `/sitecore/templates/System/Layout/Rendering Parameters/Standard Rendering Parameters`. For this POC I created a parameter template that would be a DropLink pointing to a item list of “Colors” we can use for our background style. By doing so, would give the content authors the choice of colors to select from during editing.
The datasource for “textColor” is a directory of predefined “Text Color” items.
*Note: One technical draw back with this approach is that selecting an item in a DropLink datasource field retrieves the item ID and not it’s value. More on how I overcame that issue further on.
Front-end Component
For the custom Rich Text component,
In Sitecore I cloned the existing Rich Text rendering and renamed it Color Rich Text
In the components folder under ../src/MySite/src/components, I copied “Rich Text.tsx” to “Color Rich Text.tsx“
Then we change the code as follows:
import React from ‘react’;
import { Field, RichText as JssRichText } from ‘@sitecore-jss/sitecore-jss-nextjs’;
interface Fields {
Text: Field<string>;
}
export type RichTextProps = {
params: { [key: string]: string };
fields: Fields;
};
export const Default = (props: RichTextProps): JSX.Element => {
const text = props.fields ? (
<JssRichText field={props.fields.Text} />
) : (
<span className=”is-empty-hint”>Rich text</span>
);
const id = props.params.RenderingIdentifier;
return (
<div
className={`component rich-text ${props.params.styles.trimEnd()}`}
id={id ? id : undefined}
>
<div className=”component-content”>
{text}
<style jsx>{`
.component-content {
background-color: ${props.params.textColor ? props.params.textColor.trimEnd() : ‘#FFF’};
}
`}</style>
</div>
</div>
);
};
import { Field, RichText as JssRichText } from ‘@sitecore-jss/sitecore-jss-nextjs’;
interface Fields {
Text: Field<string>;
}
export type RichTextProps = {
params: { [key: string]: string };
fields: Fields;
};
export const Default = (props: RichTextProps): JSX.Element => {
const text = props.fields ? (
<JssRichText field={props.fields.Text} />
) : (
<span className=”is-empty-hint”>Rich text</span>
);
const id = props.params.RenderingIdentifier;
return (
<div
className={`component rich-text ${props.params.styles.trimEnd()}`}
id={id ? id : undefined}
>
<div className=”component-content”>
{text}
<style jsx>{`
.component-content {
background-color: ${props.params.textColor ? props.params.textColor.trimEnd() : ‘#FFF’};
}
`}</style>
</div>
</div>
);
};
Here we are expecting the value of the parameter textColor to be sent in the props of the component as props.params.textColor .
Now that these pieces are in place, I added this component to a page via regular Sitecore Experience Editor.
Connecting Pages Editor to your local
Next I connected pages.sitecore.io to my local environment by adding this entry in “Local Storage” in your browser and navigate to pages.sitecore.io
“Sitecore.Pages.LocalXmlCloudUrl”: “https://xmcloudcm.localhost/”
Now we refresh the pages editor
As we can see the pages editor displays the items in the DropLink correctly, so i can select the color. So the field shows up in pages editor, however the selected color is not applied. Lets check the graphQL playground to see what is data getting sent.
See in graphQL playground:
So we see the field “textColor” coming through as a parameter, however the value is the Item ID of the selected color item, not the value. How do we overcome this hurdle?
Research and solution
I found this article by from Any Paz
https://andypaz.com/2021/01/05/serializing-rendering-parameters-in-the-layout-service/ which led me in the right direction to understand serialization of rendering parameters, however my desired result is not to get the serialized item as a string, but the actual value to be used for the background color, i.e. the Hex value stored in the value field. So after some research I realized this was actually a good start, but needed some modifications.
I created a data template that governs serialization of an item in Sitecore. This way I can determine what i want serialized.
I created a custom pipeline processor and override the existing RenderJsonRendering.Initialize pipeline to extend it to serialize only the item flagged in Sitecore to serialize.
I made these two modifications generic enough to apply wider use-cases for custom serializing custom rendering parameters.
using System.Collections.Generic;
using Sitecore.Data;
using Sitecore.Data.Items;
using System.Web;
using Sitecore.Abstractions;
using Sitecore.LayoutService.Configuration;
using Sitecore.LayoutService.ItemRendering;
using Sitecore.LayoutService.Presentation.Pipelines.RenderJsonRendering;
using Sitecore.XA.Feature.LayoutServices.Integration.Pipelines.TransformParameters;
using System.Linq;
using Newtonsoft.Json.Linq;
using Sitecore.Mvc.Presentation;
using System;
using Sitecore.LayoutService.Serialization;
namespace MySite.Foundation.LayoutService.PipelineProcessors
{
public class CustomInitialize : Initialize
{
IRenderingConfiguration _renderingConfiguration;
protected BaseCorePipelineManager PipelineManager;
public CustomInitialize(IConfiguration configuration, BaseCorePipelineManager pipelineManager)
: base(configuration)
{
PipelineManager = pipelineManager;
}
protected override RenderedJsonRendering CreateResultInstance(RenderJsonRenderingArgs args)
{
string componentName = base.GetComponentName(args.Rendering?.RenderingItem?.InnerItem);
_renderingConfiguration = args.RenderingConfiguration;
//Note: the constructor below is different for Sitecore 9.x and 10. The below will only work in Headless Services for Sitecore 10.
RenderedJsonRendering rendering = new RenderedJsonRendering
{
ComponentName = componentName ?? args.Rendering.RenderingItem.Name,
DataSource = ((!string.IsNullOrWhiteSpace(args.Rendering.DataSource)) ? args.Rendering.DataSource : args.Rendering.RenderingItem.DataSource),
RenderingParams = SerializeRenderingParams(args.Rendering),
Uid = args.Rendering.UniqueId
};
TransformParametersArgs transformParametersArgs = new TransformParametersArgs
{
Rendering = rendering
};
transformParametersArgs.CustomData.Add(“SupportedParameters”, GetAllKeys(args.Rendering.Properties[“par”]));
PipelineManager.Run(“transformParameters”, transformParametersArgs);
return transformParametersArgs.Rendering;
}
protected virtual IDictionary<string, string> SerializeRenderingParams(Rendering rendering)
{
IDictionary<string, string> paramDictionary = rendering.Parameters.ToDictionary(pair => pair.Key, pair => pair.Value);
foreach (string key in paramDictionary.Keys.ToList())
{
if (!ID.TryParse(paramDictionary[key], out var itemId)) continue;
Item item = rendering.RenderingItem.Database.GetItem(itemId);
//Check if serializable
if (item.Fields[“SerializeItem”]!=null && item.Fields[“SerializeItem”].Value == “1”)
{
paramDictionary[key] = JObject.Parse(_renderingConfiguration.ItemSerializer.Serialize(item, new SerializationOptions() { DisableEditing = true }))[“Value”]?.Value<string>(“value”);
}
using Sitecore.Data;
using Sitecore.Data.Items;
using System.Web;
using Sitecore.Abstractions;
using Sitecore.LayoutService.Configuration;
using Sitecore.LayoutService.ItemRendering;
using Sitecore.LayoutService.Presentation.Pipelines.RenderJsonRendering;
using Sitecore.XA.Feature.LayoutServices.Integration.Pipelines.TransformParameters;
using System.Linq;
using Newtonsoft.Json.Linq;
using Sitecore.Mvc.Presentation;
using System;
using Sitecore.LayoutService.Serialization;
namespace MySite.Foundation.LayoutService.PipelineProcessors
{
public class CustomInitialize : Initialize
{
IRenderingConfiguration _renderingConfiguration;
protected BaseCorePipelineManager PipelineManager;
public CustomInitialize(IConfiguration configuration, BaseCorePipelineManager pipelineManager)
: base(configuration)
{
PipelineManager = pipelineManager;
}
protected override RenderedJsonRendering CreateResultInstance(RenderJsonRenderingArgs args)
{
string componentName = base.GetComponentName(args.Rendering?.RenderingItem?.InnerItem);
_renderingConfiguration = args.RenderingConfiguration;
//Note: the constructor below is different for Sitecore 9.x and 10. The below will only work in Headless Services for Sitecore 10.
RenderedJsonRendering rendering = new RenderedJsonRendering
{
ComponentName = componentName ?? args.Rendering.RenderingItem.Name,
DataSource = ((!string.IsNullOrWhiteSpace(args.Rendering.DataSource)) ? args.Rendering.DataSource : args.Rendering.RenderingItem.DataSource),
RenderingParams = SerializeRenderingParams(args.Rendering),
Uid = args.Rendering.UniqueId
};
TransformParametersArgs transformParametersArgs = new TransformParametersArgs
{
Rendering = rendering
};
transformParametersArgs.CustomData.Add(“SupportedParameters”, GetAllKeys(args.Rendering.Properties[“par”]));
PipelineManager.Run(“transformParameters”, transformParametersArgs);
return transformParametersArgs.Rendering;
}
protected virtual IDictionary<string, string> SerializeRenderingParams(Rendering rendering)
{
IDictionary<string, string> paramDictionary = rendering.Parameters.ToDictionary(pair => pair.Key, pair => pair.Value);
foreach (string key in paramDictionary.Keys.ToList())
{
if (!ID.TryParse(paramDictionary[key], out var itemId)) continue;
Item item = rendering.RenderingItem.Database.GetItem(itemId);
//Check if serializable
if (item.Fields[“SerializeItem”]!=null && item.Fields[“SerializeItem”].Value == “1”)
{
paramDictionary[key] = JObject.Parse(_renderingConfiguration.ItemSerializer.Serialize(item, new SerializationOptions() { DisableEditing = true }))[“Value”]?.Value<string>(“value”);
}
}
return paramDictionary;
}
protected virtual string[] GetAllKeys(string par)
{
if (string.IsNullOrEmpty(par))
{
return Array.Empty<string>();
}
List<string> list = new List<string>();
string[] array = HttpUtility.UrlDecode(par).Split(‘&’);
for (int i = 0; i < array.Length; i++)
{
string[] array2 = array[i].Split(‘=’);
if (array2.Length != 0)
{
list.Add(array2[0].Trim());
}
}
return list.ToArray();
}
}
}
A quick patch into the pipeline
<configuration xmlns:patch=”http://www.sitecore.net/xmlconfig/”>
<sitecore>
<pipelines>
<group groupName=”layoutService”>
<pipelines>
<renderJsonRendering>
<processor type=”MySite.Foundation.LayoutService.PipelineProcessors.CustomInitialize, MySite” resolve=”true”
patch:instead=”*[@type=’Sitecore.XA.Feature.LayoutServices.Integration.Pipelines.RenderJsonRendering.Initialize, Sitecore.XA.Feature.LayoutServices.Integration’]”/>
</renderJsonRendering>
</pipelines>
</group>
</pipelines>
</sitecore>
</configuration>
<sitecore>
<pipelines>
<group groupName=”layoutService”>
<pipelines>
<renderJsonRendering>
<processor type=”MySite.Foundation.LayoutService.PipelineProcessors.CustomInitialize, MySite” resolve=”true”
patch:instead=”*[@type=’Sitecore.XA.Feature.LayoutServices.Integration.Pipelines.RenderJsonRendering.Initialize, Sitecore.XA.Feature.LayoutServices.Integration’]”/>
</renderJsonRendering>
</pipelines>
</group>
</pipelines>
</sitecore>
</configuration>
Now, when I look at the results coming back in the graphQL playground, we see the value returned
Now looking at the pages editor we see something interesting
Pages editor is a live editor, therefore we see the color change immediately once we select the parameter.
In the live site we see our desired result:
This was an interesting POC, and I hope to uncover more useful case-studies and POCs from the new offering of Sitecore XM Cloud.
Until next time, stay curious!
Leave A Comment