Extending the SDL Tridion 2011 Rich Text Field Format Toolbar

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&lt;br/&gt;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.

Advertisements

57 responses to “Extending the SDL Tridion 2011 Rich Text Field Format Toolbar

  1. Pingback: SDL Tridion WCM by rhertel - Pearltrees

  2. Good article. Thank you!
    Inside popupreference.Js file in _execute() when I am trying to open the new popup window,but the polyp window is opening and closing immediately. Please suggest me how to resolve this issue.
    Early response is appreciated.
    Thanks in advance.

      • alert(“Inside js”);
        Type.registerNamespace(“RTFExtensions.Commands”);

        RTFExtensions.Commands.InsertAttribute = function Commands$InsertAttribute(name) {
        Type.enableInterface(this, “RTFExtensions.Commands.InsertAttribute”);
        this.addInterface(“Tridion.Cme.Command”, [name || “InsertAttribute”]);
        this.addInterface(“Tridion.Cme.FaCommand”, [name || “InsertAttribute”]);
        };

        RTFExtensions.Commands.InsertAttribute.prototype._isAvailable = function InsertAttribute$_isAvailable(target) {
        // alert(“Inside isAv”);
        if (target.editor.getDisposed()) {
        return false;
        }

        return true;
        };

        RTFExtensions.Commands.InsertAttribute.prototype._isEnabled = function InsertAttribute$_isEnabled(target) {
        //alert(“Inside isEn”);
        if (!Tridion.OO.implementsInterface(target.editor, “Tridion.FormatArea”) || target.editor.getDisposed()) {
        return false;
        }

        return true;
        };

        RTFExtensions.Commands.InsertAttribute.prototype._execute = function InsertAttribute$_execute(target) {
        alert(“Inside execute”);
        if (target.item.isActivePopupOpened()) {
        return;
        }

        function InsertAttribute$execute$onPopupCanceled(event) {
        target.item.closeActivePopup();
        };

        var url = $config.expandEditorPath(“/Popups/Tridion_Component.aspx”, “2011Extensions”);
        alert(url);
        var popup = $popup.create(url, “toolbar=yes,width=100,height=100,resizable=yes,scrollbars=yes”, null);

        $evt.addEventHandler(popup, “submit”,
        function InsertAttribute$execute$onPopupSubmitted(event) {
        // Update FA
        var value = event.data.value;
        if (value) {
        target.editor.applyHTML(value);
        }

        // Release
        target.item.closeActivePopup();
        }
        );
        $evt.addEventHandler(popup, “unload”, InsertAttribute$execute$onPopupCanceled);
        alert(“before”);
        target.item.setActivePopup(popup);
        popup.open();
        alert(“after”);
        };

      • This is the code for ButtonReference.js, but you mentioned _execute in PopupReference.js. The code looks correct to me – the close should only happen when the submit event is fired. Did you make any changes to PopupReference.js?

  3. Sorry previous code is for insertAttribute.js which is similar to buttonreference.ja and I am not changed anything inside popup.js.Here is the code…

    alert(“Inside popupjs”);
    Type.registerNamespace(“RTFExtensions.Popups”);
    RTFExtensions.Popups.popup = function (element) {
    Type.enableInterface(this, “RTFExtensions.Popups.Popup”);
    this.addInterface(“Tridion.Cme.View”);
    };
    RTFExtensions.Popups.popup.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.popup.prototype._execute = function () {
    this.properties.HtmlValue.value = “Sample string of HTML”;
    this.fireEvent(“submit”, this.properties.HtmlValue); window.close();
    };
    $display.registerView(RTFExtensions.Popups.Popup);

  4. And even I am getting an error in popupreference.aspx at tag .
    Error:
    Validation (XHTML 1.0 Translational):Element ‘button’ must be included within a parent

    • I tried use your reference directly… It is also not working…. That documentation also I referred.
      Some times popup is opening and giving runtime error
      Please help me in this issue…

      • That could be the cause of the problem – this sample was written for GA. I have no experience yet of SP1 so, unfortunately, I don’t think I can offer any more assistance. I don’t even have an SP1 environment I can install the reference implementation in. Perhaps you could try the SDL Tridion forum?

      • Hi,
        I created a .net project for aspx page. That project I am placing inside popups folder. Do I need create one more .net project for button? If not then what are those files named Assemblyinfo.cs, properties folder,web.config…
        Please resolve this issue.
        Thanks in advance.

      • They’re files/folders created by Visual Studio. I don’t know if they’re necessary or not, I just got my implementation to work and shared the full source code…

  5. If you dont mind, please share me the complete project as zip folder to my account. Thammude in advance…

    • You already have the complete code – after a hardware failure, the only source for the code is the Tridion Practice site. Every file and folder created for the RTF button reference implementation was checked in to the code repository there. I kept nothing behind.

      • Hi,
        I am using your post. When I am trying to click on ribbon toolbar button, it is giving error as:

        The value for the ‘compilerVersion’ attribute in the provider options must be ‘v4.0’ or later if you are compiling for version 4.0 or later of the .NET Framework. To compile this Web application for version 3.5 or earlier of the .NET Framework, remove the ‘targetFramework’ attribute from the element of the Web.config .

        After changing the version from 3.5 to 4.0 it is giving web.controls.ui.core unable to load…
        Is this is the problem with IIS server? Please help me in this issue…
        Early response is appreciated…
        Thanks in advance.

      • I don’t recongnise the namespace, where is it referenced? I also don’t understand why your web.config had a difference target framework. My source code references 4.0. Are you using the source from the Tridion Practice site?

      • I am using the code from Tridion practise site itself. I am trying to open a aspx page created in .net 4.0 .

        The name space coming in the error is Tridion.Web.UI.Core.

        Kindly look into the issue and suggest
        me a solution.

        Thanks in advance.
        Early response is appreciated.

  6. I have now tested the code with a 2011 SP1 install. Besides referencing the latest verision of Tridion.Web.UI.Core and rebuilding (don’t forget to copy the dll to WebUI\WebRoot\bin) it works without modification.

      • Hi,
        Thanks for your response. I visited your link.
        My aim is to open a aspx page as popup window when I clicked on Ribbon toolbar button for that I created a aspx page in c# .net(4.0version) project.
        I simply created buttonreference.js,buttonreference.config,buttonreference.css and placed them in respective folders, made entries in system.config file and written a java script file to achive my functionality.
        And created a virtual directory in IIS and linked this folder structure.

        I placed the virtual directory under webUI/Editors. But in the link “https://code.google.com/p/tridion-practice/wiki/RTFButtonExtensionReferenceImplementation” given as it should be placed under Models. Which method is correct?

        And all those files should be placed simply or should I create a .NET solution?
        Is this is fine or do I need to follow more steps?
        I am getting confused please help me in this regard.
        Thanks in advance.

      • That should have said Editors, not Models – have been working on Models recently… I’ve fixed that in the wiki page.

        I don’t think you need a .Net solution, but I always use one as I’m not a .Net Developer and it makes things easier for me.

      • Hi,

        Thanks for your response. I created a .net solution and I am able to open the popup page.

        In order to add custom code in PopupReference.aspx.cs page, If I delete the ControlResourcesDependency,ControlResources can I return the value from the PopupReference page. If not please suggest me a way to return the value from the Popup page.

      • I would work on the assumption that the ControlResourcesDependency and ControlResources attributes are mandatory. Of course the value of the ControlResources attribute should match the name of the group in your Editor config. To return the value from the popup to the rich text field, use the approach in the reference implementation. The only flexibility here is to perhaps use a different method on the editor object than applyHTML. I haven’t investigated what the other options are, if any, so you’d be on your own if you want to try it.

  7. Hi,
    Thanks for your response. I visited your link.
    My aim is to open a aspx page as popup window when I clicked on Ribbon toolbar button for that I created a aspx page in c# .net(4.0version) project.
    I simply created buttonreference.js,buttonreference.config,buttonreference.css and placed them in respective folders, made entries in system.config file and written a java script file to achive my functionality.
    And created a virtual directory in IIS and linked this folder structure.

    I placed the virtual directory under webUI/Editors. But in the link “https://code.google.com/p/tridion-practice/wiki/RTFButtonExtensionReferenceImplementation” given as it should be placed under Models. Which method is correct?

    And all those files should placed simply or should I create a .NET solution?
    Is this is fine or do I need to follow more steps?
    I am getting confused please help me in this regard.
    Thanks in advance.

  8. Hi,
    I used multiple drop down lists in a aspx page which sends multiple server requests. So that I a unable to return the value to tridion page. Please suggest me if you know any other way. Thanks in advance.

  9. Hi,
    Thank you for the wonderful article. Its really helpful.

    I am using the same code and my requirement is, User can select some part of the text in the RTF, then a popup is needed with one text box and the Insertbutton. User can enter a text in the textbox and this should be added as a suffix to selected text in the RTF. If user selects apple and in the popup textbox if enters Orange. on click of insert button it should be appleOrange in the RTF. Right now I am getting only Orange in the RTF the selected text “Apple” is replaced with Orange expected output in RTF is appleOrange. Please guide me.

    • I’ve just been able to investigate this question. It’s a pretty simple solution. Update the onPopupSubmitted function so that it looks like this:

      $evt.addEventHandler(popup, "submit",
      function ButtonReference$execute$onPopupSubmitted(event) {
      // Update FA
      var value = event.data.value;
      if (value) {
      var currentSelection = target.editor.getSelectedHTMLElement().toString();
      target.editor.applyHTML(currentSelection + value);
      }

      // Release
      target.item.closeActivePopup();
      }
      );

      The key update here is the line that gets the current selection 😉

      • I should probably caveat this to say that this only works when you have made a selection of text in the RTF and you’re only inserting text. I can’t guarantee the behaviour if you haven’t made a selection or if you’re inserting a string of HTML.

        I’m looking in to how to make it more resilient, but at the moment it’s not too clear how to do that.

      • Hi,
        Once again thank you for the response.

        Now I am getting “[object TextRange]” instead of the selected text, I tested it with the selected text like ‘the’,’ddddddddddd’,’xxxxxx’,’bbbbb’ and for the all selected text I am getting “[object TextRange]” .
        Kindly suggest .

      • This might be down to browser inconsistencies. I’m currently talking to our R&D guys to see if there’s a suitable solution. I’ll post back here when I have more information.

      • Hi ,

        Thank you for the very quick reply.

        I am not getting your question what Are you using 2011 GA or SP1?

        Solution that you have provided to use “target.editor.getSelectedHTMLElement().toString(); ” works for Firefox browser and in IE 9.0 its returning as “[object TextRange]” .
        It seems like browser specific can you please suggest on the same.

        Thank you .
        Moses

      • Apologies, the question is are you using SDL Tridion 2011 GA or SDL Tridion 2011 SP1? I’m guessing, given who you work for, it’s 2011 SP1. I’m still waiting for feedback from our R&D team so have nothing more to add right now.

      • Hi,
        Once again Thank you for the very quick response.

        The version I have is SDL Tridion 2011 SP1 -1.
        Is there any browser compatibility issue exist.
        Please suggest .

        Thanks,
        Moses

      • There could be a browser compatibility issue. What I’d like to do is utilise the cross-browser functionality that already exists within the UI, if possible. That’s the nature of the query I have with R&D.

        Sent from my iThing

      • Hi,
        Thank you for the reply.

        As you have suggested can you please guide me the steps that are involvded in to utilise the cross-browser functionality that already exists within the UI.

        Thank you,
        Moses

  10. hi tridiofanboy… i am having a question if u reply then it will be helpful to me.i will explain it.
    “We are having a page, now we will go to component presentation tab.inside that we will have a option to insert a component and component template.So when we will click the insert button another window will appear named “insert component presentation window”, there also we will have a button insert and close.so my doubt is if already we are having few component added in our page and if we will try to insert the same combination of component and component template again , how can we restrict user to do the same.On selecting the same combination it should popup stating like the chosen combination is already present on the page plz select someother combination.”
    Kindly help me regarding this.

  11. Hi Tridionfanboy,

    This is regarding my previous postings on cross browser compatibility issue.
    My findings are below

    for IE we need to go for target.editor.getSelectedHTMLElement().htmlText
    for firefox target.editor.getSelectedHTMLElement().tostring;

    I need a clarification please guid me with your inputs

    When I add C# server code to the page to populate the dorpdown and for some other operations, the RTF functionlaity is not working. Is there any restrictions like when we have tridion controls in our page we should not have C# server codes in the same page. Please guide me.

  12. Hi Tridionfanboy,

    When we select a html element in RTF.
    eg.Hello Selected

    and when we try to replace the same with
    Hello Selected

    I used target.editor.applyHTML(value) to replace and it replaces as below

    Hello Selected

    can you please help\suggest a method to replace selected html elment in the correctway.

  13. Hi Tridionfanboy,
    When we select a html element in RTF.
    eg. (SPAN title=”1234″) Hello Selected (\SPAN)

    and when we try to replace the same with
    (SPAN title=”555″) Hello Selected (\SPAN)

    I used target.editor.applyHTML(value) to replace and it replaces as below

    (SPAN title=”1234″) (SPAN title=”555″) Hello Selected (\SPAN) (\SPAN)

    can you please help\suggest a method to replace selected html elment in the correctway.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s