I’m sure that many of you have worked with Experience Editor and have used Sitecore’s built-in functionality to spawn a “Field Editor” modal window in order to allow users to edit more complex fields (such as Multilist or Treelist fields). This is a simple and effective way for authors to have the ability to edit simple content in a WYSIWYG fashion, while also providing a way to edit the more advanced fields.

In our case, we wanted a little more control over the content authors editing, so we built a tighter editing user interface (without Sitecore’s Edit Frames) and invoked the Field Editor via JavaScript, using the “webedit:fieldeditor” data command in one of our HTML elements. See example below:

<span class="btn btn-outline btn-xs  js-edit" data-command=" webedit:fieldeditor(command={{11A701EF-5A33-4E7C-A35E-BEF29095D441}},fields=Name|Logo|Description,id={<ID of your Item>})"><i class="icon-pencil" aria-title="Edit"></i> Edit</span>

One of the main issues we faced upon successfully invoking the Field Editor was that we needed greater control over the Field Editor dialog, and wanted to take care of a few things when the dialog was presented. Here is the list of things we wanted to accomplish:

  1. If an item was in its final state, we needed to create a new version of the item.
  2. Lock the item before it was edited.
  3. Provide a more descriptive title in the dialog, based on what was being edited.
  4. Save the item as soon as the dialog was closed (instead of using the Experience Editor save button in the top-left corner).
  5. Increase the width and height of the dialog (it’s rather small on larger screens and doesn’t provide much editing space if you have a lot of fields to edit).

After some decompilation research, we finally accomplished this by creating our own Field Editor dialog, and inheriting from Sitecore’s WebEditCommand (as does the default Field Editor dialog). Our complete code for the dialog is below.

public class CustomFieldEditor : WebEditCommand
{
	public override void Execute(CommandContext context)
	{
		Assert.ArgumentNotNull((object)context, "context");
		if (context.Items.Length < 1)
			return;
		Context.ClientPage.Start((object)this, "StartFieldEditor", new ClientPipelineArgs(context.Parameters)
		{
			Parameters = {
	  {
		"uri",
		context.Items[0].Uri.ToString()
	  }
	}
		});
	}

	protected PageEditFieldEditorOptions GetOptions(ClientPipelineArgs args, NameValueCollection form)
	{
		List<FieldDescriptor> fieldDescriptorList = new List<FieldDescriptor>();
		Item obj1 = Database.GetItem(ItemUri.Parse(args.Parameters["uri"]));
		Assert.IsNotNull((object)obj1, "item");
		string parameter1 = args.Parameters["fields"];
		Assert.IsNotNullOrEmpty(parameter1, "Custom Field Editor command expects 'fields' parameter");
		string parameter2 = args.Parameters["command"];
		Assert.IsNotNullOrEmpty(parameter2, "Custom Field Editor command expects 'command' parameter");
		Item obj2 = Client.CoreDatabase.GetItem(parameter2);
		Assert.IsNotNull((object)obj2, "command item");

		// We need to check if the version is in its final state. If so, a new version should be created.
		Database masterDatabase = Sitecore.Data.Database.GetDatabase("master");
		IWorkflow workflow = masterDatabase.WorkflowProvider.GetWorkflow(obj1);
		WorkflowState state = workflow.GetState(obj1);
		Item itemToUse = obj1;

		// Make sure to lock the item before we start editing. Otherwise, an error will be thrown.
		// After the dialog is closed, the item should be unlocked also.
		if (!itemToUse.Locking.IsLocked())
		{
			itemToUse.Locking.Lock();
		}

		foreach (string fieldName in new ListString(parameter1))
		{
			if (itemToUse.Fields[fieldName] != null)
				fieldDescriptorList.Add(new FieldDescriptor(itemToUse, fieldName));
		}

		PageEditFieldEditorOptions fieldEditorOptions = new PageEditFieldEditorOptions(form, (IEnumerable<FieldDescriptor>)fieldDescriptorList);

		// Adding some additional attributes so we can set the dialog title, although it doesn't appear to work :-(.
		string dialogtitle = args.Parameters["dialogtitle"];
		dialogtitle = string.IsNullOrEmpty(dialogtitle) ? "Edit Fields" : dialogtitle;

		fieldEditorOptions.DialogTitle = dialogtitle;
		fieldEditorOptions.Title = obj2["Header"];
		fieldEditorOptions.Icon = obj2["Icon"];
		fieldEditorOptions.SaveItem = true; // This will make the dialog save the item when the 'OK' button is pressed.

		return fieldEditorOptions;
	}

	protected void StartFieldEditor(ClientPipelineArgs args)
	{
		HttpContext current = HttpContext.Current;
		if (current == null)
			return;
		Page handler = current.Handler as Page;
		if (handler == null)
			return;
		NameValueCollection form = handler.Request.Form;
		if (form == null)
			return;

		if (args.Aborted)
		{

		}

		if (!args.IsPostBack)
		{
			// Adding some additional attributes so we can set the dialog width and height.
			string width = args.Parameters["width"];
			string height = args.Parameters["height"];

			width = string.IsNullOrEmpty(width) ? "720" : width;
			height = string.IsNullOrEmpty(height) ? "520" : height;

			SheerResponse.ShowModalDialog(this.GetOptions(args, form).ToUrlString().ToString(), width, height, string.Empty, true);

			args.WaitForPostBack();
		}
		else
		{

			if (!args.HasResult)
				return;

			if(!string.IsNullOrEmpty(args.Parameters["returnURL"]))
			{
				SheerResponse.Eval("window.top.location.href='"+ args.Parameters["returnURL"] + "';");
			}
			else
			{

				// The line below is commented out because we are already saving as part of the dialog.
				// Otherwise, the state will be set on the page saying that it still needs to be saved.
				//PageEditFieldEditorOptions.Parse(args.Result).SetPageEditorFieldValues();

				// The next thing to do is unlock the item if it was locked, so that others can edit it if needed.
				Item obj1 = Database.GetItem(ItemUri.Parse(args.Parameters["uri"]));

				// We need to check if the version is in its final state. If so, a new version should be created.
				Database masterDatabase = Sitecore.Data.Database.GetDatabase("master");
				IWorkflow workflow = masterDatabase.WorkflowProvider.GetWorkflow(obj1);
				WorkflowState state = workflow.GetState(obj1);
				Item itemToUse = obj1;

				if (state.FinalState)
				{
					itemToUse = obj1.Versions.AddVersion();
					args.Parameters["uri"] = itemToUse.Uri.ToString();
				}

				// Unlock the previous version
				if (obj1 != null && obj1.Locking.IsLocked())
				{
					obj1.Locking.Unlock();
				}

				// Unlock our new version
				if (itemToUse != null && itemToUse.Locking.IsLocked())
				{
					itemToUse.Locking.Unlock();
				}

				Reload();
			}

		}
	}
}

After that, we had to register our new command by adding it to Sitecore’s configurations, so we just patched it in using our custom configuration file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <commands>
      <command name="webedit:customfieldeditor" type="Custom.FieldEditor.CustomFieldEditor, Custom"/>
    </commands>
  </sitecore>
</configuration>

And then, we modified our data command so that we would invoke our newly registered command instead of the default Field Editor command. If you notice, we now also provide the dialog title, width, and height as parameters that will be parsed by our custom Field Editor.

<span class="btn btn-outline btn-xs  js-edit" data-command=" webedit:customfieldeditor(command={{11A701EF-5A33-4E7C-A35E-BEF29095D441}},fields=Name|Logo|Description,id={<ID of your Item>},width=960,height=720,dialogtitle=Edit Custom Stuff)"><i class="icon-pencil" aria-title="Edit"></i> Edit</span>

Hope this helps in your endeavors for Field Editor customization.