Monday, January 30, 2012

The content blues

Editors are very hard to keep happy. From where I stand, most of my direct interaction points with our customers are usually System Administrators, Architects and/or Developers.

These people are "easy" to satisfy when you're dealing with a system with so many Enterprise features and extension points as SDL Tridion. The questions or scenarios I get asked to expand on will typically boil down to either something that Tridion does already, or something that we can extend Tridion to do.

I have however found the hardest Tridion user of all. The content editor. And that person, my friends, is none other than myself.

Since August last year I started publishing (with Bart Koopman's help) a weekly review of all activities that are in anyway Tridion-Community-related. The schema for this content, as you may expect if you look into it, is rather simple and made up of a few RTF fields alternated with some (limited) taxonomy and multimedia links to the images displayed.

So far nothing abnormal. Content creation is relatively painless, since I have my RTF and I put in it whatever I want.

The problem is when I start thinking about what to do now that I beat my own expectations and this weekly review is becoming an actual important starting point of every week, and there's quite a lot of data in it begging to be mined. To start with, I will need to start archiving this data in an easy to browse archive. Having all content together will eventually become unwieldy (some may argue it's already the case).

The 2nd problem is my own demands on my own data. I want, for instance, to rapidly know which week was the busiest week (week with the highest number of links to blogposts/content). Well, I can't, because I'm using RTF.

I also wanted to do a "Top 10" for the great people in the community that contribute so much every week. Again - almost impossible to do other than counting manually, since the content is in RTF.

Next, I may want to dig into all posts that relate to Event System. Or Deployer Extensions. Or Storage Extensions. Or WCF. You catch my drift, right?

What's the solution?

The solution is in making the Editor's life a living hell. Instead of the ~15 minutes it takes me now to create a weekly review (mostly it's Bart), it will probably turn into an hour long exercise at least. Every entry mentioned in there should actually be its own component, and this component should be classified by Author, Publish/Creation Date, Topic(s), Source. I would normally have no problems presenting to my customer the fact that if you want your content to be rich, YOU need to enrich it.

But it hurts when the customer is me...

Thursday, January 12, 2012

It's the little things: Creating a page breadcrumb in Tridion

Your mileage may vary, but in most cases (> 90%) pages published from Tridion will include a breadcrumb that is linked to the page's location in the site (i.e., Structure Groups).

Typically I see this breadcrumb being generated in the front-end from the Sitemap, which is not a very expensive operation but still a very silly approach. If an editor creates a page in a given Structure Group and publishes it, we already know the page's location at publish time, so why would we waste some CPU cycles on the front-end determining this?

Since I had to write this Building Block just now I thought I could share it with you.

using System.Text.RegularExpressions;
using System.Xml;
using Tridion.ContentManager;
using Tridion.ContentManager.CommunicationManagement;
using Tridion.ContentManager.ContentManagement;
using Tridion.ContentManager.Templating;
using Tridion.ContentManager.Templating.Assembly;

namespace TridionTemplates
{
    public class GetPageBreadcrumb : ITemplate
    {
        private const string _separator = " » ";
        private const string RegexPattern = @"^[\d]* ";
        private const string indexPagePattern = "index";
        private readonly Regex _regex = new Regex(RegexPattern, RegexOptions.None);

        public void Transform(Engine engine, Package package)
        {
            TemplatingLogger log = TemplatingLogger.GetLogger(GetType());
            if (package.GetByName(Package.PageName) == null)
            {
                log.Info("Do not use this template building block in Component Templates");
                return;
            }

            Page page = (Page)engine.GetObject(package.GetByName(Package.PageName));

            string output = StripNumbersFromTitle(page.OrganizationalItem.Title);
            foreach (OrganizationalItem parent in page.OrganizationalItem.GetAncestors())
            {
                output = GetLinkToSgIndexPage((StructureGroup)parent, engine.GetSession()) + _separator + output;
            }

            package.PushItem("breadcrumb", package.CreateStringItem(ContentType.Html, output));
        }

        private string StripNumbersFromTitle(string title)
        {
            return Regex.Replace(title, RegexPattern, string.Empty);
        }

        private string GetLinkToSgIndexPage(StructureGroup sg, Session session)
        {
            OrganizationalItemItemsFilter filter = new OrganizationalItemItemsFilter(session);
            filter.ItemTypes = new[] { ItemType.Page };
            string title = StripNumbersFromTitle(sg.Title);
            string pageLinkFormat = "<a tridion:href=\"{0}\">{1}</a>";
            string result = null;
            foreach (XmlElement page in sg.GetListItems(filter).ChildNodes)
            {
                if (page.Attributes["Title"].Value.ToLower().Contains(indexPagePattern))
                {
                    result = string.Format(pageLinkFormat, page.Attributes["ID"].Value, title);
                    break;
                }
            }
            if (string.IsNullOrEmpty(result))
            {
                result = title;
            }
            return result;
        }
    }
}

Yes, it does quite a lot more than just create a breadcrumb, I'll leave it up to you figure that out :)

PS - I am using OrganizationalItem.GetAncestors() in this code, which means it will only work with SDL Tridion 2011 SP1.