In this article, I will focus on a customization for Multi-list with Search. This scenario is not common. But sometimes you will need to customize a field in Sitecore. I will show you the steps that I followed to customize the Multi-List with Search.
Step 1: Prepare your component
I created a List View Component which contains multiple items.
This item will contains 2 fields:
- Title – Single-Line Text
- Description – Multi-Line Text
As we know, The Multi-List with Search by default shows the Item Name / Display Name, but as per client requirements we need to display and search using another field or even another item relation field.
Step 2: Create custom control
For this operation you can duplicate existing Multi-List with Search and name it “Custom Multilist with Search” or name that make sense to you.
Then, scroll down to Control Field and name something like “contentSpecificExtension:CustomMultilistWithSearch”
Step 3: Create custom Search.ashx Service and page load control class
Mutli-List with Search as you know it use Solr search to fetch the options. So in that case we will override search functionality to display the name as we required. Also we need to change the page load rendering to display the desired name.
Page Load Control Class
To create the class that will control the UI, we need to create a class named CustomMultilistWithSearch.cs
public class CustomMultilistWithSearch : BucketList
{
protected override string ScriptParameters => string.Format("'{0}'", (object)string.Join("', '", (object)this.ID, (object)this.ClientID, (object)this.PageNumber, (object)"/sitecore/shell/Applications/Buckets/Services/CustomSearchSearch.ashx", (object)this.Filter, (object)SearchHelper.GetDatabaseUrlParameter("&"), (object)this.TypeHereToSearch, (object)this.Of, (object)this.EnableSetNewStartLocation));
public override string OutputString(Item item)
{
Item bucketItemOrParent = item.GetParentBucketItemOrParent();
//string str = bucketItemOrParent != null ? " - " + bucketItemOrParent.DisplayName : string.Empty;
return item.Fields["Title"]?.Value;
}
}
In this script we are going to override ScriptParameters and noticed that is pointing to a CustomSearch.ashx file. And OuputString here we can add the code to display the desired name.
Then Website\sitecore\shell\Applications\Buckets\Services
create a file which specified the class Handler that we are going to use to override the Search Behavior.
<%@ WebHandler="" Language="C#" CodeBehind="Search.ashx.cs" Class="Website.Areas.DemoSite.Infraestructure.CustomControls.Services.Buckets.Search" %>
Finally create a class Website.Areas.DemoSite.Infraestructure.CustomControls.Services.Buckets.Search.cs
which contains the Search Logic
public class Search : IHttpHandler, IRequiresSessionState
{
#region Private Variables
private string _pageNumber = "1";
private int _itemsPerPage;
private readonly List<SearchStringModel> _searchQuery = new List<SearchStringModel>();
private bool _runFacet;
private Stopwatch _stopwatch;
private bool isJSONP;
private string callback;
#endregion
private ICollection<SitecoreUISearchResultItem> FullSearch(List<SearchStringModel> currentSearchString, out int hitCount, int pageNumber, int pageSize, string sortField, string sortDirection)
{
var results = new List<SitecoreUISearchResultItem>();
// YOur Search Logic Here
return results
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToList();
}
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "application/json";
context.Response.ContentEncoding = Encoding.UTF8;
isJSONP = false;
this._itemsPerPage = 20; // Constants.SettingsItem.Fields[Constants.ItemsPerPageText].Value.IsNumeric() ? int.Parse(Constants.SettingsItem.Fields[Constants.ItemsPerPageText].Value) : 20;
this.ExtractSearchQuery(context.Request.Form);
this._stopwatch = new Stopwatch();
this._stopwatch.Start();
this.StoreUserContextSearches();
int hitCount;
var debugMode = SearchHelper.GetDebug(this._searchQuery).IsNotEmpty();
this._itemsPerPage = SearchHelper.GetPageSize(this._searchQuery).IsNumeric() ? int.Parse(SearchHelper.GetPageSize(this._searchQuery)) : 20;
var items = FullSearch(this._searchQuery, out hitCount, pageNumber: int.Parse(this._pageNumber), pageSize: this._itemsPerPage, sortField: SearchHelper.GetSort(this._searchQuery), sortDirection: SearchHelper.GetOrderDirection(this._searchQuery));
var itemsCount = hitCount;
var pageNumbers = ((itemsCount % this._itemsPerPage) == 0) ? (itemsCount / this._itemsPerPage) : ((itemsCount / this._itemsPerPage) + 1);
var currentPage = int.Parse(this._pageNumber);
var startItemIdx = (currentPage - 1) * this._itemsPerPage;
if (startItemIdx >= itemsCount)
{
currentPage = 1;
}
if (items != null)
{
foreach (var sitecoreItem in items)
{
var innerItem = sitecoreItem.GetItem();
if (innerItem != null)
{
sitecoreItem.TemplateName = innerItem.TemplateName;
var parentBucketItemOrParent = innerItem.GetParentBucketItemOrParent();
if (parentBucketItemOrParent != null)
{
sitecoreItem.Bucket = parentBucketItemOrParent.Name;
}
else
{
sitecoreItem.Bucket = "sitecore";
}
sitecoreItem.CreatedDate = innerItem.Statistics.Created;
//sitecoreItem.Language = innerItem.Language.CultureInfo.TwoLetterISOLanguageName;
sitecoreItem.Path = innerItem.Paths.IsMediaItem
? innerItem.Paths.MediaPath
: innerItem.Paths.FullPath;
sitecoreItem.CreatedBy = innerItem.Statistics.CreatedBy != string.Empty ? innerItem.Statistics.CreatedBy : innerItem.Statistics.UpdatedBy;
if (sitecoreItem.Content.IsNullOrEmpty())
{
sitecoreItem.Content = string.Empty;
}
if (sitecoreItem.ImagePath.IsNullOrEmpty())
{
sitecoreItem.ImagePath = "/temp/IconCache/" + sitecoreItem.GetItem().Template.Icon;
}
}
}
}
if (!this._runFacet)
{
context.Response.Write(callback + "(" + JsonConvert.SerializeObject(new FullSearch
{
PageNumbers = pageNumbers,
items = items,
launchType = GetEditorLaunchType(),
SearchTime = this.SearchTime,
SearchCount = itemsCount.ToString(),
CurrentPage = currentPage,
Location = Context.ContentDatabase.GetItem(this.LocationFilter) != null ? Context.ContentDatabase.GetItem(this.LocationFilter).Name : "Unknown"
}) + ")");
this._stopwatch.Stop();
}
}
private static string GetEditorLaunchType()
{
return "contenteditor:launchtab";
}
private void StoreUserContextSearches()
{
var searchQuery = this._searchQuery.Where(query => !string.IsNullOrEmpty(query.Value)).Aggregate(string.Empty, (current, query) => current + query.Type + ":" + query.Value + ";");
if (ClientContext.GetValue("Searches") == null)
{
ClientContext.SetValue("Searches", string.Empty);
}
if (!ClientContext.GetValue("Searches").ToString().Contains("|" + searchQuery + "|"))
{
ClientContext.SetValue("Searches", ClientContext.GetValue("Searches") + "|" + searchQuery + "|");
}
}
private void ExtractSearchQuery(NameValueCollection searchQuery)
{
var fromField = false;
if (searchQuery.Keys[0] == "fromBucketListField")
{
fromField = true;
}
this._runFacet = false;
for (var i = 0; i < searchQuery.Keys.Count; i++)
{
if (searchQuery.Keys[i] == "callback")
{
isJSONP = true;
callback = searchQuery[searchQuery.Keys[i]];
continue;
}
if (searchQuery.Keys[i] != "pageNumber")
{
if (searchQuery.Keys[i] == "fromBucketListField" || searchQuery.Keys[i] == "filterText")
{
this._searchQuery.Add(new SearchStringModel
{
Type = "text",
Value = searchQuery[i]
});
}
else
{
if (fromField)
{
this.BuildSearchStringModelFromFieldQuery(searchQuery, i);
}
else
{
this.BuildSearchStringModelFromQuery(searchQuery, i);
}
}
}
else if (searchQuery.Keys[i] == "pageSize")
{
this.ExtractPageSizeFromQuery(searchQuery, i);
}
else
{
this.ExtractPageNumberFromQuery(searchQuery, i);
}
if (!fromField)
{
i++;
}
}
}
private void ExtractPageSizeFromQuery(NameValueCollection searchQuery, int i)
{
var pageSize = searchQuery[searchQuery.Keys[i]];
if (pageSize == "0" || pageSize == string.Empty)
{
this._itemsPerPage = 20;
}
else
{
this._itemsPerPage = int.Parse(pageSize);
}
}
private void ExtractPageNumberFromQuery(NameValueCollection searchQuery, int i)
{
this._pageNumber = searchQuery[searchQuery.Keys[i]];
if (this._pageNumber == "0")
{
this._pageNumber = "1";
}
}
private void BuildSearchStringModelFromFieldQuery(NameValueCollection searchQuery, int i)
{
if (searchQuery.Keys[i] == "pageNumber")
{
this.ExtractPageNumberFromQuery(searchQuery, i);
}
else
{
this._searchQuery.Add(new SearchStringModel
{
Type = searchQuery.Keys[i],
Value = searchQuery[searchQuery.Keys[i]]
});
}
}
private void BuildSearchStringModelFromQuery(NameValueCollection searchQuery, int i)
{
// if (i % 2 == 0)
// {
if (searchQuery[searchQuery.Keys[i]] != "Query")
{
if (searchQuery.Keys[i] != "_")
{
string type = searchQuery[searchQuery.Keys[i]];
string value = searchQuery[searchQuery.Keys[i + 1]];
// It is better to NOT do a '*' match then to simply not add the clause!
if (!(type == "text" && value == "*"))
{
this._searchQuery.Add(new SearchStringModel
{
Type = type,
Value = value
});
}
}
}
//}
}
private string getSiteIdFromName(string name)
{
SiteContext siteContext = SiteContextFactory.GetSiteContext(SiteManager.GetSite(name).Name);
var db = Context.ContentDatabase ?? Context.Database;
return db.GetItem(siteContext.StartPath).ID.ToString();
}
protected string Lang
{
get { return WebUtil.GetQueryString("la"); }
}
protected string LocationFilter
{
get { return string.IsNullOrEmpty(WebUtil.GetQueryString("id")) ? WebUtil.ExtractUrlParm("id", HttpContext.Current.Request.UrlReferrer.AbsoluteUri) : WebUtil.ExtractUrlParm("id", HttpContext.Current.Request.UrlReferrer.AbsoluteUri); }
}
protected string Db
{
get { return WebUtil.GetQueryString("db"); }
}
protected string SearchTime
{
get { return this._stopwatch.Elapsed.ToString(); }
}
protected Database ContentDatabase
{
get { return Factory.GetDatabase(this.Db) ?? Context.ContentDatabase; }
}
public bool IsReusable
{
get
{
return false;
}
}
}
I hope you could find useful this blog post.