Custom Field for Sitecore Multi-list with Search

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.

Figure 1: List View Data source

This item will contains 2 fields:

  • Title – Single-Line Text
  • Description – Multi-Line Text
Figure 2: List Item

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.

Read more

Check programmatically if rendering exists in Sitecore

Sometimes you will need to check programmatically if rendering is already added to a Presentation Details in Sitecore. In my case Content Authors doesn’t want to add a modal in renderings each time that they added another component to the view. Also, you can add multiple times that Widget rendering on this page, but the modal should be added once.

To provide a solution I added a helper I will show you the code that I used.


  public static class RenderingHelper
    {
        public static bool HasRendering(string guid)
        {

            var refs = Sitecore.Context.Item.Visualization.GetRenderings(Context.Device, false);
            if (!refs.Any())
                return false;

            var renderingReferences = refs.Where(r => r.RenderingID.ToString().Equals(guid, StringComparison.InvariantCultureIgnoreCase)).ToList();

            if (renderingReferences.Any())
                return true;

            return false;

        }
    }

Then, when you implement the validation the view you can add something like this


@if (!RenderingHelper.HasRendering("{--YOUR RENDERING ID HERE--}"))
{
    @Html.Sitecore().Rendering("{--YOUR RENDERING ID HERE--}", new
{
    DataSource = "{--YOUR DATASOURCE ITEM ID HERE--}"
})
}


I hope this will be useful for you. Happy Coding.

Read more