Category Archives: Rule

Create your own TimePicker(macro) personalization rule in Sitecore

timepickermacro

Once more unto the breach, dear friends, once more πŸ™‚ This time I want to share with you how to make your own TimePicker macro, which will be used by a custom rule.

I needed a rule where I could present content at a specific time during the day. That means I’m NOT interested in the date, ONLY the time.
There are a bunch of nice rules but not a “Time rule”. So what to do?
Well let’s do a rule then, unfortunately I could not find a TimePicker macro.
Something like this would be nice:
[TimeField,Time,,pick a time]

So what is a macro? A macro is used by the rules. A typical macro could be the datetimepicker or a numeric input.
Here is how the datetimepicker macro is used in a rule:
[DateField,DateTime,,the date]
The macros are listed under Rules at: /sitecore/system/Settings/Rules/Definitions/Macros
macros

What we need to do is to create a new macro. Let’s start by adding/creating a new macro template in /sitecore/system/Settings/Rules/Definitions/Macros. We will call it – Time.
timemacroempty
We will come back to the macro item, when we have some code to point to πŸ™‚

We want the Timepicker macro to be similar to the Datetimepicker macro but without the date. Why not use dotPeek to find out how Sitecore did. After a lot of hair pulling I finally figured it out.

Let me give you a quick explanation on how it works:
The macro will do a SheerResponse call(ShowModalDialog), here it will give an url to an XML file (see it as a page/dialog for the datetimepicker).
The url points to the default.aspx page in Sitecore/shell, which will “generate” a dialog containing a datetimepicker. The xml file will also need a “code beside” in order to present the datetime picker.

So for the datetimepicker we have the following:
DateTimeMacro.cs (in Sitecore.Kernel)
DateTimeSelector.xml (in folder Sitecore\shell\Applications\Dialogs\DateTimeSelectors)
DateTimeSelector.cs – The “code beside” for the xml file (in Sitecore.Client)

Now lets do the same for our new Timepicker, first we create the macro:

public class TimeMacro : IRuleMacro
{
	public void Execute(XElement element, string name, UrlString parameters, string value)
	{


		Assert.ArgumentNotNull((object) element, "element");
		Assert.ArgumentNotNull((object) name, "name");
		Assert.ArgumentNotNull((object) parameters, "parameters");
		Assert.ArgumentNotNull((object) value, "value");

		SheerResponse.ShowModalDialog(new UrlString(UIUtil.GetUri("control:Sitecore.Shell.Applications.Dialogs.TimeSelector")).ToString(), "580px","475px", string.Empty, true);

	}

}

This little puppy:
UIUtil.GetUri("control:Sitecore.Shell.Applications.Dialogs.TimeSelector")
Generates the following url:
/sitecore/shell/default.aspx?xmlcontrol=Sitecore.Shell.Applications.Dialogs.TimeSelector

Time to get confused πŸ™‚ Sitecore.Shell.Applications.Dialogs.TimeSelector is not an xml file, it’s an element in an xml file. Here is our new xml file:

<?xml version="1.0" encoding="utf-8" ?>
<control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense">
  <Sitecore.Shell.Applications.Dialogs.TimeSelector>
    <FormDialog Header="Time" Text="Select a time." Icon="People/32x32/clock.png">
      <CodeBeside Type="Sandbox.Sitecore.Dialogs.CustomTimeSelector, Sandbox.Sitecore" />
      
      <div style="text-align: center;">
        <TimePicker ID="Time"/>
      </div>

    </FormDialog>
  </Sitecore.Shell.Applications.Dialogs.TimeSelector>
</control>

This is how the popup/dialog will look like.
I put the xml file together with the DatetimeSelector file in folder:
Sitecore\shell\Applications\Dialogs\DateTimeSelectors

Let’s do the code beside class(defined in the xml file), here you find the TimePicker control with the property name Time.

public class CustomTimeSelector : DialogForm
{
	/// <summary>Gets or sets the time.</summary>
	/// <value>The time.</value>
	protected TimePicker Time { get; set; }

	/// <summary>Raises the load event.</summary>
	/// <param name="e">The <see cref="T:System.EventArgs" /> instance containing the event data.</param>
	protected override void OnLoad(EventArgs e)
	{
		base.OnLoad(e);
		if (Context.ClientPage.IsEvent)
			return;

		Time.Value = DateTime.Now.TimeOfDay.ToString();
	}

	/// <summary>Handles a click on the OK button.</summary>
	/// <param name="sender">The sender.</param>
	/// <param name="args">The arguments.</param>
	protected override void OnOK(object sender, EventArgs args)
	{
		SheerResponse.SetDialogValue(Time.Value);
		base.OnOK(sender, args);
	}
}

The TimePicker, that was indeed confusing for me. I started out by creating a new TimePicker field, adding it to the core database etc. I thought it would be a normal field in the content editor. But that was so wrong… Here we use the Sitecore.Web.UI.HtmlControls. The good thing is that I did not have to create a TimePicker control, there is already one(used by the DatetimePicker control).

When ok button is hit, we will set the time value in the macro(in a rule).

Ok, so now we have done the macro. We will now update the new macro item, Time, in Sitecore. We need to point to the code for the macro class:
macroset

If you want to test it, you can easily browse to the following url:
your site/sitecore/shell/default.aspx?xmlcontrol=Sitecore.Shell.Applications.Dialogs.TimeSelector
controlinaction

To make it complete, we need a rule. We can call it – Time of day. Check out my previous post how to make a custom rule, Compare dates in your Sitecore personalization rule.
The text for the rule:
when current time is [operatorid,Operator,,compares to] [TimeField,Time,,pick a time]
Notice our new macro: [TimeField,Time,,pick a time] πŸ™‚

Here is the code for the rule. The TimeField property will hold the time that was selected from the Timepicker macro.

public class TimeOfDayCondition<T> : OperatorCondition<T> where T : RuleContext
{

	public string TimeField { get; set; }

	protected override bool Execute(T ruleContext)
	{
		Assert.ArgumentNotNull((object)ruleContext, "ruleContext");
		
		ConditionOperator conditionOperator = base.GetOperator();

		TimeSpan? timeToCompare = ConvertTime(TimeField);

		if (!timeToCompare.HasValue)
			return false;

		return TimeSpanComparer(DateTime.Now.TimeOfDay, timeToCompare.Value, conditionOperator);
		
	}


	private TimeSpan? ConvertTime(string timeToConvert)
	{
		DateTime time;
		if (!DateTime.TryParse(timeToConvert, out time))
			return null;

		return time.TimeOfDay;

	}


	private bool TimeSpanComparer(TimeSpan timeSpan1, TimeSpan timeSpan2, ConditionOperator conditionOperator)
	{
			
		switch (conditionOperator)
		{
			case ConditionOperator.Equal:
				return timeSpan1.Equals(timeSpan2);
			case ConditionOperator.LessThan:
				return timeSpan1 < timeSpan2;
			case ConditionOperator.LessThanOrEqual:
				return timeSpan1 <= timeSpan2;
			case ConditionOperator.GreaterThan:
				return timeSpan1 > timeSpan2;
			case ConditionOperator.GreaterThanOrEqual:
				return timeSpan1 >= timeSpan2;
			case ConditionOperator.NotEqual:
				return !timeSpan1.Equals(timeSpan2);
			default:
				return false;
		}

	}

}

In method TimeSpanComparer we will compare current time with the time in the TimeField property, for that we will use the Operator condition(LessThan, GreaterThan etc.)

Here is the rule in action:
timepickermacro

Keep on personalization out there.

That’s all for now folks πŸ™‚

Advertisements

Compare dates in your Sitecore personalization rule

experienceeditorcompa

Hello good people,

First of all I would like to thank Sitecore for awarding me the Sitecore’s Technology MVP 2017. I’m truly honoured.

Today I’m going to share with you how easy it is to create/make a date comparer rule.

In this scenario we will do a pretty dumb rule πŸ™‚
There will be two values(date picker) which will be compared by using the operator condition.

Jeff Darchuk has a great post on how to create/add rules in Sitecore – Lets use that rules engine!
So why not follow his instructions πŸ™‚

1. Create a new tag to identify our new group of rules, we will call it TestRules.
/sitecore/system/Settings/Rules/Definitions/Tags
createtag

2. Go to path: /sitecore/system/Settings/Rules/Definitions/Elements. Here you will see a bunch of rules, lets create a new Element Group and call it TestRules:
createelementfolder

3. Associate the new tag TestRules with the rule element folder(TestRules) by setting the default tag on the rule element.
tagdefault

4. We want the rule to be accessible when the editors are working in the Experience Editor(Presentation Details), let’s add the new tag(TestRules) to the conditional rendering rules:
rulecontext

Now let us create the rule and select the condition alternative:
createrule

So now we have a an empty condition rule called – DateComparerRule. Next will be to add some logic to it, we will add the two date fields and a condition comparer.
createrule

Here is the “text” code:
when [DateFieldOne,DateTime,,first date] [operatorid,Operator,,compares to] [DateFieldTwo,DateTime,,second date]

Let me give you a quick explanation.
DateFieldOne and DateFieldTwo – These are the properties in our custom rule
DateTime – This is the datepicker
“first date” and “second date” – The texts you want to show the editor, when he/she is using the rule.

operatorid – Will hold what “operator condition” you have selected(greater than, less than etc)
Operator – The operator picker
“compares to” – The text you want to show to the editor, when he/she is using the rule.

Ok so lets see how the rule look and feels for the editors.
experienceeditor
experienceeditordateexperienceeditorcompa

Yes it seems to work ok, next will be to add some code for the rule.

Here is the actual code for our custom rule,DateComparerRule. We need to inherit from OperatorCondition in order to compare the two date field properties: DateFieldOne and DateFieldTwo.

public class DateComparerRule<T> : OperatorCondition<T> where T : RuleContext
{

	public string DateFieldOne { get; set; }

	public string DateFieldTwo { get; set; }

	protected override bool Execute(T ruleContext)
	{
		Assert.ArgumentNotNull((object)ruleContext, "ruleContext");

		if (!IsDate(DateFieldOne) || !IsDate(DateFieldTwo))
			return false;

		ConditionOperator conditionOperator = base.GetOperator();

		return DateComparer(DateUtil.ParseDateTime(DateFieldOne, DateTime.MinValue), DateUtil.ParseDateTime(DateFieldTwo, DateTime.MinValue), conditionOperator);
	}

	private bool IsDate(string date)
	{
		DateTime tempDate;
		return DateTime.TryParse(date, out tempDate);
	}

	private bool DateComparer(DateTime date1, DateTime date2, ConditionOperator conditionOperator)
	{
			
		switch (conditionOperator)
		{
			case ConditionOperator.Equal:
				return date1.Equals(date2);
			case ConditionOperator.LessThan:
				return date1 < date2;
			case ConditionOperator.LessThanOrEqual:
				return date1 <= date2;
			case ConditionOperator.GreaterThan:
				return date1 > date2;
			case ConditionOperator.GreaterThanOrEqual:
				return date1 >= date2;
			case ConditionOperator.NotEqual:
				return !date1.Equals(date2);
			default:
				return false;
		}

	}
	
}

In order to get what ConditionOperator the editor has selected(greater than, less than etc.) we use the base method base.GetOperator(). From method DateComparer we will return the outcome of the comparison between the two properties: DateFieldOne and DateFieldTwo.

What we have left is to update the new rule, DateComparer, in Sitecore. We need to tell it where to find the code we have created. We do that by updating field Type(in section Script) with the following:
Sandbox.Sitecore.Rules.Conditions.DateComparer;Sandbox.Sitecore

This is not a big thing but I want you guys to see how easy it is to make your own custom rules.

Keep on personalization out there.

That’s all for now folks πŸ™‚

Make a custom device detector in Sitecore “Poor mans device detector”

detectorDone

First I would like to thank Sitecore for the great honor of being rewarded Sitecore MVP. I am truly thankful, please visit my fellow Sitecore MVP’s all over the world – Sitecore MVPs 2016

In my recent post, Deeplinking to mobile apps using Sitecore Device Detection, I described how easy it is to detect devices using Sitecore’s awesome feature – Sitecore Device Detector. I really love it and it’s so easy to use thanks to the device rules which lets you trigger goals/events and personalize content. This awesome feature is not for free but it’s worth every penny.

This post is for you who only want to use part of the functionality and don’t want to pay for it, I was thinking of calling it “Poor mans device detector” πŸ™‚

I got the idea when I was going through all the nice rules in Sitecore 8.1, there are so many just waiting for you to use
Rules

I noticed a rule that really could come in handy – User Agent. It allows you to look at the visitors user agent and do actions.
UserAgentRule

But it turns out that particular rule is not accessible from renderings when using personalization rules.
RenderRules
Why is that? I used my favorite tool, dotPeek, to do some peeking in the code.

using Sitecore.Rules.Conditions;
using System.Web;

namespace Sitecore.Rules.Devices
{
  /// <summary>
  /// User agent condition class.
  /// 
  /// </summary>
  /// <typeparam name="T">The rule context.</typeparam>
  public class UserAgentCondition<T> : StringOperatorCondition<T> where T : DeviceRuleContext
  {
    /// <summary>
    /// Gets or sets the value.
    /// 
    /// </summary>
    /// 
    /// <value>
    /// The value.
    /// </value>
    public string Value { get; set; }

    /// <summary>
    /// Executes the specified rule context.
    /// 
    /// </summary>
    /// <param name="ruleContext">The rule context.</param>
    /// <returns>
    /// <c>True</c>, if the condition succeeds, otherwise <c>false</c>.
    /// </returns>
    protected override bool Execute(T ruleContext)
    {
      HttpContextBase httpContext = ruleContext.HttpContext;
      if (httpContext == null || string.IsNullOrEmpty(this.Value) || (httpContext.Request == null || string.IsNullOrEmpty(httpContext.Request.UserAgent)))
        return false;
      return this.Compare(httpContext.Request.UserAgent, this.Value);
    }
  }
}

It turns out that the rule inherits DeviceRuleContext and that means it will only work when using devices:
useragentDevices

So what to do? We have the code from the rule we want(thanks to dotPeek). We just need a little change, instead of inherit from DeviceRuleContext we inherit directly from RuleContext.

namespace VisionsInCode.Foundation.SitecoreCustomizations.Rules
{

  using System.Web;
  using Sitecore.Diagnostics;
  using Sitecore.Rules;
  using Sitecore.Rules.Conditions;

  public class PoorMansDeviceDetectorCondition<T> : StringOperatorCondition<T> where T : RuleContext
  {

    public string Value { get; set; }

    /// <summary>
    /// Testable UserAgent
    /// </summary>
    public string UserAgent { get; set; }


    /// <summary>
    /// Executes the specified rule context.
    /// 
    /// </summary>
    /// <param name="ruleContext">The rule context.</param>
    /// <returns>
    /// <c>True</c>, if the condition succeeds, otherwise <c>false</c>.
    /// </returns>
    protected override bool Execute(T ruleContext)
    {
      Assert.ArgumentNotNull(ruleContext, "ruleContext");

      if (string.IsNullOrWhiteSpace(UserAgent))
        this.UserAgent = HttpContext.Current.Request.UserAgent;

      if (string.IsNullOrEmpty(this.Value) || (string.IsNullOrEmpty(this.UserAgent)))
        return false;

      return this.Compare(this.UserAgent, this.Value);
    }

  }

}

I also added the UserAgent property for testing purposes.

To test the rule we will use Sitecore FakeDB. Thanks to the great post Unit Testing Custom Rules, Actions, and Conditions with FakeDb – Part 1 – Testing Conditions by Brian Beckham. This made it easier to write the tests.

namespace VisionsInCode.Foundation.SitecoreCustomizations.Tests.Rules
{
  using FluentAssertions;
  using Sitecore.FakeDb;
  using Sitecore.Rules;
  using VisionsInCode.Foundation.SitecoreCustomizations.Rules;
  using VisionsInCode.Foundation.SitecoreCustomizations.Tests.Extensions;
  using Xunit;

  public class PoorMansDeviceDetectorConditionTests
  {
   
    private void SetupDb(Db database)
    {
      database.Add(new DbItem("Settings")
      {
        ParentID = Sitecore.ItemIDs.SystemRoot,
        Children =
        {
          new Sitecore.FakeDb.DbItem("Rules")
          {
            new Sitecore.FakeDb.DbItem("Definitions")
            {
              new Sitecore.FakeDb.DbItem("String Operators")
              {
                new Sitecore.FakeDb.DbItem(Constants.StringOperations.Contains.ItemName, Constants.StringOperations.Contains.ItemID),
                new Sitecore.FakeDb.DbItem(Constants.StringOperations.MatchesTheRegularExpression.ItemName, Constants.StringOperations.MatchesTheRegularExpression.ItemID)

              }
              
            }
          }
        }

      });
    }

    [Theory]
    [InlineAutoDbData("Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4", "iPhone", true)]
    public void DoesUserAgentContainValueCondition(string userAgent, string containsUserAgentValue, bool expectedResult, Db database)
    {


      SetupDb(database);


      RuleContext ruleContext = new RuleContext();

      PoorMansDeviceDetectorCondition<RuleContext> customUserAgentCondition = new PoorMansDeviceDetectorCondition<RuleContext>()
      {
        OperatorId = Constants.StringOperations.Contains.ItemID.ToString(),
        Value = containsUserAgentValue,
        UserAgent = userAgent
      };

      var ruleStack = new RuleStack();

      // act
      customUserAgentCondition.Evaluate(ruleContext, ruleStack);

      // assert
      ruleStack.Should().HaveCount(1);

      object value = ruleStack.Pop();

      value.Should().Be(expectedResult);

    }

    [Theory]
    [InlineAutoDbData("Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4", "^(?!.*(iPhone|iPod|iPad|Android|BlackBerry|IEMobile))", false)]
    public void DoesUserAgentMatchesTheRegularExpressionValueCondition(string userAgent, string regularExpressionValue, bool expectedResult, Db database)
    {


      SetupDb(database);


      RuleContext ruleContext = new RuleContext();

      PoorMansDeviceDetectorCondition<RuleContext> customUserAgentCondition = new PoorMansDeviceDetectorCondition<RuleContext>()
      {
        OperatorId = Constants.StringOperations.MatchesTheRegularExpression.ItemID.ToString(),
        Value = regularExpressionValue,
        UserAgent = userAgent
      };

      var ruleStack = new RuleStack();

      // act
      customUserAgentCondition.Evaluate(ruleContext, ruleStack);

      // assert
      ruleStack.Should().HaveCount(1);

      object value = ruleStack.Pop();

      value.Should().Be(expectedResult);

    }
  }
}

What is left now is to add the new rule in Sitecore, lets put it in /sitecore/system/Settings/Rules/Definitions/Elements/Device and name it Poor Mans Device Detector.
rulePoorMan

Now we have a User Agent rule when we want to personalize content for renderings.
poorMansDeviceDetectorInRenderings

I used my favorite framework – Sitecore Habitat. Feel free to check out the code on the Github – GoranHalvarsson/Habitat

That’s all for now folks πŸ™‚