Adding a button to the Rich Text Editor in Sitecore 6.4

February 17, 2012

Since there were some significant changes in Sitecore's RTE when they upgraded to Telerik's new libraries from Sitecore 6.3 to 6.4. I'd written previously about adding buttons to the RTE before and was lookin for a good reason to write a supplemental article. Thankfully the new editor actually simplifies a bit of what you'll need to do from the earlier version of Sitecore.

I was recently asked by a content editor how to insert a YouTube video on a page in Sitecore and after thinking about the best approach to respond with my options were to tell the user to change to HTML mode in the Rich Text Editor (RTE) or for me to install one of the Shared Source Modules. I looked over YouTube Integration and as thoughtfully designed as it was I thought it would require too much effort for something I thought could be much simpler. I decided to extend the RTE with an additional button that opened a dialog window you could paste the IFrame embed code into.

There were a few reasons I chose this route. First I wanted a way for editors to paste html code into the window without having to switch to HTML mode. Second I knew that just pasting in the IFrame html would support HTML5 and mobile browsers. This was important since we recently upgraded our Brightcove accounts to support HTML5 for mobile devices. Finally I just like the idea of content editors being able to add media inline with content, which is most often how editors ask for it.

It's important to understand a little about the lifecycle of the RTE and the buttons before you start so I'll give a quick overview and then fill in the rest of the specifics throughout the article. When you open RTE by clicking the "Show Editor" button, there are a number of css and js files and button items that are loaded. The buttons themselves are defined in the Core database, separated into profile folders so that you can setup different groups of controls for different fields. You'll find them under "/sitecore/system/Settings/Html Editor Profiles/". A button can define a Javascript click event in one of it's fields. This event will need to live in the RichText Editor.js file. It lives under "/sitecore/shell/controls/rich text editor". This click event handler method will make a call to a SheerUI control you define and can pass data through the querystring and the callback Javascript method. This SheerUI control, like most .NET pages has a class file backing it. You can get the querystring data and manipulate it and then pass it back to the javascript file that you include on the SheerUI control. This method will build you're return object and pass it back to the callback handler.

If you're still not too clear on the details don't worry I'll be walking you through the rest and I'll try to clear up the grey areas.

I started by adding a button to the rich text editor. Jump into Core to the location you want it to appear. Mine was: "/sitecore/system/Settings/Html Editor Profiles/Rich Text Default/Toolbar Links". Right-click and add an "__Html Editor Button".

insert button

I named it "Insert Iframe".

insert iframe

Under the "Configure" section at the top I changed the icon.

iframe button

Type in the name of the javascript click event. In my case it was "InsertIframe".

click event

Next we need to create the files that support the click event. You'll want to add a folder at "/sitecore/shell/controls/rich text editor". The folder I created was "InsertIframe" and in it I added two files: insertiframe.xml and insertiframe.js.

sitecore insert iframe

The InsertIframe.xml is the SheerUI control that controls the design of the popup window when you click the button. If you haven't heard of Sitecore's SheerUI already, Sitecore has a information and tutorials here. It will help you understand the basics. This file includes a Javascript file and the backing class reference. The Javascript include here DOES need to have a "." between the start and end tag to work. Why I don't know but trust me otherwise you'll lose precious daylight trying to figure out why it never loads. Here's the code for it:

<?xml version="1.0" encoding="utf-8" ?> 
<control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense">
<RichText.InsertIframe>
<FormDialog Icon="Network/32x32/link.png" Header="Insert an IFrame" Text="Insert the IFrame code to Insert." OKButton="Insert">
<script Type="text/javascript" Language="javascript" Src="InsertIframe/InsertIframe.js">.</script>
<CodeBeside Type="YourLibrary.XmlControls.InsertIframe, YourLibrary"/>
<GridPanel Width="100%" Height="100%" Style="table-layout:fixed">
<Memo ID="memCode" Style="height:100%;width:100%;border-top:1px solid #919b9c" ></Memo>
</GridPanel>
</FormDialog>
</RichText.InsertIframe>
</control>

The InsertIframe.js file contains the return handler and cancel methods from the C# code. It's included in the InsertIframe.xml file. The "scClose" method builds the return object and sends it to the callback handler. Here's the code for that:

function scClose(text) {
    var returnValue = {
        Text: text
    };

    getRadWindow().close(returnValue);
}

function GetDialogArguments() {
    return getRadWindow().ClientParameters;
}

function getRadWindow() {
    if (window.radWindow) {
        return window.radWindow;
    }

    if (window.frameElement && window.frameElement.radWindow) {
        return window.frameElement.radWindow;
    }

    return null;
}

var isRadWindow = true;

var radWindow = getRadWindow();

if (radWindow) {
    if (window.dialogArguments) {
        radWindow.Window = window;
    }
}

function scCancel() {

    getRadWindow().close();
}

function scCloseWebEdit(embedTag) {
    window.returnValue = embedTag;
    window.close();
}

if (window.focus && Prototype.Browser.Gecko) {
    window.focus();
}

Then there's the class you'll need in your library. This handles any manipulation you'll want to do before pasting it back into the RTE. This is referenced on the InsertIframe.xml. OnLoad it sets the text with the querystring value. It also calls the Javascript methods at the end of the "OnOk" and "OnCancel" methods. I've labeled it InsertIframe.cs and you will need to build this class into your library. Here's the code:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sitecore.Web.UI.Pages;
using Sitecore.Diagnostics;
using Sitecore;
using Sitecore.Web;
using Sitecore.Web.UI.Sheer;

namespace YourLibrary.XmlControls
{
	public class InsertIframe : DialogForm
	{
		// Fields
		protected Sitecore.Web.UI.HtmlControls.Memo memCode;

		//setup page
		protected override void OnLoad(EventArgs e) {
			Assert.ArgumentNotNull(e, "e");
			base.OnLoad(e);
			if (!Context.ClientPage.IsEvent) {
				this.Mode = WebUtil.GetQueryString("mo");
				string text = WebUtil.GetQueryString("selectedText");

				//set textbox text to selected text
				memCode.Value = text;
			}
		}

		//pressed ok
		protected override void OnOK(object sender, EventArgs args) {
			Assert.ArgumentNotNull(sender, "sender");
			Assert.ArgumentNotNull(args, "args");

			string code = memCode.Value;

			//encode it and send it back to the rich text editor
			if (this.Mode == "webedit") {
				SheerResponse.SetDialogValue(StringUtil.EscapeJavascriptString(code));
				base.OnOK(sender, args);
			} else {
				SheerResponse.Eval("scClose(" + StringUtil.EscapeJavascriptString(code) + ")");
			}
		}

		//cancelled
		protected override void OnCancel(object sender, EventArgs args) {
			Assert.ArgumentNotNull(sender, "sender");
			Assert.ArgumentNotNull(args, "args");
			if (this.Mode == "webedit") {
				base.OnCancel(sender, args);
			} else {
				SheerResponse.Eval("scCancel()");
			}
		}

		// Properties
		protected string Mode {
			get {
				string str = StringUtil.GetString(base.ServerProperties["Mode"]);
				if (!string.IsNullOrEmpty(str)) {
					return str;
				}
				return "shell";
			}
			set {
				Assert.ArgumentNotNull(value, "value");
				base.ServerProperties["Mode"] = value;
			}
		}
	}
}

Now you need to define the specific Javascript click event you referenced on the button you created in the Core database. This code needs to be added to the RichText Commands.js file. This file is loaded into the context when you open the RTE. Then when you click on the button it calls the method defined below which loads your SheerUI control. You'll notice that the SheerUI is called through a "showExternalDialog" method with querystring attached and the height, width and callback (scInsertFrame) defined in following parameters. I'm also passing in the selected text so that once you paste in an IFrame you can reselect it later and edit it. The callback method is also defined below this method and just pastes the returning value back into the RTE.

 RadEditorCommandList["InsertIframe"] = function (commandName, editor, args) {

    var html = editor.getSelectionHtml();

    scEditor = editor;

    editor.showExternalDialog(
		"/sitecore/shell/default.aspx?xmlcontrol=RichText.InsertIframe&la=" + scLanguage + "&selectedText=" + escape(html),
		null, //argument
		500, //width
		200, //height
		scInsertIframe, //callback
		null, // callback args
		"Insert IFrame",
		true, //modal
		Telerik.Web.UI.WindowBehaviors.Close, // behaviors
		false, //showStatusBar
		false //showTitleBar
	);
};

function scInsertIframe(sender, returnValue) {
    if (!returnValue) {
        return;
    }

    scEditor.pasteHtml(unescape(returnValue.Text), "DocumentManager");
}

Alright that's all the coding you'll need to do. Now you'll need to build the class, logout, clear your browser cache and log back in and when you open the RTE and click the button you'll see this.

 

From here you can paste in the IFrame code and change the dimensions to match and click "Insert".

 

Remember that the click event method also supports editing existing IFrame content, and well any html content, so you can select an existing item and, for example, change the dimensions.

 

So for closing remarks, this is a basic tool that really just pastes and loads HTML into a popup so you can edit it. You could use it as a tool to inspect small amounts of HTML like links, titles, images etc. instead of switching to HTML mode. Hopefully you'll at least learn a bit more about how the RTE works so that you can learn to harness it yourself and craft intuitive tools to ease the burden of content management for your editors.