Avoiding the Page Template Mistake
This blog post is part 3 in a series on “Creating good Sitecore solutions”. Rather than constantly going back and adding edits to the previous posts, just follow this link to get an overview of the series.
A few thoughts
As promised in my last post; The Page Template Mistake; I will now attempt to put my money where my mouth is (so to speak) and provide something more concrete as to how the Page Template Mistake can be avoided, and how your implementation might carry forward without encountering it.
Before I do however, I will just clarify a few things that perhaps got lost in the previous posts.
I do believe that there is a time and a place for Page Templates. It just follows much later in the process and is a much more dynamic entity than what was certainly the case for my own solutions. I would not recommend content editors being forced to add any and all components to a blank “page canvas” for every page they create – we have Page Templates, Layout Presets and other technologies to help them out here.
What I advocate is, that you move the entire consideration of Page Templates much much further back in the process. Move it to the very end. Both to help you verify and assess components as the project comes about (if you’re tasked with that role), but also to help developers break free of the Page Template mindset entirely.
Your project will be better off for it.
My next post will be coming full circle, and demonstrate how Page Templates can be set up once you have all your Component Templates in place and – and I feel this point is important, because this used to be such a hassle for me in the past – how you can easily and quickly mock up new Page Templates as the situation requires, without breaking a sweat of what might break. Think yellow screens, think “Object Reference Not Set…” – all the common mishaps that you’ve experienced when tossing components onto a newly set up Page Template in the past.
Lastly; You’re not doing this specifically when “designing for the Sitecore Page Editor” as a few have suggested. That is a misunderstanding. What I am suggesting here is an approach that you would be taking if you’ve decided to make a good Sitecore solution. That it also happens to be a prerequisite for most meaningful work in the Sitecore Page Editor is consequential and accidental, you should not set out to “design for the Page Editor” at all.
After all – in doing so – you are inferring that this is a specific task, possibly an extra task. It is not. What I am suggesting is cost free, adds no overhead to your project. If done up front, that is. If you start by deciding to build a good Sitecore solution.
And on that note, let’s get to it.
Implementing a Component Template
To serve as an example, I’ll pick a component from the – in theory – up and coming CorePoint IT website (my own one-man consulting company). I say in theory because just as the mechanics own car, somehow working on this website always seems to end up fairly low on my priority list ;-)
I’ll pick something simple to start off with. In the HTML I’ve had done for the site, it looks like this:
Looking at the HTML snippet for this component, it looks like this.
<div id="topBanner"> <img src="images/banners/glass.gif" alt="" /> <div id="topBannerContent"> <h4>The company that does anything...</h4> <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.</p> </div> <a href="#">Read more >></a> </div>
No big surprises there. Not too happy about the “id” attributes in there but let’s set aside that discussion for the time being.
I identify 5 tokens (tokens is a bad term. Anyone have another suggestion?) in there that I would like to see content managed on this component.
- Heading
- Body Text
- Image
- Link Text
- Link
Breaking up the link near the bottom into two separate entities is a habit I’ve adopted. While it is possible on a Sitecore “General Link” for content editors to specify things such as Link Text; this doesn’t hold true for “Internal Link”. Doing it like this as a convention just saves a lot of grief.
Setting up the Component Template
While this may look apparent, there are quite many (of my own) conventions in play here. I recommend you follow them, each of them will grant you “+2 to the overall Sitecore goodness feel”.
- Give your component a good name. As “good” is subjective, here’s a few examples of component names that are NOT good (all of them from real life):
- phInnerHeadingBox
- HeroBoxTopRight
- Content Spot
- Set an icon for your Component Template. First thing, no delay. And while you can try and be creative and find an icon that “matches what your component does”, you will likely fail – the icon set is too generic for that. Don’t worry though, as it doesn’t really matter WHAT icon you choose – just that you choose one, and choose the same one in the steps to follow.
- Place your fields in one “Section” named in exactly the same manner as your component.
- Assign the icon from above, to that Section as well. Here’s how:
- Name your fields, prefixed with the Component Name.
- This is controversial, I know. But given that Sitecore does not distinguish Sections when addressing fields, and you cannot make any assumptions at this stage about the Page Template that your component will be living on (since you don’t know, and never will) – doing it like this provides the Path of Least Surprise later on.
- Also keep in mind; field names mean nothing (really) when your content editor user is in the Page Editor – and given that the same user is given the context via the Section in the Content Editor, they won’t have much trouble scanning the fields for the one they need there either.
- Set a short help text for each of your fields. This often overlooked step will mean a world of difference for your Content Editor users, takes you about 10 seconds per field (at this stage in the process) and grants you “Sitecore goodness” karma. If you haven’t done this before – try it. Take my word for it. Here’s how:
And you’re done. For now.
Setting up the Component
Proceed with your method of choice to set up the Sublayout Component in Sitecore. (Covering everything involved in this step probably could use a blog post of it’s own. I deem this out of scope for this one). Name it the same as your Component Template.
Carry out these steps:
- Set the icon for the Component to the same icon you chose for the Component Template.
- Set the Datasource Template to your Component Template.
- Finally take a snapshot of your component, and set up the Component thumbnail.
- If your Component is part of a multi-site solution and look completely different between the sites, leave out this step. Sitecore requires customization to be able to do site-specific thumbnails, something I might cover in a later post. Tweet if you want it. @cassidydotdk ;-)
If there is no thumbnail defined, Sitecore will show an enlarged version of your chosen icon for the component when adding it in the Page Editor. Not ideal, but still sticks with the established convention pretty good. - Set it up like this.
And that’s pretty much it. There may be additional things to consider when setting up your Component; Rendering Parameters and so on – I’m going to skip that for this post, as it doesn’t lean towards the points I am making here. I know I say that a lot, but hey… this post is going to be more than long enough already.
Implementing the Component
Like a TV chef, I’m going to skip ahead quickly and show you the resulting Sublayout .ASCX. The layout I’ve created does nothing but set up one Placeholder; “content” and include the relevant CSS files. For those of you riding the MVC wave, I’m sure you can adapt.
The .ASCX
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Top Banner.ascx.cs" Inherits="Website.layouts.CorePoint.Top_Banner" %> <div id="topBanner"> <sc:Image runat="server" Field="Top Banner Image" ID="sciImage"/> <div id="topBannerContent"> <asp:PlaceHolder runat="server" ID="phHeading"> <h4><sc:Text runat="server" Field="Top Banner Heading" ID="sctHeading"/></h4> </asp:PlaceHolder> <sc:Text runat="server" Field="Top Banner Body Text" ID="sctBodyText"/> </div> <asp:PlaceHolder runat="server" ID="phLink"> <sc:Link runat="server" Field="Top Banner Link" ID="sclLink"> <sc:Text runat="server" Field="Top Banner Link Text" ID="sclLinkText"/> </sc:Link> </asp:PlaceHolder> </div>
The codebehind is empty, at this point.
Creating a Data Item based on my Component Template
I then create an item, based on my newly created Component Template.
And to further stress my point that Page Templates have no place at this point, I will just proceed to modify the presentation details of the default /content/home item that ships with Sitecore.
Setting up the Sitecore page
I change the presentation details for the item, to look like this. As mentioned; the Layout only holds the basics – and a placeholder keyed “content”.
And finally I configure my Top Banner component to use my newly created Content Item as a datasource.
After all this, I do a quick publish – and this is what I see. If I hadn’t read The Page Template Mistake or John Wests post on how to apply data sources to components, I might be surprised at this point.
Nothing shows. Your first instinct might be to have a look at the page source. It looks like this.
So essentially; the component gets rendered ok. There’s just nothing in it.
And this is the crust of the Page Template Mistake. Sitecore does nothing (for you) to respond to your configuration; setting the datasource to your content item. And I do believe this is the first reason many venture down that mistaken road and begin making the Page Template Mistake in the first place. There is no immediately obvious way to get to the datasource, and the standard Sitecore web controls don’t respond to it. So one retorts to hitting the Sitecore.Context.Item, and from here on out your fate is sealed.
Fortunately there’s a better way to approach this. Let’s introduce a bit of codebehind.
Implementing the Component code (basic)
public partial class Top_Banner : UserControl { protected Item _actionItem = null; public Item ActionItem { get { if ( _actionItem == null ) { var sl = Parent as Sublayout; if ( sl != null ) { if ( !string.IsNullOrEmpty( sl.DataSource ) ) { Item datasourceItem = Sitecore.Context.Database.GetItem( sl.DataSource, Sitecore.Context.Language ); if ( datasourceItem != null && datasourceItem.Versions.GetVersions().Any() ) _actionItem = datasourceItem; } } } if ( _actionItem == null ) _actionItem = Sitecore.Context.Item; return _actionItem; } } protected void Page_Load( object sender, EventArgs e ) { sctHeading.Item = sctBodyText.Item = sciImage.Item = sclLink.Item = sctBodyText.Item = ActionItem; } }
And the result.
This is more or less Datasource 101. Implement like this, and you are more or less imitating XSL Renderings and how $sc_item is set up to work by default.
Look here’s the thing:
If you do this, and you do it consistently, your solution is already in the top N percent of well implemented Sitecore solutions. Good solutions. DMS enabled solutions. Almost by default.
You can work your way up from here, and I will give a few examples of that. But it starts here. It starts by deciding that what you want to do, is build a good Sitecore solution. You could do nothing more than just this in your solution, and you’d have pretty much all DMS scenarios covered. I really am not kidding.
Just one gotcha; Sitecore 7 expands upon the Datasource concept. Since I am blogging in the context of personal experience and practices I myself have found to be successful – I cannot account for how the above code fits with Sitecore 7. I have no live sites under my belt at this stage that uses Sitecore 7. My initial guess would be; “it’s probably fine” however.
Implementing the code (intermediate)
The next natural step up from here would be to push the ActionItem code to a base class, and make all your Components inherit from here. Quite many of my readers of the previous posts in this series point this out – and until recently this has also been my own sole approach to this problem.
The base class could look like this.
public class BaseSublayout : UserControl { #region :: Action Item :: protected Item _actionItem = null; public Item ActionItem { get { if (_actionItem == null) { var sl = Parent as Sublayout; if (sl != null) { if (!string.IsNullOrEmpty(sl.DataSource)) { Item datasourceItem = Sitecore.Context.Database.GetItem(sl.DataSource, Sitecore.Context.Language); if (datasourceItem != null && datasourceItem.Versions.GetVersions().Any()) _actionItem = datasourceItem; } } } if (_actionItem == null) _actionItem = Sitecore.Context.Item; return _actionItem; } } #endregion protected void ApplyActionItemRecursively() { var ai = ActionItem; foreach ( Control c in Controls ) RecurseControls( c, ai ); } private void RecurseControls( Control parent, Item actionItem ) { if ( parent.GetType().FullName.StartsWith( "Sitecore.Web.UI.WebControls" ) ) { var itemProp = parent.GetType().GetProperty( "Item" ); if ( itemProp != null ) itemProp.SetValue( parent, actionItem ); } foreach( Control c in parent.Controls ) RecurseControls( c, actionItem ); } }
And with this, every one of your components would look like this in their basic form. This is where my claim “this adds no cost to your solution”. I’ll say it again; implementing Components this way carries no significant overhead. You set up this base class once, and that’s it.
public partial class Top_Banner : BaseSublayout { protected void Page_Load( object sender, EventArgs e ) { ApplyActionItemRecursively(); } }
All I have left to do, is a slight bit of housekeeping. This is another convention I apply and if you stick to it, I am fairly confident your content editor users are going to love it.
public partial class Top_Banner : BaseSublayout { protected void Page_Load( object sender, EventArgs e ) { ApplyActionItemRecursively(); if ( string.IsNullOrWhiteSpace( ActionItem[ "Top Banner Heading" ] ) ) phHeading.Visible = false; if ( string.IsNullOrWhiteSpace( ActionItem["Top Banner Link Text"] ) ) phLink.Visible = false; } }
And that’s it, essentially. The Component is done. It’s well behaved, it can be used in M/V tests – call it “DMS Enabled” if you must. And if all your components are implemented in this manner, your life when creating Page Templates later on will be a heck of a lot easier. Painless is more like it. And it never cost you a dime.
For that, however, you are going to have to wait for the next post in this series.
You can stop reading now :-)
But for those persistent enough to make it this far in an already excessively long post; I’ll demonstrate where I am currently at myself in my pursuits of a mythological “best practice” in this area. I will be brief and post mostly code with minimal commentary. If you’re at this level of Sitecore implementation experience, I’m sure it will make sense.
Implementing the code (advanced)
The problem with the above approach is of course, as many commenters have pointed out. In real life, not all components fit nicely into this pattern. Sitecore themselves sort of indicate this as well; every default XSLT rendering has a $home variable defined (albeit commented out). Sometimes applying “Datasource or Context Item” just isn’t good enough.
Think Headers and Footer Components for instance. While you likely could be implementing a Datasourced Header component on one of your (deep) base templates, for many purposes this just isn’t practical.
Mark Ursino rightly mentions this problem in a comment;
“Global information (e.g. header and footer) cannot really be componentized too well unless you use standard values on a very low level "base page" template to assign the same header and footer data source items to all pages. I typically instead just define a predefined structure in a global area to support the header and footer.”.
Mark, I’m with you here, but I still believe that any component that “breaks out” of the imaginary “bounding box” that is its Component Template is creating an anti pattern in the solution – not unlike how Global Variables do it for traditional structured programming.
And there’s likely many similar scenarios.
Fortunately this can be resolved easily as well – without tweaking much in the code I just presented. AND – and this is important – without starting to make up a “meta CMS in the CMS” by adding global configuration structures and similar. I’ve taken this approach myself, and I always found them to be inhibiting me sooner or later in the lifetime of the solution.
Here’s what I suggest.
Have your components adhere to relevant strategies for resolving the ActionItem. Mostly – what I just lined out above will be fine. For some components, it would be more appropriate to EITHER respond to the Datasource (if set) or retort to $home. For others again; perhaps responding to Datassource (WHATEVER else you do; always always always respect the Datasource if one has been set. Always. Please. Having a “global header” that cannot be datasourced to make a quick micro-site is just a right pain) and retorting to crawling “up the tree” until you find an item that inherits from your Component Template.
It looks like this. I’ve abbreviated slightly. It’s going to be a long paste, so I’ll quit writing now and just let the code do the rest of the talking (mostly).
Until next time :-)
namespace Website.layouts.CorePoint { public abstract class ActionItemStrategy { public abstract Item Resolve( Control c ); } /// <summary> /// Classic Datasource handling /// </summary> public class DatasourceOrContextItemStrategy : ActionItemStrategy { private Language _fallbackLanguage;
public DatasourceOrContextItemStrategy( Language fallbackLanguage = null ) { _fallbackLanguage = fallbackLanguage; }
public override Item Resolve( Control c ) { var sl = c.Parent as Sublayout; if ( sl != null ) { if ( !string.IsNullOrEmpty( sl.DataSource ) ) { Item datasourceItem = Context.Database.GetItem( sl.DataSource, Context.Language ); if ( datasourceItem != null && datasourceItem.Versions.GetVersions().Any() ) return datasourceItem; if ( _fallbackLanguage != null ) { datasourceItem = Context.Database.GetItem( sl.DataSource, _fallbackLanguage ); if ( datasourceItem != null && datasourceItem.Versions.GetVersions().Any() ) return datasourceItem; } } } return null; } } /// <summary> /// Resolves the ActionItem by datasource and falls back to Site Root. /// </summary> public class DatasourceOrHomeItemStrategy : ActionItemStrategy { public override Item Resolve( Control c ) { Item datasourceItem = new DatasourceOrContextItemStrategy().Resolve( c ); if ( datasourceItem != null ) return datasourceItem; Item home = Context.Database.GetItem( Context.Site.StartPath, Context.Language ); if ( home.Versions.GetVersions().Any() ) return home; return null; } } public class BaseSublayout : UserControl { protected Item _actionItem = null; public Item ActionItem { get { if ( _actionItem == null ) { _actionItem = GetActionItemStrategy().Resolve( this ); if ( _actionItem == null ) _actionItem = Sitecore.Context.Item; } return _actionItem; } } protected virtual ActionItemStrategy GetActionItemStrategy() { return new DatasourceOrContextItemStrategy(); } protected void ApplyActionItemRecursively() { Item ai = ActionItem; foreach ( Control c in Controls ) RecurseControls( c, ai ); } private void RecurseControls( Control parent, Item actionItem ) { if ( parent.GetType().FullName.StartsWith( "Sitecore.Web.UI.WebControls" ) ) { PropertyInfo itemProp = parent.GetType().GetProperty( "Item" ); if ( itemProp != null ) itemProp.SetValue( parent, actionItem ); } foreach ( Control c in parent.Controls ) RecurseControls( c, actionItem ); } } }
And with this in place, the individual Component implementations could “do nothing” – in which case they would just apply default behaviour. But for some cases, overriding this behaviour is desirable – so we configure these with simple overrides. A few examples.
Site Header Component
protected override ActionItemStrategy GetActionItemStrategy() { return new DatasourceOrHomeItemStrategy(); }
Implementing Language Fallback
Make this your default sublayout, overriding the default I listed above.
protected override ActionItemStrategy GetActionItemStrategy() { return new DatasourceOrContextItemStrategy( Sitecore.Globalization.Language.Parse( "en" ) ); }
Make up your own as you go along. Not too many though – or the point of this whole exercise gets lots completely. I can attest from experience however; just the two first strategies solved the very large majority of my component worries. And I don’t implement any meta-structures any longer to support neither menus, nor headers or footers, or pretty much anything else for that matter.
And the flexibility this approach gives me in setting up any and all Page Templates that my content editors require; is near phenomenal. More on that next time.
6 comments:
Greate article. Read the previous article as well and I came to the same insight when Sitecore 6.4.1 was first release where the Page Designer was introduced. Great to see that other have struggled with the same problem and come to the same conclusion :D
Another great post again!
Regarding your comment to my comment: I actually do exactly what you said, maybe I just didn't make it clear in my other bullet point. ALL sublayouts access the DataSource property which is our version of your ActionItem. In our base class the DataSource is set to the "parent sublayout" data source if there is one, otherwise falls to the context item. So our header component still assigns all field renderers to DataSource ("ActionItem"). The only difference we have is a bit of extra code that says: set the data source for this sublayout to the global location of header (some utility data access code in a parent wrapping "composite sublayout"). So, if we were to manually set the data source in Layout Details it would still respect that. Hope this helps clarify!
Great series of posts you have going here. Totally agree with your advice.
There are a couple things that bug me about the pattern (in general, not your implementation) though:
Sitecore doesn't really provide a good method of organizing all of these little component data source items (most of which may apply to a single page only) and preventing spaghetti once you get a bunch of them. Annnnd...
Workflow. As soon as you start workflowing component templates, the page editor starts getting really confusing as to what is and is not currently publishable. 'Preview mode' becomes a bit of a fallacy, because you're not really approving a whole page at a time.
I've messed around with solving both of those problems (some event handling for organizing page-specific components, and some automatic workflow advancement that only sort of works for the latter). I'd love to hear your thoughts about this too :)
@Kamsar, I agree completely. And the problems you describe is probably high among the reasons, implementors go down the Page Template approach.
I do have some suggestions on how to lessen the burden you describe here, and I plan to cover some of it when discussing Page Templates in my next post.
It's a difficult problem to find any one-fixes-all solutions to; but we'll see what comes of it all :-)
Great article
Hello,
Great article! I am a newbie to Sitecore so I am a bit confused about how to set up my architecture. In your example you have a top banner...what if on the page you had a top banner...some content underneath(maybe some text and a asp.net listbox) and then a footer...would those all be seperate components?
Post a Comment