Wednesday, February 22, 2012

Cascading renderings in Sitecore Page Editor [Experimental]


Ok, here is something completely experimental. Consider the following real world example from Sitecore’s website. You have a section called “Customers” and you have a left rail where the leftnav typically sits. Now your marketers want the “call-to-action” rendering to be placed below the leftnav:
 SNAGHTML9837b0a

…and what’s important, have it cascade to all child pages (let’s take this as an example).
Another thing they want to make sure is to lock it down on those child pages so nobody can do anything with it in Page Editor (move, change properties, etc,)

Pretty interesting scenario, heh?

Now let’s take the latest Sitecore 6.5.0 and get it done. The thought process is the following. Let’s find a place where Sitecore assembles the page dynamically, and look out for a way to inject our “call-to-action” rendering based on our business rules.

First place to look is the <insertRenderings /> pipeline introduced in 6.4. Before that all the page assembly magic was happening within <renderLayout /> pipeline.

The <insertRenderings /> pipeline looks like this:

<insertRenderings>
  <processor type="Sitecore.Pipelines.InsertRenderings.Processors.GetItem, 
Sitecore.Kernel"
/>
  <processor type="Sitecore.Pipelines.InsertRenderings.Processors.AddPageDesignerRenderings,
Sitecore.Kernel"
/>
  <processor type="Sitecore.Pipelines.InsertRenderings.Processors.AddRenderings, 
Sitecore.Kernel"
/>
  <processor type="Sitecore.Pipelines.InsertRenderings.Processors.EvaluateConditions,

Sitecore.Kernel"
/>
</insertRenderings>

There is a processor called “AddRenderings”, so why don’t we create our own, pasted it below it and “enrich” the rendering collection based on our custom logic and call it “AddCascadedRenderings”:

   1: public class AddCascadedRenderings : InsertRenderingsProcessor
   2:    {
   3:        public override void Process(InsertRenderingsArgs args)
   4:        {
   5:            Assert.ArgumentNotNull(args, "args");
   6:            if (args.ContextItem == null) return;
   7:  
   8:            var device = Context.Device;
   9:            if (device == null) return;
  10:  
  11:            if (Context.Site.Name != "website") return;
  12:  
  13:            if (args.ContextItem.Parent == null) return;
  14:  
  15:            var parentRenderings = args.ContextItem.Parent.Visualization.GetRenderings(device, true);
  16:  
  17:            var cascadedRenderings = parentRenderings.Where(rendering => rendering.Settings.Cascaded());
  18:  
  19:            args.Renderings.AddRange(cascadedRenderings);
  20:        }
  21:    }

Remember that this is quite an experimental prototype code, so we are going to skip all safety and boundary checks.
Basically what we are doing here is:

  1. getting the renderings from the parent item on line 15.
  2. filtering out the rendering collection from the parent item to have only those renderings that are set as “cascaded” on line 17.
    This is an extension method that will be demonstrated later.
  3. adding the “cascaded” renderings to the arguments.

The aforementioned extension method is fairly simple, it’s reading the parameters passed on to the rendering and checks for “cascade” checkbox value:

   1: public static bool Cascaded(this RenderingSettings settings)
   2: {
   3:    var parameters = WebUtil.ParseQueryString(settings.Parameters);
   4:    return parameters != null && parameters["cascade"] == "1";
   5: }

This obviously means that we need to have a custom “Parameters Template” specified for our “call-to-action” rendering, which would give us ability to have similar experience in Page Editor:

SNAGHTML9918ad3

The last thing would be to disable the rendering from being editable on child pages, but I would leave that for the next post.

Here is the result of our actions:

SNAGHTML996d21f

SNAGHTML9970417

Stay tuned for more…

3 comments:

GadgetFreak said...

Great concept Alex. I'd like to extend the approach to allow an editor to remove a cascaded rendering from an individual page. My idea is to add another field to the custom parameters template of type Treelist to store the excluded page items. I would then need to handle the delete button click on the rendering to add a page to this list. AddCascadedRenderings would be modified to exit if the page id matched an item in the ExcludedPages field. Do you think this is a good approach?

Sufian said...

This sounds really cool, I like the idea, my question is how is it better than creating a new template for children of the item and inserting the sublayout on that template?

David Peel said...

Love this. We have a similar solution for 6.2 but with the new pipeline and (next time) editing restrictions this is way better. Thanks Alex.