Saturday, July 10, 2010

"Set Relative DataSource" action makes total personalization effortless.

Some people say that true personalization is too complex and expensive to set up and maintain, but with Sitecore it's incredibly easy to setup personalization rules, configure rule-based content, etc. In general, to make some personalized content, you should perform the following actions:
  1. Create a personalization rule (add conditions and actions, let's say "Set DataSource" action to point personalized content folder). 
  2. Apply it to the rendering or sublayout.
While second step cannot be avoided (unless you use global rules), the first one sometimes looks redundant, as the conditions  of the most rules are very similar, especially in location-based ones. The only difference is a DataSource. The main goal of creating "SetRelativeDataSource" action is breaking such dependency and making "clicks only" personalization setup possible.

The idea is following: let the rule control only "personalized" part of the DataSource, taking the "base"  DataSource part from the control.

Let's say, you have a lot of sub-trees with personalized content for different controls:

 

In order to change content folder by the DataSource, you should also have corresponding rules:

















Let's take a look at the rules from "Floating Spot" and "Header Spot" folders:

1) "Floating Spot":









2) "Header Spot":









As you can see, the rules are almost identical. Is it possible to use common rules for all content instead? Sure!

All you have to do  is to add a new action(I called it "Set Relative DataSource") here:

/sitecore/system/Settings/Rules/Conditional Renderings/Actions

compile the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sitecore.Rules.Actions;
using Sitecore.Rules.ConditionalRenderings;
using Sitecore.Diagnostics;
using Sitecore.Layouts;
using Sitecore.Data.Items;
using Sitecore.Data;

namespace Sitecore.CustomRules
{
    public abstract class SetRelativeDataSource<t> : RuleAction<t> where T : ConditionalRenderingsRuleContext
    {
        // Methods
        protected SetRelativeDataSource()
        {
        }

        protected void Apply(T ruleContext, string folder)
        {
            Assert.ArgumentNotNull(ruleContext, "ruleContext");
            Assert.ArgumentNotNull(folder, "dataSource");
            RenderingSettings settings = ruleContext.Reference.Settings;
            RenderingCaching caching = settings.Caching;
            if (caching.Cacheable && !caching.VaryByData)
            {
                string name = "unknown";
                RenderingItem renderingItem = ruleContext.Reference.RenderingItem;
                if (renderingItem != null)
                {
                    name = renderingItem.Name;
                }
                else
                {
                    ID renderingID = ruleContext.Reference.RenderingID;
                    if (renderingID != (ID)null)
                    {
                        name = renderingID.ToString();
                    }
                }
                Log.Warn(string.Format("A rule overwrites the data source of the item {0} which is cachable and not vary by data. Possibly, this value is not be used as the rendering is cached.", name), base.GetType());
            }
            settings.DataSource = "/" + folder;
        }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sitecore.Rules.ConditionalRenderings;
using Sitecore.Diagnostics;

namespace Sitecore.CustomRules
{
    public class SetRelativeDataSourceAction<t> : SetRelativeDataSource<t> where T : ConditionalRenderingsRuleContext
    {
        // Fields
        private string folder;

        // Methods
        public override void Apply(T ruleContext)
        {
            Assert.ArgumentNotNull(ruleContext, "ruleContext");
            base.Apply(ruleContext, this.DataSource);
        }

        // Properties
        public string DataSource
        {
            get
            {
                return (this.folder ?? string.Empty);
            }
            set
            {
                Assert.ArgumentNotNull(value, "value");
                this.folder = value;
            }
        }
    }
}

set the "Text" field value to "set relative data source to [DataSource,Text,,Value]" and fill the "Type" field according to your namespace.

Now, instead creating and maintaining hundreds of rules, you can only have few "base" rules and personalize content without creating new ones:












you should only fill the "personalized" or "relative" part













and set the correct DataSource on a control:


















New controls that you add to the pages will not require new rules anymore, and they will be as easy to setup as before.

Simple, isn't it? There's so much cool stuff you can do with Sitecore Online Marketing Suite that it's impossible to describe it all in a blog. You can read more about Rules Engine here: Rules Engine Cookbook and about OMS here: Online Marketing Suite Cookbook.

1 comment:

  1. Hi. Nice idea. But will be good if I will be able to provide either static item path or XPath (or even Sitecore query) in Data source of control.
    For example. I have template SectionOverview. And i have following content:

    |-Section 1
    | +-Page Setup
    | +-Featured banners
    | |-Global
    | |-USA
    | +-Danmark
    +-Section 2
    +-Page Setup
    +-Featured banners
    |-Global
    |-USA
    +-Danmark
    (formating is broken :( )

    I use 1 template (SectionOverview) for both items: Section 1; Section 2. This template contains presentation (in _Standard Values).
    I cannot set Data Source = /sitecore/content/Home/Section 1. Because the same presentation will be used by Section 2 item.
    I want to write a query instead of static item path - find item with template XXX.
    Can you provide example how to implement this wish?
    Thanks.

    ReplyDelete