How to Alphabetize the Selected List of a Field in Sitecore

This is a continuation of my previous blog post, in which explained how to remove Sitecore list field icons (up/down, etc). The project I was working on was a two-part request, and this post will explain the first part and how it was handled.

Client wanted a field on their item to be sorted alphabetically (technically the Selected list of that field). This is best handled by creating a new TreeList field that will inherit from TreeList. Or, if you are using MultiList or some other list type, that should work fine as well. Let’s get started.

  1. The first thing you will need to do is create a new class for your custom list field. Ideally you would place this in the Fields folder of your project, if you have one. Edit the class so that it inherits from the appropriate base class, i.e. TreeList.
  2. Then, create a new custom field in Sitecore and publish it. For instructions on how to do that, see my previous blog (link here). In fact, if you already went through the instructions of that post, this is already done.
  3. Going back to your class created in Step 1, you’ll want to override the OnLoad() event and add in the logic to alphabetize. My method was (pseudo code):
    1. Get the current list of guids in the field.
    2. Convert each to an item and add it to a generic list.
    3. Use a generic OrderBy() to sort the list by item display name.
    4. Build a string of pipe-delimited, guids from the sorted list, and set the list field’s value to that.

The result was the following code:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using Sitecore;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.Resources;
using Sitecore.SecurityModel;
using Sitecore.Shell.Applications.ContentEditor;
using Sitecore.Web.UI.HtmlControls;
using Sitecore.Web.UI.Sheer;
using Sitecore.Web.UI.WebControls;

namespace your.namespace.here
{
    public class AlphaTreeList : TreeList
    {
        protected override void OnLoad(EventArgs args)
        {
            if (!string.IsNullOrEmpty(base.ItemID))
            {
                SortAssociatedProductsAlpha(Sitecore.Context.ContentDatabase.GetItem(base.ItemID));
            }

            base.OnLoad(args);
        }

        private void SortAssociatedProductsAlpha(Item i)
        {
            using (new SecurityDisabler())
            {
                var associatedProducts = i.Fields["Associated Products"];
                var alphaGuids = GetAlphabetizedGuidString(i, associatedProducts);

                if (alphaGuids.Equals(associatedProducts.Value)) return;

                if (alphaGuids != string.Empty)
                {
                    using (new EditContext(i))
                    {
                        this.Value = alphaGuids;
                    }
                }
            }
        }

        private string GetAlphabetizedGuidString(Item i, Field f)
        {
            List<Item> items = new List<Item>();
            StringBuilder scGuidBuilder = new StringBuilder();

            if (i != null && f != null)
            {
                foreach (ID guid in ((MultilistField)f).TargetIDs)
                {
                    Item target = Sitecore.Data.Database.GetDatabase("master").Items.GetItem(guid);
                    items.Add(target);
                }

                // Sort it by item name.
                items = items.OrderBy(o => o.DisplayName, StringComparer.OrdinalIgnoreCase).ToList();

                // Build a string of pipe-delimited guids.
                foreach (Item item in items)
                {
                    scGuidBuilder.Append(item.ID);
                    scGuidBuilder.Append("|");
                }

                // Return string which is a list of guids. And remove that last pipe.
                return scGuidBuilder.ToString().TrimEnd('|');
            }

            return string.Empty;
        }
    }
}

Lastly, you should change the field type for this field in your environment to your new custom field type.

This should work every time you open the item or add a new item to the field’s Selected list and save.

How to Remove Up/Down Arrow Icons from a TreeList field in Sitecore

I was recently requested to update one of our client’s TreeList fields so that the Selected list was alphabetically sorted. Part of this change was to hide those blue up/down arrow buttons to the right of the selected list on the field. While it’s not as easy as I was hoping it would be, I found a solution for this. It involves rendering the field in a completely different way – through code.

2015-12-01 09_02_06-Sitecore

First, add a new class file for your field type. I put mine under a Fields folder in my project, but it can be anywhere technically. Make no changes to the class at this point.

Next, create a new field type in Sitecore. To do that, in the Core database, navigate to sitecore/system/Field types/List Types. Right click the List Type folder and create your list type – I believe it should be created from the System/Templates/Template field type. Once created, fill in the Assembly (Visual Studio Project name) and Class (Visual Studio path to your field type class) fields. Save and publish the list type.

 

2015-11-30 18_13_26-Sitecore

 

Next,we will implement the rendering change for our new field. I used Reflector to get the source from Treeist.cs. It’s actually in the OnLoad event, which I found interesting. Anyway, I have supplied a code sample – note it’s rather long; Sitecore is basically gathering all the info, and assembling the HTML dynamically:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using Sitecore;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.Resources;
using Sitecore.SecurityModel;
using Sitecore.Shell.Applications.ContentEditor;
using Sitecore.Web.UI.HtmlControls;
using Sitecore.Web.UI.Sheer;
using Sitecore.Web.UI.WebControls;

namespace Your.Namespace.Here
{
    public class AlphaTreeList : TreeList
    {
        private Listbox _listBox;

        protected override void OnLoad(EventArgs e)
        {
            RenderTreeListWithoutUpDownArrows(e);
        }

        private string FormTemplateFilterForDisplay()
        {
            // Taken from TreeList.cs using reflector

            if ((string.IsNullOrEmpty(this.IncludeTemplatesForDisplay) && string.IsNullOrEmpty(this.ExcludeTemplatesForDisplay)) && (string.IsNullOrEmpty(this.IncludeItemsForDisplay) && string.IsNullOrEmpty(this.ExcludeItemsForDisplay)))
            {
                return string.Empty;
            }
            string str = string.Empty;
            string str2 = ("," + this.IncludeTemplatesForDisplay + ",").ToLowerInvariant();
            string str3 = ("," + this.ExcludeTemplatesForDisplay + ",").ToLowerInvariant();
            string str4 = "," + this.IncludeItemsForDisplay + ",";
            string str5 = "," + this.ExcludeItemsForDisplay + ",";
            if (!string.IsNullOrEmpty(this.IncludeTemplatesForDisplay))
            {
                if (!string.IsNullOrEmpty(str))
                {
                    str = str + " and ";
                }
                str = str + string.Format("(contains('{0}', ',' + @@templateid + ',') or contains('{0}', ',' + @@templatekey + ','))", str2);
            }
            if (!string.IsNullOrEmpty(this.ExcludeTemplatesForDisplay))
            {
                if (!string.IsNullOrEmpty(str))
                {
                    str = str + " and ";
                }
                str = str + string.Format("not (contains('{0}', ',' + @@templateid + ',') or contains('{0}', ',' + @@templatekey + ','))", str3);
            }
            if (!string.IsNullOrEmpty(this.IncludeItemsForDisplay))
            {
                if (!string.IsNullOrEmpty(str))
                {
                    str = str + " and ";
                }
                str = str + string.Format("(contains('{0}', ',' + @@id + ',') or contains('{0}', ',' + @@key + ','))", str4);
            }
            if (string.IsNullOrEmpty(this.ExcludeItemsForDisplay))
            {
                return str;
            }
            if (!string.IsNullOrEmpty(str))
            {
                str = str + " and ";
            }
            return (str + string.Format("not (contains('{0}', ',' + @@id + ',') or contains('{0}', ',' + @@key + ','))", str5));
        }

        private void RenderTreeListWithoutUpDownArrows(EventArgs args)
        {
            // Taken from TreeList.cs OnLoad() using reflector

            Assert.ArgumentNotNull(args, "args");

            if (!Sitecore.Context.ClientPage.IsEvent)
            {
                this.SetProperties();
                GridPanel panel = new GridPanel();
                this.Controls.Add(panel);
                panel.Columns = 4;
                this.GetControlAttributes();
                foreach (string str in base.Attributes.Keys)
                {
                    panel.Attributes.Add(str, base.Attributes[str]);
                }
                panel.Attributes["id"] = this.ID;
                panel.Style["margin"] = "0px 0px 4px 0px";
                base.SetViewStateString("ID", this.ID);
                Literal literal = new Literal("All")
                {
                    Class = "scContentControlMultilistCaption"
                };
                panel.Controls.Add(literal);
                panel.SetExtensibleProperty(literal, "Width", "50%");
                panel.SetExtensibleProperty(literal, "Row.Height", "20px");
                LiteralControl control = new LiteralControl(Images.GetSpacer(0x18, 1));
                panel.Controls.Add(control);
                panel.SetExtensibleProperty(control, "Width", "24px");
                literal = new Literal("Selected")
                {
                    Class = "scContentControlMultilistCaption"
                };
                panel.Controls.Add(literal);
                panel.SetExtensibleProperty(literal, "Width", "50%");
                control = new LiteralControl(Images.GetSpacer(0x18, 1));
                panel.Controls.Add(control);
                panel.SetExtensibleProperty(control, "Width", "24px");
                Scrollbox scrollbox = new Scrollbox();
                scrollbox.ID = Sitecore.Web.UI.HtmlControls.Control.GetUniqueID("S");
                panel.Controls.Add(scrollbox);
                if (!UIUtil.IsIE())
                {
                    scrollbox.Padding = "0px";
                }
                scrollbox.Style["border"] = UIUtil.IsIE() ? ((string)"3px window-inset") : ((string)"2px inset silver");
                object obj2 = base.ServerProperties["SetMasters.FixBorder"];
                if (((UIUtil.IsFirefox() && (obj2 != null)) && (obj2.ToString() == "true")) || UIUtil.IsWebkit())
                {
                    scrollbox.Style["height"] = "100%";
                    scrollbox.Style["-moz-box-sizing"] = "border-box";
                }
                panel.SetExtensibleProperty(scrollbox, "rowspan", "2");
                panel.SetExtensibleProperty(scrollbox, "VAlign", "top");
                TreeviewEx ex = new TreeviewEx();
                ex.ID = this.ID + "_all";
                scrollbox.Controls.Add(ex);
                ex.DblClick = this.ID + ".Add";
                ex.AllowDragging = false;
                ImageBuilder builder = new ImageBuilder
                {
                    Src = "Applications/16x16/nav_right_blue.png",
                    ID = this.ID + "_right",
                    Width = 0x10,
                    Height = 0x10,
                    Margin = UIUtil.IsIE() ? ((string)"2px") : ((string)"2px 0px 2px 2px"),
                    OnClick = Sitecore.Context.ClientPage.GetClientEvent(this.ID + ".Add")
                };
                ImageBuilder builder2 = new ImageBuilder
                {
                    Src = "Applications/16x16/nav_left_blue.png",
                    ID = this.ID + "_left",
                    Width = 0x10,
                    Height = 0x10,
                    Margin = UIUtil.IsIE() ? ((string)"2px") : ((string)"2px 0px 2px 2px"),
                    OnClick = Sitecore.Context.ClientPage.GetClientEvent(this.ID + ".Remove")
                };
                LiteralControl control2 = new LiteralControl(builder + "<br/>" + builder2);
                panel.Controls.Add(control2);
                panel.SetExtensibleProperty(control2, "Width", "30");
                panel.SetExtensibleProperty(control2, "Align", "center");
                panel.SetExtensibleProperty(control2, "VAlign", "top");
                panel.SetExtensibleProperty(control2, "rowspan", "2");
                Listbox listbox = new Listbox();
                panel.Controls.Add(listbox);
                panel.SetExtensibleProperty(listbox, "VAlign", "top");
                panel.SetExtensibleProperty(listbox, "Height", "100%");
                this._listBox = listbox;
                listbox.ID = this.ID + "_selected";
                listbox.DblClick = this.ID + ".Remove";
                listbox.Style["width"] = "100%";
                listbox.Size = "10";
                listbox.Attributes["onchange"] = "javascript:document.getElementById('" + this.ID +
                                                 "_help').innerHTML=this.selectedIndex>=0?this.options[this.selectedIndex].innerHTML:''";
                listbox.Attributes["class"] = "scContentControlMultilistBox";
                this._listBox.TrackModified = false;
                ex.Enabled = !this.ReadOnly;
                listbox.Disabled = this.ReadOnly;
                panel.SetExtensibleProperty(control2, "Width", "30");
                panel.SetExtensibleProperty(control2, "Align", "center");
                panel.SetExtensibleProperty(control2, "VAlign", "top");
                panel.SetExtensibleProperty(control2, "rowspan", "2");
                panel.Controls.Add(new LiteralControl("
")); DataContext context = new DataContext(); panel.Controls.Add(context); context.ID = Sitecore.Web.UI.HtmlControls.Control.GetUniqueID("D"); context.Filter = this.FormTemplateFilterForDisplay(); ex.DataContext = context.ID; ex.DisplayFieldName = this.DisplayFieldName; context.DataViewName = "Master"; if (!string.IsNullOrEmpty(this.DatabaseName)) { context.Parameters = "databasename=" + this.DatabaseName; } context.Root = this.DataSource; context.Language = Language.Parse(this.ItemLanguage); panel.Fixed = true; ex.ShowRoot = true; panel.SetExtensibleProperty(scrollbox, "Height", "100%"); this.RestoreState(); } } private void RestoreState() { // Taken from TreeList.cs using reflector string[] strArray = this.Value.Split((char[])new char[] { '|' }); if (strArray.Length > 0) { Database contentDatabase = Sitecore.Context.ContentDatabase; if (!string.IsNullOrEmpty(this.DatabaseName)) { contentDatabase = Factory.GetDatabase(this.DatabaseName); } for (int i = 0; i < strArray.Length; i = (int)(i + 1)) { string path = strArray[i]; if (!string.IsNullOrEmpty(path)) { ListItem item = new ListItem(); item.ID = Sitecore.Web.UI.HtmlControls.Control.GetUniqueID("I"); this._listBox.Controls.Add(item); item.Value = item.ID + "|" + path; Item item2 = contentDatabase.GetItem(path); if (item2 != null) { item.Header = this.GetHeaderValue(item2); } else { item.Header = path + ' ' + Translate.Text("[Item not found]"); } } } SheerResponse.Refresh(this._listBox); } } private void SetProperties() { // Taken from TreeList.cs using reflector string id = StringUtil.GetString((string[])new string[] { this.Source }); if (Sitecore.Data.ID.IsID(id)) { this.DataSource = this.Source; } else if ((this.Source != null) && !id.Trim().StartsWith("/", StringComparison.OrdinalIgnoreCase)) { this.ExcludeTemplatesForSelection = StringUtil.ExtractParameter("ExcludeTemplatesForSelection", this.Source).Trim(); this.IncludeTemplatesForSelection = StringUtil.ExtractParameter("IncludeTemplatesForSelection", this.Source).Trim(); this.IncludeTemplatesForDisplay = StringUtil.ExtractParameter("IncludeTemplatesForDisplay", this.Source).Trim(); this.ExcludeTemplatesForDisplay = StringUtil.ExtractParameter("ExcludeTemplatesForDisplay", this.Source).Trim(); this.ExcludeItemsForDisplay = StringUtil.ExtractParameter("ExcludeItemsForDisplay", this.Source).Trim(); this.IncludeItemsForDisplay = StringUtil.ExtractParameter("IncludeItemsForDisplay", this.Source).Trim(); string str2 = StringUtil.ExtractParameter("AllowMultipleSelection", this.Source).Trim().ToLowerInvariant(); this.AllowMultipleSelection = (bool)(string.Compare(str2, "yes", StringComparison.InvariantCultureIgnoreCase) == 0); this.DataSource = StringUtil.ExtractParameter("DataSource", this.Source).Trim().ToLowerInvariant(); this.DatabaseName = StringUtil.ExtractParameter("databasename", this.Source).Trim().ToLowerInvariant(); } else { this.DataSource = this.Source; } } } }

The above code is a bit different from the source I pulled with reflector. I had to change a number of unrecognizable method calls, like get_Item get_ID. For example,

panel.Attributes.set_Item("id", this.get_ID());

would become

panel.Attributes["id"] = this.ID;

Also, I did not call base.OnLoad() after the rendering code above. If you do, you may get errors in Sitecore. Looking at the logs you might get errors saying, “Multiple controls with the same ID ‘Field[SomeNumber]_all’ were found.”. Make sure not to render the field twice. 😉

Finally, notice that the RenderTrreeListWithoutUpDownArrows() submethod is missing the code that adds the up and down arrows – this was my objective:

ImageBuilder builder3 = new ImageBuilder
            {
                Src = "Applications/16x16/nav_up_blue.png",
                ID = this.ID + "_up",
                Width = 0x10,
                Height = 0x10,
                Margin = "2px",
                OnClick = Sitecore.Context.ClientPage.GetClientEvent(this.ID + ".Up")
            };
            ImageBuilder builder4 = new ImageBuilder
            {
                Src = "Applications/16x16/nav_down_blue.png",
                ID = this.ID + "_down",
                Width = 0x10,
                Height = 0x10,
                Margin = "2px",
                OnClick = Sitecore.Context.ClientPage.GetClientEvent(this.ID + ".Down")
            };
            control2 = new LiteralControl(builder3 + "<br/>" + builder4);
            panel.Controls.Add(control2);

You could also do this for left and right arrows, or another element of the field.

Finally, change the field type on your item’s template to your new custom field type, and publish.

The result is a custom TreeList field that simply does not have up/down arrows.

2015-12-01 09_05_42-Sitecore

I hope this helps you. I will write another blog about how I handled sorting my custom TreeList field’s selected section alphabetically, so look for that  to pop up soon. If you have any questions, feel free to comment.