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.

1 comment:

Frank said...

Nice way of looking for index pages.

If you want to use it on older versions, you can do something like:

StructureGroup parent = page.OrganizationalItem as StructureGroup;
while (parent != null)
{
output = ... + output;
parent = parent.OrganizationalItem as StructureGroup;
}