Alternate URLs in SDL Tridion 2011

Why?

Clients often ask about how they can configure alternate URLs for the Pages they manage in SDL Tridion. Normally I tell them it’s entirely possible, and talk a bit about how it would be handled. In a .Net environment, I explain, we’d likely use an HttpModule that queries a data source to find a matching target URL for the requested URL. This data source might be an XML file, or it might be a DB such as the Content Broker.

How?

My personal preference has always been using the Content Broker to handle this. The alternate URL information can be stored as metadata for the appropriate Page. This metadata is then available for query in the Content Broker. Nice and simple really – a request comes in, it’s checked against the metadata in the Broker, if a match is found we redirect to the target Page, otherwise the request continues as normal.

I won’t go in to the full technical details as the code can speak for itself. Follow the link below to the Tridion Practice site to find the code and documentation.

When?

That’s a very good question. Building this has been on my todo list for quite some time now (I have an email containing relevant information in Inbox from Septermber 2009!) but for various reasons have never found the time to put it together. Fortunately, in relation to an upcoming POC, I was able to find the time this week. Apart from some issues getting the module to trigger for requests without a file extension it was as easy as I’d suspected.

Where?

You can find the documentation and the source code on the Tridion Practice site. Feel free to post any questions on here if you have any.

Advertisement

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.

2011 GUI Extensions and SiteEdit 2009 SP3

I’ve recently been working on a GUI extension for 2011. Following the great work being done in the Power Tools project I refactored my server-side functionality in to a Model. This brings my extension in line with the approach used by our R&D guys when they create extensions. It also, apparently, broke SiteEdit in my environment

Investigation

It didn’t take much figuring out the cause of my problem – the only change to my environment between SiteEdit working and the problem appearing was the refactoring of my extension. A quick tweak of the configuration to disable it had SiteEdit working again. I launched Firebug to see if It would highlight anything untoward, and it was clear to see that SiteEdit was trying load my Model!

I thought this was strange, but a quick email to R&D confirmed that this was the expected behaviour. Apparently SiteEdit loads the CME in the background when it starts.

Solution

The solution was a a simple one in the end – when you configure your Model’s virtual directory in CME website in IIS, you need to make sure you also do it in the SiteEdit website. It wasn’t clear to me whether it’s good practice to do the same for your Editors, but I didn’t and it wasn’t an issue for me.

SDL Tridion 2011 AppData

Recently I’ve been working on a GUI extension that involves determining the URL of a published Page and the shortening it using bitly. For the use case I have in mind it doesn’t make sense to get a new short URL every time the extension functionality is called for a specific Page. This means I needed a way to persist the shortened URL, so it could be reused as and when needed.

I knew some of my colleagues had solved the problem in extensions they’d developed so I put the question to them. I received a few answers, and fewer options than answers, but the general recommendation was to use “AppData”.

Here’s what Frank has to say about AppData:

One of the new features in Tridion 2011 that has not gotten much public attention is the so-called application data (AppData for friends). The application data feature gives us a mechanism to store additional, application-specific data with every Tridion item. So if you have a Component in Tridion that corresponds to some file outside in the real world, you could store the path to the file in the Component’s AppData. You can of course also store that information in a metadata field. But doing so exposes the information to every user that has access to the Component. The beauty (and curse) of AppData is that it is only available through the API. It is meant to be produced and consumed by an add-on application.

So, pretty much exactly what I wanted 🙂

Using AppData

Now understanding what I should use I set about understanding how to use it.  The new Tridion .Net API may well have support for AppData, but as the recommended approach for communication with the core application is to use the Core Service, and all the feedback I was getting was telling me to use the Core Service, I was concentrating on, of course, the Core Service. It’s actually a very simple process, as the examples below should show.

When working with AppData you need to provide an application id and, optionally, and item id. The application id is the identifier for the custom application, against which the relevant data is stored. The optional item id allows you to tie the application’s data to a specific Tridion item. Whether or not you specify an item id depends on the type of data you want to store. In my case it was relevant to a selected Page so I would be specifying the item id.

With the code examples it’s important to note that I’m using a proxy class for the Core Service, and a static helper method to get the client object. The focus of the examples is to show how to use the client to interact with AppData, not to show how to create a client connection to the Core Service.

Getting AppData

CoreService2010Client client = ServiceClient.GetCoreServiceClient();
string appDataValue = string.Empty;
ApplicationData appData = client.ReadApplicationData(itemId, applicationId);

if (appData != null)
{
    Byte[] data = appData.Data;
    appDataValue = Encoding.Unicode.GetString(data);
}

client.Close();

As you can see, to get the data you call .ReadApplicationData, specifying the appropriate ids. If the AppData object exists, you get the data as a byte array and it to an encoded string. This string can be the value itself, as it was in my case, or it could be JSON or XML representing a more complex set of information. For the latter you would need to process the value further to extra the information you require.

Setting AppData

CoreService2010Client client = ServiceClient.GetCoreServiceClient();
Byte[] byteData = Encoding.Unicode.GetBytes(valueToStore);
ApplicationData appData = new ApplicationData
{
    ApplicationId = applicationId,
    Data = byteData,
    TypeId = url.GetType().ToString()
};
client.SaveApplicationData(itemId, new[] { appData });
client.Close();

Setting AppData is almost the exact reversal of the process to get it.  You create an AppData object, convert the string to store to an encoded byte array, set the application id, and then save against the relevant item id. The big difference here is that you can save multiple AppData objects in one go as they’re passed to .SaveApplicationData as an array. I’m not sure how often you’d actually do this, but it’s nice to know that it’s an option.

Deleting AppData

Of course, if you can get or set AppData it’s also handy to be able to delete it. I can imagine that you might tie this in to a delete event in the Event System to make sure that you don’t end up with lots of orphaned application data in your database.

CoreService2010Client client = ServiceClient.GetCoreServiceClient();
client.DeleteApplicationData(itemId, applicationId);
client.Close();

Nice and simple here – call .DeleteApplicationData for the appropriate item id and application id and it’s job done.

Additional Info

If you don’t want to tie your application data storage to a specific item you just need to use null where the samples show itemId. It’s important to remember that, when using AppData, you’re storing information in the Tridion database. Overuse has the potential to bloat the db, so make careful consideration when taking this route. AppData is also not versioned, so if you overwrite a value you will lose the previous value. It may be possible to specify a version number in the item id (if you’re using it) – something like tcm:xx-xx-xx-v1, where v1 represents version 1 of the relevant item (but I’m sure you knew that already).

SDL Tridion MVP Retreat 2011

Most of you reading this will likely be aware that the SDL Tridion MVP retreat just took place. I’m not going to go in to the details of what happened as Jules has already done this, and I experienced far less drama than he did. What I am going to do is write about what I gained from the weekend.

Knowledge

The biggest thing I took from the weekend was that, despite promising to do more last year, I haven’t been contributing to the community as much as I’d like. Yes I’m active on the forum, but I’d planned to blog more and, as the lack of content on this blog attests to, I haven’t done that. This post is the first step of my attempt to rectify the situation. I hesitate to commit to a firm commitment to blogging though, as the main reason for not doing so has been lack of time. The second step of my new plan was to install the WordPress app on my iPad so I can blog when I’m on the train. Hopefully these will be enough to allow to share more of the knowledge I pick up every day I work on demos or talk to customers about how SDL Tridion can help them.

Extensions

My next big learning was around 2011 GUI extensions. This year, like last year, we focussed on GUI extensions as the deliverable from the event. To justify the investment in the program we need to deliver something tangible back to the community – without it the trip is just a jolly, and had no value to SDL (who pay the bill).

Last year’s focus was on social extensions, while this year it was migrating the Power Tools. For those that don’t know, the Power Tools are a set of, mainly administrative, tools that simplify carrying out certain tasks within SDL Tridion. They have been around for a number of years now, and were built for previous versions. They also depend heavily on certain existing parts of the GUI in the versions they work with. Given the changes to the GUI in 2011, they largely no longer worked. Coupled with the fact that there are new, improved APIs available, they were a more than suitable candidate for the output from the retreat.

Having said all that, I didn’t actively take part in the work on the Power Tools, although I do plan to in the future. I had my own agenda which, thanks to the prep work done by Chris on the Power Tools, I was able to follow. I’ve been trying to continue my work on the social extensions I began at the retreat last year, with limited success. This was partly due to limited bandwidth, but also due to lack of knowledge on how to proceed with the ideas I had. Thankfully Chris’ groundwork filled enough of the knowledge gap for me to get moving again.

Chris had spent some time creating a base for the continued development of the Power Tools, including a reference implementation of a WCF service and a JavaScript proxy to allow it to be called from a GUI. Thew principle idea being that this approach solves the issues around the complexity of calling complex business logic in JavaScript as it can all be handled in the service’s .Net code. This was exactly what I was missing so I set about refactoring it for my own use. I won’t go in to detail here about what I’m working on, but I am planning to blog about it when I have it more complete than it currently is.

For me this retreat was essential as without it I would either have been waiting forever to gain that knowledge, or would never have gained it at all.

Friendship

Finally, a big take away for me from the retreat is friendship. I already knew the attendees, if not personally, then by reputation. Of those I knew personally this was the first time I’d seen many of them since the last retreat, some even longer than that. For the others, this was my first time meeting them.

Of course we all share a common interest – the SDL Tridion community – but I was surprised to see just how well we all got on together. I was the kind of relationship that I’ve seen take weeks or months to build in other situations. We had four days of great company, great banter, and great cooperation, while still enjoying, to the best of our ability, the great location, great food, and plentiful drink. We even managed, I believe, to still deliver to the promise of the event and produce a meaningful output. I think the deliverable was never really in question, but it wouldn’t have been anywhere near as enjoyable to do it without the genuine friendship shown by the attendees for each other

I consider myself to be extremely lucky to be recognised as a peer of those who I joined in Portugal, as well as those who didn’t/couldn’t/wouldn’t make it to the retreat. I hope that I can continue to be perceived in this way.

SiteEdit 2009 & JavaScript trickery

Problem

Recently I was preparing a demo for a prospect. Part of what I was doing involved making one of their product pages siteeditable. The page in question contained a lot of content, but the presentation was simplified through the use of tabs. The client had used prototype to achieve this, although I don’t believe this had any specific impact on the problem this caused. I wanted to be able to edit all of the content from all of the tabs, however this was proving somewhat difficult – only the first visible tab was outlined correctly, and when other tabs were selected the original outlining remained. I needed to find a way to make all of the content visible.

Analysis

Reading the description of the problem no doubt there are those of you who are saying “easy, just change the design to show all the tabbed content all of the time” and , of course, this is one solution that would work. However, I wanted to maintain the integrity of the design, so I needed a solution that would only make the tabbed content temporarily available.

The direction I needed to go in was obvious – with JavaScript being used to hide the content in the tabs, and with SiteEdit being largely JavaScript-based, my solution was to be a JavaScript solution. So far so good, but how was I going to get my JavaScript to run only when I wanted it to? With maintaining the design integrity still clear in my mind I decided I needed a solution that would only trigger when SiteEdit was active, so I  needed a way to identify when this was true.

Fortunately working for SDL means that I have access to resources in R&D that are not only in a position to offer advice, but are also happy to do so. After a flurry of emails, and a couple of false starts (apparently it’s really easy to determine if you’re viewing the staging site through the SE proxy or not), the pieces of the puzzle began to fall in to place.

Solution

My solution used SiteEdit events to capture when the Component Presentation was selected (clicked) – literally the “componentPresentationClicked” event:

function pageInit() {
    // Special functionality when CP selected
    var onComponentPresentationClicked = function() {
        // Code goes here
    }

    // if SiteEdit is active (Page is viewed through proxy) attach special functionality
    if (typeof(top.$SeEventManager) != "undefined") {
        var m = top.$SeEventManager.getInstance();
        m.Subscribe("componentPresentationClicked", onComponentPresentationClicked);

        try {
            Event.observe(window, 'unload', function() {
                m.Unsubscribe("componentPresentationClicked", onComponentPresentationClicked);
            });
        } catch (err) {
            // probably should do something here...
        }
    }
}

document.observe("dom:loaded",function() { pageInit(); });

In the end it was straight-forward to achieve, although I’ve since learned that there might be a more appropriate event to hook in to, but hopefully this will be useful information. And don’t forget, if you’re trying anything like this then Firebug is your friend 😉