Sitecore dataviews

Have you ever needed a way of customising the content tree in Sitecore? I had, and got some excellent insights on how to do this, and some more info about Sitecore internals from the fantastic Sitecore support team!

I had a scenario where I wanted to filter out some items from the content tree, in both the Content Editor and from the Navigation bar in the Page Editor, that where not relevant to certain users. In my particular case, I have a common content tree that is translated into many languages, but some sections, such as a news section, contains a lot of locally produced items. Those items typically have only one language version, so those sections becomes very cluttered, with a lot of items that is just irrelevant to all the other content editors. I.e. there are a lot of items that don’t have a version on the current language, and there’s usually no need to translate them either.

So, I wanted a way to hide those non-translated items from the content tree, depending on the current selected language. With some help from Sitecore support, we came up with the idea of implementing our own Master Dataview.

In Sitecore config, there is a <dataviews> section, defining classes that provides the item data to the content tree for different contexts, such as the Master view (i.e. the normal content editor), the Recycle bin, File system etc. In a standard Sitecore setup, the content tree is provided by Sitecore.Web.UI.HtmlControls.MasterDataView. In Sitecore 7 or above, it’s replaced by a bucket data view. That’s the one that hides all the bucketed items in an item bucket, depending on your view settings.

Untranslated Items ribbon checkbox

Untranslated Items checkbox in the Versions ribbon in Sitecore 7.2

So, by just implementing our own MasterDataView, we can do some hacking and solve this. In my case, I remove all the items that doesn’t have a local version in the current language, unless the show “Untranslated items” checkbox in the Versions pane is selected, like this:

protected override void GetChildItems (ItemCollection children, Item parent)
{
    base.GetChildItems(children, parent);
    FilterByLanguage(children);
}

private void FilterByLanguage(ItemCollection children)
{
    if (ToggleUntranslatedItems.ShowUntranslatedItems)
        return;

    try
    {
        if (!parent.Paths.FullPath.StartsWith("/sitecore/content/", StringComparison.InvariantCultureIgnoreCase))
            return;

        for (int i = children.Count - 1; i >= 0; --i)
        {
            if (children[i].Versions.Count == 0)
            children.RemoveAt(i);
        }
    }
    catch (Exception ex)
    {
        Log.Error("Error filtering language view", ex, this);
    }
}

The FilterByLanguage method just removes items from the children collection. For convenience, I decided to limit this filtering to the content tree. Unfortunately there’s not much else we can do here because the data model that’s sent to the view are the native Items. This means we cannot change its content. I’d like to be able to other cool things, such as changing appearance etc. Please Sitecore, replace this with a DTO model instead.

Adding the “Untranslated items” checkbox in the View pane turned out to be a bit tricky. The checkbox itself is just an extra item in the Versions Chunk of the core database. I added a new contenteditor:toggleuntranslateditems click command to it and implemented it as show below. Basically I store the state of the checkbox in the current user registry.

public class ToggleUntranslatedItems : Command
{
    public static bool ShowUntranslatedItems
    {
        get
        {
            return Registry.GetBool("/Current_User/UserOptions.View.ShowUntranslatedItems", true);
        }
        set
        {
            Registry.SetBool("/Current_User/UserOptions.View.ShowUntranslatedItems", value);
        }
    }

    public override CommandState QueryState(CommandContext context)
    {
        Assert.IsNotNull(context, "context is null");
        return !ShowUntranslatedItems ? CommandState.Enabled : CommandState.Down;
    }

    public override string GetClick(CommandContext context, string click)
    {
        return "ShowUntranslatedItems_Click";
    }
}

Having that command refreshing the content editor was a different story though. It turned out this wasn’t easily extensible. In order for this to work, I first had to let the custom command fire an event when the checkbox was clicked. It’s the ShowUntranslatedItems_Click event in the code above. Then I had to extend the Sitecore.Shell.Applications.ContentManager.ContentEditorForm class and let it listen to this event. The event then refreshes the content tree properly like this:

public class ExtendedContentEditorForm : Sitecore.Shell.Applications.ContentManager.ContentEditorForm
{
    protected void ShowUntranslatedItems_Click()
    {
        var currentPipelineArgs = Context.ClientPage.CurrentPipelineArgs as ClientPipelineArgs;
        Assert.IsNotNull(currentPipelineArgs, typeof(ClientPipelineArgs));
        if (SheerResponse.CheckModified())
        {
            ToggleUntranslatedItems.ShowUntranslatedItems = !ToggleUntranslatedItems.ShowUntranslatedItems;

            var item = this.ContentEditorDataContext.GetFolder();
            if (item != null)
                this.Tree_Refresh(item.ID.ToString());

            this.Refresh();
        }
    }
}

Putting it all together, the /sitecore/shell/Applications/Content Manager/Default.aspx file needs to be modified so that it uses my own extended ContentEditorForm in the <sc:CodeBeside> directive like this:

<sc:CodeBeside runat="server" Type="Stendahls.LanguageItemFilter.ExtendedContentEditorForm" />

Finally, here’s the config patch I used to make it all happen. Ensure you apply this after any bucket config files:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:x="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <commands>
      <command name="contenteditor:toggleuntranslateditems" type="Stendahls.LanguageItemFilter.ToggleUntranslatedItems,Stendahls.LanguageItemFilter"/>
    </commands>
    
    <dataviews>
      <dataview name="Master">
        <patch:attribute name="assembly">Stendahls.LanguageItemFilter</patch:attribute>
        <patch:attribute name="type">Stendahls.LanguageItemFilter.MasterDataView</patch:attribute>
      </dataview>
    </dataviews>
  </sitecore>
</configuration>

I’ve tried this in Sitecore 6.6, 7.2, 7.5 and it even works in Sitecore 8. Nice! Here are two screenshots from a clean Sitecore 8 where the Sample Item isn’t translated into Japanese by default. As you see, the Sample Item disappears when unchecking the show Untranslated Items checkbox.

Untranslated Items ribbon checkbox turned on

Untranslated Items ribbon checkbox turned off

Happy hacking!

2 thoughts on “Sitecore dataviews

  1. Really nice trick Mikael! In our sitecore instance with multisite module, we have tried to achieve Content Editor for site specific items only, but achieved by JS itself. I will definitely go with this approach too.

    Also, I have a question, changing dataviews will affect any other places?

    • Hi

      I haven’t looked too deep into this. As you mention, it doesn’t do this change in the “Navigation Bar” in the Content Editor (aka Experience Editor), so your JS trick is probably the right way to go for there. It’s a bit strange that Sitecore doesn’t use the DataView throughout the whole solution. Hope they’ll fix this soon.

      There are a few other DataViews you can hook into as well, such as FileSystem, that could potentially solve some other unrelated issues.

      / Mikael

Leave a Reply