Specifying query and parameters for Sitecore Treelist Field Source

Just before the Christmas break I got into a conversation with Robbert Hock on Slackchat about Sitecore fields and specifying a queryable source for the Treelist field in Sitecore 8.1. As you may be aware, several of the Sitecore fields support specifying an XPath query in the field source and others support an enhanced syntax using parameters.

From Sitecore 7.2 Update-2 the Treelist and TreelistEx fields also started to support specifying a query in the Source field.

Released Sitecore CMS and DMS 7.2 rev. 140526 (7.2 Update-2)
Field types
The Treelist and TreelistEx classes now support Sitecore query in the source for these field types. (319249)

Unfortunately, as the code is currently implemented, you cannot specify both a query AND parameters to restrict the items. Looking through the implementation I believe there is a flaw to the logic which causes this “bug”, which looks something like this:

if (@string.StartsWith("query:"))
   ....
else if (ID.IsID(@string))
  ....
else if (this.Source != null && !@string.Trim().StartsWith("/", StringComparison.OrdinalIgnoreCase))
  ....

When you want to specify a datasource and parameters you have to specify the source using a querystring in the format datasource=ABC&param1=XYZ&param2=XYZ&param3=XYZ. If you specify a query AND parameters the above logic falls through the 3rd statement and the query is never run through the getLookupSourceItems pipeline to resolve, so Sitecore defaults the source item to the tree root. So either it starts with query: and it does not take parameters into account, or you include Datasource=query: and it does not resolve the query….

A solution to this was posted to Community Forum but having dug through the Sitecore Kernel, I suggested the following code instead:

using System;
using System.Collections.Generic;
using System.Linq;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Web.UI.HtmlControls.Data;

namespace MyProject.CMS.Custom.Controls
{
    public class Treelist : Sitecore.Shell.Applications.ContentEditor.TreeList 
    {
        private string _dataSource = string.Empty;
        public override string DataSource
        {
            get
            {
                if (_dataSource.StartsWith("query:"))
                {
                    if (Sitecore.Context.ContentDatabase == null || base.ItemID == null)
                        return null;
                    Item current = Sitecore.Context.ContentDatabase.GetItem(base.ItemID);

                    Item obj = null;
                    try
                    {
                        obj = Enumerable.FirstOrDefault<Item>((IEnumerable<Item>)LookupSources.GetItems(current, _dataSource));
                    }
                    catch (Exception ex)
                    {
                        Log.Error("Treelist field failed to execute query.", ex, (object)this);
                    }
                    if (obj == null)
                        return null;
                    return obj.Paths.FullPath;
                }
                return _dataSource;
            }
            set { _dataSource = value; }
        }
    }
}

Github Gist

This reads more cleanly, leaves much of the default functionality of the original field intact and there is much less code duplication. Additionally, the query is run through the getLookupSourceItems pipeline, as the query would be if you had just specified a query without any parameters and as such the getter uses the same code from the original SetProperties() method – it’s copy/pasted from that method to this overridden property and unfortunately the original method is private. If you have added your own processors into this pipeline then they are also run through using this code.

To use this new field compile the above code in to project, switch over to the core database and then create a new field type – you can duplicate the existing Treelist field located in /sitecore/system/Field types/List Types/Treelist. Delete the existing Control field and instead set the ASSEMBLY and CLASS fields to point to your implementation.

Usage is then simply setting the source like so, you must prepend the datasource with query:

datasource=query:./../../../MyItem&amp;IncludeTemplatesForDisplay=MySectionTemplate&amp;IncludeTemplatesForSelection=SelectableTemplate

8 comments

  1. Peter K · May 13, 2016

    Thanks for this. Have you noticed though, that the “value” supplied in set_datasource is lowercase? For example, I see “query:ancestor::*[@@templatename=’firstsitedef’]”, where the configuration is “query:ancestor::*[@@templatename=’FirstSiteDef’]”.

    • jammykam · May 20, 2016

      That’s interesting, it’s not something I have looked at and I can’t recall off the top of my head whether the query is case sensitive or not though.

    • Sergey Perepechin · March 24, 2017

      I found this today too, and I found a workaround.

      so this query didn’t work for me
      ./ancestor-or-self::*[@@templatename=’My Template Name’]/Globals/*[@@templatename=’My Template 2′]

      I have replaced it with this query:
      datasource=query:./ancestor-or-self::*[CompareCaseInsensitive(@@templatename,’My Template Name’)]/globals/*[CompareCaseInsensitive(@@templatename, ‘My Template 2’)]&IncludeTemplatesForSelection=Tag

      so you can see that I am using CompareCaseInsensitive in order to perform a case insensitive comparison.

      P.S. @jammykam – thank you for this custom control. it helped me a lot.

  2. Sergey Perepechin · March 24, 2017

    I found that we cannot return null for the DataSource property, because in that case Content Editor will crash with the exception.
    so this
    if (obj == null) return null:
    must be replaced with this:
    if (obj == null) return string.Empty;

  3. Pingback: 5 passos para o Sitecore suportar Source Parameters e Datasources dinamicos em fields TreeList – Sitecore Brasil
  4. Pingback: My simple Sitecore development framework – Version 1.7 – Walking on clouds
  5. Vinod Laxman Chavan · August 4, 2022

    I am also facing the same lowercase issue. Sitecore tokens are getting converted to lowercase and those are not getting resolved. How can we handle that?
    query:$sharedSites/Data/Topic

    • jammykam · August 4, 2022

      Did you try the suggestion above to use CompareCaseInsensitive in the query? Otherwise you should trying logging a bug with Sitecore Support, thanks.

Leave a comment