Custom field editor for all of the template's fields

17 April 2016 at 00:00 by Stefan Sluijter - Post a comment

A couple of weeks ago I walked into an open meeting room where a Sitecore training provided by Ruud was on a break. I had a quick peek of what they would be discussing after the break and had a moment of clarity. Can we have a field editor available on every component, without having to define all fields every single time?

While researching how to write the code I came across the blogpost of Dheer Rajpoot
I want to give credit to Dheer Rajpoot for his great blogpost on how to write a custom field editor, it made my life a lot easier! It has more detailed information on how to write a custom field editor and therefor I will keep my article on a basic level. You can find the blog post here: Customizing Page Editor Ribbon in Sitecore : Part 3

Creating a field editor to use within an edit frame is fairly easy in Sitecore. You specify the Title, Icon and most importantly the fields of the template you're editing. We would like to skip this step and make the button available for every component, without having to define a edit frame. Sitecore already offers functionality like this. When you clicked on a component in the Experience Editor and go to "More" you find a button to "edit the related item". The downside of this button is that it will load the Content Editor halfway on your screen, it also takes some time to load this view. So in most cases you switch to a different tab in the browser to locate the item in the Content Editor.

We want to inherit from the FieldEditorCommand class and make use of the standard Field Editor Frame. In order to do this we have to override three of it's methods.

  • QueryState - This method is responsible of showing/hiding the button
  • Execute - This method is responsible of retrieving all the fields of the template, Looping through the fields of the template and passing them along in the arguments. We filter out the standard Sitecore fields starting with "__" and concat the strings. Ending every field with a pipe ("|") except for the last field. Finally we start the Field Editor frame with: Context.ClientPage.Start(this, "StartFieldEditor", args);
  • GetOptions -All what's left to do is making sure the frame has the fields and sections on it. We do this by creating FieldDescription objects and adding them to the frame. Also pay attention the the PageEditFieldEditorOptions object. By setting the PreserveSections and ShowSections to true we have a Field Editor frame divided in sections.

FieldEditorAll.cs


namespace Sitecore.Foundation.SitecoreExtensions.FieldEditor
{
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Linq;
    using Sitecore.Data;
    using Sitecore.Data.Items;
    using Sitecore.Diagnostics;
    using Sitecore.Shell.Applications.WebEdit;
    using Sitecore.Shell.Framework.Commands;
    using Sitecore.Web.UI.Sheer;

    // The field editor all.
    public class FieldEditorAll : Shell.Applications.WebEdit.Commands.FieldEditorCommand
    {
        // The name of the parameter in ClientPipelineArgs containing 
        // Sitecore item identification information.
        private const string URI = "uri";

        public override void Execute(CommandContext context)
        {
            Assert.ArgumentNotNull(context, "context");
            if (context.Items.Length >= 1)
            {
                ClientPipelineArgs args = new ClientPipelineArgs(context.Parameters);
                args.Parameters.Add(URI, context.Items[0].Uri.ToString());

                var allFields = string.Empty;

                // Loop through all fields on the template
                // Filter the standard Sitecore fields available on every template, starting with "__"
                foreach (var field in context.Items[0].Template.Fields.Where(x => !x.Name.StartsWith("__")))
                {
                    // name|name2|...
                    allFields = string.Concat(allFields, field.Name, "|");
                }

                // Remove the last '|' of the allFields string
                if (allFields.EndsWith("|"))
                {
                    allFields = allFields.TrimEnd(allFields[allFields.Length - 1]);
                }

                args.Parameters["fields"] = allFields;
                Context.ClientPage.Start(this, "StartFieldEditor", args);
            }
        }

        // Gets the configured Field Editor options.
        // Options determine both the look of Field Editor and the actual fields available for editing.
        // The pipeline arguments. Current item URI is accessible as 'uri' parameter
        protected override PageEditFieldEditorOptions GetOptions(ClientPipelineArgs args, NameValueCollection form)
        {
            Assert.IsNotNull(args, "args");
            Assert.IsNotNull(form, "form");
            Assert.IsNotNullOrEmpty(args.Parameters[URI], URI);
            ItemUri uri = ItemUri.Parse(args.Parameters[URI]);
            Assert.IsNotNull(uri, URI);

            Assert.IsNotNullOrEmpty(args.Parameters["fields"], "Field Editor command expects 'fields' parameter");
            string fieldsParameter = args.Parameters["fields"];

            Item item = Database.GetItem(uri);
            Assert.IsNotNull(item, "item");

            List fields = new List();
            foreach (string fieldName in fieldsParameter.Split('|'))
            {
                if (item.Fields[fieldName] != null)
                {
                    fields.Add(new FieldDescriptor(item, fieldName));
                }
            }

            // Field editor options.
            PageEditFieldEditorOptions options = new PageEditFieldEditorOptions(form, fields)
            {
                PreserveSections = true, 
                ShowSections = true, 
            };

            return options;
        }

        // Determine if the command button should be displayed or hidden.
        public override CommandState QueryState(CommandContext context)
        {
            return Context.PageMode.IsExperienceEditor ? CommandState.Enabled : CommandState.Hidden;
        }
    }
}

Now we've created the class that is going to do all the work for us we have to make sure that Sitecore knows what to do. We'll start by adding a few lines to our config.


<sitecore>
   <commands>
       <command name="webeditcustom:fieldeditorall" type="Sitecore.Foundation.SitecoreExtensions.FieldEditor.FieldEditorAll, Sitecore.Foundation.SitecoreExtensions"></command>
   </commands>
</sitecore>

The last modification we have to do is creating an item in the Sitecore's core database. Navigate to /sitecore/content/Applications/WebEdit/Default Rendering Buttons and create a new item based on the /sitecore/templates/System/WebEdit/WebEdit Button template. Make sure that the Click field matches the name defined in the config. Side note: All items in this folder are automatically put on a component, you can modify the "Type" to "Sticky" so it's always visible in the Experience Editor.


The results of the steps above is a custom Field editor frame that is available on every component automatically populated with all of the template's fields.

I want to mention that there is room for improvement for this solution. Right now there is no code to honour the sort order of a field. This will result in the fact that fields within the same section can differ from the template’s sort order.

Thanks for reading my article. The code is tested on Sitecore 8.1 revision 151207. The code and configs are available for download here: Download FieldEditorAll.zip. I've commited this change to the Habitat project and my pull request is currently pending. 

Latest