Hacking the layout rendering

I recently worked on our own company web page where the criteria is that all Sub Pages to a page in the top navigations (Top Page) should be rendered as sections on that page. The Sub Pages should be individually browsable. You should be able to edit them using the Page Editor. And as a personal goal it should support nested placeholders. Extra if the DMS personalisation could work.

Previously I had done a similar solution. But as the developer I am, I wanted to try doing it differently. The previous solution was to have a rendering go through all child items and extract all layout definitions in a specific placeholder and load the control using Page.LoadControl(“~/path”) and add the control to a regular asp placeholder on Page_Load. This didn’t allow nested placeholders and off course not the DMS features.

The plan

I knew that Sitecore at some point had to load all the renderings for the item and my plan was to add the renderings of the child items at the same time so that Sitecore would think that the layouts of the child items actually belonged to the Top Page.

So using .NET Reflector we set out to find the best place to do our magic. And after a short search it was quite obvious that we should look at the renderLayout pipeline and in the InsertRenderings processor. This processor takes all the Items layout definitions and adds them to the Sitecore.Context.Page which is what will be rendered for the current request.

Creating the Pipeline

We created our own processor and added it right after the InsertRenderings processor which looks like this.

public class FetchChildLayoutDefinition : RenderLayoutProcessor
    {
        public override void Process(RenderLayoutArgs args)
        {

            var item = Sitecore.Context.Item;
            if(item==null) return;
            if (!item.TemplateID.Equals(new ID("{99F41F1F-47B2-422A-82A9-0650B102989D}"))) return;
            if (!Sitecore.Context.PageMode.IsNormal) return;

            using (new ProfileSection("Insert child renderings into page."))
            {
                DeviceItem device = Sitecore.Context.Device;

                foreach (var child in item.Children.Where(ItemTools.IsPublished))
                {
                    var renderings = child.Visualization.GetRenderings(device, true);
                    foreach (var renderingReference in renderings)
                    {
                        if (!renderingReference.Placeholder.Equals("content", StringComparison.InvariantCultureIgnoreCase) && !renderingReference.Placeholder.StartsWith("/main/content", StringComparison.InvariantCultureIgnoreCase)) continue;
                        renderingReference.Settings.DataSource = string.IsNullOrEmpty(renderingReference.Settings.DataSource) ? child.Paths.FullPath : renderingReference.Settings.DataSource;
                        Sitecore.Context.Page.AddRendering(renderingReference);
                    }

                }
            }
        }
    }

 

A quick explanations of the processor. We only wanted to do this for a specific page on the site which has its own Templates. So we check if the current item is based on that Template. Since we actually add all the renderings so that Sitecore think they belong to the current page, this also applied for the Page Editor, so when we saved the Top Page with all the renderings gathered from the child items it ended up saving all these renderings as well which wasn’t desirable. So we hade to check that we weren’t in any other mode than normal. After that we iterate all published children and extract all renderings using the current device. Since the sub pages should be browsable it contains elements such as a header, footer navigations and so on, these renderings were already added to the Top Page, so we wanted to ignore these common renderings. To solve this we assigned a placeholder “content” that we used as a base and only used renderings that were added inside this placeholder or nested within. There are two ways you can add a rendering to a placeholder, you can either write the placeholders name like “content” or you can write the nested path of the placeholder like “/main/content” which the Page Editor does when adding a rendering, so we had to look for both of them. And when we found a rendering that fulfilled the placeholder criteria, we add that to Sitecore.Context.Page. And that’s it. Right…?

Multiple placeholder problem

Well this actually worked, somewhat… when we only had renderings in the content placeholder. When we started nesting placeholders and renderings within the “content” placeholder the problem started. It was kind of an obvious problem that I was counting on. If a rendering with the placeholder “subpageprecontent” would be added to the “content” placeholder on multiple sub pages and this rendering would be collected and added to the Top Page, all these “subpageprecontent” placeholder would have the same adress “/main/content/subpageprecontent”. And if something would have been added to that placeholder, it all would end up in the first occurrence of the “subpageprecontent” placeholder since there isn’t anything unique about them. So to solve this, we would have to make the placeholders unique. This got me thinking of a prototype module I’ve previously used called DynamicKeyPlaceholder which basically adds something to the key of the placeholder to make it unique. When adding a rendering to an item a new Guid is generated for that rendering and added as the attribute UniqueID in the layout definition xml. We ended up using that guid and concatenated it with the key of the placeholders in the renderings added to the “content” placeholder resulting in a unique placeholder path looking something like this “/main/content/subpageprecontent{108127af-7f86-44e6-9fce-14baf564faed}”. Below you can see a screen shot of how it looks like in Sitecore.

Now we had unique placeholders even if it was the same placeholder in the same rendering because of the UniqueID generated for the rendering.

Did it work with the DMS?

As a bonus, since we add the entire rendering to with all conditions for the rendering to the Top Page so that Sitecore thinks it belongs to that page. It actually worked with the DMS out of the box. We didn’t have to do anything else to get this working. Pretty cool huh 😀

 

3 Replies to “Hacking the layout rendering”

Leave a Reply

Your email address will not be published. Required fields are marked *