Improving your Sitecore IA with relative DataSource Locations

As someone famous** once said, with great power comes great responsibility – and the power of Sitecore’s component-based page model puts a lot of responsibility on us developers to create a structure for component data sources that makes sense to content editors. The two most common patterns I find myself using are that of having a “shared content” folder somewhere in the content tree which reusable DataSource items live in, and having items as children of the component’s page. When using the “shared content” folder you can easily set the DataSource Location field for your UI component to point to location where all the relevant data gets filed, but you can’t easily do that if you want to have your DataSource items as children of the page. So you tend to end up leaving the DataSource Location field blank to allow the user to pick the current page as the place to create the new item.

Experience shows that doesn’t work too well in practice. When you don’t control where the DataSource items get stored, they tend to end up getting spread around the content tree and making a bit of a mess of the IA. That lead to some thinking about how we might be able to improve on this situation – is it possible to force the DataSource Location to be relative to the current item?

Well, a bit of digging through the code and some experimentation says it’s not too hard to provide relative DataSource Locations. You can set up a UI component with a relative path for the DataSource Location, and then manually create a folder under your page:

One

This just works! When you click the “Set Associated Content” button, the tree view shows the right folder:

Two

But sadly this doesn’t work in the situation where the folder called “Items” doesn’t exist. In that case, Sitecore tries to deal with the error condition of “that folder doesn’t exist” by setting the root of the tree above to the root of the content tree…. Not so good…

We could deal with this by using a Branch Template – when you create your page that could automatically create the Items folder too. But the maintenance of that approach is a bit tedious because you probably end up with one Branch Template for every one of your Page Templates, and you have to remember to change all your Insert Options to match. What would be much better is if we could magically create the “Items” folder whenever it was needed.

After a bit of research, I discover that when Sitecore puts up the “Select the Associated Content” dialog box, in the background it runs the “getRenderingDatasource” pipeline in order to work out what to show in the tree view. So extending this pipeline should enable us to ensure that the location exists. We can create an extension class by providing a method called “Process” which accepts the correct arguments object:

namespace Testing
{
  public class CreateRelativeDataSourceFolder
  {
    public void Process(GetRenderingDatasourceArgs args)
    {
    }
  }
}

And then we can add it to the pipeline with a quick configuration patch:

<processor patch:before="processor[@type='Sitecore.Pipelines.GetRenderingDatasource.GetDatasourceLocation, Sitecore.Kernel']" type="Testing.CreateRelativeDataSourceFolder, Testing"/>

The “patch:before” attribute here tells Sitecore to insert this new item at the start of the pipeline, before Sitecore attempts to load the appropriate item – thus giving us the chance to create it first if necessary.

The first thing we need to do is get the value of the DataSource Location field for the component we’re setting the data source for. The GetRenderingDatasourceArgs parameter that i passed in to our pipeline processor includes a reference to this data – the args.RenderingItem property gives us access to the Sitecore Item for the UI component. So with that item we can grab the value of the field that stores the DataSource Location. We can get the ID of this field from the Sitecore UI, and write a quick bit of code to get the value and check it’s valid.

public class CreateRelativeDataSourceFolder
{
  private static ID DataSourceLocationField = new ID("{B5B27AF1-25EF-405C-87CE-369B3A004016}");
  private static string RelativePath = "./";

  public void Process(GetRenderingDatasourceArgs args)
  {
    string dataSourceLocation = args.RenderingItem.Fields[DataSourceLocationField].Value;
    if (string.IsNullOrWhiteSpace(dataSourceLocation))
    {
      return;
    }
    if (!dataSourceLocation.StartsWith(RelativePath))
    {
      return;
    }
  }
}

Once we’ve got the value of the field we check that it’s not empty and that it starts with a “./” relative path. If either of these isn’t true then this pipeline component has nothing to do and we can bail out and let the rest of the pipeline sort things out for us.

With that done, the next step is to work out what the full Sitecore path of our relative item would be, and then check if this item exists in the database. If it does exist then we have nothing to do – we can just return control to the rest of the pipeline.  But if the item doesn’t exist, we can create it. And that means adding a few more lines of code. To create an item you need to have a name for it and to have the Template ID for the sort of item to create. In this case, the name is just the path specified for the DataSourceLocation without the preceding “./” on the front. And the ID for the “Folder” template is easy to find from the Sitecore UI. So that extends our basic code to this:

public class CreateRelativeDataSourceFolder
{
  private static ID DataSourceLocationField = new ID("{B5B27AF1-25EF-405C-87CE-369B3A004016}");
  private static ID FolderTemplateID = new ID("{A87A00B1-E6DB-45AB-8B54-636FEC3B5523}");
  private static TemplateID FolderTemplate =  new TemplateID(FolderTemplateID);
  private static string RelativePath = "./";

  public void Process(GetRenderingDatasourceArgs args)
  {
    string dataSourceLocation = args.RenderingItem.Fields[DataSourceLocationField].Value;

    if (string.IsNullOrWhiteSpace(dataSourceLocation))
    {
      return;
    }

    if (!dataSourceLocation.StartsWith(RelativePath))
    {
      return;
    }

    if (string.IsNullOrWhiteSpace(args.ContextItemPath))
    {
      return;
    }

    string subFolderPath = args.ContextItemPath + dataSourceLocation.Substring(1);

    if (args.ContentDatabase.GetItem(subFolderPath) != null)
    {
      return;
    }

    Item currentItem = args.ContentDatabase.GetItem(args.ContextItemPath);

    if (currentItem == null)
    {
      return;
    }

    string newItemName = dataSourceLocation.Substring(2);

    using (new SecurityDisabler())
    {
      currentItem.Add(newItemName, FolderTemplate);
    }
  }
}

Recompile that, give it a test and now the child folder will be automatically created if it does not exist – success!

** The internet isn’t entirely sure if that was Stan Lee writing Uncle Ben from Spiderman, or Voltaire. Pick whichever you prefer…

Advertisements

3 thoughts on “Improving your Sitecore IA with relative DataSource Locations

  1. Hi

    I have implemented this i a Sitecore 7.2 MVC solution.
    When inserting a new item on a page that does not already have a Items folder, the Items folder and the new item is created, but the item is not added to the presentation details of the page. This only occurs when a Items folder does not already exist for that specific page.
    No Javascript error in the console and not exceptions in the log.
    When inserting a new item on a page that does already have a Items folder, the item is added to the presentation details of the page.

      • Sitecore Support helped with the solution:

        WebEditRibbon is subscribed to the ItemCreated notification and reloads the page if any new item is created during the request.
        That is why the page is reloaded and all changes (inserted rendering, etc.) are lost when you create the folder for datasources.

        As a workaround please enclose the currentItem.Add(newItemName, FolderTemplate); statement in your processor that creates folders for datasources like the following:

        Client.Site.Notifications.Disabled = true;
        currentItem.Add(newItemName, FolderTemplate);
        Client.Site.Notifications.Disabled = false;

Leave a Reply

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s