The Datasource Location field on renderings in Sitecore supports a nice feature that I've taken for granted--relative paths. For example, if you want content authors to put the data items for a particular rendering under the item that the rendering is on in the content tree, you can specify the Datasource Location as ./.

The Treelist field has a bunch of options that you can set in its Source to control what it displays and what content authors can and can't select. Dan Cruickshank wrote a nice blog post covering these options on the Fishtank blog. I created a template in Sitecore recently where I wanted its Treelist field to point to items nested under itself in the content tree. To my surprise, the datasource parameter does not support relative paths like the Datasource Location field on the rendering template. You can fake it by setting the Source field to an XPath query like this:

query:./descendant-or-self::*

But the problem with the XPath query is that you can't use the other parameters available to the Treelist such as IncludeTemplatesForSelection to limit the items that content authors can select in the Treelist.

After some digging in the Sitecore source code with dotPeek, I came up with a solution to extend the Treelist field to support relative paths like the Datasource Location field on renderings. Here are the steps.

Create a FieldHelper to Transform Relative Source Path Field into an Absolute Path

Add this class to your Sitecore solution. The TransformRelativeSourcePath function will take the source field of your Treelist and convert a relative path, ./, into an absolute path pointing to the current item.

using Sitecore.Web;

namespace SitecoreDemo.Web.Infrastructure
{
  public static class FieldHelpers
  {
    public static string TransformRelativeSourcePath(string source, string itemId)
    {
      if (string.IsNullOrWhiteSpace(source) || string.IsNullOrWhiteSpace(itemId)) return source;

      var parsedSource = WebUtil.ParseQueryString(source);
      var datasource = parsedSource["datasource"] ?? string.Empty;

      if (!datasource.StartsWith("./")) return source;
      if (Sitecore.Context.ContentDatabase == null) return source;

      var currentItem = Sitecore.Context.ContentDatabase.GetItem(itemId);
      if (currentItem == null) return source;

      parsedSource["datasource"] = currentItem.Paths.FullPath + datasource.Remove(0, 1);
      return WebUtil.BuildQueryString(parsedSource, false);
    }
  }
}

Extend the Sitecore Treelist Control to use the FieldHelper

Add this class to your Sitecore solution. This extends the code-behind for the Treelist control used by Sitecore that is rendered on items with a Treelist field.

using System;
using Sitecore.Shell.Applications.ContentEditor;

namespace SitecoreDemo.Web.Infrastructure
{
  public class RelativeSourcePathTreeList : TreeList
  {
    protected override void OnLoad(EventArgs args)
    {
      if (Source != null)
      {
        Source = FieldHelpers.TransformRelativeSourcePath(Source, ItemID);
      }
      base.OnLoad(args);
    }
  }
}

Switch to the Core database in Sitecore and navigate to the Treelist item at /sitecore/system/Field types/List Types. Clear the Control field and set the Assembly field to the assembly that contains your RelativeSourcePathTreeList class and set the Class field to the fully-qualified name of your RelativeSourcePathTreeList class as shown below:

Treelist item in the Core database.

For reference, the default value of the Control field is content:TemplateFieldSource.

Override Template Field Source Field Links Validation to Support Relative Path

The above code is sufficient for Treelist fields to support relative paths; however, if you edit the field directly in the content editor and save, you will get a warning message about broken links in the Source field:

Warning about broken links in the Source field.

Since we know this is a valid link now, we must extend the TemplateFieldSourceField:

using System.Linq;
using Sitecore.Data.Fields;
using Sitecore.Links;

namespace SitecoreDemo.Web.Infrastructure
{
  public class CustomTemplateFieldSourceField : TemplateFieldSourceField
  {
    public CustomTemplateFieldSourceField(Field innerField) : base(innerField)
    {
    }

    public override void ValidateLinks(LinksValidationResult result)
    {
      base.ValidateLinks(result);
      if (result.IsEmpty) return;

      var fieldType = InnerField.Item["Type"].ToLower();
      if (fieldType != "treelist") return;

      var relativeLinks = result.Links.Where(l => l.TargetPath.StartsWith("./")).ToArray();
      foreach (var relativeLink in relativeLinks)
      {
        result.Links.Remove(relativeLink);
      }
    }
  }
}

Here we're overriding the ValidateLinks method so that relative-path links in the Source field of Treelists are removed from the validation results after validation. I wish there was a way to prevent links that are relative from being marked as invalid, but it's not possible with the way the TemplateFieldSourceField class is implemented currently. Simply removing the false positives after validation is our best bet for now.

Now plug the custom field into Sitecore by opening FieldTypes.config in /App_Config of your Sitecore install's web root and update the Template Field Source field type as follows:

<fieldType name="Template Field Source" type="SitecoreDemo.Web.Infrastructure.CustomTemplateFieldSourceField, SitecoreDemo.Web" />

Test It Out

If you've done everything correctly, your Treelist fields will now support relative paths in the datasource parameter with all of the other options. Here are some screenshots of it in action in my SitecoreDemo instance.

Treelist field using Source options.

Treelist field in action with relative path.

Final Thoughts

I'm surprised that the Treelist field doesn't support relative paths as a datasource parameter out of the box, but the fact that we can extend the Treelist field ourselves to support it is a testament to Sitecore's flexibility.

In this post I've demonstrated how to extend the Treelist field to support this functionality, but it shouldn't be too much work to port this functionality over to the TreelistEx, Multilist, and Multilist with Search fields as well.

Although I've tested this code in my local Sitecore instance, it definitely isn't production-tested and is just something that I threw together as a proof of concept. Let me know in the comments if you use this and if you made any improvements and I'll update the post for all to enjoy.