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:
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:
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.
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.