2014 SDL Tridion MVP Retreat

The past few days has been filled with excitement and awe as SDL hosted their fifth Tridion MVP Retreat. I was extremely lucky to have had the honor of getting to go to Portugal for a second time. Last year the group of Tridionaughts got to visit the castle town of Óbidos, and this year we got to gaze our eyes upon the fortified city of Évora. The day started just like the previous year, the sleep deprived flew into the Lisbon airport to meet with the hungover who had decided to join the party a day (or more) earlier. This year though I got to journey on the adventure with my good friend and first time MVP winner Frank Taylor.

The Roman Temple

The Roman Temple at night as we make our way for dinner at the Pousada Dos Loios.

Courtyard of the Albergaria do Calvário

Hanging out in the courtyard of our hotel… did I mention we had the whole hotel to ourselves?

A private shuttle picked up everyone from the airport and shuttled us to Évora, where we were to stay at the Albergaria do Calvário, a hotel that was booked solely to our MVP group (possibly due to some noise complaints from outside guests at the hotel of the previous year’s retreat). For a little background on the kind of group that we are, its definitely not uncommon to see some of us out still drinking outside at 0400 in the morning singing along loud and drunkenly with a ukulele playing.

Outside of our conference room

Outside of our conference room at the M’Ar De Ar

Once we checked into our hotel, we headed across the street to the M’Ar De Ar where our conference/war room (and lunches) awaited us. After a multi course lunch, the event began with Nuno discussing the road map for Tridion and its products. The rest of the MVP business for the next days was focused on the SDL Tridion Reference Implementation. We discussed what it was in detail, the architecture of how it was built, installation and feedback, and future features that are in the works or needing recruitment to help out on.

Post Work Events

Singing in the Courtyard

Raimond and Quirijn showing their musical talent the first evening in the courtyard at the hotel.

Our first evening’s event was dinner at Pousada Dos Loios, which was a multi-course meal (thinking back, which meal didn’t have multiple courses?) that included octopus. I was a little bit worried when the octopus first came out, as the ones I’ve had before were always a bit rubbery. But I have to say, it was probably the most tender and delightful octopus I’ve ever eaten. My only regret is not taking a picture of it to share with you! After the dinner we shared some drinks at the restaurant’s bar, and finally retired the night in the courtyard of our hotel where we drank even more as our very own talented musicians Nick, Raimond, Quirijn, and Dominic provided a night of sing along music with a guitar, ukuleles, and harmonicas. Various members dropped off through the night, but the party continued til around four in the morn (to the dismay of some who were trying to sleep).

Beautiful view of the grounds at Adega Da Cartuxa

Beautiful view of the grounds at Adega Da Cartuxa

Are those barrels of wine for us?

Are those barrels of wine for us?

A chapel at the Convento Do Espinheiro

A tour through a chapel at the Convento Do Espinheiro

Our second evening’s event kicked off to Adega Da Cartuxa, a winery that produces one of Nuno’s most favorites (so if you ever need to bribe him, you know where to go). We got a tour of the building, watched a couple of videos, and got a wine tasting. Following the winery, the group continued on to the luxurious Convento Do Espinheiro, a historic national monument where we got a tour and then a feast. Following the feast was more drinking, pool playing, and our own personal pianist (Quirijn!). The night ended back at the courtyard of our hotel, but most of the group retired a lot earlier than the previous night.

Will Price serving drinks at the  Convento Do Espinheiro

Will Price serving drinks at the Convento Do Espinheiro

Saturday’s events started right after our final lunch at the M’Ar De Ar. The activities started with a guided tour around the city. One of the places we visited that stood out most to me was the Bones Chapel, a room where the walls and ceiling are covered in skulls and bones. Although the skulls look like they belong to children, the guide said it was due to age and shrinking. From the tour we went directly to Kartodromo de Évora, where we had a serious go kart race. It started with a 5 minute warm up, followed by a 15 minute qualifier which determined the positions that we would start in for the actual race. I must admit that after the 15 minute qualifier I was about sore and done…

At the Bones Chapel

At the Bones Chapel. We were told this is where MVPs end up when they stop sharing.

but alas no rest for the wicked as we entered the 30 minutes of the actual race. It was great times as many of us spun out and crashed into each other, and the race ended with the venue handing out trophies and awards to the winners and finishers. It was pretty intense, and each of us were drenched in sweat by the race’s end.

Our war room at the M’Ar De Ar conference room

Our war room on the last morning at the M’Ar De Ar conference room

Winners of the go kart race!

Winners of the go kart race… Nick in First, Chris in Second, and Julian in Third

Finally the night found us once again at our hotel’s courtyard where a three hour buffet of various traditional Portuguese foods awaited us. The night ended as any other night with your fellow MVP’s would: with good food, good drinks, good music (this time Frank and Robert Curlette joined in on the Triangle), and good friends.

Thanks You’s

A special thank you to SDL again for hosting this award program for the community and for making those who are selected to feel beyond special and appreciated. For those of you who have not yet won but are thinking of trying for it – keep sharing and don’t stop. Believe me, the experience of spending several days with your fellow Tridionaughts is well worth any amount of time spent sharing your knowledge and helping others. And the MVP community and its events are one that you will remember through a lifetime.

Another special thank you to Carla Osorio for organizing yet another smashingly great year and the amazing job in keeping a drunken lot such as ourselves organized and together. And another thank you to Paulo Linhares for helping with the organizing as well as the threats and cursing that helped us keep in line and not end up mugged and arrested somewhere. And of course to Nuno, as this program would not be where it is today without him.

Hanging out on a wall of the Adega Da Cartuxa

Hanging out on a wall of the Adega Da Cartuxa

Til next time my friends, hopefully I’ll be there and you as well!

Link Attributes Extension: Final Touch Ups

Welcome to the third installment (and hopefully final) work on our Link Attributes GUI Extension. In our first post, we discovered how to customize one of the Tridion default popup dialogs (the Link popup for our extension). The post gave birth to the GUI Extension piece of our extension and added special hash parameters to the link’s href attribute to represent the attributes that needed to be created. In our second post, we created a Template Building Block that would be responsible for converting the hash parameters from the GUI Extension into actual attributes on the link.

Our updated Link Attributes dialog!

Our updated Link Attributes dialog!

If you’d rather just skip the reading and download the code, you can find it here.

Today we’ll be adding some finishing touches and enhancements to the work. The biggest piece missing was the ability to add multiple link attributes onto our hyperlink. There was also an issue if the url contained a hash string that wasn’t in a key = value format (ie www.example.com#noKeyValue). For our multiple fields, we want to have a delete button that will either delete the field completely if there are more than one field available, or just clear the field if its the only one remaining. Clicking the add button will insert an additional empty field set.

HashCollection.js

Our first update is to our HashCollection.js file. Our update here is strictly to allow existing hash strings in the url that is not in the key equals value format (ie “#someValue” vs “#someValue=blah”).

Type.registerNamespace("ContentBloom.Extensions");

/**
 * HashCollection is a collection of styles on a link's hash parameters.  This class is responsible
 * for getting, setting, and clearing link attributes.
 *
 * @constructor
 */
ContentBloom.Extensions.HashCollection = function (url) {
    var hashString,
        params,
        param,
        i;

    /**
     * The link attribute prefix.  This prefix is applied to any hash params that are for link attributes only.
     * @type {string}
     * @private
     */
    this._linkAttributePrefix = "linkAttr-";

    /**
     * Whether or not the hash collection contains link attributes. 
     * @type {boolean}
     */
    hasLinkAttributes: false;

    /**
     * The original url passed into the HashCollection instance.
     * @type {string}
     */
    this.url = url;

    /**
     * The params on the url
     * @type {object[]}
     */
    this.params = [];

    // only populate the params if the url contains a hashstring
    if (url.indexOf("#") !== -1) {
        hashString = url.substring(url.indexOf("#") + 1);

        params = hashString.split('&');
        for (i = 0; i < params.length; i++) {
            param = params[i].split('=');
            this.params[this.params.length] = { property: param[0], value: param[1] };
            if (!this.hasLinkAttributes && param[0].indexOf(this._linkAttributePrefix) === 0) {
                this.hasLinkAttributes = true;
            }
        }
    }
};

// StyleCollection prototype members
ContentBloom.Extensions.HashCollection.prototype = {

    /**
     * Clears all params from the collection that are specific to Link Attributes.
     */
    clearLinkAttributes: function () {
        var length = this.params.length,
            param,
            i;

        while (length--) {
            param = this.params[length];
            if (param.property.indexOf(this._linkAttributePrefix) === 0) {
                this.params.splice(length, 1);
            }
        }
        this.hasLinkAttributes = false;
    },

    /**
     * Gets the hash string (starting with '#') only of the wrapped url. Supplies link attribute hash params at the end of the string.
     *
     * @returns {string}
     */
    getHashString: function () {
        var hashString = "#",
            linkAttributes = "",
            length = this.params.length,
            param,
            i;

        if (length === 0) {
            return "";
        }

        for (i = 0; i < length; i++) {
            param = this.params[i];
            if (param.property.indexOf(this._linkAttributePrefix) === 0) {
                if (i > 0) {
                    linkAttributes += "&";
                }
                linkAttributes += "{0}={1}".format(param.property, param.value);
            } else {
                if (i > 0) {
                    hashString += "&";
                }
                if (param.value) {
                    hashString += "{0}={1}".format(param.property, param.value);
                } else {
                    hashString += param.property;
                }
            }
        }


        return hashString + linkAttributes;
    },

    /**
     * If a link attribute property is supplied, strips the prefix and returns just the key. Else just reutrns the key.
     *
     * @returns {string}
     */
    getKey: function (key) {
        if (key.indexOf(this._linkAttributePrefix) === 0) {
            return key.substring(9);
        }
        return key;
    },

    /**
     * Gets a link attribute param by its key.
     *
     * @param {string} key - the key to retrieve (excluding the link attribute prefix)
     * @returns {Object|null} null if no attribute is found with given key.
     */
    getLinkAttribute: function (key) {
        var length = this.params.length,
            param,
            i;

        for (i = 0; i < length; i++) {
            param = this.params[i];
            if (param.property === this._linkAttributePrefix + key) {
                return param;
            }
        }
        return null;
    },

    /**
     * Gets only the parameters that are link attribute parameters.
     */
    getLinkAttributes: function () {
        var linkAttributes = [],
            length = this.params.length,
            param,
            i;

        if (!this.hasLinkAttributes) {
            return linkAttributes;
        }

        for (i = 0; i < length; i++) {
            param = this.params[i];
            if (param.property.indexOf(this._linkAttributePrefix) === 0) {
                linkAttributes[linkAttributes.length] = param;
            }
        }

        return linkAttributes;
    },

    /**
     * Returns a new url including the hash string generated by any parameters.
     *
     * @returns {string}
     */
    getHashedUrl: function () {
        var url;

        if (this.url.indexOf("#") !== -1) {
            url = this.url.substring(0, this.url.indexOf("#"));
        } else {
            url = this.url;
        }

        return url + this.getHashString();
    },

    /**
     * Sets a link attribute param.  If attribute already exists, updates it.
     *
     * @param {string} key - The key of the link attribute (exluding link attribute prefix).
     * @param {string} value - the value of the link attribute.
     */
    setLinkAttribute: function (key, value) {
        var param = this.getLinkAttribute(key);
        if (param !== null) {
            param.value = value;
        } else {
            this.params[this.params.length] = { property: this._linkAttributePrefix + key, value: value };
        }
        this.hasLinkAttributes = true;
    }

}

LinkAttributes.js

Our next update is to the LinkAttributes.js file, where we are adding the ability to add new rows, delete existing rows, and some extra markup for the button controls. We’ve also had to add a way to allow the popup window to dynamically alter its height based off the adding/removing of rows.

Type.registerNamespace("ContentBloom.Extensions");

/**
 * Represents functionality for adding custom attributes onto links from the Link Popup dialog.
 */
ContentBloom.Extensions.LinkAttributes = {

    /**
     * The table row in the popup containing the key value pairs of links.
     * @type {JQuery}
     */
    attributeContainer: null,

    /**
     * The url input field.
     * @type {JQuery}
     */
    urlField: null,

    /**
     * Initializes the LinkAttributes object.
     */
    init: function () {
        var self = this;

        this.customizeLinkObject();
        $jq('#rowTarget').parent().append('<tr id="customAttributes" valign="top"><td>Attributes:</td><td id="attributeContainer" colspan="2"></td><td colspan="2" valign="bottom"><div class="addLinkAttributesRow"></div></td></tr>');

        this.attributeContainer = $jq('#attributeContainer');
        this.urlField = $jq("#FieldUrl");
        
        this.insertKeyValuePair();
        this.initValues();

        // add new rows
        $jq("#customAttributes .addLinkAttributesRow").click(function () {
            self.insertKeyValuePair();
        });

        // delete rows, or clear input fields
        this.attributeContainer.on("click", ".deleteLinkAttributesRow", function () {
            var row =$jq(this).parent();

            if ($jq("div.linkAttributeSet").length > 1) {
                // remove the row if there are more than one fieldset...
                row.remove();
                window.resizeBy(0, -$jq("#attributeContainer div").height());
            } else {
                // clear the fields if there's only one row
                $jq('input.key, input.value', this.attributeContainer).val('');
            }
        });
    },

    /**
     * Initializes input field values on the gui (url, link attributes...).
     */
    initValues: function () {
        var hashParams,
            linkAttributes,
            attribute,
            length,
            i,
            oldLink = window.dialogArguments && window.dialogArguments.link ? window.dialogArguments.link : {};


        if (oldLink.href) {
            hashParams = new ContentBloom.Extensions.HashCollection(oldLink.href);
            linkAttributes = hashParams.getLinkAttributes();
            length = linkAttributes.length;

            if (hashParams.hasLinkAttributes) {
                for (i = 0; i < length; i++) {
                    attribute = linkAttributes[i];
                    if (i === 0) {
                        $jq('.key', this.attributeContainer).val(hashParams.getKey(attribute.property));
                        $jq('.value', this.attributeContainer).val(attribute.value);
                    } else {
                        this.insertKeyValuePair(hashParams.getKey(attribute.property), attribute.value);
                    }
                }
                hashParams.clearLinkAttributes();
                this.urlField.val(hashParams.getHashedUrl());
            }
        }
    },

    /**
     * Gets an array of custom attributes based on text fields in the gui.
     *
     * @returns {object[]}
     */
    getCustomAttributes: function () {
        var attributes = [];

        $jq('div.linkAttributeSet', this.attributeContainer).each(function(index, element) {
            var key = $jq('.key', element).val(),
                value = $jq('.value', element).val();

            if (key) {
                attributes[attributes.length] = { key: key, value: value };
            }
        });

        return attributes;
    },

    /**
     * Inserts a new key value pair of text fields into the attribute container.
     *
     * @param {string=} key - The optional key to set the key input field to.
     * @param {string=} value - The optional value to set the value input field to.
     */
    insertKeyValuePair: function (key, value) {
        if (key == undefined) {
            key = "";
        }
        if (value == undefined) {
            value = "";
        }
        this.attributeContainer
            .append('<div class="linkAttributeSet"><label>Key</label><input class="key" type="text" value="' + key + 
                '" /> <label>Value</label><input class="value" type="text" value="' + value + '" /><div class="deleteLinkAttributesRow"></div></div>');
        window.resizeBy(0, $jq("#attributeContainer div").height());
    },

    /**
     * Modifies the Link._buildNewLinkHtml method to also apply the custom link attributes. Monkey patches, so existing method is still used and not replaced.
     */
    customizeLinkObject: function () {
        var self = this,
            originalFn = Tridion.Cme.Views.Link.prototype._buildNewLinkHtml;
            

        Tridion.Cme.Views.Link.prototype._buildNewLinkHtml = function Link$_buildNewCustomLinkHtml() {
            var link,
                hashParams,
                attributes = self.getCustomAttributes(),
                attribute,
                i;

            originalFn.apply(this);

            hashParams = new ContentBloom.Extensions.HashCollection(this.properties.NewLink.href);
            hashParams.clearLinkAttributes();

            for (i = 0; i < attributes.length; i++) {
                attribute = attributes[i];
                hashParams.setLinkAttribute(attribute.key, attribute.value);
            }

            this.properties.NewLink.href = hashParams.getHashedUrl();
        };
    }
};

// We call init only after document has loaded.
$jq(function () {
    ContentBloom.Extensions.LinkAttributes.init();
});

LinkAttributes.css

And finally, our stylesheet needs to be updated to be updated to support our newly added elements to our customization. We’re reusing some of the icons that are exists in Tridion for our delete and add buttons.

#attributeContainer {
    border: solid 1px #a1a9b2;
    border-bottom-width: 0;
}

    #attributeContainer label {
        font-weight: bold;
        margin-right: 6px;
    }

    #attributeContainer input {
        width: 30%;
    }

.addLinkAttributesRow {
    height: 22px;
    width: 22px;
    background-image: url("/WebUI/Editors/CME/Themes/Carbon/Images/Icons/add.16x16_v6.1.0.55920.93_.png");
    background-position: center;
    background-repeat: no-repeat;
    cursor: pointer;
    position: relative;
    bottom: 8px;
}

.deleteLinkAttributesRow {
    height: 22px;
    width: 22px;
    background-image: url("/WebUI/Editors/CME/Themes/Carbon/Images/Icons/delete.16x16_v6.1.0.55920.93_.png");
    background-position: center;
    background-repeat: no-repeat;
    cursor: pointer;
    display: inline-block;
    position: relative;
    top: 6px;
    left: 5px;
}

And that’s it… our Editor.config file requires no change and our Template Building Block from our previous post and our TBB was already setup to handle the multiple hash parameters.

If you are interested in using this extension and don’t feel like piecing everything together from this and the past two articles, you can download the extension here. A quick installation documentation is provided to help you get it setup.

Enjoy and stay coding my friends!

Link Attributes Extension: Template Building Block

Earlier we talked about customizing an existing default popup in Tridion, and the post gave birth to the start of our Link Attributes GUI Extension.  As a recap, we were creating an extension that would allow us to add custom link attributes onto our hyperlinks added by the Hyperlink button.  We decided that the GUI Extension would add hash parameters to the link’s href attribute, and that’s pretty much where the post left off.  Today we will be adding a Template Building Block that will be responsible for stripping out the hash parameters that were added by the GUI Extension and converting them to attributes on the link element itself.

In another post we mentioned using HtmlAgilityPack in our Template Building Blocks, so although the regex for this task would be fairly simple, I’ll be using that library in our TBB.  If you are going to be using this extension and this TBB yourself, you’ll have to add the HtmlAgilityPack DLL to the GAC on your Tridion servers as well as reference it in your TBB project.

Next you will want to create a new class in your TBB project and add the following code:

using System;
using System;
using HtmlAgilityPack;
using Tridion.ContentManager.Templating;
using Tridion.ContentManager.Templating.Assembly;

namespace ContentBlooom.TemplateBuildingBlocks
{
    /// <summary>
    /// This TBB is responsible for converting the linkAttr- hash params created by the GUI Extension  into actual
    /// attributes on the link element that they've been placed.
    /// </summary>
    [TcmTemplateTitle("Link Attributes Converter")]
    public class LinkAttributesConverter : ITemplate
    {
        public void Transform(Engine engine, Package package)
        {
            bool outputModified = false;
            Item outputItem = package.GetByName(Package.OutputName);
            string outputString = outputItem.GetAsString();

            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(outputString);
            doc.OptionOutputOriginalCase = true;

            var linksWithAttributes = doc.DocumentNode.SelectNodes("//a[contains(@href, 'linkAttr-')]");
            if (linksWithAttributes == null)
            {
                return;
            }
            
            foreach (var link in linksWithAttributes)
            {
                string url = link.Attributes["href"].Value;
                string hashString = url.Substring(url.IndexOf("#") + 1);
                string[] hashParams = hashString.Replace("&amp;", "&").Split('&');
                
                bool hasLinkAttributes = false;

                url = url.Substring(0, url.IndexOf("#"));
                hashString = String.Empty;

                foreach (string hashParam in hashParams)
                {
                    if (hashParam.StartsWith("linkAttr-"))
                    {
                        // If its a link attribute, add it as an attribute and then remove it from the hash string
                        hasLinkAttributes = true;
                        outputModified = true;

                        string[] rule = hashParam.Split('=');
                        string attributeKey = rule[0];
                        string attributeValue = rule[1];

                        link.Attributes.Add(attributeKey.Replace("linkAttr-", String.Empty), attributeValue);
                        url = url.Replace(hashParam, String.Empty);
                    }
                    else
                    {
                        // Keep any existing hash info there...
                        hashString += hashString.Length == 0 ? "#" : "&amp;";
                        hashString += hashParam;
                    }
                }
                if (hasLinkAttributes)
                {
                    link.Attributes["href"].Value = url + hashString;
                }
            }

            if (outputModified)
            {
                package.Remove(outputItem);
                outputItem.SetAsString(doc.DocumentNode.OuterHtml);
                package.PushItem(Package.OutputName, outputItem);
            }
        }
    }
}

Simple, no? You’ll want to put this TBB directly after your Building Block that provides the output (your DWT, Razor… whatever). Or even possibly as the first item in your Default Finish Actions. Make sure to create a component that has some links created with our modified Link popup from the earlier article, and run and execute.

Output prior to Link Attribute Converter:

<article>
    <p>Testing link attributes and what not and other stuff.</p>  
    <p>Should include a <a href="tcm:14-103941#linkAttr-newLink=test" title="a title">component link</a> as well as a normal <a href="http://www.example.com#linkAttr-custom=check" title="blah">http type of link</a>.</p>
    <p>We also need to see how it plays with <a href="http://www.test.com#blah=meh&amp;linkAttr-t=v">links with hashes</a> already.</p>
    <p><a href="mailto:e@mail.com#a=b&amp;c=d&amp;linkAttr-mailed=them" title="title">Multi hashed Link</a></p>
 </article>

And our output after our TBB:

 <article>
     <p>Testing link attributes and what not and other stuff.</p>  
     <p>Should include a <a href="tcm:14-103941" title="a title" newlink="test">component link</a> as well as a normal <a href="http://www.example.com" title="blah" custom="check">http type of link</a>.</p>
     <p>We also need to see how it plays with <a href="http://www.test.com#blah=meh" t="v">links with hashes</a> already.</p>
     <p><a href="mailto:e@mail.com#a=b&amp;c=d" title="title" mailed="them">Multi hashed Link</a></p>
 </article>

Not Quite Done

Our extension is not quite done just yet. Stay tuned once more as we add some enhancements and fixes to our little extension, such as the ability to add more than just one link attribute!

Customizing An Existing Tridion Popup Dialog

Ever had a need or an itch to completely modify and customize one of those default Tridion popup dialogs? Perhaps you even had a desire to overload an existing JavaScript function from one of those dialogs, but didn’t want to just copy/paste or replace the existing one entirely? Today I’ll be giving you a walk through of adding some custom fields and functionality to the Link popup. Before I begin, I want to give a special thanks to Frank Taylor as we worked on this kind of functionality together not too long ago. And by working together, I mean that I merely made some suggestions and he did all the work so that one day I could steal it as a reference point and write the post you are reading today. So if you find anything on this page useful, feel free to also give Mr. Frank a shout out! And respectively, if you think this article is complete crap and the code here somehow murders your beloved cat, feel free to leave a burning bag of poo on his front door step. Just ping me and I’ll give you his personal home address.

If you’re not interested in reading and just want the code that was done in this post, you can find it here.

The Mission

Our mission today is to add the ability for content authors to add extra custom attributes onto their links in rich text fields via the Hyperlink button in the ribbon bar.  For the sake of simplicity, we’ll only add a single set of a key value pair today, and save this enhanced feature for a post for another day.  Also, today’s post will only focus on the GUI Extension piece of this puzzle as the point of today’s blog is more focused on the “how do I customize an existing dialog?”

Analysis

At first thought, we could apply our custom link attribute directly to the link elements from the rich text field.  However, these would just get stripped away in an out of the box Tridion setup due to being invalid attributes.  Although we could configure Tridion to prevent this from happening (for information on how to do this, check Nick’s post here), I wanted to take a more generic approach where we could add attributes regardless of this configuration.  So what we’ll be doing instead is adding these special link attributes as hash parameters to the href attribute.  We’ll give them a special prefix of “linkAttr-” to distinguish our hash parameters from any that may already be a part of the url supplied.  This means that we’ll also need to create a Template Building Block that will search for these specially marked hash params, turn them into attributes on the link element, and remove them from the hash string.  As mentioned earlier, since we’ll only be focusing on the GUI Extension piece today, we’ll leave that for the next blog post.

Assumptions

I’m going to assume that you are already somewhat familiar with developing Tridion GUI Extensions, and will only be going over code and configuration that is specific to this extension. If you are looking for a tutorial that teaches you how to create a Hello World from scratch, there’s already quite a few good ones out there. We’re also going to assume that we’ll be using jQuery and that one doesn’t already exist. We’ll be adding jQuery to a global $jq variable.

The JavaScript

We’re going to start by creating a utility class, “HashCollection”.  This class will be responsible for extracting the hash parameters from a given url, as well as maintaining, adding and clearing of our link attributes.

Type.registerNamespace("ContentBloom.Extensions");

/**
 * HashCollection is a collection of styles on a link's hash parameters.  This class is responsible
 * for getting, setting, and clearing link attributes.
 *
 * @constructor
 */
ContentBloom.Extensions.HashCollection = function (url) {
    var hashString,
        params,
        param,
        i;

    /**
     * The link attribute prefix.  This prefix is applied to any hash params that are for link attributes only.
     * @type {string}
     * @private
     */
    this._linkAttributePrefix = "linkAttr-";

    /**
     * Whether or not the hash collection contains link attributes. 
     * @type {boolean}
     */
    hasLinkAttributes: false;

    /**
     * The original url passed into the HashCollection instance.
     * @type {string}
     */
    this.url = url;

    /**
     * The params on the url
     * @type {object[]}
     */
    this.params = [];

    if (url.indexOf("#") !== -1) {
        hashString = url.substring(url.indexOf("#") + 1);

        params = hashString.split('&');
        for (i = 0; i < params.length; i++) {
            param = params[i].split('=');
            this.params[this.params.length] = { property: param[0].trim(), value: param[1].trim() };
            if (!this.hasLinkAttributes && param[0].trim().indexOf(this._linkAttributePrefix) === 0) {
                this.hasLinkAttributes = true;
            }
        }
    }
};

// StyleCollection prototype members
ContentBloom.Extensions.HashCollection.prototype = {

    /**
     * Clears all params from the collection that are specific to Link Attributes.
     */
    clearLinkAttributes: function () {
        var length = this.params.length,
            param,
            i;

        while (length--) {
            param = this.params[length];
            if (param.property.indexOf(this._linkAttributePrefix) === 0) {
                this.params.splice(length, 1);
            }
        }
        this.hasLinkAttributes = false;
    },

    /**
     * Gets the hash string (starting with '#') only of the wrapped url. Supplies link attribute hash params at the end of the string.
     *
     * @returns {string}
     */
    getHashString: function () {
        var hashString = "#",
            linkAttributes = "",
            length = this.params.length,
            param,
            i;

        if (length === 0) {
            return "";
        }

        for (i = 0; i < length; i++) {
            param = this.params[i];
            if (param.property.indexOf(this._linkAttributePrefix) === 0) {
                if (i > 0) {
                    linkAttributes += "&";
                }
                linkAttributes += "{0}={1}".format(param.property, param.value);
            } else {
                if (i > 0) {
                    hashString += "&";
                }
                hashString += "{0}={1}".format(param.property, param.value);
            }
        }


        return hashString + linkAttributes;
    },

    /**
     * If a link attribute property is supplied, strips the prefix and returns just the key. Else just reutrns the key.
     *
     * @returns {string}
     */
    getKey: function (key) {
        if (key.indexOf(this._linkAttributePrefix) === 0) {
            return key.substring(9);
        }
        return key;
    },

    /**
     * Gets a link attribute param by its key.
     *
     * @param {string} key - the key to retrieve (excluding the link attribute prefix)
     * @returns {Object|null} null if no attribute is found with given key.
     */
    getLinkAttribute: function (key) {
        var length = this.params.length,
            param,
            i;

        for (i = 0; i < length; i++) {
            param = this.params[i];
            if (param.property === this._linkAttributePrefix + key) {
                return param;
            }
        }
        return null;
    },

    /**
     * Gets only the parameters that are link attribute parameters.
     */
    getLinkAttributes: function () {
        var linkAttributes = [],
            length = this.params.length,
            param,
            i;

        if (!this.hasLinkAttributes) {
            return linkAttributes;
        }

        for (i = 0; i < length; i++) {
            param = this.params[i];
            if (param.property.indexOf(this._linkAttributePrefix) === 0) {
                linkAttributes[linkAttributes.length] = param;
            }
        }

        return linkAttributes;
    },

    /**
     * Returns a new url including the hash string generated by any parameters.
     *
     * @returns {string}
     */
    getHashedUrl: function () {
        var url;

        if (this.url.indexOf("#") !== -1) {
            url = this.url.substring(0, this.url.indexOf("#"));
        } else {
            url = this.url;
        }

        return url + this.getHashString();
    },

    /**
     * Sets a link attribute param.  If attribute already exists, updates it.
     *
     * @param {string} key - The key of the link attribute (exluding link attribute prefix).
     * @param {string} value - the value of the link attribute.
     */
    setLinkAttribute: function (key, value) {
        var param = this.getLinkAttribute(key);
        if (param !== null) {
            param.value = value;
        } else {
            this.params[this.params.length] = { property: this._linkAttributePrefix + key, value: value };
        }
        this.hasLinkAttributes = true;
    }

}

Now that we have our utility class, let’s write the code that’s going to be doing the real work.

Type.registerNamespace("ContentBloom.Extensions");

/**
 * Represents functionality for adding custom attributes onto links from the Link Popup dialog.
 */
ContentBloom.Extensions.LinkAttributes = {

    /**
     * The table row in the popup containing the key value pairs of links.
     * @type {JQuery}
     */
    attributeContainer: null,

    /**
     * The url input field.
     * @type {JQuery}
     */
    urlField: null,

    /**
     * Initializes the LinkAttributes object.
     */
    init: function () {
        this.customizeLinkObject();
        $jq('#rowTarget').parent().append('<tr id="customAttributes"><td>Attributes:</td><td id="attributeContainer" colspan="4"></td></tr>');

        this.attributeContainer = $jq('#attributeContainer');
        this.urlField = $jq("#FieldUrl");
        
        this.insertKeyValuePair();
        this.initValues();
    },

    /**
     * Initializes input field values on the gui (url, link attributes...).
     */
    initValues: function () {
        var hashParams,
            linkAttributes,
            attribute,
            length,
            i,
            oldLink = window.dialogArguments && window.dialogArguments.link ? window.dialogArguments.link : {};


        if (oldLink.href) {
            hashParams = new ContentBloom.Extensions.HashCollection(oldLink.href);
            linkAttributes = hashParams.getLinkAttributes();
            length = linkAttributes.length;

            if (hashParams.hasLinkAttributes) {
                this.attributeContainer.html('');
                for (i = 0; i < length; i++) {
                    attribute = linkAttributes[i];
                    this.insertKeyValuePair(hashParams.getKey(attribute.property), attribute.value);
                }
                hashParams.clearLinkAttributes();
                this.urlField.val(hashParams.getHashedUrl();
            }
        }
    },

    /**
     * Gets an array of custom attributes based on text fields in the gui.
     *
     * @returns {object[]}
     */
    getCustomAttributes: function () {
        var attributes = [];

        $jq('div', this.attributeContainer).each(function(index, element) {
            var key = $jq('.key', element).val(),
                value = $jq('.value', element).val();

            if (key) {
                attributes[attributes.length] = { key: key, value: value };
            }
        });

        return attributes;
    },

    /**
     * Inserts a new key value pair of text fields into the attribute container.
     *
     * @param {string=} key - The optional key to set the key input field to.
     * @param {string=} value - The optional value to set the value input field to.
     */
    insertKeyValuePair: function (key, value) {
        if (key == undefined) {
            key = "";
        }
        if (value == undefined) {
            value = "";
        }
        this.attributeContainer
            .append('<div><label>Key</label><input class="key" type="text" value="' + key + 
                '" /> <label>Value</label><input class="value" type="text" value="' + value + '" /></div>');
    },

    /**
     * Modifies the Link._buildNewLinkHtml method to also apply the custom link attributes. Monkey patches, so existing method is still used and not replaced.
     */
    customizeLinkObject: function () {
        var self = this,
            originalFn = Tridion.Cme.Views.Link.prototype._buildNewLinkHtml;
            

        Tridion.Cme.Views.Link.prototype._buildNewLinkHtml = function Link$_buildNewCustomLinkHtml() {
            var link,
                hashParams,
                attributes = self.getCustomAttributes(),
                attribute,
                i;

            originalFn.apply(this);

            hashParams = new ContentBloom.Extensions.HashCollection(this.properties.NewLink.href);
            hashParams.clearLinkAttributes();

            for (i = 0; i < attributes.length; i++) {
                attribute = attributes[i];
                hashParams.setLinkAttribute(attribute.key, attribute.value);
            }

            this.properties.NewLink.href = hashParams.getHashedUrl();
        };
    }
};

// We call init only after document has loaded.
$jq(function () {
    ContentBloom.Extensions.LinkAttributes.init();
});

On Load

It’s important to notice that at the end of this script, we are making a call to our ContentBloom.Extensions.LinkAttributes.init() method only after the document loaded. If we were to call the init method immediately, we’d get some unexpected results.

initValues

You’ll notice here that we check if this is an existing hyperlink that has already been given link attributes. If the hash string contains our specially marked values, we’ll populate or key/value fields appropriately.

Future Proofing

You’ll also notice that although we are only displaying a single set of a key/value pair, our code is already setup to handle multiple sets.

Monkey Patching _buildNewLinkHtml

Here’s another cool trick that we are doing… we are adding functionality to the existing Tridion.Cme.Views.Link.prototype._buildNewLinkHtml method without actually modifying any of the existing code (which would be bad). We’re using a little closure magic here in our customizeLinkObject method to create a copy of the existing function, overwrite the existing function, and ensure that the original function still gets called prior to our custom code (while ensuring that the context of “this” remains for the Link instance).

The Stylesheet

Not too much going on here at this stage of our extension. We’re just making sure out labels stand out… depending on the browser you are on, this may display nicely or get wrapped onto a second line. We’ll come back in another post to pretty-ify it up a lil more though.

/**
 * Yep, not a too lot going on in here just yet...
 */
#LinkPopup #customAttributes label {
    font-weight: bold; // tempted to throw an !important in there just for you Frank...
}

Configuration

Here’s where everything comes into play that allows us to load our newly created JS and CSS files onto the existing Link Popup page. What we want to do here is create an Extension Group. An Extension Group allows us to extend an already existing Group and add additional files to it. In this case, we want to extend the “Tridion.Web.UI.Editors.CME.Views.Popups.Link” group. Since the configuration of a GUI Extension seems to be the biggest gotcha, I’ll go over the process for this extension piece by piece.

...
<?xml version="1.0"?>
<Configuration xmlns="http://www.sdltridion.com/2009/GUI/Configuration/Merge"
               xmlns:cfg="http://www.sdltridion.com/2009/GUI/Configuration"
               xmlns:ext="http://www.sdltridion.com/2009/GUI/extensions"
               xmlns:cmenu="http://www.sdltridion.com/2009/GUI/extensions/ContextMenu">

  <resources cache="true">
    <cfg:filters />
    <cfg:extensiongroups>
      <cfg:extensiongroup name="ContentBloom.ExtensionGroups.Link">
        <cfg:extension target="Tridion.Web.UI.Editors.CME.Views.Popups.Link">
          <cfg:insertafter>ContentBloom.Resources.LinkAttributes</cfg:insertafter>
        </cfg:extension>
      </cfg:extensiongroup>
    </cfg:extensiongroups>
    <cfg:groups>
     <cfg:group name="ContentBloom.Resources.LinkAttributes">
        <cfg:fileset>
          <cfg:file type="script">/js/libs/jquery.js</cfg:file>
          <cfg:file type="script">/js/HashCollection.js</cfg:file>
          <cfg:file type="script">/js/LinkAttributes.js</cfg:file>
          <cfg:file type="style">/css/LinkAttributes.css</cfg:file>
        </cfg:fileset>
      </cfg:group>
    </cfg:groups>
  </resources>
  <definitionfiles />
  <extensions>
      <ext:editorextensions/>
      <ext:dataextenders/>
  </extensions>
  <commands />
  <contextmenus />
  <localization />
  <settings>
    <defaultpage />
    <navigatorurl />
    <editurls/>
    <listdefinitions/>
    <itemicons/>
    <theme>
      <path/>
      <resourcegroup />
    </theme>
    <resourceextensions>
        <resourceextension>ContentBloom.ExtensionGroups.Link</resourceextension>
    </resourceextensions>
    <customconfiguration/>
  </settings>
</Configuration>

Creating the Resource Group

First we create our <cfg:group> element within the <cfg:groups> element. Our resource group is telling Tridion which resource files to include into our little extension… in our case we are loading our JS and CSS files.

Creating the Extension Group

This is where we inject our resources into an already existing resource group. We’ll need to create a <cfg:extensiongroup /> element within the <cfg:extensionsgroups /> element with a unique name attribute.  Within the <cfg:extensiongroup /> element, you’ll want to create a <cfg:extension /> element that has a target attribute that contains a value of the existing resource group that we want to extend (here we are extending “Tridion.Web.UI.Editors.CME.Views.Popups.Link”).  And within that <cfg:extension /> element, you’ll want to create a <cfg:insertafter /> element that contains the name of the resource group that we created in the above step.

Don’t Forget the Resource Extension!

One final piece to this configuration… in the settings element, we want to create a <resourceextensions> element that contains a <resourceextension> element. The value of this element should be that unique name that we gave to our extensiongroup that we created above.

Deploy!

And there you have the starts of the Link Attributes extension! You will have to deploy the extension as normal… a.) virtual directory creation… b.) Add the editor configuration in System.config… c.) Increment the modification attribute. Open up a component with a rich text field, create a Hyperlink using the Hyperlink button, and you should be in business! You may not yet be able to create actual link attributes, but at least you’ll be able to see your customizations as well as modified href attribute on the links!

Our extension of version 1 in action!

Our extension of version 1 in action!

Conclusions

Although our work for this Link Attributes Extension is not yet complete, you should have now be well armed in case you ever have to customize any of the default popup dialogs.

And for those of you who are actually interested in the completion of this extension, stay tuned for future posts where we’ll work on the TBB, as well as add the ability to add multiple link attributes (and fix up some issues that you may have noticed).

You can find the code to what we’ve done today here.

Stay coding my friends!

Updates

8/21/2014 – Follow up post for building out the Template Building Block

Retrieving, Archiving, and Disposing Messages with Anguilla’s MessageCenter

Inspired by yet another question on StackExchange, I’ve decided to do a follow up on a previous post on using MessageCenter. While in the previous post we focused on the creation and registration of new messages and notifications, today we are going to discuss the retrieval, archiving, and disposing (removing) of messages within the message center. Before we continue, you should know that archiving a message in Anguilla means that is shows up as dark grey (ones that you’ve read) in the list of messages, while disposing means that it doesn’t show up in the list at all.

Retrieving

You can get an Array of all the messages using the getMessages() method of MessageCenter.

$messages.getMessages().forEach(function (message) {
    // do cool things with each message
});

The getMessages method does return ALL messages, including those that are inactive (archived) or disposed. If instead you want to get only the active messages, you could use the getActiveMessages() method.

$messages.getActiveMessages().forEach(function (message) {
    // do cool things with your active messages
});

But what if you wanted to get a list of all disposed messages… or a list of all archived messages? Although we’ll talk about how to dispose and archive individual messages later in the article, one thing to understand is that when a message gets archived, the message.properties.inactive property gets set to true, and when a message gets disposed, the message.properties.disposed property gets set to true. Knowing this, we can now filter our results based on those properties:

$messages.getMessages().forEach(function (message) {
    if (!message.properties.disposed) {
        console.log("This message has not been disposed yet!");
    }
    if (!message.properties.inactive) {
        console.log("This message has not been archived yet!");
    }
    if (!message.isActive()) {
        console.log("The isActive method can also be used to check the .properties.inactive property!  There is no isDiposed one though!");
    }
});

You can even filter on other things, like the type of messages or dates!

$messages.getMessages().forEach(function (message) {
    if (!message.properties.disposed && message.isActive()) {
        if (message.getClassName() === "error") {
            console.log("We have an error message that was given on " + message.getDate() + "!");
        }
    }
});

Archiving

When a message is archived, it shows up as gray in the message center. A message gets archived when you acknowledge it (clicking the “close” button), automatically (like with progress messages), or when you programatically archive it. You can do this with a message’s doArchive() method. The following snippet will archive any active messages that are out there:

$messages.getActiveMessages().forEach(function (message) {
    message.doArchive();
});

Disposing

Disposing of a message makes it so that it doesn’t show up in the GUI list at all. Besides marking the message.properties.disposed property as true, it also seems to clean up some of the other properties and data that existed on the message. So this means that even though the message is still in the list of messages after being disposed, there’s really no useful data on it except for that disposed property. You can easily dispose of a message by calling its dispose() method.

$messages.getActiveMessages().forEach(function (message) {
    message.doArchive();
    message.dispose();
});

You’ll notice that in the above example we are calling doArchive() first. This is VERY important to do, because if you dispose of an active method without first archiving it, the message will remain counted as “unread” in the message center’s counter. This will leave our users in a confused state as they are being notified that they have new messages, but there is nothing in this list.

Show/Hide Archived Messages

You’ve probably noticed the checkbox in your Message Center that allows you to “Show Archived Messages”. Unchecking this box will hide any archived messages in the list. The anguilla API also allows you to view and manipulate this setting.

if ($messages.getIsShowArchivedMessages()) {
   // the checkbox is checked!
   $messages.setIsShowArchivedMessages(false); // but not anymore!
}

And with that I leave you with more power and knowledge to manipulate and bend the Message Center to your will!

Content Bloom Global Summit 2014

Content Bloom Sign

Our sign outside of our Halifax location

This past week has been such an extraordinary week with the rest of the Bloomers at the Content Bloom Global Summit, our annual company event. While last year they held the event in the charming Belgium spot, this year was hosted at the beautiful Halifax location in Nova Scotia. The events were a week long experience, arriving on Sunday and leaving the following Sunday, filled with socializing, team bonding, training, presentations, workshops, demos, and just great fun. Every person got to give a couple of presentations that they specialize in, and the group itself was very active in participating and asking questions. And with a group like Content Bloom, many great questions and conversations were had for each presentation!

Group photo at Peggy's Cove!

Group photo at Peggy’s Cove!

This year’s topics included Tridion Templating, DD4T, Media Manager, Advanced Tridion Architectures, TCDL, Event System, Core Service API Usage, Workflow, and other cool Tridion items. Other presentations included advanced .NET and Java development, Java for .NET guys, front end development, and overviews of the company and its processes in general.

Rob himself talking about Tridion's TCDL tags at the Halifax office.

Rob himself talking about Tridion’s TCDL tags at the Halifax office.

An outdoor presentation by Primmer on Tridion Workflow after a rocky coastline hike! He was threatened to be thrown in if the presentation sucked.

An outdoor presentation by Primmer on Tridion Workflow after a rocky coastline hike! He was threatened to be thrown in if the presentation sucked.

But of course the week wasn’t just about learning, it was getting to know one another and seeing each other face to face. Our evenings were spent going on runs through hilly forests, going on hikes, diving into lakes and freezing cold ocean waters, exploring the local sites and scenery, beach lounging, barbecuing at Nick’s house, and of course being the group that we are, much much pub crawling! There was also some climbing up something they call The Wave, a concrete sculpture that just beckons you to reach the top of it (even though there are signs saying not to climb). I’m sure it was a hilarious sight for any passerby, a large group in the middle of a slightly drizzly night running up a concrete slope and nearly breaking their necks as they slipped and came tumbling back down. I also attempted to skateboard (or long board?) for the first time ever, which resulted in me doing the splits and learning how not to stop yourself while speeding up down a hill.

Some much needed relaxation on a beach after an intense week of presentations and training!

Some much needed relaxation on a beach after an intense week of presentations and training!

Much thanks to John, Nick, Miles and Oksana for organizing and running the event! It was an incredible experience of a week and an honor to be among such a talented and fun group of peers!

Our last night of the summit at "Your Father's Moustache"

Our last night of the summit at “Your Father’s Moustache”

Searching and Modifying Output In Tridion Template Building Blocks With HtmlAgilityPack

In your Tridion career, you’ve probably written countless of C# Template Building Blocks. You’ve probably rolled your own custom Link Resolver, your own “Add or Extract Binaries From…”, your own cleanup templates… dozens of Building Blocks where the goal was to search for specific html patterns and attributes or modify the output in some way. And, if you’re like me, you’ve probably had to write numerous regex expressions to accomplish your tasks. I’ll admit that I’m no regex ninja or anything, its usually through trial and error that I get my regular expressions working correctly. Recently however, I attempted to write an expression that handled nested elements that could go any number of levels deep, with possibility to have several different variations of the pattern that I was looking for. My regex skills was just not good enough, and I thought for sure there must be a better way to parse the html output string of these patterns.

The search was short, but I found exactly what I was looking for: Html Agility Pack

Using HtmlAgilityPack in your Tridion Template Building Block is simple… just reference the DLL, and make sure you install the DLL into the GAC on the CMS and Publishing servers.

Now that we are set up and ready to run, let’s go ahead and write some code that will take the output from the package, and load it into an HtmlDocument.  Your code should look something like the following:

using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Tridion.ContentManager.Templating;
using Tridion.ContentManager.Templating.Assembly;
using HtmlAgilityPack;

namespace CodedWeapon.Samples
{
    [TcmTemplateTitle("HtmlAgilityPack Tester")]
    public class HtmlAgilityPackTester : ITemplate
    {
        private TemplatingLogger _logger = null;

	protected TemplatingLogger Logger
	{
		get
		{
			if (_logger == null) 
                            _logger = TemplatingLogger.GetLogger(this.GetType());

			return _logger;
		}
	}

        public override void Transform(Engine engine, Package package)
        {
            Item outputItem = package.GetByName(Package.OutputName);
            string outputString = outputItem.GetAsString();

            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(outputString);
        }
    }
}

If you’re familiar with XmlDocument, then HtmlDocument should not look so strange to you. All we are doing in the above is grabbing the output, and passing the output (as a string) into our HtmlDocument instance. Simple enough right? And now for the fun… seeing how simple it is to use this library to get the exact elements that we are looking for. Since a lot of the time we work with links, I’ll show some examples of grabbing some anchor tags.

Grabbing Every Anchor

Lets say we want to grab every anchor element present on the output:

HtmlNodeCollection nodes = doc.DocumentNode.Descendants("a");

You can loop over each of the nodes and perform any necessary operations that you want:

foreach (var node in nodes)
{
    Logger.Debug("Found node with url: " + anchor.Attributes["href"].Value); 
}

Just like with XmlDocument, you can also use XPath:

HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//a");

Querying for Specific Elements

You can use XPath to search for the specific elements that you are looking for. For example, if we wanted to match all links with a specific url:

HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//a[@href=\"http://www.example.com\"]");

Or what if we wanted to grab every link that actually contains a title attribute:

HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//a[@title]");

Or better yet… what if we want check any node that does not contain the title attribute at all?

HtmlNodeCollection nodes = doc.DocumentNode.SelectNodes("//a[not(@title)]")

If you’re not a fan of XPath, then you’ll be happy to know that you can also use LINQ like in this following example where we are looking for any link that has an href attribute of “#”:

var nodes = doc.DocumentNode.Descendants()
    .Where(n => n.Attributes["href"] != null && n.Attributes["href"].Value.Equals("#"));

Modifying Output

Normally when we’re searching for elements it’s because we may need to modify the markup in someway, either by adding attributes, removing elements, etc. In the following example, we are adding a custom attribute onto our anchors that contain a value of “#” in the href attribute:

foreach (var node in doc.DocumentNode.SelectNodes("//a[@href=\"#\"]"))
{
    node.Attributes.Add("data-my-custom-attribute", "true");
}

But what if we have a more complicated requirement where we need to strip a <tcdl:ComponentPresentation> tag (but ensure that we keep the inside markup). One may be tempted to try the following:

foreach (var node in doc.DocumentNode.Descendants("tcdl:ComponentPresentation"))
{
    node.ParentNode.RemoveChild(node, true); // this strips the tcdl tag while preserving the children... but its modifying the collection...
}

package.Remove(outputItem);
outputItem.SetAsString(doc.DocumentNode.OuterHtml);
package.PushItem(Package.OutputName, outputItem);

However, while running the above, you’ll get an error that says Collection was modified; enumeration operation may not execute. This is because you cannot modify the collection (add/remove) while enumerating it. But, we can keep a record of the nodes and operate on them after:

List nodesToChange = new List();
foreach (var node in doc.DocumentNode.Descendants("tcdl:ComponentPresentation"))
{
    nodesToChange.Add(node);
}

// Now we strip the wrapping tags...
foreach (var node in nodesToChange)
{
    node.ParentNode.RemoveChild(node, true);
}

package.Remove(outputItem);
outputItem.SetAsString(doc.DocumentNode.OuterHtml);
package.PushItem(Package.OutputName, outputItem);

Hopefully this will end the regex blues out there for anyone who may be struggling!

Reloading/Refreshing Lists in the Shortcuts Area

A recent question on stack exchange regarding the ability to refresh or reload the favorites section using the Anguilla API has sparked this post, where I thought I would expand a little bit on the answer.  What the asker was doing was modifying the favorites (in this case the names), but needed the favorites section to be reloaded in order to reflect these changes. The shortcuts area is within an iFrame, so my first thought was to use some regular JavaScript to reload the iFrame itself via:

$('#FavoritesTree iframe').contentWindow.location.reload(true);

But as the asker of the question realized, this actually causes the entire Tridion GUI to be loaded into the shortcuts area!  Definitely not what we want to do!

Luckily for us, Anguilla has a way for us to unload the lists which will automatically cause them to reload. If we want to refresh all of the lists inside of the shortcuts area, we can do the folllowing:

$models.getItem("cme:userfavs").unloadLists();

So what’s going on in the above statement? $models.getItem("cme:userfavs") retrieves the shortcuts area which is represented by the type Tridion.Cme.Model.FavoritesFolder. This object contains the method unloadLists() which does the magic for us. But suppose you only want to refresh a specific list in the shortcuts area?

$models.getItem("cme:shortcuts").unloadLists();
$models.getItem("cme:custompgs").unloadLists();
$models.getItem("cme:usrtsks").unloadLists();

Again, with each of these items you can just call the unloadLists() method to refresh that specific list. You can even target the child lists of these lists… for example, $models.getItem("cme:chklst"); which would get the Checked-out Items from the My Tasks. If you’re trying to figure out how you can get the identifier to pass to $models.getItem(), a good place to look is the address bar after clicking on the list you wish to target with your code. For example, after clicking on the “Checked-out Items”, you should see “Dashboard.aspx#locationId=cme:chklst” at the end of the address bar. The value of the locationId is what you want to use.

Happy coding everyone!

Back From The Dead

Hello dear friends and fellow Tridionaughts! Too long has it been since my last post, update, and involvement in my beloved Tridion community. But hopefully this post will be the first of many more to come as I crawl forth back from the dead, not as a flesh and brain eating zombie (but how cool would that be right? well… maybe not…), but optimistically as a once again involved blogger and Tridionaught. A lot has happened in my life since my disappearance, but most important of which was the birth of my beautiful daughter Lillian on April 19th. My army of mini-me developers will soon be a reality!

And a farewell to colleagues…

It’s with a heavy heart that I bid farewell to my colleagues and friends at Tahzoo as I travel wide eyed down the road to new and exciting adventures. Leaving behind something that I was a part of for four years was no easy decision, for it was a joy and pleasure to work along side my colleagues who’ve became great friends to me.

And a hello to the new…

With excitement I now say hello and greetings to my new colleagues and family at Content Bloom, a company started and created for similar beliefs and values that I hold myself by fellow Tridionaughts and MVP’s John Winter and Nickoli Roussakov. I’ve had the pleasure of getting to know and talk to John and Nick, as well as some of the other Bloomers, and I am thrilled to be working along side such fellow tech enthusiasts.

I hope to fuel the fires of creativity and innovation with such a company that has the passion and commitment to both technology and the client. Will that mean more blog posts for the Tridion community? Or maybe the birth of Razor Mediator 2.0? Maybe even finishing up some of the blog posts that I’ve started and never got around to wrapping up and publishing.

Or…?

Client Templating For Tridion – Part I

As mentioned in my previous post, the MVP’s actually got down to grind out some work and code during the MVP Retreat. Nuno gives a nice overview of the open source projects in his post. I joined the Client Side Templating team, which was led by Will Price and also included Angel Puntero and Mihai Cădariu. The following post(s) will give some context to the details of that project.

Just show me the code…

If you don’t want to be bored by reading and just want to check out the project itself, feel free to check out the project site’s wiki pages and code.

Current Templating Models

Publish Time Rendering

Currently Tridion works out of the box with a templating model that gets resolved at publish time. If you have used DWT or the Razor Mediator, you have been using this such model. This is great for publishing static content, but what happens when you want to start diving into dynamic content? Sure, you can publish dynamic component presentations. With DCP’s, your presentations are still being resolved at publish time, but you can pull the content in dynamically on your pages via the Content Delivery API.

Dynamic Rendering or Request Time Rendering

Even with using the DCP’s, your development process can still end up feeling very inflexible and hacky at times. That’s where such frameworks such as CWA or DD4T come to the rescue. These awesome frameworks offer an MVC application side templating approach to your projects. Your templates actually live on the application server, and are rendered dynamically at request time. With CWA, your page views are dynamic while your component presentations are still rendered at publish time, and then your views pull the data and presentations in. With DD4T, templating is completely removed from the CMS side of things, and both the page and component views are handled by the application. These frameworks offer a much larger flexability in the way you can architect your projects, and even some added bonuses. For example, with DD4T (and using ASP.NET MVC), you can do your template development inside of Visual Studio using Razor and get the best out of the Intellisense tools. There’s no better feeling than getting drop down help and documentation as you code along with immediate errors and warnings without having to compile the project.

But Why Client Side Templating?

You’ve probably seen a lot of client side frameworks becoming extremely popular as of late. That’s because the benefits of letting the client’s browser handle some (or sometimes all) of the templating for you has numerous benefits. The number one benefit of course is the reduction costs. If you think about it, besides images and other binaries of course, the bulk of the size of your requests isn’t necessarily the data you are serving, but rather the markup itself. Most of your pages probably have the exact same markup, and only the data its relaying is different. Say if you had a 1000 Product pages. If your visitor visits each of those pages, your server has had to render the exact same markup for that page, and serve it, 1000 times, even though its pretty much the exact same page. But what if instead you only loaded the markup in the form of a template once, cache it, and for each of the 1000 pages only send your customer the raw json data needed for the page? You’ve now not only saved on bandwidth costs, you’ve saved on processing that your server would normally have to do for template rendering, and you’ve most likely decreased your web applications load time. Awesome, right?

CT4T

And that’s where CT4T comes into the picture. It’s a set of tools and API’s to easily add client side templating to your projects. The best part is, because its just JavaScript, its flexible enough to allow you to mix and match the different publishing models to meet your project needs. You can continue using your DD4T or CWA project if you wish to and just augment it with some client side goodness. Or you can mix a bit of old fashioned static pages published from Tridion with client side templating. Or you could be daring and create a completely new single page web application. The choices are yours.

To Be Continued…

In my next post I will discuss a bit about the architecture and decisions that were made so far. Remember, this is an open source community project, and if you feel like this is something that interests you, or better yet something that your client would love to have, I encourage you to contribute and join in. The CT4T project is still in its infancy and has a long way to go, and you have the chance to participate to create something great.

Join Us!