Hello Alchemy4Tridion

About a year ago I made a move to join the folks at Content Bloom, and although change is something that usually terrifies me, I don’t think there could have been a better choice that I could have made. There is nothing that I can say that would do any justice at describing this past year. My colleagues are all legends and some of the most brilliant, enthusiastic, and encouraging people that I have worked with. I’ve gotten to participate on some exciting projects at various clients. I’ve gotten to hang out with my fellow Bloomers at Global Summits, MVP Retreats, Tridion User Groups, and other events. And for the first time, I got to witness the all too often loosely thrown around phrase of “we culture innovation and creativity in our employees” to be held true to the highest extent. So rather than continuing to hear me gush on about the company that I work for, let me introduce the latest project that we at Content Bloom are proud to present…

What is Alchemy4Tridion?

There’s been a lot of features and thought put into Alchemy4Tridion (aka A4T) and there’s even more on the roadmap that we have planned for it, which makes describing what A4T is and isn’t a little difficult as it continues to grow and evolve.

Plugin Framework

At its core, Alchemy4Tridion is a plugin and extension framework that allows developers to create plugins that extend Tridion through its various extension points, while removing a lot of the pain and frustration that most developers face while attempting to hack away at building one (see Frank’s description of how he feels this process works in the GUI Extensions are so… section of his article). The framework does away with a lot of the boiler plate code and configuration hell while trying to be as direct and to the point of “just the minimum amount” as to what items your plugin actually requires.

It also introduces new ways to install your extensions which in turn reduces the amount of time needed… one click installation of plugins from the Web Store (see Repository section) or drag and dropping plugin files right to the GUI.

Plugin Manager

Alchemy4Tridion manages all of the plugins that are installed, allowing you to easily view, configure, and uninstall plugins without leaving the comforts of the Tridion GUI.

Plugin Repository

The Alchemy Web Store introduces a repository collection of all plugins that are available for Alchemy4Tridion. Developers can upload their plugins, and organizations can then search and find the plugins that they wish to have in order to transmute Tridion into the Content Management System of their dreams. Depending on the network an organization is behind, users can also search the plugins via Alchemy4Tridion as well as click the “Install” button to install them!

Review System

To provide a level of trust to organizations, plugins uploaded to the Web Store must go through a review process before they are publicly listed and downloadable. This initial set of reviews is done by the Alchemy team itself. The Web Store also provides means so that users can both rate and provide comments on plugins that are available for your research, as well as metrics to see how popular certain plugins are and how many others are using it.

Community

More than just a framework and web store, Alchemy is a community of developers, partners, customers, and end users working together to ensure not only quality of plugins found at the Web Store but growth of the framework and tooling. More features are on the road map to make contributing and participating in the community both fun and rewarding.

Implementation & Development Tooling

Last but not least, Alchemy4Tridion can be considered a tool set to aid developers in Tridion implementations to streamline their efforts for more efficient results and best practices, whether it be from plugins that provide the features they need or via Visual Studio extensions (planned features) to provide tools and utilities to assist, validate, and automate their development efforts.

Join the Community

Stay tuned for more articles about Alchemy! And if any of this seems interesting so far, it would be a pleasure to have you join the community in our current testing phase to help Alchemy progress towards its 1.0 release.

To download, please register at www.alchemywebstore.com and then click the download link in the right hand column. Then just run the MSI installer on your CMS instance!

To read up more on Alchemy4Tridion usage, walk throughs, and APIs, check out our online documentation!

Or if you just want just want to see an example of a plugin project, check out our HelloWorld sample or explore the A4T project on GitHub.

Related Links

Announcing the Alchemy Web Store
Installing Alchemy4Tridion

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!

Razor Mediator Version 1.3.2 Released

A new minor version of the Razor Mediator has been released and can be found at the Google Code site. For anyone who is interested in installing Razor Mediator on Tridion 2013, you will find this release especially important to you. Prior to this version, the Installer will throw an error and the installation will fail.

Besides being able to install without an error, this version also changes how the configuration is done slightly. Prior to 1.3.2, the template ID for Razor Mediator was generated during installation by selecting the highest free available ID. With an out of the box Tridion setup, this would normally have resulted in a template type of “8″. Tridion 2013 comes with a new XSLT Mediator, but they have left the ID’s of 8 and 9 empty. So, installation for 1.3.2 will attempt to use 8 if its available, otherwise it’ll pick the ID just like it use to do.

An important thing to note is that you may have to manually modify this ID if you are porting from another system that used a different ID for the Mediator. If the ID’s don’t match up, you will get an error during the content porter process.

Thanks to Nicholas Vander Ende, Frank Taylor, and Piti Itharat for reporting and troubleshooting the installation error in 2013. A special thanks to Nicholas for actually finding the fix to the problem as well.

ComponentPresentations and ComponentTemplateModel

Thanks to Chris Curry for spotting that ComponentPresentationModel’s Template property was not returning a ComponentTemplateModel type, but rather just the Tridion’s ComponentTemplate type. This means you would have to grab the ItemFields in order to fetch fields. This version fixes the CompoenntPresentationModel’s Template property.

@foreach (var cp in ComponentPresentations) {
    <div>cp.Template.Fields.FieldName</div>
}

Get Version From Template

Although you can get the Razor Mediator version by looking at the Tridion.ContentManager.config file, sometimes you may not have access to the server and need another quick way to get the version. 1.3.2 comes with a “Version” property in the base template that you can output to check the version.

    <div>Version: @Version</div>

Models.GetComponentTemplate

The ModelUtilities class now comes with a GetComponentTemplate method to easily pull out ComponentTemplateModels.

@{
    var ct = Models.GetComponentTemplate("tcm:1-2345-32");
}

Thanks again to everyone for your feedback and suggestions!

Razor Mediator Version 1.3.1 Released

The next version of the Razor Mediator, version 1.3.1, is now up and ready to be downloaded at its Google Code site, and its updated documentation can be found here. This update includes mainly fixes for issues reported, though it does include a couple of new (or features that were missing) features as well. If after upgrading to version 1.3 and you have been receiving errors while attempting to do imports from the configuration, this may be the release for you!

Fixes for Imports

As mentioned, version 1.3 caused some issues when trying to use the new Where Used functionality along with global imports from the configuration. Errors included messages similar to “tcm:0-234-2048 does not exist”.

Fixes to Documentation

Thanks to Robert Curlette for supplying documentation that removed the smart quotes from the code samples in the documentation. The new version of the documentation and forward will be based off of his ascii documentation.

Non-Cache DynamicPackage

Thanks to Dominic Cronin for supplying a patch for the DynamicPackage to make it not cache the package’s values. This will ensure you don’t run into issues if the context of those package items gets changed during the razor’s scope.

Indexes for DynamicItemFields and DynamicPackage

You can now access fields for ItemFields and for the Package using indexers. This can help when creating generic templates, or when working with package items that contain dots in the item names.

@Fields["FieldName"]
@Package["SomeName"]
@Package["Some.Name.With.Dots"]

GetFields and GetFieldNames

Also to assist with the creation of making generic templates, DynamicItemFields now has a GetFields() and a GetFieldNames() method. GetFieldNames() returns an array of strings containing the field names, while GetFields() returns the underlying Dictionary<string, object> that represents the ItemFields.

@foreach (string name in @Fields.GetFieldNames()) {
    <span><strong>@name</strong>: @Fields[name]</span>
}

// var field is of type KeyValuePair<string, object>
@foreach (var field in @Fields.GetFields()) {
    <span><strong>@field.Key</strong>: @field.Value</span>
}

Fix to IsSiteEditEnabled

The IsSiteEditEnabled property would throw an error when working with a Publication Target that never had its Site Edit enabled or disabled yet. This is now fixed.

ParentKeywords and RelatedKeywords

These properties have been added to the KeywordModel, and both return a List of KeywordModel’s.

@foreach (var kw in @Fields.SomeKeyword.ParentKeywords) {
    <span>@kw.Title</span>
}

Thanks again to everyone who has been posting suggestions, issues, and fixes. Special thanks to Robert Curlette who has been the guinea pig for most of these updates!

Razor Mediator Version 1.3 Released

Its taken much longer than I had originally anticipated, but the next version of the Razor Mediator has just been submitted to the SDL team. As usual, you can find the installer for this package here and the updated documentation for this version here. So what exactly is new in this package?

Working Where Used Functionality

Have a lot of Razor Imports and sad because its hard to track down where exactly they are used? Or have the blues from Content Porting since you have to make sure you CP your import files first before the rest of your Razor Templates? The great news is that with Version 1.3, including razor imports either through importRazor(“path”) statements or via the razor.mediator configuration setting will now finally be set as a reference so that these imports show up as Where Used! Just in case for some odd reason you absolutely do not like this features, there is also a new element in the config called “importSettings”, which allows you to turn on/off this feature. You can even turn on/off imports from importRazor statements and imports from the configuration separately.

<importSettings includeConfigWhereUsed="true" includeImportWhereUsed="true" replaceRelativePaths="false" />

The Where Used won’t kick in automatically once you upgrade to this version… you’ll have to Save your Razor Templates for the references to take place.

Relative WebDav URLs Supported!

Thanks’s to Will Price for suggesting this one! Import statements in version 1.2 only accepted TcmUri’s and full lengthy WebDav URL’s. As of Version 1.3, you can now supply relative WebDav URL paths! These paths to the imports are relative to the Razor Template that’s calling them.

@importRazor("Same Level Helper Functions.cshtml")
@importRazor("./Also Same Level Helper Functions.cshtml")
@importRazor("My Helper Functions/Global Helper Functions.cshtml")
@importRazor("My Helper Functions/Nav Functions/Main Navigation Functions.cshtml")
@importRazor("../Previous Level Functions.cshtml")
@importRazor("../../Even More Previous Level Functions.cshtml")

You probably noticed that “replaceRelativePaths” attribute in the previous importSettings config example? When set to “false” (out of the box setting), your relative import paths will stay relative. But if for some reason you would like these to automatically be transformed into the full WebDav URL, set this to true and upon saving your templates, the paths will be turned into the full paths.

Site Edit Enabled!

Razor Mediator 1.3 now includes a property named “IsSiteEditEnabled”. As the name may suggest, this property will check the Publication Target you are publishing to to see whether or not SiteEdit (inline editing) is enabled. This is useful for when you want to render specific HTML (like regions!) only for your editable staging site. This only works for Tridion UI 2012, and not previous versions of SiteEdit.

@if (IsSiteEditEnabled) {
    <strong>This page is SiteEdit Enabled!</strong>
}

Also added by default to version 1.3 is an enhancement request that came in for the RenderComponentField methods. Prior to Version 1.3, these methods would throw an error if the field was empty. Now these methods will spit out an empty tcdl tag. You can also have it spit out an empty string by using the new last argument to this field. The following is assuming that “Fields.FieldName” is empty.

@RenderComponentField("Fields.FieldName", 0)
@RenderComponentField("Fields.FieldName", 0, false)

In the first example, an empty tcdl tag like <tcdl:ComponentField name=”Fields.FieldName” index=”0″></tcdl:ComponentField> will get output so that you still have a section on your staging page for adding text to this area. The second example will just output an empty string and no tcdl tag.

Template Models

Version 1.3 now includes Template Models for the quick and easy access to template metadata that you have grown use to! @ComponentTemplate (accessible only from Component Templates), @PageTemplate and @Page.PageTemplate (accessible only when there’s a page that’s accessible), and @RazorTemplate (the Razor Template Building Block itself).

@if (IsComponentTemplate) {
    <span>@ComponentTemplate.Metadata.FieldName</span>
}

@if (Page != null) {
    <text>The following can be accessed from both Page Templates and Component Templates (only when a Page object is available though)</text>
    <span>@PageTemplate.Metadata.FieldName or @Page.PageTemplate.Metadata.FieldName</span>
}

@if (IsPageTemplate) {
    <text>While this example will only work when its a Page Template.</text>
    <span>@PageTemplate.Metadata.FieldName or @Page.PageTemplate.Metadata.FieldName</span>
}

<span>@RazorTemplate.Metadata.FieldName</span>

Other Changes and Fixes

For a full list of updates and fixes that were made in this version, please check out the Change Log.

Thanks To You

And I just wanted to personally thank everyone who has supported this project through kind comments, reporting issues, bugs and suggestions, and for testing the many features. This project definitely would not have come this far without you guys. :)

Razor Helpers and Functions

Now that the latest version of the Razor Mediator for Tridion (v 1.2) allows you to import, I thought perhaps it would be helpful if I go over the Razor syntax for creating Razor Helper functions, or just adding normal functions in general.  Using these in your Razor Templates greatly enhances the functionality that you can perform in your templates.  On my latest project at a client, we developed some pretty advanced templates, and I found myself thinking (and shuddering) of how it would have to have been done if done using Dreamweaver templating.

Normal Functions

You can declare functions in your Razor code and call it just like you were calling any of the built in functions using @functions. Remember, you can have as many @functions sections as you want in your templates, you are not just limited to one.

@functions {
    public string HelloWorld(string name) {
        return "Hello " + name;
    }
}

The above defines a function called “HelloWorld” that you can start using in your template code (or callable in other functions).

<strong>@HelloWorld("John")</strong>

Helper Functions

Helper Functions are also callable via your template code, however Helper Functions allows for Razor syntax and HTML markup from within the function itself.

@helper HelloWorld(string name) {
    <div>Hello <em>@name</em>!</div>
}

The Helper Function would then work exactly the same as the non-Helper Function created above.

<strong>@HelloWorld("John")</strong>

You can mix and match your functions as well.  Following is just a couple more quick examples.

@functions {
    public string GetExtraAttributes(Models.ComponentModel component) {
        string attributes = String.Empty;
        if (component.Metadata.KeyValuePairs != null) {
            foreach (var values in component.Metadata.KeyValuePairs) {
                attributes += String.Format("{0}="{1}"", values.Key, values.Value);
            }
        }
        return attributes;
    }
}

@helper RenderComponentLink(Models.ComponentModel component) {
    <a href="@component.ID" @GetExtraAttributes(component)>@component.Title</a>
}

@helper WrapInParagraphTags(string input) {
    if (input != null && !input.StartsWith("<p")) {
        <p>@input</p>
    } else {
        @input
    }
}

Razor Mediator 4 Tridion Version 1.2 Released

Hi readers! My apologies for the lack of blog writing over the past month, but things have been extremely busy on the development front. After much feedback and help with testing from colleagues, I am pleased to announce the release of the Razor Mediator Version 1.2. I put it up for submission to SDL Tridion World yesterday, but if you can’t wait, you can also grab it at its Google Code Project Site. There are a lot of fixes, updates, and new features added to this version, so you can probably expect some more posts coming soon with even more examples and tutorials than what is in the new updated documentation. So, what are some of these new updates and features you ask?

Ability to Import!

That’s right, you heard correctly. You can now import other Razor Template Building Blocks or even external Razor Templates stored in text files on the CMS Server to your templates. You can do this globally via the configuration to allow you to import templates to all of your Razor Templates (or even to all of your Razor Templates in one publication), or at the Template level via the @importRazor(“PathToYourTemplate”) command.

Updated GetComponentPresentationsByTemplate and GetComponentPresentationsBySchema

These methods have been updated to accept multiple parameters. So, that means you can now do something like:

@foreach (var cp in GetComponentPresentationsByTemplate("Template One Name", "Template Two Name", "Template Three Name")) {

}

RenderComponentPresentations and RenderComponentPresentationsByTemplate

Two new utility methods have been added to the base template that allows you to just render templates quickly. RenderComponentPresentations() will render all ComponentPresentations on the Page, while RenderComponentPresentationsByTemplate(param string[] templateNames) will only render the ComponentPresentations given the passed template names.

<div id="allTemplates">
    @RenderComponentPresentations()
</div>
<div id="someTemplates">
    @RenderComponentPresentationsByTemplate("Template One Name", "Template Two Name")
</div>

Better Compile Error Message

When you receive a compile error upon saving your Razor Template, you will now be shown a more informative message that also displays the line of code in question. Remember though, that the line of code that is shown is the generated C# code, and not your actual Razor code. This means that “<span>@blasadsfdsaf</span>” would show the line as being “Write(blasadsfdsaf);”. Also, the error displayed to the user in Tridion will no longer include Warning messages.

Index, IsFirst, and IsLast Properties

To further help you write cleaner code, the properties “Index”, “IsFirst” and “IsLast” has been added to the ComponentPresentationModel, ComponentModel, KeywordModel, and DynamicItemFields classes. For ComponentPresentationModels, these properties are automatically set when accessing the ComponentPresentations via the ComponentPresentations property of the base template, or by either of the GetComponentPresentationsByTemplate() or GetComponentPresentationsBySchema() methods.

@foreach (var cp in ComponentPresentations) {
    @if (cp.IsFirst) {
        <div>@cp.Component.Title is the first item.
    } else if (cp.IsLast) {
        <div>@cp.Component.Title is the last item.
    }
}

For ComponentModel and KeywordModel, these properties are automatically set when accessing them via the DynamicItemFields (that is, when they are set as a multi-valued field of course).

@foreach (var kw in Component.Fields.SomeKeywords) {
    <div class="@(kw.Index % 2 == 0 ? "alt1" : "alt2")">@kw.Title</div>
}
@foreach (var comp in Fields.SomeComponents) {
    @if (comp.IsLast) {
        <span>We only wanted the last ComponentLink item!</span>
    }
}

These properties are automatically set for DynamicItemFields when it is accessed via the DynamicItemFields as a multi-valued EmbeddedSchemaField.

@foreach (var embeddedFields in Fields.SomeEmbeddedFields) {
    <div class="@(embeddedFields.IsLast ? "last" : String.Empty)">@embeddedFields.Address (@embeddedFields.ZipCode)</div>
}

Quick Access to Debug Writing

Pre version 1.2, the Razor Mediator documentation said that you could write logging statements like @Log.Debug(“Your Message”). This was actually incorrect and would have thrown an error… you would of had to write them as @{ Log.Debug(“Your Message”); }. As of version 1.2, you can now do @Debug(“Your Debug”), @Info(“Your Info”), @Warning(“Your Warning”), and @Error(“Your Error”).

Of Fixes and More

There are a couple other minor goodies that have been added, as well as some critical fixes and updates that include caching and thread safety (for publishing) fixes. If you are already using a previous version of the Razor Mediator, I would definitely recommend updating to version 1.2. You can view the Change Log on the Google Code project for a full list of all the updates and fixes made in this release.

Version 1.3?

Yes, a Version 1.3 is now in the works, with even more helpers/utilities to make your templating life a bit less difficult, and some more features to help empower your abilities. And of course any more bugs or issues that you report to me. Much thanks to you for your feedback!

Razor Mediator Version 1.1 Released

I am happy to announce that the Razor Mediator version 1.1 has been submitted to SDL to be updated on SDL Tridion World. In the meantime, you can grab the v1.1 installer at its Google Code project site. So what exactly has been updated since version 1.0?

KeywordModel

Just like the other Tridion items that have been wrapped in Model classes, the Keyword class has finally been wrapped as well.  This means you can easily access a Keyword’s metadata just like the other objects as well.

@Fields.SomeKeywordField.Metadata.SomeMetaField

All fields that would normally return a KeywordField will now return a KeywordModel (or List<KeywordModel> for multi-valued fields).  The @Models.GetKeyword(string) method also returns a KeywordModel.

GetComponentPresentationsBySchema(string schemaName)

A new utility method has been added to the base template class to allow you to easily grab all Component Presentations filtered by the component’s schema title.

@foreach (var cp in GetComponentPresentationsBySchema("Article Schema")) {
    <text>@cp.RenderComponentPresentation()</text>
}

DynamicPackage

This feature was listed in version 1.0 but wasn’t actually used.  All calls to the @Package property was really just returning the Tridion Package instance.  So, what exactly can you do with DynamicPackage?  You can easily grab package items and parameter values in your templates using dot notation.  For backwards compatibility, GetByName(string name), GetByType(ContentType type), and GetValue(string name).  The following would all work assuming there was either a package item of a string type named “ItemName” or a parameter field with xml name of “ItemName”.

@Package.ItemName
@Package.GetByName("ItemName").GetAsString()
@Package.GetValue("ItemName")

And some fixes…

Besides the several new features, some fixes were also added.  For a complete list of fixes, feel free to check out the Change Log page.  If you come across more issues, please let me know so that I can get them fixed ASAP. And if you have some cool ideas for possible features that you want to see in the Razor Mediator, shoot them over as well.

What’s Next?

I’m planning on putting an easy way to extend your Razor templates using reusable helper methods and functions.  So look forward to version 1.2 coming out sometime in the hopefully near future, as well as another blog post to show you examples of creating helper methods in your current templates!

Razor Mediator: Including Code From An External DLL

The Razor Mediator allows you to easily add other libraries and namespaces to your Razor templates.  Due to an issue found with version one, you will want to get the latest installer from the Google Code site (or just download the latest version 1.0.1 installer here).  If you have version 1.0.1 or later you should be in the clear and hopefully problem free.

For this tutorial we’re just going to create a really simple class in a separate project and then allow our Razor templates to use that new class. After this tutorial you should have a pretty good understanding of how to use the namespaces and assemblies sections of the razor.mediator configuration.

First, create a new project in Visual Studio.  We’ll name it “RazorSample.Test”.

Next, create the following class in your new project.

namespace RazorSample.Test
{
    public class MyClass
    {
        public static string Hello()
        {
            return "Hello World!";
        }

        public static string Hello(string name)
        {
            return "Hello " + name + "!";
        }
    }
}

I did mention that it would be a simple class! We’ve got two methods and I think it can be safe to assume that I don’t need to explain what they do. Compile the project, and copy the newly created DLL to your CMS server. For this tutorial, I’ve put mine at C:MyLibraryRazorSample.Test.dll.

Next you’ll want to modify the Tridion.ContentManager.config file located at %Tridion Install Path%config on your Tridion CMS server. Search for the razor.mediator section, then update the assemblies section to add your newly created DLL.

<assemblies>
      <add assembly="C:MyLibraryRazorSample.Test.dll" />
</assemblies>

Go ahead and restart the Tridion COM+ package.

Now go ahead and create a new Template Building Block in Tridion called “Razor Test” (or whatever you wish to name it). From the Source tab, make sure to select “RazorTemplate” for the Template Type, and then add the following:

<div>@RazorSample.Test.MyClass.Hello()</div>
<div>@RazorSample.Test.MyClass.Hello(Component.Title)</div>

Save and close. Now go ahead and open up Template Builder and create a new Compound Component Template. Add the “Razor Test” building block from the previous step, and then Run the template using any Component. You should see both of your newly created method returns in the output:

<div>Hello World!</div>
<div>A Test Component!</div>

Congratulations, you’ve just executed code from another assembly in your Razor Template! That’s great and all, but what if you want to load an assembly that’s in the GAC instead of on the file system? In order to get our sample assembly into the GAC, we’ll have to give it a strong name key. If you are unfamiliar with this, just proceed with the following steps.

  1. Right click the RazorSample.Test project in the Solution Explorer of Visual Studio and click Properties.
  2. In the left column, select the Signing tab.
  3. Check the “Sign the assembly” check box.
  4. In the dropdown underneath the “Choose a strong name key file:” label, select “”.
  5. In the Create Strong Name Key dialog, enter “RazorSample.Test” for the Key file name and uncheck the “Protect my key file with a password” option.
  6. Click OK.
  7. Save the project and recompile.

Once you have your RazorSample.Test project recompiled with a Strong Name Key, deploy it to the GAC on the Tridion server. For simplicity, I made the RazorSample.Test project target .NET 3.5 so that I could just drag and drop the DLL to C:Windowsassembly. Once your DLL is deployed to the GAC, edit the assemblies section in the Tridion.ContentManager.config file to look like the following.

<assemblies>
    <add assembly="RazorSample.Test, Version=1.0.1.0, Culture=neutral, PublicKeyToken=60ad7434f03dfcdc" />
</assemblies>

You’ll notice that the only difference in the config between loading it from the GAC and from the file system is that the file system is an absolute file path while the GAC is the fully qualified name.  Remember, your Public Key Token will be different than the one you see above.  If you are unsure how to get the Public Key Token, find your DLL at C:Windowsassembly (assuming that DLL is .NET 3.5 or less).  You’ll see the info you need in the Public Key Token column, or by right clicking your DLL and selecting Properties.  Once the configuration is saved, restart the Tridion COM+ package again. Running the template in Template Builder again should result in the same output.

Now what about the namespaces section? Well unless you don’t mind using the full namespace to your classes, this is where that section will come in hand. Reopen the Tridion.ContentManager.config file once again and modify to look like the following.

<namespaces>
  <add namespace="RazorSample.Test" />
</namespaces>
<assemblies>
  <add assembly="RazorSample.Test, Version=1.0.1.0, Culture=neutral, PublicKeyToken=60ad7434f03dfcdc" />
</assemblies>

After restarting the Tridion COM+ package, reopen your “Razor Test” Razor Template and modify the razor to look like:

<div>@MyClass.Hello()</div>
<div>@MyClass.Hello(Component.Title)</div>

Save and close. Restart the Tridion COM+ package. Retest your template. And enjoy the bliss of adding your own useful functionality to your Razor templates.