Update rendering datasources on item copy

Out of the box, Sitecore does not update the rendering datasources of items that you might copy or duplicate. There is a straightforward way of accomplishing this however. This example code works based on the premise that it will only try to update presentation details of an item if the datasource for the source item rendering is located under the source item at some path and that when copied, that same datasource will be in a “similar” path to the copied item. For example: Banner image under Copy of Sample page, is at the same path as Banner Image for the Sample Page, except of course for the page item name it’s under.

Create an ItemCopied Handler

This is a 2 step process.

First create a class to act as the handler for the ItemCopied event that is published by Sitecore.

using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Events;
using Sitecore.Layouts;
using System;

namespace SampleSite.EventHandlers
{
    public class ItemCopiedHandler
    {
        public void OnItemCopied(object sender, EventArgs args)
        {

        }
    }
}

Second, create the configuration we need so that the ItemCopied handler we just created will get called.

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <events>
      <event name="item:copied">
        <handler type="SampleSite.EventHandlers.ItemCopiedHandler, SampleSite" method="OnItemCopied"/>
      </event>          
    </events>
  </sitecore>
</configuration>

Add the rendering datasource replacement logic

Notice the method in the config declaration. That string is the name of the method inside our class that Sitecore will call. In that method, we will receive EventArgs. We use the ExtractParameter method to get the source and the copy items from the EventArgs we are passed.

Next, we do a few null checks and make sure our source and copy items have layouts. No need to run this on other items that may be copied in the tree that don’t have layouts.

You will notice a bit of looping happening. This is to make sure we are doing this change for any device that is configured and for each rendering on the item.

The part of the code that does our translation are these 2 lines:

var path = datasourceItem.Paths.Path.Replace(source.Name, copy.Name);
var newPathItem = GetItem(path);

We are simply getting the path for the datasource item that is configured for a rendering. We replace the source item name with the copy item name. We the then try to get that item based on the path. If we find an item, we update the rendering datasource to use that item in the copied version.

using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Events;
using Sitecore.Layouts;
using System;

namespace SampleSite.EventHandlers
{
    public class ItemCopiedHandler
    {
        private Database _database = null;

        public void OnItemCopied(object sender, EventArgs args)
        {
            Item source = Event.ExtractParameter(args, 0) as Item;
            Item copy = Event.ExtractParameter(args, 1) as Item;

            if (source != null && copy != null)
            {
                if (source.Visualization?.Layout != null && copy.Visualization?.Layout != null)
                {
                    UpdateRenderingDatasources(copy, source);
                }
            }
        }

        protected void UpdateRenderingDatasources(Item copy, Item source)
        {
            LayoutField layoutField = new LayoutField(copy.Fields[Sitecore.FieldIDs.FinalLayoutField]);
            LayoutDefinition layoutDefinition = LayoutDefinition.Parse(layoutField.Value);

            foreach (DeviceDefinition device in layoutDefinition.Devices)
            {
                if (device.Renderings != null)
                {
                    for (var i = 0; i < device.Renderings.Count; i++)
                    {
                        RenderingDefinition rendering = (RenderingDefinition)device.Renderings[i];
                        if (!string.IsNullOrWhiteSpace(rendering.Datasource))
                        {
                            var guid = rendering.Datasource;
                            var datasourceItem = GetItem(new ID(guid));
                            if (datasourceItem != null)
                            {
                                var path = datasourceItem.Paths.Path.Replace(source.Name, copy.Name);
                                var newPathItem = GetItem(path);

                                if (newPathItem != null)
                                {
                                    rendering.Datasource = newPathItem.ID.ToString();
                                    copy.Editing.BeginEdit();
                                    layoutField.Value = layoutDefinition.ToXml();
                                    copy.Editing.EndEdit();
                                }
                            }
                        }
                    }
                }
            }
        }

        private Item GetItem(string path)
        {
            return Database.GetItem(path);
        }

        private Item GetItem(ID id)
        {
            return Database.GetItem(id);
        }

        private Database Database
        {
            get
            {
                if (_database != null) return _database;

                _database = Sitecore.Configuration.Factory.GetDatabase("master");

                return _database;
            }
        }
    }
}

About Phil Paris

Hi, my name is Phil Paris and I’m a Sitecore Architect and general Sitecore enthusiast. I’ve been working with Sitecore since 2013. Through this blog I will be sharing Sitecore insights, tips and tricks, best practices and general knowledge with the hopes to further the community at large. Please feel free to reach out to me at any time!

View all posts by Phil Paris →