The Problem
A client recently asked for a product demo. They had specific scenarions to be followed, including the creation/insertion of mathematical equations. The sample content suggested that they wanted the equations to be displayed inline with textual content. In other words, the equations would need to be added to the source of a rich text field.
I figured that if I could find an equation editor that would run in an HTML page then I could somehow extend the RTF format toolbar to launch the functionality, then inserting whatever the editor returned in to the RTF at the location of the cursor. It was actually very easy to find a suitable editor – CodeCogs have a javascript-based editor that can be embeded in an HTML page which creates image representations of the described equations. Having found the right editor I then had to figure out how to extend the toolbar to enable the functionality. A quick email to R&D provide me with the lead I needed – the toolbar can be extended in the same way you would extend a ribbon toolbar elsewhere in the interface.
Knowing where I was going to, and knowing which direction to start off in, but not knowing the exact route I was going to follow to get to my destination, I set off. I achieved a moderate degree of succuess – adding a button to the toolbar which opened a popup containing the editor, and returning a fixed string of HTML to the RTF. Unfortunately some conflict between the Tridion javascript and the 3rd party javascript meant that I couldn’t get the end-to-end process to work completely, but it did give me an understanding of how to approach this type of extension. So that the effort was not all in vain I refactored what I’d created to deliver a reference implementation. The key elements of the extension are detailed below.
Solution
Adding the toolbar button
There are plenty of examples available that show how to add a button to a ribbon toolbar in SDL Tridion 2011, so I won’t go in to the details of this. There is some specific configuration though, so I will cover this. I wanted to create a button in a custom group on the format toolbar that appeared only when editing Components. This is how it’s configured:
<ext:ribbontoolbars>
<ext:add>
<!-- RIBBON TAB -->
<!-- GROUPS -->
<ext:extension assignid="ExtensionGroup" pageid="FormatPage" name="RTF Extensions">
<ext:group/>
<ext:apply>
<ext:view name="ComponentView">
<ext:control id="ItemToolbar"/>
</ext:view>
</ext:apply>
</ext:extension>
<!-- BUTTONS -->
<ext:extension pageid="FormatPage" groupid="ExtensionGroup" name="Button<br/>Reference" assignid="ButtonReference">
<ext:command>ButtonReference</ext:command>
<ext:title>Button Reference</ext:title>
<ext:dependencies>
<cfg:dependency>RTFExtensions.ButtonReference.Commands</cfg:dependency>
</ext:dependencies>
<ext:apply>
<ext:view name="ComponentView">
<ext:control id="ItemToolbar"/>
</ext:view>
</ext:apply>
</ext:extension>
</ext:add>
</ext:ribbontoolbars>
You can see from this config snippet that I configured an RTF Extensions group with the ID “ExtensionGroup”, associated with the “FormatPage” – this is the ID of the Format ribbon tab, on which my group, and the button will appear. The button is also associated with “FormatPage”, as well as my custom group. The final part of the picture is where the group and the button are applied. I specified the “ComponentView” view as I wanted this to only appear when editing a Component. For the extension to appear for all item types apparently the name attribute can be omitted. For other item types, just specify the appropriate view name, e.g. PublicationView, FolderView, etc.
Controlling the availability of the button
Once I had the button appearing when I wanted it to, the next step was to make sure it was only active when the RTF was active. Having been use to configuring extension availability based on the selection of an item in a list view I was stuck again on how to proceed. Another email to R&D pointed me towards the existing toolbar button implementations to see how it’s done out-of-the-box. This is what I was able to find:
RTFExtensions.Commands.ButtonReference.prototype._isAvailable = function ButtonReference$_isAvailable(target) {
if (target.editor.getDisposed()) {
return false;
}
return true;
};
RTFExtensions.Commands.ButtonReference.prototype._isEnabled = function ButtonReference$_isEnabled(target) {
if (!Tridion.OO.implementsInterface(target.editor, "Tridion.FormatArea") || target.editor.getDisposed()) {
return false;
}
return true;
};
There was a little more to it than that, but this is what was relevant to my extension. Essentially the “selection”, or target is related to the cursor location, and contains the RTF editor. I didn’t investigate, but I assume that if I’d selected a block of text then the target would reference this.
Opening the popup
This will probably be the shortest part of this post as opening a popup is easy – I just used the $popup object. What looked like it would be tricky would be to get hold of the HTML created in the popup so it could be insterted in to the RTF at the cursor. More emails to R&D had me checking out more of the existing code used by the CME.
Getting the return value from the popup
It turns out that this is relatively straight-forward, but is handled in 2 parts. The first part is to “prime” the popup to provide the return value, which is done before the popup is opened. The second part is to initiate the return of the value, which is handled with in the popup. Both parts are tied to events.
Here’s the code needed before the popup is opened (in fact, the complete execute function):
RTFExtensions.Commands.ButtonReference.prototype._execute = function ButtonReference$_execute(target) {
if (target.item.isActivePopupOpened()) {
return;
}
function ButtonReference$execute$onPopupCanceled(event) {
target.item.closeActivePopup();
};
var url = $config.expandEditorPath("/Popups/PopupReference.aspx", "ButtonReference");
var popup = $popup.create(url, "toolbar=no,width=100,height=100,resizable=yes,scrollbars=yes", null);
$evt.addEventHandler(popup, "submit",
function ButtonReference$execute$onPopupSubmitted(event) {
// Update FA
var value = event.data.value;
if (value) {
target.editor.applyHTML(value);
}
// Release
target.item.closeActivePopup();
}
);
$evt.addEventHandler(popup, "unload", ButtonReference$execute$onPopupCanceled);
target.item.setActivePopup(popup);
popup.open();
};
You can see that I’m setting a function to handle the submit event. This function gets the return value from the event data and applies it to the selected RTF editor. To compliment this code, the javascript for the popup needs to fire the submit event. Here’s the code that sets the return value and triggers the event:
Type.registerNamespace("RTFExtensions.Popups");
RTFExtensions.Popups.PopupReference = function (element) {
Type.enableInterface(this, "RTFExtensions.Popups.PopupReference");
this.addInterface("Tridion.Cme.View");
};
RTFExtensions.Popups.PopupReference.prototype.initialize = function () {
$log.message("Initializing Button Reference popup...");
this.callBase("Tridion.Cme.View", "initialize");
var p = this.properties;
var c = p.controls;
p.HtmlValue = { value: null };
c.InsertButton = $controls.getControl($("#InsertButton"), "Tridion.Controls.Button");
$evt.addEventHandler(c.InsertButton, "click", this.getDelegate(this._execute));
};
RTFExtensions.Popups.PopupReference.prototype._execute = function () {
this.properties.HtmlValue.value = "<p><u>Sample string of HTML</u></p>";
this.fireEvent("submit", this.properties.HtmlValue);
window.close();
};
$display.registerView(RTFExtensions.Popups.PopupReference);
You can see that a button control on the popup page is referenced, an event handler is added to deal with the button being clicked, and in the click-executed function the submit event is fired with the HTML to be added to the RTF as a parameter.
What’s in the popup?
The source below is from the popup page in my reference implementation. The important parts to note are the Tridion UI controls namespace reference in the html element and the button (that uses the reference). The button ties back to the javascript in the previous section.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="PopupReference.aspx.cs" Inherits="Button.Reference.Popups.PopupReference" %>
<%@ Import Namespace="Tridion.Web.UI.Core" %>
<%@ Import Namespace="Tridion.Web.UI" %>
<%@ Import Namespace="System.Web" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:c="http://www.sdltridion.com/web/ui/controls">
<head id="Head1" runat="server">
<title>Reference Button Popup</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<h1>Reference Button Popup</h1>
<p>
<c:button id="InsertButton" runat="server" label="Insert" />
</p>
</div>
</form>
</body>
</html>
The popup implementation javascript and the Tridion core dependencies are added in the code-behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Tridion.Web.UI.Controls;
using Tridion.Web.UI.Core.Controls;
namespace Button.Reference.Popups
{
[ControlResourcesDependency(new Type[] { typeof(Popup), typeof(Tridion.Web.UI.Controls.Button), typeof(Stack), typeof(Dropdown), typeof(List) })]
[ControlResources("RTFExtensions.ButtonReference")]
public partial class PopupReference : TridionPage
{
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
TridionManager tm = new TridionManager();
tm.Editor = "RTFButtonReference";
System.Web.UI.HtmlControls.HtmlGenericControl dep = new System.Web.UI.HtmlControls.HtmlGenericControl("dependency");
dep.InnerText = "Tridion.Web.UI.Editors.CME";
tm.dependencies.Add(dep);
System.Web.UI.HtmlControls.HtmlGenericControl dep2 = new System.Web.UI.HtmlControls.HtmlGenericControl("dependency");
dep2.InnerText = "Tridion.Web.UI.Editors.CME.commands";
tm.dependencies.Add(dep2);
//Add them to the Head section
this.Header.Controls.Add(tm); //At(0, tm);
}
}
The line “[ControlResources(“RTFExtensions.ButtonReference”)]” links the popup to the javascript and style references in the extension config. The code in OnInit adds the relevant core dependencies. Without these some of API calls made, e.g. .fireEvent, won’t work.
Wrap-up
Hopefully this post has helped to explain how to add a RTF toolbar button. It’s not meant as a step-by-step tutorial, more as a introduction to some of the steps implemented to make an RTF button extension work. The source code has been shared through the Tridion Practice site on Google Code. Feel free to download the source and play with it.