Project Lifecycle

Adding a button to Rich Text Editor in Sitecore

February 5, 2011

Sitecore is an incredibly good platform that provides a lot of functionality out of the box, but one of the great things about Sitecore is that if there is something custom that you'd like to add in, there's always a way to do it. I'm going to walk through the process of adding a custom piece of functionality to Sitecore's Rich Text Editor. There are a lot of reasons you would want to do this but for the sake of this example we'll build a simple text insertion demo that you can expand upon.

To start you'll need to create a button in the wysiwyg toolbar. You will need to switch over to the core database and browse to /sitecore/system/Settings/Html Editor Profiles. It's up to you where you end up creating the button but for now I'm just going to go into /sitecore/system/Settings/Html Editor Profiles/Rich Text Full/Toolbar 1 and create an "__Html Editor Button" and name it InsertText. Once you've got your button you're going to want to go to the "configure" tab at the top and change the icon to something recognizeable. Make sure that when you've selected the image it's 16x16 otherwise it won't look right in the wysiwyg. Next you'll want to add in a value for the "Click" field. This value will be used in javascript to call your function we're going to stay consistent and give it the value "InsertText". Now go back into the master database and open a Rich Text Editor field with the correct profile to view the button and ensure it's displaying.

Next thing to do is create the javascript method that will launch your modal window. In Visual Studio or whatever editor you're using open /sitecore/shell/controls/Rich Text Editor/RichText Commands.js. This file is included when the wysiwyg is launched and will need to contain your code or a reference to it to support your button. Add in the following code:

RadEditorCommandList["InsertText"] = function(commandName, editor, tool) {
	var args = editor.GetDialogParameters(commandName);
	var html = editor.GetSelectionHtml();
 	scEditor = editor;

	editor.ShowDialog(
		"/sitecore/shell/default.aspx?xmlcontrol=RichText.InsertText&la=" + scLanguage + "&selectedText=" + escape(html),
		null, //argument
		600, //width
		500, //height
		scInsertText,
		null,
		"Insert Text");
};
function scInsertText(returnValue) {
	if (returnValue) {
		scEditor.PasteHtml(returnValue.text);
	}
}

The first thing you'll notice is the "RadEditorCommandList["InsertText"] = function". This is where you're defining the function that is referenced in the "Click" field of your button in Sitecore. You'll also want to know that you have an editor object that will be giving you access to the selected text from the wysiwyg field. The most important thing you'll want to do in this function aside from gathering any data from the selected text (such as querystring params from a url) is to call your dialog window. In this case our dialog window will be leveraging Sitecore's sheer UI. If you haven't heard of this already, Sitecore has a tutorial here. It will help you understand the basics. The "editor.ShowDialog" function calls the /sitecore/shell/default.aspx file with the xmlcontrol RichText.InsertText and selected text from the wysiwyg as params. You also have the ability to set the height, width and callback method. Before we get to the xml control let's go over the callback method first. When you're leaving you're xml control you'll be calling another javascript method that will define your returnValue object. The returnValue object is passed to your callback method and you can then process the values. In this case I'm just interested in pasting the returned text back into the wysiwyg editor. In some cases you'll find that you're returning a value that is intended for the html view and needs to be converted to design view. You can use this callback function to handle converting your text before pasting it. A function to handle the conversion would look like this:

function scConvertText(html) {
	try {
		var win = window.dialogArguments[0];
	}catch (e) {win = window;}

	var form = win.scForm;
	if (form != null) {
		var request = new win.scRequest();
		request.form = "html=" + encodeURIComponent(html);
		request.build("", "", "", 'Convert(\"' + scMode + '\")', true);
		var url = "/sitecore/shell/Applications/Content Manager/Execute.aspx?cmd=Convert&mode=" + scMode;
		request.url = url;
		request.send();
		var r = "";
		if (request.httpRequest != null && request.httpRequest.status == "200") {
			r = request.httpRequest.responseText;
		}
		return r;
	}
 }

This is making a call to another utility that will do the conversion for you and return the text.

Now we need to create your dialog window. In the /sitecore/shell/controls/Rich Text Editor/ folder copy the existing InsertLink folder and name it InsertText. Inside there you'll see two files: InsertLink.js and InsertLink.xml. You'll want to rename both of them to InsertText.js and InsertText.xml respectively. Open the InsertText.js file and modify the "scClose" function to look like this:

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

This is the function that you're xml control will call that defines you're return value which is passed to your callback function. Here we're just setting the text property on the returnValue. Now save and close that and open the InsertText.xml file. This is where we're using Sitecore's sheer UI. This xml file will be the designed layout of your dialog window. It will also reference a class file from a binary library that will be used to control the events. You can overwrite the existing content with this:

<control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense">
	<richtext.inserttext>
		<formdialog icon="Network/32x32/link.png" header="Insert Text" text="Insert the text you want." okbutton="Insert">

			<script type="text/javascript" language="javascript" src="/sitecore/shell/RadControls/Editor/Scripts/7_2_0/RadWindow.js">.</script>
			<script type="text/javascript" language="javascript" src="InsertCode/InsertText.js">.</script>

			<codebeside type="Library.ClassName,BinaryFileName"></codebeside>
			<gridpanel width="100%" height="100%" style="table-layout:fixed">      
				<memo id="memoText" style="height:100%;width:100%;border-top:1px solid #919b9c"></memo>
			</gridpanel>
		</formdialog>
	</richtext.inserttext>
</control>

You'll notice the "RichText.InsertText" tag wrapping most of the content. This will line up with the name of the xmlControl called in the javascript dialog function. Here we're just defining the class that handles the events, including some functional javascript, a submit button and a text (Memo) field to capture the text you want to enter.

Now that the UI of the dialog is setup you're going to have to create the class that is referenced in the UI. The class will look like this:

public class InsertTextForm : DialogForm {
 
	// Front End Object Reference
  	protected Sitecore.Web.UI.HtmlControls.Memo memoText;
  
	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 from the wysiwyg
			memoText.Value = text;
		}
	}

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

		//send it back
		if (this.Mode == "webedit") {
			SheerResponse.SetDialogValue(StringUtil.EscapeJavascriptString(memoText));
			base.OnOK(sender, args);
		} else {
			//call javascript method in InsertText.js    
			SheerResponse.Eval("scClose(" + StringUtil.EscapeJavascriptString(memoText) + ")");
		}
	}

	// Local property accessor
	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;
		}
	}
 }

All right, now you're going to want to compile your class and restart your browser so that any javascript used isn't cached. Then open up you're wysiwyg editor and click the InsertText button. You'll see your dialog window popup and you can enter some text which, when you click the "Insert" button will insert the text back into the wysiwyg editor.

Now this example is pretty generic and by now you're asking yourself, "why would I ever do that?" In this particular instance I was demonstrating how I insert code blocks into my text. I use the Memo field to format the code and on the "OK" click I wrap it in a <pre> tag with a "Code" class. I realize that inserting text by means of another text field is entirely useless but as a demo it's as simple as you can get for something that requires so many moving parts. It should however show you how all the pieces are tied together and allow you customize the code as needed to insert information compiled by accessing the content tree and process selected text from the wysiwyg.

There will be times when your client/manager will want you to be able to insert some functional content inline on the body of the main text area. This will provide that capability. You could simply replace the Memo field in this instance with a treelist have a user select an item and then output a server control tag. If you're unfamiliary with a server control you can read on MSDN about it or go for a tutorial here first, but the basic premise is that you design a UI control like a Literal and display something yourself. The reason I would suggest that path is that the wysiwyg editor will display a server control like an object that you can select and change the attributes on. A very little known but powerful feature in Sitecore.

Comments

Alistair Deneys
March 20, 2011
Very nice post Mark. I have been going through a design that requires a custom RTE dialog and after trying to describe all the steps to do so in the design document I thought it would make a good blog post. Seems you beat me to it. Thanks!
Ivan
April 4, 2011
Hi Mark, thanks for the useful post. I got some issue with your sample code, base.ServerProperties["Mode"], it seems serverProperties cannot apply indexing with it? I assume this base is inherited from Sitecore.Web.UI.Pages.DialogForm. Did I miss any other inheritance? Cheers, Ivan
mark stiles
April 14, 2011
The DialogForm in this example is actually a Sitecore class, so the inheritance is a little different. The chain starts with YourNamespace.YourClassName -> Sitecore.Web.UI.Pages.DialogForm -> Sitecore.Web.UI.Sheer.BaseForm -> Sitecore.Web.UI.Sheer.IMessageHandler. Hope that helps.
Eric Famiglietti
January 4, 2012
Hi Mark, I am having trouble getting this to work correctly. I have created the button in the WYSIWYG toolbar, but can't get the JavaScript function to fire. Even though I have added the function to the /sitecore/shell/controls/Rich Text Editor/RichText Commands.js file I am still getting the error message "The command EmbedBrightcoveVideo is not implemented yet." Any ideas of what could be wrong? I am using Sitecore 6.4. Thanks, Eric
Mark Stiles
January 9, 2012
From what you were asking I believe you're referring to the Brightcove/Sitecore integration setup. From the specific error you were receiving it looks like the contents of the "RichText Commands-Brightcove.js" weren't pasted into the "RichText Commands.js" file.
Nona
October 17, 2013
Looks like the editor allows separation of js files for custom settings. If you want to keep your js file for custom commands separate, it's import to ensure that the config file for the module includes the setting to add the custom js file to the HTML Editor. <clientscripts> <htmleditor> <script language="javascript" src="/sitecore/shell/Controls/Rich Text Editor/RichText Commands-Brightcove.js"></script> </htmleditor> </clientscripts>
Bob Murray
October 24, 2013
Also, at least for me using Sitecore 6.3, be careful of the casing in the xml file. The example has all lower case, and I could not get anything to work until I matched the camel casing used in the InsertLink.xml. This includes attributes. I spent a good 3 hours trying to figure out why my CodeBeside code wasn't firing. Correcting the casing fixed it. But a great article overall. This gets me well on the way to implementing a client request. Thanks.