Three simple patterns for finding a target page

Quite often when you’re putting together a website, you find yourself needing to link the current page to some sort of shared page for an action. “Click here to read terms and conditions” is a common example – where all your products need to be able to link to the Ts & Cs page.

A naive implementation might just hard code the path here, but that is inherently fragile. Renaming or moving the page can easily break your code. So what better approaches might you consider for meeting your requirement while allowing editors flexibility? Having talked about these patterns with colleagues a few times recently, I thought I’d write down some of the basics as a reference for future conversations.

One: Centralised configuration

If you have a single target page which is linked to from many places, centralising the configuration can be a good solution. You create a single item somewhere in your content tree which contains configuration settings, and you give it a field to select this target page. For example:

Site Configuration

You can create a template like CentralConfiguration which, for the purposes of this example, contains a single DropTree field to let you pick the correct Ts&Cs. This field should have “required” validation attached, as you want to ensure that Editors do select a value.

This is a singleton type of item – there must be exactly one of it in your solution. Hence, chances are, you’ll create it in development, commit it to your source control, and deploy it. Hence your code can refer to it by ID.

So in your solution you might define a constant for that ID:

public static class SiteIdentifiers
{
    public static readonly ID CentralConfigurationItem = new ID("{B2D1731F-C74C-4E55-A147-B64F1652C7FC}");
}

and then you can have a simple method to fetch the required URL from anywhere in your site:

public string FetchTermsAndConditionsUrl()
{
    var configItem = Sitecore.Context.Database.GetItem(SiteIdentifiers.CentralConfigurationItem);

    var termsPageId = configItem.Fields["TermsPage"].Value;

    var termsPageItem = Sitecore.Context.Database.GetItem(termsPageId);

    return LinkManager.GetItemUrl(termsPageItem);
}

or, alternately using Sitecore’s field types: (Which do largely the same thing under the surface)

public string FetchTermsAndConditionsUrl()
{
    var configItem = Sitecore.Context.Database.GetItem(SiteIdentifiers.CentralConfigurationItem);

    ReferenceField termsPageField = configItem.Fields["TermsPage"];
    var termsPageItem = termsPageField.TargetItem;

    return LinkManager.GetItemUrl(termsPageItem);
}

In real-world code you’d want some error checking here (especially for empty values or missing items). Also your central config item often ends up used in lots of places, so fetching it once per request (or less – depending on how often it changes) can be an important optimisation. ORM tools like Glass can also simplify implementing something like this.

Some types of solution don’t allow you to know the specific ID of your central configuration item at compile time, however. Maybe the client is creating all the content, or you’re shipping a “toolkit” for creating multiple sites in a single instance of Sitecore. If you find yourself in that scenario you could try either making the ID a configuration item in the Sitecore config, or you could try a “find it by is Template ID” approach, similar to that discussed below.

Two: Look for an item with the right template

An alternative approach is to make some simple assumptions about where an item might be located and what template it’s based on, and then query for it. For example, you might decide that the Terms & Conditions page will always be a direct child of the site root, and use a special “Terms and Conditions” template.

Again, you’d probably want to define the template to find as a constant:

public static class SiteIdentifiers
{
    public static readonly ID TermsAndConditionsTemplate = new ID("{76036F5E-CBCE-46D1-AF0A-4143F9B557AA}");
}

and then you need to find your root item, and run a query from it:

public string FetchTermsAndConditionsUrl()
{
    var rootItem = Sitecore.Context.Database.GetItem(Sitecore.Context.Site.StartPath);

    var query = string.Format("*[@@templateid='{0}']", SiteIdentifiers.TermsAndConditionsTemplate);

    var termsPageItem = rootItem.Axes.SelectSingleItem(query);

    return LinkManager.GetItemUrl(termsPageItem);
}

One way to find the root item of your site is to use Sitecore.Context.Site.StartPath to get the data from your web.config‘s <site> element. As above, you’d want to test for missing data or errors in your code. Similar techniques can be used to find the target item if it’s a child of the current item, or elsewhere in the tree.

One advantage of this approach is that sometimes you need your target item to change depending on where you are in the content tree. For example where the Terms and Conditions vary between different sales regions. In that scenario, having a single central config item (as per option one above) doesn’t work. An approach like this one, where you search below the sales region that owns the product, can find the appropriate item more easily.

Three: You can always just make it a field…

Sometimes though, you hit a scenario where you need lots of different terms and conditions pages, and the appropriate page changes often between products in a way that’s not obviously reflected in your content tree. In that scenario, you probably want to fall back to letting editors select the right item manually for each product.

In this example, consider adding a field to specify the Ts & Cs page in the template of the product page. This gives the most flexibility and a trivially simple implementation – it’s just a simple link on your page, maybe using the sc:Link control or a suitable equivalent. However this is at the expense of the most effort for content entry.

So there you go. No rocket science here, just three possibilities for finding the right page at runtime.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s