How to create an Experience Editor default rendering button

I recently had a client request to speed up the process of adding a component to multiple language version of an item. Rather than going to each language version and adding a component to a placeholder, I created a default rendering button to set up the component on one language version of the item and copy it to all the other language versions.

The first thing I did was navigate to /sitecore/content/Applications/WebEdit/Default Rendering Buttons in the core database and added an item based off the /sitecore/templates/System/WebEdit/WebEdit Button template called Copy to other language versions.

In order to get the click I had to do two more steps. First, I added a command to call into my custom code.

[code language="xml"]<configuration xmlns:patch=""> <sitecore> <commands> <command name="webedit:CopyToOtherLanguageVersions" type="Hi.Shared.BrentsCoolCopyRendLangVersModule.CopyToOtherLanguageVersions, Hi.Shared.BrentsCoolCopyRendLangVersModule"/> </commands> </sitecore></configuration>[/code]

Next, I changed the /sitecore/shell/Applications/Page Modes/ChromeTypes/RenderingChromeType.js and added a new runcommand so I can pass get the renderings unique id in my code. I first changed the javascript here:

[code language="JavaScript"]handleMessage: function(message, params, sender) { switch (message) { case "chrome:rendering:sort": this.sort(); break; case "chrome:rendering:properties": this.editProperties(); break; case "chrome:rendering:propertiescompleted": this.editPropertiesCompleted(); break; case "chrome:rendering:delete": this.deleteControl(); break; case "chrome:rendering:morph": this.morph(params); break; case "chrome:rendering:morphcompleted": this.morphCompleted(, params.openProperties); break; case "chrome:rendering:personalize": if (Sitecore.PageModes.Personalization) { this.personalize(params.command); } break; case "chrome:rendering:personalizationcompleted": if (Sitecore.PageModes.Personalization) { this.presonalizationCompleted(params, sender); } break; case "chrome:personalization:conditionchange": if (Sitecore.PageModes.Personalization) { this.changeCondition(, sender); } break; case "chrome:rendering:editvariations": if (Sitecore.PageModes.Testing) { this.editVariations(params.command, sender); } break; case "chrome:rendering:editvariationscompleted": if (Sitecore.PageModes.Testing) { this.editVariationsCompleted(params, sender); } break; case "chrome:testing:variationchange": if (Sitecore.PageModes.Testing) { this.changeVariation(, sender); } break; case "chrome:rendering:runcommand": this.runcommand(params.command, sender); break; } },[/code]

Lines 51-53 are the added code and next I added the runcommand function to RenderingChromeType.js:

[code language="JavaScript"]runcommand: function(commandName, sender) { var ribbon = Sitecore.PageModes.PageEditor.ribbon(); Sitecore.PageModes.PageEditor.layoutDefinitionControl().value = Sitecore.PageModes.PageEditor.layout().val(); var controlId = this.controlId(); if (sender) { controlId = sender.controlId(); }

Sitecore.PageModes.PageEditor.postRequest(commandName + "(uniqueId=" + this.uniqueId() + ",controlId=" + controlId + ")"); },[/code]

Next, I opened up dotPeek to look at how webedit:personalize at Sitecore.Shell.Applications.WebEdit.Commands.Personalize, Sitecore.ExperienceEditor was written so I could follow that pattern. I came up with this class.

[code language="csharp"]using Sitecore;using Sitecore.Data;using Sitecore.Data.Items;using Sitecore.Diagnostics;using Sitecore.Globalization;using Sitecore.Layouts;using Sitecore.Shell.Applications.WebEdit.Commands;using Sitecore.Shell.Framework.Commands;using Sitecore.Web;using Sitecore.Web.UI.Sheer;using System;using System.Collections.Generic;using System.Collections.Specialized;using System.Linq;using System.Text;using System.Threading.Tasks;

namespace Hi.Shared.BrentsCoolCopyRendLangVersModule{ [Serializable] public class CopyToOtherLanguageVersions : WebEditCommand { public CopyToOtherLanguageVersions() { }

protected virtual string ConvertToXml(string layout) { Assert.ArgumentNotNull(layout, "layout"); return WebEditUtil.ConvertJSONLayoutToXML(layout); }

public override void Execute(CommandContext context) { Assert.ArgumentNotNull(context, "context"); ItemUri itemUri = ItemUri.ParseQueryString(); if (itemUri != null) { Item item = Database.GetItem(itemUri); if (item != null && !WebEditUtil.CanDesignItem(item)) { SheerResponse.Alert("The action cannot be executed because of security restrictions.", new string[0]); return; } }

string formValue = WebUtil.GetFormValue("scLayout"); Assert.IsNotNullOrEmpty(formValue, "Layout Definition"); string str = ShortID.Decode(WebUtil.GetFormValue("scDeviceID")); Assert.IsNotNullOrEmpty(str, "device ID"); string str1 = ShortID.Decode(context.Parameters["uniqueId"]); Assert.IsNotNullOrEmpty(str1, "Unique ID"); string xml = this.ConvertToXml(formValue); Assert.IsNotNullOrEmpty(xml, "convertedLayoutDefition");

NameValueCollection nameValueCollection = new NameValueCollection(); nameValueCollection["deviceId"] = str; nameValueCollection["uniqueId"] = str1; nameValueCollection["contextItemUri"] = itemUri != null ? itemUri.ToString() : string.Empty;

Context.ClientPage.Start(this, "Run", nameValueCollection); }

[UsedImplicitly] protected void Run(ClientPipelineArgs args) { string deviceId; string uniqueId; string contextItemUri;

Assert.ArgumentNotNull(args, "args"); try { deviceId = args.Parameters["deviceId"]; Assert.IsNotNull(deviceId, "deviceId"); uniqueId = args.Parameters["uniqueId"]; Assert.IsNotNull(uniqueId, "uniqueId"); contextItemUri = args.Parameters["contextItemUri"]; Assert.IsNotNull(contextItemUri, "contextItemUri"); } catch { throw; }

if (args.IsPostBack) { if (args.Result == "yes") { if (!RunCopy(deviceId, uniqueId, contextItemUri)) { SheerResponse.Alert("Cannont copy this rendering. Please save first."); } } } else { try { SheerResponse.Confirm("Are you sure you want to copy renderings to the most recent language versions?"); args.WaitForPostBack(); } catch { throw; } } }

public bool RunCopy(string deviceId, string uniqueId, string contextItemUri) { Item i = Database.GetItem(new ItemUri(contextItemUri)); RenderingDefinition rd = i.GetRenderingDefinitionByUniqueId(uniqueId, deviceId); if (i != null && rd != null) { List<Language> langList = i.Languages.Where(e => !e.CultureInfo.Name.Equals(i.Language.CultureInfo.Name)).ToList(); foreach (Language l in langList) { Item li = i.Database.GetItem(i.ID, l); if (li != null && li.Versions.Count > 0) { li.CopyRenderingReference(rd, deviceId); } } return true; } return false; } }}[/code]

The difference between my code and Sitecore.Shell.Applications.WebEdit.Commands.Personalize is that I have removed the session code to keep track of the dialog. The RunCopy method is what does the bulk of the work to copy rendering references to the other language versions. This is done with a help of a few extension methods located here.

When it's all done, you will have a nice rendering button to help content authors.