Recentemente eu trabalhei em um projeto multi-site em Sitecoore com requisitos específicos. Eu precisava que o field Treelist suportasse queries com datasources dinamicos funcionando em paralelo com source parameters. Neste caso é importante mencionar que esse field era usado no contexto de um template customizado da media library, que nesse caso, não tinha contexto do site, então isso era necessário ser determinado dinamicamente.
Como ja explicado nesse post do Kamruz, queries e source parameters não funcionam em paralalo sem criar um novo field.
O resultado final que estava tentando alcançar, era o field Source declarado dessa forma:
DataSource=query:../../&ExcludeTemplatesForSelection=NotDesirableTemplate.
Mas há um problema com a query acima. Eu precisava de alguma forma determinar o caminho dinamicamente, já que como mencionado, seria necessário primeiro determinar o site e colocar o caminho baseado no site.
Ficou confuso? Lembre: Estamos tentando ter o field TreeList suportando Sources dinamicos e ainda usar paremeters, por exemplo ExcludeTemplatesForSelection.
Abaixo eu descrevo 5 passos, para implementar esse requisito com algumas considerações importantes de se ter presente.
1. Criar um token para a query
DataSource=query:#mytoken#&ExcludeTemplatesForSelection=NotDesirableTemplate.
Seguinte nós precisamos fazer duas coisas: Extender o field TreeList e implementar uma forma de manipular o #mytoken#, para substituir o token com o caminho do site.
2. Criar um novo Field
Após logar no Sitecore e acessar o Desktop, mude para o banco Core e crie um novo List Type. Para fazer isso, navegue até o caminho: /sitecore/system/Field Types/List Types e crie um novo List Type. No meu exemplo eu chamei de ‘CustomTreeList’.
Depois preencha os campos Assembly e Class. Abaixo uma imagem de como ficou definido no meu caso:
3. Implement o novo TreeList Field
Agora precisamos implementar o novo field e o token definido no passo 1. Eu usei o código do Kamruz como base e adaptei para este requisito. No final ficou definido dessa forma:
namespace MyNamespace.CustomFields
{
public class CustomTreeList : Sitecore.Shell.Applications.ContentEditor.TreeList
{
private string _ds = string.Empty;
/// <summary>
/// Override the TreeList DataSource property to support the mytoken on the Source
/// </summary>
public override string DataSource
{
get
{
if (_ds.StartsWith("query:"))
{
if (Sitecore.Context.ContentDatabase == null || base.ItemID == null)
return null;
var current = Sitecore.Context.ContentDatabase.GetItem(base.ItemID);
if (_ds.Contains("#mytoken#"))
{
//uses the current media item to resolve what the tenant which it belongs to
var myTenant = Tenant.GetTenant(new ID(ItemID));
if (myTenant != null)
{
var sourceFolder = myTenant.GetChildren().FirstOrDefault(i=>; i.Template.ID.ToString() == Constants.Home.ID);
if (sourceFolder != null)
{
//found the Home item under the tenant item so replace the datasource with the Home Path
_ds = _ds.Replace("#mytoken#", sourceFolder.Paths.FullPath);
}
}
else
{
Log.Warn("Could not find tenant for item "+ItemID+ " : the path /sitecore/content is returned by default. This could be because the item is _standard values" , ItemID);
_ds = "/sitecore/content";
}
}
Item item = null;
try
{
item = Enumerable.FirstOrDefault<Item>((IEnumerable<Item>)LookupSources.GetItems(current, _ds));
}
catch (Exception ex)
{
Log.Error("Treelist field failed executing the query", ex, (object)this);
}
return item == null ? null : item.Paths.FullPath;
}
return _ds;
}
set { _ds = value; }
}
}
}
O que esse código esta fazendo é, pegar o #mytoken# e definido no Source e sobrescrever com o item Home do Site que ele pertence.
Por exemplo, se voce tem uma árvore de conteúdo que tem um item de midia que resolve para o site 1. após executar esse código, o resultado do Source ficaria assim definido:
DataSource=query:/Sitecore/Content/Site1/Home&ExcludeTemplatesForSelection=NotDesirableTemplate.
Isso funciona, porém não acabamos ainda.
Este é um field TreeList, o que significa que ele usa o Link Database. De maneira que se queira manter as funcionalidades relacionadas a links quebrados, atualização de links, etc, você precisa informar o Sitecore o que precisa acontecer quando esse field for feito o rebuild no painel de controle do Sitecore.
Outra coisa que será necessário fazer é garantir que esse field funciona com Content Search.
4. Atualizar o Link Database
Conforme mencionado, para manter compatibilidade com as funcionalidades do link database, voce precisa implementar um novo field que deriva do tipo CustomField e registrá-lo. A forma de fazer isso é bem explicada aqui. Registrando o field:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<fieldTypes>
<!--Used to update the link database-->
<fieldType name="CustomTreeList" type="MyNamespace.CustomFields.CustomTreeListType, MyNamespace" />
</fieldTypes>
</sitecore>
</configuration>
Implementação da Classe:
namespace MyNamespace.CustomFields
{
public class CustomTreeListType : CustomField
{
public CustomTreeListType(Field innerField) : base(innerField)
{
}
public CustomTreeListType(Field innerField, string runtimeValue) : base(innerField, runtimeValue)
{
}
private void AddLink(ID id)
{
if (!Value.EndsWith("|") && Value.Length > 0)
{
Value += "|";
}
Value += id.ToString();
}
private void ClearLink(ID targetItemID)
{
Value = Value.Replace(targetItemID.ToString(), "");
Value = Value.Replace("||", "|");
Value = Value.TrimEnd('|');
Value = Value.TrimStart('|');
}
private void ClearAllInvalidLinks()
{
foreach (var itemId in Value.Split('|'))
{
var targetItem = InnerField.Database.GetItem(new ID(itemId));
if (targetItem != null)
ClearLink(targetItem.ID);
}
}
public override void Relink(ItemLink itemLink, Item newLink)
{
Assert.ArgumentNotNull(itemLink, "itemLink");
Assert.ArgumentNotNull(newLink, "newLink");
Database database = Factory.GetDatabase(itemLink.TargetDatabaseName);
if (database != null)
{
Item targetItem = database.GetItem(itemLink.TargetItemID);
if (targetItem == null)
{
ClearLink(itemLink.TargetItemID);
}
else
{
if (Value.Contains(itemLink.TargetItemID.ToString()))
{
Value = Value.Replace(itemLink.TargetItemID.ToString(), newLink.ID.ToString());
}
else
{
AddLink(newLink.ID);
}
}
}
}
public override void RemoveLink(ItemLink itemLink)
{
Assert.ArgumentNotNull(itemLink, "itemLink");
ClearLink(itemLink.TargetItemID);
}
public override void UpdateLink(ItemLink itemLink)
{
Assert.ArgumentNotNull(itemLink, "itemLink");
Database database = Factory.GetDatabase(itemLink.TargetDatabaseName);
if (database == null)
{
ClearLink(itemLink.TargetItemID);
}
else
{
ClearAllInvalidLinks();
Item targetItem = database.GetItem(itemLink.TargetItemID);
if (targetItem != null)
AddLink(targetItem.ID);
}
}
public override void ValidateLinks(LinksValidationResult result)
{
Assert.ArgumentNotNull(result, "result");
string itemIds = this.Value;
if (string.IsNullOrEmpty(itemIds))
{
Log.Info("Validate links ID is empty", itemIds);
return;
}
Log.Info("IDs = "+Value, result);
foreach (var itemId in this.Value.Split('|'))
{
var targetItem = this.InnerField.Database.GetItem(new ID(itemId));
if (targetItem != null)
result.AddValidLink(targetItem, targetItem.Paths.FullPath);
else
result.AddBrokenLink(itemId);
}
}
}
}
5. Mantendo compatibilidade com Content Search
Estamos quase finalizando! A última parte é armazenar o novo field de maneira que ele funcione com Content Search. Do contrário, você terá problemas como esse here.
Como explicado na resposta do Richar Seal, a maneira defazer os resultados returnarem é criando um novo arquivo de configuração que muda o valor de StorageType=Yes. No meu caso, fico assim definido:
FieldTypeName=”CustomTreeList” and StorageType=”Yes.”
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<contentSearch>
<indexConfigurations>
<defaultLuceneIndexConfiguration>
<fieldMap>
<fieldTypes hint="raw:AddFieldByFieldTypeName">
<!--needed to set the storagetype=Yes so the field shows within the index-->
<fieldType fieldTypeName="customtreelist" storageType="YES" indexType="TOKENIZED" vectorType="NO" boost="1f" type="System.String" settingType="Sitecore.ContentSearch.LuceneProvider.LuceneSearchFieldConfiguration, Sitecore.ContentSearch.LuceneProvider" patch:after="*[1]" />
</fieldTypes>
</fieldMap>
</defaultLuceneIndexConfiguration>
</indexConfigurations>
</contentSearch>
</sitecore>
</configuration>
Espero que este post seja util para vocês!