Implementing Tabbed Select Rendering Dialog in Sitecore new

Looking for better ways to organize components while selecting them using Select Rendering dialog in Page Editor, I came across this module (unable to find it on Sitecore Marketplace) and decided to implement it. The approach seemed pretty straight forward till I actually started implementing it. Here are a few lessons learnt during the implementation.

The idea is to go from this:


To:



Since I couldn't find the module on Sitecore market place I decided to write this post describing the full implementation, including the code:


  • Find the following file and create a backup
<sitecore web root>\sitecore\shell\Applications\Dialogs\SelectRendering\SelectRendering.xml

  • Edit the file to include the following line below "Renderings" element
<Tabstrip ID="Tabs" Width="100%" Height="100%" Background="white" Padding="0px" style="position:absolute; width:100%;top:0px"/>
  • Edit the codebeside element as follows
<CodeBeside Type="<custom namespace>.CustomSelectRenderingForm, <containing library"/>
  • Create a new class for the CodeBeside and call it "CustomSelectRenderingForm.cs"
  • Add a reference to Sitecore.Client.dll 
  • Copy the following code to CustomSelectRenderingForm.cs. And do not forget to fill in the correct namespace. The magic happens in OnLoad method. Let me know what you have any comments.

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;
using Sitecore;
using Sitecore.Collections;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Data.Templates;
using Sitecore.Diagnostics;
using Sitecore.Globalization;
using Sitecore.Resources;
using Sitecore.Shell.Applications.Dialogs.ItemLister;
using Sitecore.Shell.Applications.Dialogs.SelectItem;
using Sitecore.Shell.Applications.Dialogs.SelectItemWithThumbnail;
using Sitecore.Shell.Applications.Dialogs.SelectRendering;
using Sitecore.Shell.Controls.Splitters;
using Sitecore.Shell.DeviceSimulation;
using Sitecore.Web;
using Sitecore.Web.UI.HtmlControls;
using Sitecore.Web.UI.Sheer;
using Sitecore.Web.UI.WebControls;

namespace <custom namespace>
{
    [UsedImplicitly]
    public class CustomSelectRenderingForm : SelectItemWithThumbnailForm
    {
        protected Tabstrip Tabs;

        /// <summary>
        /// Renderings preview container
        /// 
        /// </summary>
        protected Scrollbox Renderings;
        /// <summary>
        /// The vertical splitter
        /// 
        /// </summary>
        protected VSplitterXmlControl TreeSplitter;
        /// <summary>
        /// Treeview container
        /// 
        /// </summary>
        protected Scrollbox TreeviewContainer;

        /// <summary>
        /// Gets or sets a value indicating whether this instance is open properties checked.
        /// 
        /// </summary>
        /// 
        /// <value>
        /// <c>true</c> if this instance is open properties checked; otherwise, <c>false</c>.
        /// 
        /// </value>
        [UsedImplicitly]
        protected bool IsOpenPropertiesChecked
        {
            get
            {
                return (string)this.ServerProperties["IsChecked"] == "1";
            }
            set
            {
                this.ServerProperties["IsChecked"] = value ? (object)"1" : (object)"0";
            }
        }

        /// <summary>
        /// Gets or sets the open properties.
        /// 
        /// </summary>
        /// 
        /// <value>
        /// The open properties.
        /// </value>
        [UsedImplicitly]
        protected Checkbox OpenProperties { get; set; }

        /// <summary>
        /// Gets or sets the open properties border.
        /// 
        /// </summary>
        /// 
        /// <value>
        /// The open properties border.
        /// </value>
        [UsedImplicitly]
        protected Border OpenPropertiesBorder { get; set; }

        /// <summary>
        /// Gets or sets the name of the placeholder.
        /// 
        /// </summary>
        /// 
        /// <value>
        /// The name of the placeholder.
        /// </value>
        [UsedImplicitly]
        protected Edit PlaceholderName { get; set; }

        /// <summary>
        /// Gets or sets the placeholder name border.
        /// 
        /// </summary>
        /// 
        /// <value>
        /// The placeholder name border.
        /// </value>
        [UsedImplicitly]
        protected Border PlaceholderNameBorder { get; set; }

        /// <summary>
        /// Gets the filter.
        /// </summary>
        /// <param name="options">The options.</param>
        /// <returns>
        /// The filter.
        /// </returns>
        protected override string GetFilter(SelectItemOptions options)
        {
            Assert.ArgumentNotNull((object)options, "options");
            if (options.IncludeTemplatesForDisplay.Count == 0 && options.ExcludeTemplatesForDisplay.Count == 0)
                return string.Empty;
            string list1 = SelectItemForm.GetList(options.IncludeTemplatesForDisplay);
            string list2 = SelectItemForm.GetList(options.ExcludeTemplatesForDisplay);
            if (options.IncludeTemplatesForDisplay.Count > 0 && options.ExcludeTemplatesForDisplay.Count > 0)
                return string.Format("(contains('{0}', ',' + @@templateid + ',') or contains('{0}', ',' + @@templatekey + ',')) and  not (contains('{1}', ',' + @@templateid + ',') or contains('{1}', ',' + @@templatekey + ','))", (object)list1, (object)list2);
            if (options.IncludeTemplatesForDisplay.Count > 0)
                return string.Format("(contains('{0}', ',' + @@templateid + ',') or contains('{0}', ',' + @@templatekey + ','))", (object)list1);
            string str1 = "{B4A0FB13-9758-427C-A7EB-1A406C045192}";
            string str2 = "{B87CD5F0-4E72-429D-90A3-B285F1D038CA}";
            string str3 = "{75D27C2B-5F88-4CC8-B1DE-8412A1628408}";
            return string.Format("not (contains('{0}', ',' + @@templateid + ',') or contains('{0}', ',' + @@templatekey + ',') or @@name='Placeholder Settings' or @@name='Devices' or @@name='Layouts' or @@id='{1}' or @@id='{2}' or @@id='{3}' or @@id='{4}')", (object)list2, (object)str1, (object)DeviceSimulationUtil.SimulatorsFolderId, (object)str2, (object)str3);
        }

        /// <summary>
        /// Defines if item is rendering
        /// </summary>
        /// <param name="item">The item</param>
        /// <returns>
        /// <c>true</c> of item is a rendering item, and <c>false</c> otherwise
        /// </returns>
        protected bool IsItemRendering(Item item)
        {
            return ItemUtil.IsRenderingItem(item);
        }

        /// <summary>
        /// Defines if the item can be selected in the dialog
        /// </summary>
        /// <param name="item">The item to check</param>
        /// <returns>
        /// <c>true</c> if selectable; otherwise, <c>false</c>
        /// </returns>
        protected override bool IsItemSelectable(Item item)
        {
            Assert.ArgumentNotNull((object)item, "item");
            return this.IsItemRendering(item);
        }

        /// <summary>
        /// Handles click on a non-rendering preview
        /// </summary>
        /// <param name="item">The non-rendering item.</param>
        protected override void OnItemClick(Item item)
        {
            Assert.ArgumentNotNull((object)item, "item");
            ItemCollection children = this.DataContext.GetChildren(item);
            if (children != null && children.Count > 0)
            {
                this.Treeview.SetSelectedItem(item);
                this.Renderings.InnerHtml = this.RenderPreviews(children);
            }
            else
                SheerResponse.Alert("Please select a rendering item", new string[0]);
            this.SetOpenPropertiesState(item);
        }

        /// <summary>
        /// Raises the load event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        protected override void OnLoad(EventArgs e)
        {
            Assert.ArgumentNotNull((object)e, "e");
            base.OnLoad(e);
            if (Context.ClientPage.IsEvent)
                return;
            this.IsOpenPropertiesChecked = Registry.GetBool("/Current_User/SelectRendering/IsOpenPropertiesChecked");
            SelectRenderingOptions renderingOptions = SelectItemOptions.Parse<SelectRenderingOptions>();
            if (renderingOptions.ShowOpenProperties)
            {
                this.OpenPropertiesBorder.Visible = true;
                this.OpenProperties.Checked = this.IsOpenPropertiesChecked;
            }
            if (renderingOptions.ShowPlaceholderName)
            {
                this.PlaceholderNameBorder.Visible = true;
                this.PlaceholderName.Value = renderingOptions.PlaceholderName;
            }
            if (!renderingOptions.ShowTree)
            {
                this.TreeviewContainer.Class = string.Empty;
                this.TreeviewContainer.Visible = false;
                this.TreeSplitter.Visible = false;
                GridPanel gridPanel = this.TreeviewContainer.Parent as GridPanel;
                if (gridPanel != null)
                    gridPanel.SetExtensibleProperty((System.Web.UI.Control)this.TreeviewContainer, "class", "scDisplayNone");

                //old code
                    //this.Renderings.InnerHtml = this.RenderPreviews((IEnumerable<Item>)renderingOptions.Items);
                //end old code

                //new code
                this.Renderings.Visible = false;
                var gruppedSublayouts = renderingOptions.Items.GroupBy(i => i.Parent.Name);

                //Add new tab for each folder
                foreach (IGrouping<string, Item> gruppedSublayout in gruppedSublayouts)
                {
                    var newTab = new Tab();
                    newTab.Header = gruppedSublayout.Key;
                    var newScrollbox = new Scrollbox();
                    newScrollbox.Class = "scScrollbox scFixSize scFixSize4";
                    newScrollbox.Background = "white";
                    newScrollbox.Padding = "0px";
                    newScrollbox.Width = new Unit(100, UnitType.Percentage);
                    newScrollbox.Height = new Unit(100, UnitType.Percentage);
                    newScrollbox.InnerHtml = RenderPreviews(gruppedSublayout);
                    newTab.Controls.Add(newScrollbox);
                    Tabs.Controls.Add(newTab);
                }
            }
            else
            {
                var gridPanel = Renderings.Parent as GridPanel;
                if (gridPanel != null)
                {
                    gridPanel.SetExtensibleProperty(Tabs, "class",
                                                    "scDisplayNone");
                }
            }
            //end new code
            this.SetOpenPropertiesState(renderingOptions.SelectedItem);
        }

        /// <summary>
        /// Handles a click on the OK button.
        /// </summary>
        /// <param name="sender"/><param name="args"/>
        /// <remarks>
        /// When the user clicks OK, the dialog is closed by calling
        ///             the <see cref="M:Sitecore.Web.UI.Sheer.ClientResponse.CloseWindow">CloseWindow</see> method.
        /// </remarks>
        /// <contract><requires name="sender" condition="not null"/><requires name="args" condition="not null"/></contract>
        protected override void OnOK(object sender, EventArgs args)
        {
            Assert.ArgumentNotNull(sender, "sender");
            Assert.ArgumentNotNull((object)args, "args");
            if (!string.IsNullOrEmpty(this.SelectedItemId))
            {
                this.SetDialogResult(ShortID.Parse(this.SelectedItemId).ToID().ToString());
            }
            else
            {
                Item selectionItem = this.Treeview.GetSelectionItem();
                if (selectionItem != null && this.IsItemRendering(selectionItem))
                    this.SetDialogResult(selectionItem.ID.ToString());
                else
                    SheerResponse.Alert("Please select a rendering item", new string[0]);
            }
        }

        /// <summary>
        /// Handles click on rendering preview
        /// </summary>
        /// <param name="item">The rendering item.</param>
        protected override void OnSelectableItemClick(Item item)
        {
            Assert.ArgumentNotNull((object)item, "item");
            this.SetOpenPropertiesState(item);
        }

        /// <summary>
        /// Sets the dialog result.
        /// </summary>
        /// <param name="selectedRenderingId">The selected rendering id</param>
        protected void SetDialogResult(string selectedRenderingId)
        {
            Assert.ArgumentNotNull((object)selectedRenderingId, "selectedRenderingId");
            if (!this.OpenProperties.Disabled)
                Registry.SetBool("/Current_User/SelectRendering/IsOpenPropertiesChecked", this.IsOpenPropertiesChecked);
            SheerResponse.SetDialogValue(selectedRenderingId + "," + WebUtil.GetFormValue("PlaceholderName").Replace(",", "-c-") + "," + (this.OpenProperties.Checked ? "1" : "0"));
            SheerResponse.CloseWindow();
        }

        /// <summary>
        /// Sets the dialog result.
        /// </summary>
        /// <param name="selectedItem">The selected item.</param>
        protected override void SetDialogResult(Item selectedItem)
        {
            Assert.ArgumentNotNull((object)selectedItem, "selectedItem");
            this.SetDialogResult(selectedItem.ID.ToString());
        }

        /// <summary>
        /// Handles the Treeview click event.
        /// </summary>
        [UsedImplicitly]
        protected void Treeview_Click()
        {
            Item selectionItem = this.Treeview.GetSelectionItem();
            if (selectionItem != null)
            {
                this.SelectedItemId = string.Empty;
                ItemCollection children = this.DataContext.GetChildren(selectionItem);
                this.Renderings.InnerHtml = children == null || children.Count <= 0 ? this.RenderEmptyPreview(selectionItem) : this.RenderPreviews(children);
            }
            this.SetOpenPropertiesState(selectionItem);
        }

        /// <summary>
        /// Renders empty preview
        /// </summary>
        /// <param name="item">The item</param>
        /// <returns>
        /// Previews markup
        /// </returns>
        private string RenderEmptyPreview(Item item)
        {
            HtmlTextWriter output = new HtmlTextWriter((TextWriter)new StringWriter());
            output.Write("<table class='scEmptyPreview'>");
            output.Write("<tbody>");
            output.Write("<tr>");
            output.Write("<td>");
            if (item == null)
                output.Write(Translate.Text("None available."));
            else if (this.IsItemRendering(item))
            {
                output.Write("<div class='scImageContainer'>");
                output.Write("<span style='height:100%; width:1px; display:inline-block;'></span>");
                string str = item.Appearance.Icon;
                int num1 = 48;
                int num2 = 48;
                if (!string.IsNullOrEmpty(item.Appearance.Thumbnail) && item.Appearance.Thumbnail != Settings.DefaultThumbnail)
                {
                    string thumbnailSrc = UIUtil.GetThumbnailSrc(item, 128, 128);
                    if (!string.IsNullOrEmpty(thumbnailSrc))
                    {
                        str = thumbnailSrc;
                        num1 = 128;
                        num2 = 128;
                    }
                }
                new ImageBuilder()
                {
                    Align = "absmiddle",
                    Src = str,
                    Width = num2,
                    Height = num1
                }.Render(output);
                output.Write("</div>");
                output.Write("<span class='scDisplayName'>");
                output.Write(item.DisplayName);
                output.Write("</span>");
            }
            else
                output.Write(Translate.Text("Please select a rendering item"));
            output.Write("</td>");
            output.Write("</tr>");
            output.Write("</tbody>");
            output.Write("</table>");
            return output.InnerWriter.ToString();
        }

        /// <summary>
        /// Renders previews
        /// </summary>
        /// <param name="items">The items</param>
        /// <returns>
        /// Previews markup
        /// </returns>
        private string RenderPreviews(IEnumerable<Item> items)
        {
            Assert.ArgumentNotNull((object)items, "items");
            HtmlTextWriter output = new HtmlTextWriter((TextWriter)new StringWriter());
            bool flag = false;
            foreach (Item obj in items)
            {
                this.RenderItemPreview(obj, output);
                flag = true;
            }
            if (!flag)
                return this.RenderEmptyPreview((Item)null);
            else
                return output.InnerWriter.ToString();
        }

        /// <summary>
        /// Renders previews
        /// </summary>
        /// <param name="items">The items</param>
        /// <returns>
        /// Previews markup
        /// </returns>
        private string RenderPreviews(ItemCollection items)
        {
            Assert.ArgumentNotNull((object)items, "items");
            HtmlTextWriter output = new HtmlTextWriter((TextWriter)new StringWriter());
            foreach (Item obj in (CollectionBase)items)
                this.RenderItemPreview(obj, output);
            return output.InnerWriter.ToString();
        }

        /// <summary>
        /// Renders the help.
        /// </summary>
        /// <param name="item">The item.</param>
        private void SetOpenPropertiesState(Item item)
        {
            if (item == null || !this.IsItemRendering(item))
            {
                this.OpenProperties.Disabled = true;
                this.OpenProperties.Checked = false;
            }
            else
            {
                switch (item["Open Properties After Add"])
                {
                    case "-":
                    case "":
                        this.OpenProperties.Disabled = false;
                        this.OpenProperties.Checked = this.IsOpenPropertiesChecked;
                        break;
                    case "0":
                        if (!this.OpenProperties.Disabled)
                            this.IsOpenPropertiesChecked = this.OpenProperties.Checked;
                        this.OpenProperties.Disabled = true;
                        this.OpenProperties.Checked = false;
                        break;
                    case "1":
                        if (!this.OpenProperties.Disabled)
                            this.IsOpenPropertiesChecked = this.OpenProperties.Checked;
                        this.OpenProperties.Disabled = true;
                        this.OpenProperties.Checked = true;
                        break;
                }
            }
        }
    }
}

Comments

  1. This is an excellent post and solution. Thanks!

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Hello Varun,

    Thanks for this information. We are looking to implement exact scenario.

    But my que. is , do we still need to implement code for this ? or Is there any configuration available for Sitecore 8.1 and later ?

    Looking forward to hear from you

    Thanks

    ReplyDelete
    Replies
    1. If you are using Sitecore's SXA, you have a much better drag-drop toolbox for components. Otherwise, this folder based grouping is not OOTB functionality in any version of Sitecore so you will need to implement this manually.

      Delete
  4. Just re using it one more time...thanks

    ReplyDelete
  5. Varun,
    We had this fix implements a long time ago on an older version of sitecore for us. I am trying to put this back into our 8.1 version of Sitecore and your instructions are great. One question though. How do you get the items to display alphabetically? They were alphabetically on the previous version.

    Thanks!

    ReplyDelete
    Replies
    1. You should be able to sort the tabs by applying the sort by clause on "gruppedSublayouts" (and yes it's a typo)

      //Add new tab for each folder
      foreach (IGrouping gruppedSublayout in gruppedSublayouts)

      Sorry for the late response.

      Delete

Post a Comment

Popular posts from this blog

RESOLVED: Solr Exceptions - Document contains at least one immense term in field

First look at Sitecore XM Cloud: Part 4 - Creating a new Site

Is Rendered Item Valid XHtml Document Could not find schema information warnings during publish item Sitecore 7.2