Home » Clones » Show Clones of An Item In the Sitecore CMS

Show Clones of An Item In the Sitecore CMS

Sitecore Technology MVP 2016
Sitecore MVP 2015
Sitecore MVP 2014

Enter your email address to follow this blog and receive notifications of new posts by email.

“Out of the box”, there is no specific way to see all clones for a given item — at least I haven’t found one yet.

One could see these by looking at referrers of an item:

navigate-links-dropdown-clones

Unfortunately, referrers in this dropdown aren’t just reserved for clones — all referrers of the item will display in this dropdown. An example would include an item referencing the item in a Droplink field.

In a previous post, I provided a solution for auto-cloning new subitems to clones of their parents. That solution leveraged an instance of the ItemClonesGatherer utility class I defined in that post to return a collection of clones for a given item.

Yesterday, I realized this class could be reused for a feature to show a listing of clones for an item in Sitecore, and this post showcases that solution.

I had to come up with a medium for displaying a list of clones of an item. I decided I would display these in a new content editor tab.

I recalled reading an article by Sitecore MVP Mark Stiles on adding new Editor tabs in Sitecore.

As Mark Stiles had done in his post, I created a stand-alone ASP.NET web form (/sitecore modules/Shell/ShowClones/default.aspx). This web form will display the content of our new “Show Clones” tab:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Sitecore650rev120706.sitecore_modules.Shell.ShowClones.Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Show Clones</title>
    <style type="text/css">
        h1
        {
            color: #072d6b;
            font-size: 14pt;
            margin-bottom: 5px;
        }
        a
        {
            text-decoration: none;
            font-size: 8pt;
            font-family: Tahoma;
        }
        a:hover
        {
            text-decoration: underline;
        }
        #clones
        {
            margin: 10px;
        }
        #clone_listing
        {
            list-style-type: none;
            margin: 0 0 0 15px;
        }
        #clone_listing li
        {
            margin: 0;
        }
        #clone_listing li + li
        {
            margin-top: 5px;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <div id="clones">
            <h1>Clones</h1>
            <asp:MultiView ID="mvClones" ActiveViewIndex="0" runat="server">
                <asp:View ID="vClones" runat="server">
                    <asp:Repeater ID="rptClones" runat="server">
                        <HeaderTemplate>
                            <ol id="clone_listing">
                        </HeaderTemplate>
                        <ItemTemplate>
                            <li>
                                <asp:HyperLink 
                                    ID="hlClone" 
                                    NavigateUrl="#"
                                    onclick='<%# string.Format("parent.scForm.invoke(\"item:load(id={0})\"); return false;", Eval("ID").ToString()) %>'
                                    Text='<%# Eval("Paths.FullPath") %>'
                                    runat="server" />
                            </li>
                        </ItemTemplate>
                        <FooterTemplate>
                            </ol>
                        </FooterTemplate>
                    </asp:Repeater>
                </asp:View>
                <asp:View ID="vNoClones" runat="server">
                    <script type="text/javascript">
                        parent.scContent.closeEditorTab('ShowClones');
                    </script>
                </asp:View>
            </asp:MultiView>
        </div>
    </form>
</body>
</html>

The web form’s code-behind:

using System;
using System.Collections.Generic;

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.Web;

using Sitecore.Sandbox.Utilities.Gatherers.Base;
using Sitecore.Sandbox.Utilities.Gatherers;

namespace Sitecore650rev120706.sitecore_modules.Shell.ShowClones
{
    public partial class Default : System.Web.UI.Page
    {
        private static readonly IItemsGatherer ClonesGatherer = ItemClonesGatherer.CreateNewItemClonesGatherer();

        protected void Page_Load(object sender, EventArgs e)
        {
            ShowClones();
        }

        private void ShowClones()
        {
            BindRepeater();
            ToggleViews();
        }

        private void BindRepeater()
        {
            rptClones.DataSource = GetClones();
            rptClones.DataBind();
        }

        private void ToggleViews()
        {
            if (rptClones.Items.Count > 0)
            {
                mvClones.SetActiveView(vClones);
            }
            else
            {
                mvClones.SetActiveView(vNoClones);
            }
        }

        private IEnumerable<Item> GetClones()
        {
            Item item = GetItem();
            if(item == null)
            {
                return new List<Item>();
            }

            ClonesGatherer.Source = item;
            return ClonesGatherer.Gather();
        }

        private Item GetItem()
        {
            Item item = null;

            try
            {
                item = Sitecore.Context.ContentDatabase.GetItem(GetID(), GetLanguage(), GetVersion());
            }
            catch(Exception ex)
            {
                Log.Error(this.ToString(), ex, this);
            }

            return item;
        }

        private ID GetID()
        {
            Sitecore.Data.ID id;
            if(Sitecore.Data.ID.TryParse(WebUtil.GetQueryString("id"), out id))
            {
                return id;
            }

            return Sitecore.Data.ID.Null;
        }

        private Language GetLanguage()
        {
            Language language;
            if(Language.TryParse(WebUtil.GetQueryString("la"), out language))
            {
                return language;
            }

            return Sitecore.Context.Language;
        }

        private Sitecore.Data.Version GetVersion()
        {
            Sitecore.Data.Version version;
            if (Sitecore.Data.Version.TryParse(WebUtil.GetQueryString("vs"), out version))
            {
                return version;
            }

            return Sitecore.Data.Version.Latest;
        }
    }
}

The code-behind above uses an instance of the ItemClonesGatherer class to get all clones for the passed item. If clones are found, these are bound to a repeater to display them as links.

If there are no clones, the Multiview in the web form is toggled to render JavaScript to close the tab — the tab should not be open if the item does not have any clones.

After revisiting Mark’s article, and realized I needed a different solution: one that will allow me to add a new tab on the fly.

Such a solution should only allow the tab to open when an item has clones, and it should not be be associated with any templates — it must be template oblivious.

I stumbled upon a command in one of the Sitecore DLLs — which one it was is evading me at the moment — and noticed it was using an instance of Sitecore.Web.UI.Framework.Scripts.ShowEditorTab. I decided to take a chance on using an instance of this object, hoping it might open up a new content editor tab on the fly.

Using that command as a model, I came up with the following command:

using System.Collections.Generic;
using System.Linq;

using Sitecore.Sandbox.Utilities.Gatherers.Base;
using Sitecore.Sandbox.Utilities.Gatherers;

using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.Resources;
using Sitecore.Shell.Framework.Commands;
using Sitecore.Text;
using Sitecore.Web;
using Sitecore.Web.UI.Framework.Scripts;
using Sitecore.Web.UI.Sheer;

namespace Sitecore.Sandbox.Commands
{
    public class ShowClones : Command
    {
        private static readonly IItemsGatherer ClonesGatherer = ItemClonesGatherer.CreateNewItemClonesGatherer();

        public override void Execute(CommandContext commandContext)
        {
            ShowClonesEditorTab(GetItem(commandContext));
        }

        private static void ShowClonesEditorTab(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            const string command = "contenteditor:pagedesigner";
            const string id = "ShowClones";

            bool shouldClickEditorTab = IsEditorTabOpen(command);

            if (IsEditorTabOpen(command))
            {
                ClickOpenEditorTab(id);
                return;
            }
            
            OpenEditorTab(CreateNewShowClonesEditorTab(item, command, id));
        }

        private static bool IsEditorTabOpen(string command)
        {
            Assert.ArgumentNotNullOrEmpty(command, "command");
            return WebUtil.GetFormValue("scEditorTabs").Contains(command);
        }

        private static void ClickOpenEditorTab(string id)
        {
            Assert.ArgumentNotNullOrEmpty(id, "id");
            SheerResponse.Eval(string.Format("scContent.onEditorTabClick(null, null, '{0}')", id));
        }

        private static void OpenEditorTab(ShowEditorTab tab)
        {
            Assert.ArgumentNotNull(tab, "tab");
            SheerResponse.Eval(tab.ToString());
        }

        private static ShowEditorTab CreateNewShowClonesEditorTab(Item item, string command, string id)
        {
            Assert.ArgumentNotNull(item, "item");
            Assert.ArgumentNotNullOrEmpty(command, "command");
            Assert.ArgumentNotNullOrEmpty(id, "id");

            UrlString urlString = new UrlString("/sitecore modules/shell/showclones/default.aspx");
            item.Uri.AddToUrlString(urlString);
            UIUtil.AddContentDatabaseParameter(urlString);
            return new ShowEditorTab
            {
                Command = command,
                Header = Translate.Text("Show Clones"),
                Icon = Images.GetThemedImageSource("Network/32x32/link_view.png"),
                Url = urlString.ToString(),
                Id = id,
                Closeable = true,
                Activate = true
            };
        }

        public override CommandState QueryState(CommandContext context)
        {
            if (!HasClones(GetItem(context)))
            {
                return CommandState.Hidden;
            }

            return CommandState.Enabled;
        }

        private static Item GetItem(CommandContext context)
        {
            Assert.ArgumentNotNull(context, "context");
            return context.Items.FirstOrDefault();
        }

        private static bool HasClones(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            return GetClones(item).Any();
        }

        private static IEnumerable<Item> GetClones(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            ClonesGatherer.Source = item;
            return ClonesGatherer.Gather();
        }
    }
}

The command above uses an instance of ItemClonesGatherer to get all clones for the item in the content tree, and ensures it is visible when the item has clones. The logic hides the command when the item does not have clones.

When the command is invoked, it will open a new “Show Clones” tab, or set focus on the “Show Clones” tab if it’s already present.

I then registered the command above in a patch include configuration file:

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <commands>
      <command name="item:showclones" type="Sitecore.Sandbox.Commands.ShowClones,Sitecore.Sandbox"/>
    </commands>
  </sitecore>
</configuration>

Now, we need to lock and load this command in Sitecore. I switched over to the core database, and added a new item context menu option:

show-clones-command

Let’s see this in action.

I first created a bunch of clones:

clones-in-content-tree

On my source item, I right-clicked, and was presented with a new context menu option to “Show Clones”:

show-clones-context-menu

After clicking the “Show Clones” context menu option, a new “Show Clones” tab appeared:

show-clones-tab

I then clicked on one of the links in the “Show Clones” tab, and was brought to its associated clone:

was-brough-to-clone

If you have other ideas around using clones in Sitecore, or if you know of another way of listing clones of an item, please leave a comment.

Advertisement

1 Comment

  1. Received this in an email from Sitecore MVP Mark Ursino:

    Hi Mike –

    Just saw your recent blog post on clones. What a good topic. I’ve tried to comment before but WordPress always messes up my account and it never works. I was going to say that I actually created a module to do this in the past:

    http://firebreaksice.com/custom-sitecore-clones-listing-ribbon-button/

    http://marketplace.sitecore.net/en/Modules/Custom_Clones_Listing.aspx

    Keep up the great posts!

Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: