Getting to the source

The so-called Sitecore tree is not really a tree. I always view it more as a graph, as you don't only have a parent/child relationship, you also have items pointing or relating to other items. There are several fields that allow you to connect items.

In general, when you define those fields, you have to specify which items the author can choose from to create this relationship. We could say there are two main types of fields: the ones that present a flat list to choose from (Multilist, Checklist, Droplist, Droplink); and the ones that present a tree (Treelist, TreelistEx, Droptree).

The possibilities for defining the source for both types are different. Why? Because if you have a flat list, you can just add to that list any item you want. If you are using a tree to show the possible options, you are limited to only showing items within a branch. Or to put it in another way, if you want to show an item in the tree, the parent must also be shown, or the item is the root of the tree.

Let's talk in more detail about those fields that present a flat list of items to choose from. You usually configure that list by setting an item's path or ID as the field source. The field will present as choices all the children of the item specified.

Let's see a quick example. Let's imagine I have a set of Tags. Each tag has a multilist field that allows me to select other related tags.

tags and related tags

In order to configure this, we would use the id or the path of the Categories (Folder) item as the source. This is fine for most cases.

Relative sources

But what would happen if we have several Category folders? A field is only defined once, and can only have one source definition. Let's say we want to show as possible related categories only the ones that are within the same subfolder. The solution is to use Sitecore Query (also known as Sitecore XPath) to define the source.

Sitecore query is inspired in XPath, their syntax is very similar, and can easily cater for this scenario. Simply enter this: query:../* as the field source. Notice we prepend the expression with the word query and a colon, this tells Sitecore to interpret the source as a Sitecore query. In this case the expression is very easy '..' points to the parent, and the '/*' gets all the children. So, all the children of the parent, i.e. all the siblings.

source with a query

Sitecore query allows you to filter by properties, like template, or by field values. Just remember it is actually looking at items in your database (or your cache), so a query that traverses the whole tree (particularly if it's a big tree) could be expensive. To limit the damage, by default queries only return 100 elements (as defined in the Query.MaxItems setting). If you want to learn about Sitecore query check this comprehensive article by John West.

Fine tuning our source

In our example above, we had a field that allows to selected related tags. However there is a slight problem. When we did the query, "all the children of the parent" is not really the same as siblings. It is siblings plus the item itself . This is not right since something should not be related to itself.

How can we solve this? We can't really do it with Sitecore query alone. But, as it is the norm with Sitecore, we can figure out a solution. It turns out Sitecore invokes a certain pipeline getLookUpDataSources when resolving the source of a Multilist.

We could add a step there which would allow us to modify the source to get the result we want. We are going to allow the usage of a token '$id' which we will replace in the pipeline with the corresponding id of the current item.

using Sitecore.Pipelines.GetLookupSourceItems;

namespace DataSources  
{
     public class ExtraQueryTokens
     {
          public void Process(GetLookupSourceItemsArgs args)
          {
               args.Source = args.Source.Replace("$id", args.Item.ID.ToString());
          }
     }
}

We just need to make sure the code runs before the pipeline step that resolves the query.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">  
  <sitecore>
    <pipelines>
      <getLookupSourceItems>
        <processor type="DataSources.ExtraQueryTokens, DataSources" patch:before="processor[@type='Sitecore.Pipelines.GetLookupSourceItems.ProcessQuerySource, Sitecore.Kernel']" />
      </getLookupSourceItems>
    </pipelines>
  </sitecore>
</configuration>  

And then we can rewrite the query as query:../*[@@id!='$id']. The token '$id' will get replaced by the actual id of the current item before the query is evaluated.

Siblings only

Another solution

There is however another more flexible solution. We could also prepend our source with the word code and then point to a method which must return an array of items.
This would allow us to write any code we want, not depending on Sitecore query at all; we could use the indexes from ContentSearch or any methods in the API to retrieve the items we want.

We must create a class that implements Sitecore.Buckets.FieldTypes.IDataSource (beware you need to add a reference to Sitecore.Buckets):

using Sitecore.Buckets.FieldTypes;  
using System.Linq;

namespace DataSources  
{
     public class Siblings : IDataSource
    {

         public Sitecore.Data.Items.Item[] ListQuery(Sitecore.Data.Items.Item item)
         {
              return item.Parent.Children.Where(i => i.ID != item.ID).ToArray();
         }
    }
}

and add (according to the snippet above) code:DataSources.Siblings,DataSources, as the source of our field (careful, it is case sensitive).

Final thoughts

This idea of creating the class and binding it with the code prefix is pretty powerful, and it is very simple to implement. In my opinion it has one big downside though: I can't easily parametrize it. I can't pass extra information to that class from my field source.

When would that be relevant? Let's imagine we want to use ContentSearch to populate our field source. Obviously it would be very useful to be able to pass, at the very least, the actual term we want to search for.

In those cases overriding the pipeline is the way to go. I could create my own prefix, let's say search and pass my own expression.

As a final (very raw) example:

using Sitecore.ContentSearch;  
using Sitecore.ContentSearch.SearchTypes;  
using Sitecore.Pipelines.GetLookupSourceItems;  
using System.Linq;

namespace DataSources  
{
     public class Search
     {
          public void Process(GetLookupSourceItemsArgs args)
          {
               if (!args.Source.StartsWith("search:")) return;

               var searchterms = args.Source.Substring(7);
               var index = ContentSearchManager.GetIndex("sitecore_master_index");
               using (var context = index.CreateSearchContext())
               {
                    var results = context.GetQueryable<SearchResultItem>()
                         .Where(i => i.Content.Contains(searchterms))
                         .Where(i => i.Language == args.Item.Language.Name);
                         .Take(200)
                         .Select(i => i.GetItem())
                         .ToArray()
                    args.Result.AddRange(results);
               }
               // this is important; otherwise our args.Result could be changed 
               // by another pipeline step
               args.AbortPipeline();
          }
     }
}
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">  
  <sitecore>
    <pipelines>
      <getLookupSourceItems>
        <processor type="DataSources.Search, DataSources" patch:before="processor[@type='Sitecore.Pipelines.GetLookupSourceItems.ProcessQuerySource, Sitecore.Kernel']" />
      </getLookupSourceItems>
    </pipelines>
  </sitecore>
</configuration>