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 πŸ™‚

Handle URL redirects in a Sitecore multisite solution using FileMapProvider

redirect-me-baby_3

I would like to share with you guys how you could do URL redirects in a Sitecore multisite solution with Microsoft’s URL Rewrite Module using the FileMapProvider.

FilemapProvider is very cool, it reads the URL mappings from a text file.

It can be used instead of the built-in rewrite maps functionality when the amount of rewrite map entries is very large and it is not practical to keep them in a web.config file.

And the best thing of all:
No need to do any custom pipelines in Sitecore – it’s clean πŸ™‚
You don’t have to do an IIS reset after the redirect file has been changed/updated.

The idea is this:
We have a Sitecore solution with several websites, each website will have a number of redirects. In Sitecore we will have a Bucket with “URL Redirects” items, each item will contain an old and a new url. From the Content Editor we want to be able to click on a command button that will trigger a remote event(we need it to work in a multi-server environment), which will write the redirects to a text file(for the FileMapProvider).

How about divide the work into following:
1. Configure the FileMapProvider/s.
2. Setup the URL redirects in Sitecore.
3. Create the command button in Sitecore Content Editor.
4. Create the (remote) event and write redirects to file.

1. Configure the FileMapProvider/s.

The FileMapProvider is part of the URL Rewrite Extensibility Samples, which allows us to use custom providers for the rewrite\redirect rules – in our case: external .txt file with URL’s.

/old/catalog/product; /new/category/product
/old/contactus/index; /new/contactus

In our multisite scenario we will have two websites running, testsite1.com and testsite2.com. Which means we will have two FileMapProvider’s, one for each website.

Let’s have a look at how the FilemapProviders are configured in the UrlRewrite.config. Yes it’s a web.config transformation(because Sitecore config file patching only works within the sitecore section)

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <system.webServer>
	<rewrite>
	  <providers>
		<provider name="FileMapProviderForTestSite1" type="FileMapProvider, Microsoft.Web.Iis.Rewrite.Providers, Version=7.1.761.0, Culture=neutral, PublicKeyToken=0545b0627da60a5f"  xdt:Transform="Insert">
		  <settings>
			<add key="FilePath" value="D:\Files\Data\UrlRedirectsForTestSite1.txt"/>
			<add key="IgnoreCase" value="1"/>
			<add key="Separator" value=";"/>
		  </settings>
		</provider>
		<provider name="FileMapProviderForTestSite2" type="FileMapProvider, Microsoft.Web.Iis.Rewrite.Providers, Version=7.1.761.0, Culture=neutral, PublicKeyToken=0545b0627da60a5f"  xdt:Transform="Insert">
		  <settings>
			<add key="FilePath" value="D:\Files\Data\UrlRedirectsForTestSite2.txt"/>
			<add key="IgnoreCase" value="1"/>
			<add key="Separator" value=";"/>
		  </settings>
		</provider>
	  </providers>
	  <rules>
		<rule name="RuleForFileMapProviderForTestSite1" stopProcessing="false" xdt:Transform="Insert">
		  <match url="(.*)"/>
		  <conditions>
		    <add input="{HTTP_HOST}" pattern="^testsite1.com$"/>
			<add input="{FileMapProviderForTestSite1:{REQUEST_URI}}" pattern="(.+)"/>
		  </conditions>
		  <action type="Redirect" url="{C:1}" appendQueryString="false"/>
		 </rule>
		 <rule name="RuleForFileMapProviderForTestSite2" stopProcessing="false" xdt:Transform="Insert">
		  <match url="(.*)"/>
		  <conditions>
		    <add input="{HTTP_HOST}" pattern="^testsite2.com$"/>
			<add input="{FileMapProviderForTestSite2:{REQUEST_URI}}" pattern="(.+)"/>
		  </conditions>
		  <action type="Redirect" url="{C:1}" appendQueryString="false"/>
		</rule>
	  </rules>
	</rewrite>
  </system.webServer>
</configuration>		

If you notice there are two FileMapProvider configurations, one provider for each site – how cool is that πŸ™‚ The provider points to a text file(containing the redirects for that specific site).
Now in order to get the redirect magic to work we need to set up a rule. If you look at the rule RuleForFileMapProviderForTestSite1, you will see that this rule will only work for website – testsite1.com.
We do that by using pattern(below) to check if it matches with HTTP_HOST:

pattern="^testsite1.com$"

Then we use/get the “site” specific provider, FileMapProviderForTestSite1, to do the redirect.

Tip!
If you guys don’t want to install the URL Rewrite Extensibility Sample on a test or production server, fear not. Just grab it from your GAC on your local dev machine.
Fire up your PowerShell(for windows) and use the SUBST Command. Suppose you want to create a G Drive (G for GAC), use the following command:

SUBST G: C:\WINDOWS\ASSEMBLY

Look in folder G:\GAC_MSIL and you will find Microsoft.Web.Iis.Rewrite.Providers πŸ™‚

2. Setup the URL redirects in Sitecore

Next will be to setup the URL redirects in sitecore. The folder structure for the redirects.
treeredirects

UrlRedirectsItem: It will contain two fields – NewUrl and OldUrl
redirectitem

UrlRedirectsFolderItem: I’ve added a field in order to figure out what site it belongs to.
folderitem

3. Create the command button in Sitecore Content Editor

Next will be to create the command that will trigger the writing of redirects to a text file. I wanted the button only to be visible when the “UrlRedirects folder” is selected/marked:
contenteditorcommand
I also added a confirm dialog, in case the editor wants to cancel the request.

In the Sitecore Core database we will add the command button, we will use the Large Button template. Navigate to /sitecore/content/Applications/Content Editor/Ribbons/Contextual Ribbons and create the new ribbon – Sync Url Redirects:
corecommand
We will also create a new command – SyncUrlRedirects:ToFile

In order for the command button only to be visible for a specific template(in this case the Url Redirects folder) we need to go to the template and then in the Appearance Section locate the drop tree field, Ribbon. And finally select our newly created ribbon – Sync Url Redirects:
templatecommand

We will patch the new command to a config file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <commands>
      <command name="SyncUrlRedirects:ToFile" type="Sandbox.Website.Commands.SyncUrlRedirectsFromSitecoreToFileCommand,Sandbox.Website" />
    </commands>
  </sitecore>
</configuration> 

Now we need to add some code for the command button.
In the Execute method we will verify that we are standing on the UrlRedirectsSiteFolder item, next will be to get all the children(deriving from template UrlRedirect). The GetChildrenByTemplateWithFallback method is an extension method from the Sitecore.ContentSearch.Utilities.
I need to send some parameters to the Confirm dialog(number of redirect items and the item id of the Redirects Folder) and finally call/execute the Confirm dialog by using Sitecore.Context.ClientPage.Start.

public class SyncUrlRedirectsFromSitecoreToFileCommand : Command
{

	private const string QueryParamRedirectsToBeSynced = "count";
	private const string QueryParamFolderId = "folderid";

	public override void Execute(CommandContext context)
	{
		Assert.ArgumentNotNull(context, "context");

		Assert.IsNotNull(context.Items, "context items are null");

		Assert.IsTrue(context.Items.Length > 0, "context items length is 0");

		Item item = context.Items[0];

		Assert.IsNotNull(item, "Item is null");

		Assert.IsTrue(item.IsDerived(SitecoreTemplates.UrlRedirectsSiteFolder.ID), "Item is not of template UrlRedirectsSiteFolder");

		int numberOfRedirects =
		item.GetChildrenByTemplateWithFallback(SitecoreTemplates.UrlRedirect.ID.ToString()).Count();

		context.Parameters.Add(QueryParamRedirectsToBeSynced, numberOfRedirects.ToString());
		context.Parameters.Add(QueryParamFolderId, item.ID.ToString());

		Sitecore.Context.ClientPage.Start(this, "Confirm", context.Parameters);
	}

	protected void Confirm(ClientPipelineArgs args)
	{

		if (args.IsPostBack)
		{
			switch (args.Result)
			{
				case "yes":
					SyncUrlRedirectsEvent syncUrlRedirectsEvent = new SyncUrlRedirectsEvent(args.Parameters[QueryParamFolderId]);
					Sitecore.Eventing.EventManager.QueueEvent(syncUrlRedirectsEvent, true, true);
					return;

				case "no":
					return;
			}
		}

		SheerResponse.Confirm($"It is important that you publish all redirects before you continue. {args.Parameters[QueryParamRedirectsToBeSynced]} redirects will be synced.");
		args.WaitForPostBack();

	}

	public override CommandState QueryState(CommandContext context)
	{
		Assert.ArgumentNotNull(context, "context");

		if (context.Items.Length != 1)
			return CommandState.Hidden;

		Item item = context.Items[0];

		Assert.IsNotNull(item, "First context item is null");

		Sitecore.Data.Version[] versionNumbers = item.Versions.GetVersionNumbers(false);
		if (versionNumbers == null || versionNumbers.Length == 0 || item.Appearance.ReadOnly)
			return CommandState.Disabled;

		if (!item.Access.CanWrite() || !item.Access.CanRemoveVersion() || IsLockedByOther(item))
			return CommandState.Disabled;

		return base.QueryState(context);

	}

}

In the Confirm method we will check if the if the editor hits the yes button, if so we will call the SyncUrlRedirectsEvent and pass along the item id of the Redirects Folder.

4. Create the (remote) event and write redirects to file

In order to write the redirects to a file on a CD server, we will need a remote event – SyncUrlRedirectsEvent. The event is quite simple, it will just hold the item id of the Redirects Folder.
Here is the event and its EventArgs class:

[DataContract]
public class SyncUrlRedirectsEvent
{
	public SyncUrlRedirectsEvent(string urlRedirectsSiteFolderItemId)
	{
		UrlRedirectsSiteFolderItemId = urlRedirectsSiteFolderItemId;
	}

	[DataMember]
	public string UrlRedirectsSiteFolderItemId { get; protected set; }

}

[Serializable]
public class SyncUrlRedirectsEventArgs : EventArgs, IPassNativeEventArgs
{
	public SyncUrlRedirectsEventArgs(string urlRedirectsSiteFolderItemId)
	{
		UrlRedirectsSiteFolderItemId = urlRedirectsSiteFolderItemId;
	}

	public string UrlRedirectsSiteFolderItemId { get; protected set; }
}

Next is the eventhandler, that is the one which will be called/executed on the CD server, so we can write the redirects for the FileMapProvider. Here we fetch all redirects from the “UrlRedirectsSiteFolderItem id”(which we got from the SyncUrlRedirectsEventArgs) and then write them to a text file.
I will not go into how the SyncUrlRedirectsService works, it’s quite straightforward. If you are interested in how it’s done just ping me and I will show you πŸ™‚

public class SyncUrlRedirectsEventHandler
{
	private static ILogger _logger;
	private readonly ISyncUrlRedirectsService _syncUrlRedirectsService;
	private readonly IDatabaseRepository _databaseRepository;


	public SyncUrlRedirectsEventHandler()
	{
		_logger = IocContext.NonRequestContainer.GetInstance<ILogger>();
		_syncUrlRedirectsService = IocContext.NonRequestContainer.GetInstance<ISyncUrlRedirectsService>();
		_databaseRepository = new DatabaseRepository();
			
	}

	public virtual void OnRemoteSyncUrlRedirects(object sender, EventArgs e)
	{

		if (e == null)
		{
			_logger.Error("SyncUrlRedirectsEventArgs is empty");
			return;
		}
			

		SyncUrlRedirectsEventArgs syncUrlRedirectsEventArgs = (SyncUrlRedirectsEventArgs) e;

		if (syncUrlRedirectsEventArgs.UrlRedirectsSiteFolderItemId.IsNullOrWhiteSpace())
		{
			_logger.Error("UrlRedirectsSiteFolder item id is missing in SyncUrlRedirectsEventArgs");
			return;
		}

		Item urlRedirectsSiteFolderItem =
			_databaseRepository.ContextDatabase.GetItem(new ID(syncUrlRedirectsEventArgs.UrlRedirectsSiteFolderItemId));


		if (urlRedirectsSiteFolderItem == null)
		{
			_logger.Error("UrlRedirectsSiteFolderItem does not exist on {0}. Id: {1}", _databaseRepository.ContextDatabase.Name, syncUrlRedirectsEventArgs.UrlRedirectsSiteFolderItemId);
			return;
		}

		string sitename = urlRedirectsSiteFolderItem.GetString(SitecoreTemplates.UrlRedirectsSiteFolder.Fields.UrlRedirectsSiteName);

		if (sitename.IsNullOrWhiteSpace())
		{
			_logger.Error("Site name is not set in UrlRedirectsSiteFolder item id, {0}", urlRedirectsSiteFolderItem.ID);
			return;
		}


		IList<string[]> urlRedirectsList =	urlRedirectsSiteFolderItem.GetChildrenByTemplateWithFallback(SitecoreTemplates.UrlRedirect.ID.ToString())
			.Select(
				item =>
					new[] {item.GetString(SitecoreTemplates.UrlRedirect.Fields.UrlRedirectOldUrl), item.GetString(SitecoreTemplates.UrlRedirect.Fields.UrlRedirectNewUrl) }).ToList();


		Tuple<IList<string>, string> redirectsAndFilePath =  _syncUrlRedirectsService.PrepareRedirectsAndFilePathForFile(urlRedirectsList, sitename);

		_syncUrlRedirectsService.TryWriteRedirectsToFile(redirectsAndFilePath, sitename);

	}


	public static void Run(SyncUrlRedirectsEvent syncUrlRedirectsEvent)
	{
		_logger.Information("SyncUrlRedirectsEventHandler - Run", typeof(SyncUrlRedirectsEventHandler));

		SyncUrlRedirectsEventArgs args = new SyncUrlRedirectsEventArgs(syncUrlRedirectsEvent.UrlRedirectsSiteFolderItemId);

		Event.RaiseEvent("syncurlredirects:remote", args);
	}
}

The Run method is called from a Hook.

To glue it all together(event and eventhandler) we need to register and trigger the eventhandler, for that we will use a hook. See the hook as a very light pipeline.

public class SyncUrlRedirectsHook : IHook
{
	public void Initialize()
	{
		Sitecore.Eventing.EventManager.Subscribe(new Action<SyncUrlRedirectsEvent>(SyncUrlRedirectsEventHandler.Run));
	}
}

Here is the config file for the event and the hook:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
	<hooks>
	  <hook type="Sandbox.Website.Redirect.Events.SyncUrlRedirectsHook, Sandbox.Website" />
	</hooks>
    <events>
      <event name="syncurlredirects:remote">
        <handler type="Sandbox.Website.Redirect.Events.SyncUrlRedirectsEventHandler, Sandbox.Website" method="OnRemoteSyncUrlRedirects" />
      </event>
    </events>
  </sitecore>
</configuration>

Instead of using a hook, you can use a pipeline(for the event). Here are some great documentation regarding events in Sitecore:
http://sitecore-community.github.io/docs/pipelines-and-events/events/

That’s all for now folks πŸ™‚

Extend your Sitecore “Multilist Search Control” with a SPEAK treeview

advancedsearch2

In this post I would like to share with you guys how you can extend a control, in this case the Multilist with Search control, and add some very nice SPEAK functionality to it πŸ™‚

Let say you want to add some search functionality to the “Multilist with Search” control. How about giving the editors the possibility to use a treeview for selecting one or many items. πŸ™‚

We will add a third menu item(to the current two: Select all and Deselect all), called Advanced Search, which will open the treeview in a SPEAK dialog.
advanced4

We will divide the work into following:
1. Create a custom control(“Multilist with Search”) which will inherit BucketList and add some custom functionality to it.
2. Add the new control to the Sitecore Core database together with the third (new) menu item, called Advanced search.
3. Finally we will make a pop up(SPEAK dialog) which will present the tree view.

Create a custom control

Since the control “Multilist with Search” is of type Sitecore.Buckets.FieldTypes.BucketList, we will need the custom control to inherit the type. We want to open a SPEAK dialog window from the control, we will do this by adding a third menu item(next to Select all and Deselect all).

Let me give you guys a quick explanation how the custom control works. In the DoRender method we need to store some data in hidden fields, the current item and the start search item. The method HandleMessage will be called when you press a menu item(Select all, Deselect all and the new one Advanced search). If it finds the message “bucketsearchlist:openadvancedsearch” it will run the ClientPage.Start pipeline. The pipeline will call the OpenDialog method.

using System.Text.RegularExpressions;
using Sitecore.Buckets.FieldTypes;
using Sitecore.Diagnostics;
using Sitecore.Web.UI.Sheer;

namespace Sandbox.Website.Code.SitecoreCustom.Fields
{
	public class CustomMultilistSearch : BucketList
	{


		private const string PrefixCurrentItemId = "_currentItemId";
		private const string PrefixStartSearchLocation = "_startSearchLocation";

		public override void HandleMessage(Message message)
		{


			Assert.ArgumentNotNull((object)message, "message");
			base.HandleMessage(message);

			if (message["id"] != this.ID)
				return;

			if (message.Name != "bucketsearchlist:openadvancedsearch")
				return;

			Sitecore.Context.ClientPage.Start(this, "OpenDialog");

		}

		protected void OpenDialog(ClientPipelineArgs args)
		{
			if (args.IsPostBack)
			{
				Sitecore.Context.ClientPage.SendMessage(this, "item:refresh");
				return;
			}


			string url = GenerateSpeakUrl();

			SheerResponse.ShowModalDialog(new ModalDialogOptions(url)
			{
				Width = "600",
				Height = "600",
				Response = true,
				ForceDialogSize = true,
			});

			args.WaitForPostBack(true);
		}

		private string GenerateSpeakUrl()
		{
			string itemId = Sitecore.Context.ClientPage.ClientRequest.Form[$"{this.ID}{PrefixCurrentItemId}"];

			string loadPathId = Sitecore.Context.ClientPage.ClientRequest.Form[$"{this.ID}{PrefixStartSearchLocation}"];

			string rootItemInfo = GetRootItemInfo(loadPathId);

			string url =
				$"/sitecore/client/Sandbox/Applications/Dialogs/AdvancedGroupSearch?loadpathid={loadPathId}&rootiteminfo={rootItemInfo}&id={itemId}&database={Sitecore.Context.ContentDatabase.Name}&language={Sitecore.Context.Language.Name}&version=1";

			return url;
		}


		private string GetRootItemInfo(string loadPathtId)
		{
			Item item = Sitecore.Context.ContentDatabase.GetItem(new ID(loadPathtId));

			string imgSrc = Regex.Match(ThemeManager.GetIconImage(item, 16, 16, "", ""), "<img.+?src=[\"'](.+?)[\"'].*?>", RegexOptions.IgnoreCase).Groups[1].Value;

			return $"{item.DisplayName},{Sitecore.Context.ContentDatabase.Name},{loadPathtId},{imgSrc}";

		}

	
		private string GetStartSearchLocation()
		{
			NameValueCollection nameValues = StringUtil.GetNameValues(this.Source, '=', '&');

			foreach (string allKey in nameValues.AllKeys)
				nameValues[allKey] = HttpUtility.JavaScriptStringEncode(nameValues[allKey]);

			string startSearchLocation = nameValues["StartSearchLocation"];

			if (string.IsNullOrWhiteSpace(startSearchLocation))
				return ItemIDs.RootID.ToString();
			
			startSearchLocation = this.MakeFilterQueryable(startSearchLocation);

			return !Sitecore.Buckets.Extensions.StringExtensions.IsGuid(startSearchLocation) ? ItemIDs.RootID.ToString() : startSearchLocation;
		}

		protected override void DoRender(HtmlTextWriter output)
		{

			string startSearchLocation = GetStartSearchLocation();

			output.Write($"<input id=\"{this.ID}{PrefixCurrentItemId}\" type=\"hidden\" value=\"{this.ItemID}\" />");
			output.Write($"<input id=\"{this.ID}{PrefixStartSearchLocation}\" type=\"hidden\" value=\"{startSearchLocation}\" />");
			base.DoRender(output);

		}

	}
}

The method OpenDialog will open a SPEAK dialog by using the SheerResponse.ShowModalDialog method, we will add a proper url where the SPEAK dialog is located.

The SPEAK dialog will need the following parameters:
The actual item.
The start item to search from(which will be the root node for the treeview)

If you notice in the OpenDialog method we will also handle a postback. This means when the dialog is being closed it will call the command item:refresh to refresh the current page.

Add the new control to Sitecore Core

Next step will be to add the new control to Sitecore Core. We will put the control in /sitecore/system/Field types, an easy way is to just copy the item /sitecore/system/Field types/List Types/Multilist with Search.
custoncontrol

And here we add the third menu item with the message: bucketsearchlist:openadvancedsearch.
thirdmenuitem

Make the pop up(SPEAK dialog)

The SPEAK dialog is done in Sitecore 8.1, unfortunately it means that the itemTreeView is not in SPEAK 2.

Anyways lets fire up Sitecore Rocks and start creating the SPEAK dialog:
dialogspeak

If you notice I’ve put the treeview in a tabcontrol:
tabtreeviewspeak

What we have left is the Page Code file(js file).

define(["sitecore", "/-/speak/v1/experienceprofile/CintelUtl.js"], function (_sc, cintelUtil) {
	var dialog = _sc.Definitions.App.extend({

		initialized: function () {
			console.log("Advanced Search read");
			jQuery('body').addClass('sc-fullWidth');

			var self = this;
			self.OkButton.on("click", self.SaveSelection, this);
			self.TriggerButton.on("click", self.SetSelection, this);

			self.BindData();

			self.on("tree-isExpanded", self.TreeIsExpanded, self);
			
		},
		BindData: function () {
			var self = this;

			self.SetMessageToMessageBar("notification",
				"To set the selection from the multi search list control, please click on button - Set selection");
		
			self.GetGroupData(function (groupObject) {
				self.GroupData = groupObject;
				
				self.InitTreeView();

				var timer = setInterval(function () {
					if (!self.MyTreeView.get("isBusy")) {

						clearInterval(timer);
						
						self.ExpandTreeView();
					}

				}, 500);

			});


		},
		InitTreeView: function () {
			var self = this;

			var selector = self.MyTreeView.attributes.type;
			var treeView = $('.' + selector);

			treeView.attr("data-sc-rootitem", self.GroupData.rootItemInfo);
			treeView.attr("data-sc-loadpath", self.GroupData.loadPathId);

			self.MyTreeView.viewModel.getRoot().removeChildren();
			self.MyTreeView.viewModel.checkedItemIds([]);

			self.MyTreeView.viewModel.initialized();
		},
		TreeIsExpanded: function () {
			var self = this;
			var selector = self.MyTreeView.attributes.type;
			var treeView = $('.' + selector);

			treeView.dynatree("getRoot").visit(function (node) {
				node.expand(false);
			});

			self.ProgressIndicator.set("isVisible", false);

		},
		SetMessageToMessageBar: function (level, message, actions) {
			var self = this;
			self.TreeViewMessageBar.addMessage(level, {
				text: message,
				actions: [actions],
				closable: false
			});
		},
		SetSelection: function () {
			var self = this;
			var selector = self.MyTreeView.attributes.type;
			var treeView = $('.' + selector);
			$.each(self.GroupData.itemFieldMultiListSelections.split("|"), function (index, value) {
				treeView.dynatree("getTree").getNodeByKey(value).select();
			});
		},
		ExpandTreeView: function () {
			var self = this;

			var selector = self.MyTreeView.attributes.type;
			var treeView = $('.' + selector);
			
			var counter = 0;
			var lastCounted = 0;
			var rootNode = treeView.dynatree("getRoot");
			rootNode.select(false);

			
			treeView.dynatree("getRoot").visit(function (node) {
				node.expand();
			});


			$(".dynatree-container li ul").each(function (index, root) {
				checkEm(root);
			});

			var timer = setInterval(function () {
				if (lastCounted === counter) {
					clearInterval(timer);

					self.trigger('tree-isExpanded');


				}
				lastCounted = counter;

			}, 200);
			
			function checkEm(ulnode) {

				if (typeof (ulnode) == "undefined" || ulnode == null)
					return;

				$(ulnode).find('li').each(function (i, li) {

					//Only folders will we click on
					if ($(li).find("img")[0].src.indexOf("contract") === -1) {
						$(li).find("a").trigger("click");

						setTimeout(function () {
							counter += 1;

							var ulFound = $(li).find("ul");
							checkEm(ulFound);
					
							console.log("counter : " + counter);
						},
							100);
					}
				});
			}




		},
		SaveSelection: function () {
			var self = this;
			var selector = self.MyTreeView.attributes.type;
			var treeView = $('.' + selector);
			
			var selectedItems = $.grep(treeView.dynatree("getSelectedNodes"),
							function (node) { return !node.data.isFolder; })
						.map(function (node) { return node.data.key; });



			self.GroupData.itemFieldMultiListSelections = selectedItems.join("|");

			self.UpdateSitecoreData(function (readyState, status, responseText) {
				//Error
				if (readyState === 4 && (this.status === 404 || status === 500)) {
					self.SetMessageToMessageBar("error", responseText);
				}

				//Success
				if (readyState === 4 && this.status !== 404 && status !== 500) {
					window.top.dialogClose();
				}


			});

		},
		GetGroupData: function (callback) {

			var self = this;

			var groupObject = {};
			groupObject["id"] = cintelUtil.getQueryParam("id");
			groupObject["rootItemInfo"] = cintelUtil.getQueryParam("rootiteminfo");
			groupObject["loadPathId"] = cintelUtil.getQueryParam("loadpathid");
			groupObject["language"] = cintelUtil.getQueryParam("language");
			groupObject["version"] = cintelUtil.getQueryParam("version");
			groupObject["database"] = cintelUtil.getQueryParam("database");
			groupObject["databaseUri"] = new _sc.Definitions.Data.DatabaseUri(groupObject["database"]);
			groupObject["itemUri"] = new _sc.Definitions.Data
				.ItemUri(groupObject["databaseUri"], groupObject["id"]);
			groupObject["itemVersionUri"] = new _sc.Definitions.Data
				.ItemVersionUri(groupObject["itemUri"],
					groupObject["language"],
					parseInt(groupObject["version"]));

			var db = new _sc.Definitions.Data.Database(groupObject["databaseUri"]);

			db.getItem(groupObject["itemVersionUri"],
				function (item) {

					groupObject["itemName"] = item.itemName;
					groupObject["itemDisplayname"] = item.$displayName;

					var itemFieldMultiListSelectionsField = item.getFieldById(self.Constants.CustomMultilistFieldId);
					groupObject["itemFieldMultiListSelections"] = itemFieldMultiListSelectionsField.value;


					callback(groupObject);

				});


		},
		GroupData: function () {
			var content;

			return content;
		},
		UpdateSitecoreData: function (callback) {
			var self = this;

			var request = new XMLHttpRequest();

			var url = self.StringFormat("/sitecore/api/ssc/item/{0}?database={1}&language={2}&version={3}",
				self.GroupData.id,
				self.GroupData.database,
				self.GroupData.language,
				self.GroupData.version);

			request.open("PATCH", url);

			request.setRequestHeader('Content-Type', 'application/json');

			var body = {
				'CustomMultilist': self.GroupData.itemFieldMultiListSelections,
			};

			request.send(JSON.stringify(body));

			request.onreadystatechange = function () {

				return callback(this.readyState, this.status, this.responseText);

			};
		},
		Constants: {
			"CustomMultilistFieldId": "{105BF825-145D-4278-8063-2A5E9472698E}"
		},
		StringFormat: function () {
			var s = arguments[0];
			for (var i = 0; i < arguments.length - 1; i++) {
				var reg = new RegExp("\\{" + i + "\\}", "gm");
				s = s.replace(reg, arguments[i + 1]);
			}
			return s;
		}


	});

	return dialog;
});

Let me give you a quick explanation on how it works.

In the BindData method we will get the item data by calling GetGroupData. Here we will get the selections from the Multilist Search control.

Next will be to reinitialize the treeview, yes you read it correct. Normally you set the treeview data in the properties from Sitecore Rocks but in this case we don’t know the root node(start node for the treeview) since we get it as a parameter. As you can see in method InitTreeView we need to do some tricks to make it work thanks to the Sitecore Community πŸ™‚

We will then call the ExpandTreeView method, as you noticed I’ve put in an Interval function where we wait until the treeview is loaded.

In the ExpandTreeView method we need to expand all nodes in ALL levels, if we don’t do this we will NOT be able to set the the selections from the Multilist Search control.
When it’s done with the expanding we will close it nicely by calling/trigger method TreeIsExpanded.

To set the selections we do this in method SetSelection which is triggered from the button “Set selections”.
Finally when we hit the Save button we will call the SaveSelection method to gather the checked items from the treeview and then save them to Sitecore.

That’s it and keep doing some good SPEAK stuff out there.

That’s all for now folks πŸ™‚

Fetch and save Sitecore Item in SPEAK 2 using Sitecore.Services.Client

testapppage

Hello guys, yet another post about SPEAK πŸ™‚
In this medium small post I would like to share with you how I work with Sitecore items in SPEAK.

Let’s say you need to retrieve a Sitecore item, modify it and update it.
In this example the Sitecore item is exposed in an url, like this:

https://somehost.com/sitecore/client/MyApp/Applications/TestApp/TestAppPage?id=7E4CA53A-59AA-4C08-9EEF-71DDF66E8A8E&database=master&language=en&version=1

This will be a simple page containing two text boxes and a save button(See above)
Here is the Design Layout for the page:
tasklayout
I really like the Grid rendering and guess what it’s responsive friendly πŸ™‚

To get an item I do something like this:

GetTestData: function (callback) {

	var self = this;
	var testObject = {};
	testObject["id"] = Sitecore.Speak.utils.url.parameterByName("id");
	testObject["language"] = Sitecore.Speak.utils.url.parameterByName("language");
	testObject["version"] = Sitecore.Speak.utils.url.parameterByName("version");
	testObject["database"] = Sitecore.Speak.utils.url.parameterByName("database");
	testObject["databaseUri"] = new _sc.Definitions.Data.DatabaseUri(testObject["database"]);
	testObject["itemUri"] = new _sc.Definitions.Data
		.ItemUri(testObject["databaseUri"], testObject["id"]);
	testObject["itemVersionUri"] = new _sc.Definitions.Data
		.ItemVersionUri(testObject["itemUri"],
			testObject["language"],
			parseInt(testObject["version"]));

	var db = new _sc.Definitions.Data.Database(testObject["databaseUri"]);

	db.getItem(testObject["itemVersionUri"],
		function (item) {

			testObject["itemName"] = item.itemName;
			testObject["itemDisplayname"] = item.$displayName;

			var testHeaderField = item.getFieldById(self.Constants.TestHeaderFieldId);
			testObject["itemFieldTestHeader"] = testHeaderField.value;

			var testDescriptionField = item.getFieldById(self.Constants.TestDescriptionFieldId);
			testObject["itemFieldTestDescription"] = testDescriptionField.value;

			callback(testObject);

		});


}

The reason for the callback function is because of db.getItem. When the method has retrieved the Sitecore item I’ll put the data in a JSON object.
The Sitecore.Speak.utils.url.parameterByName is indeed handy πŸ™‚
(I’m not using Sitecore.Services.Client here)

To call the GetTestData method:

BindTestData: function () {
	var self = this;

	self.GetTestData(function (testObject) {
		self.TestData = testObject;

		//Do some binding stuff to the controls on your Speak layout page
		self.MyHeaderTextBox.Value = self.TestData.itemFieldTestHeader;
		self.MyDescriptionTextBox.Value = self.TestData.itemFieldTestDescription;
	});

}

Here we will bind/set the data to the text boxes.
(self.TestData is just a container)

To update a Sitecore item we will use Sitecore.Services.Client. Thanks to Kevin Obee’s great “living” documentation, http://docs.itemserviceapi.apiary.io, he made it so much easier to understand how it works.
There is also a great post that describes the differences between Sitecore Item Web API and Sitecore.Services.Client – What is Sitecore.Services.Client?

Here is how the we update the Sitecore item using Sitecore.Services.Client

UpdateSitecoreData: function (callback) {
	var self = this;

	var request = new XMLHttpRequest();

	var url = self.StringFormat("/sitecore/api/ssc/item/{0}?database={1}&language={2}&version={3}",
		self.TestData.id,
		self.TestData.database,
		self.TestData.language,
		self.TestData.version);

	request.open("PATCH", url);

	request.setRequestHeader('Content-Type', 'application/json');

	var body = {
		'TestHeader': self.TestData.itemFieldTestHeader,
		'TestDescription': self.TestData.itemFieldTestDescription
	};

	request.send(JSON.stringify(body));

	request.onreadystatechange = function () {

		return callback(this.readyState, this.status, this.responseText);

	};
}

Again I’m using a callback, since we need to wait for the response if the save was successful or not. TestHeader and TestDescription are the field names of the Sitecore item.

Another thing, if you would get the forbidden page (You do not have permission to view this directory or page) when using the Sitecore.Services.Client. That means you probably have to take a look at the security policy settings in Sitecore.Services.Client.config. Find the setting that works best for you.

And here we call UpdateSitecoreData:

SaveTestData: function () {
	var self = this;

	//Get the data from the text boxes 
	self.TestData.itemFieldTestHeader = self.MyHeaderTextBox.Value;
	self.TestData.itemFieldTestDescription = self.MyDescriptionTextBox.Value;
	
	self.UpdateSitecoreData(function (readyState, status, responseText) {
		//Failure
		if (readyState === 4 && (this.status === 404 || status === 500)) {
			self.StatusAlertConfirmationDialog["HeaderText"] = "Save - error";
			self.StatusAlertConfirmationDialog["Title"] = "An error occurred while saving, please contact support";
			self.StatusAlertConfirmationDialog["IconType"] = "error";
			self.StatusAlertConfirmationDialog["Message"] = responseText;
			self.StatusAlertConfirmationDialog.show();
			self.StatusAlertConfirmationDialog.IsVisible = true;

		}

		//Success
		if (readyState === 4 && this.status !== 404 && status !== 500) {
			self.BindTestData();
		}


	});

}

If you succeed with the update we will “rebind” the data by calling BindTestData, if not we will show a StatusAlertConfirmationDialog(ConfirmationDialog).

Here is the full PageCode file:

(function (Speak) {

	Speak.pageCode({
		initialized: function () {
			console.log("Start TestAppPage");
			var self = this;

			self.SaveTestButton.on("click", self.ConfirmSave, this);

			self.UpdateTestDataConfirmationDialog.on("close",
				function (data) {

					if (data === "ok") {

						self.SaveSearch();
				
					}
                    self.UpdateTestDataConfirmationDialog.IsVisible = false;
				},
				this);	

			self.BindTestData();


		},
		ConfirmSave: function () {
			var self = this;
			self.UpdateTestDataConfirmationDialog.show();
			self.UpdateTestDataConfirmationDialog.IsVisible = true;
		},
		BindTestData: function () {
			var self = this;

			self.GetTestData(function (testObject) {
				self.TestData = testObject;

				//Do some binding stuff to the controls on your Speak layot page
                self.MyHeaderTextBox.Value = self.TestData.itemFieldTestHeader;
                self.MyDescriptionTextBox.Value = self.TestData.itemFieldTestDescription;
			});


		},
		GetTestData: function (callback) {

			var self = this;

			var testObject = {};
			testObject["id"] = Sitecore.Speak.utils.url.parameterByName("id");
			testObject["language"] = Sitecore.Speak.utils.url.parameterByName("language");
			testObject["version"] = Sitecore.Speak.utils.url.parameterByName("version");
			testObject["database"] = Sitecore.Speak.utils.url.parameterByName("database");
			testObject["databaseUri"] = new _sc.Definitions.Data.DatabaseUri(testObject["database"]);
			testObject["itemUri"] = new _sc.Definitions.Data
				.ItemUri(testObject["databaseUri"], testObject["id"]);
			testObject["itemVersionUri"] = new _sc.Definitions.Data
				.ItemVersionUri(testObject["itemUri"],
					testObject["language"],
					parseInt(testObject["version"]));

			var db = new _sc.Definitions.Data.Database(testObject["databaseUri"]);

			db.getItem(testObject["itemVersionUri"],
				function (item) {

					testObject["itemName"] = item.itemName;
					testObject["itemDisplayname"] = item.$displayName;

					var testHeaderField = item.getFieldById(self.Constants.TestHeaderFieldId);
					testObject["itemFieldTestHeader"] = testHeaderField.value;

					var testDescriptionField = item.getFieldById(self.Constants.TestDescriptionFieldId);
					testObject["itemFieldTestDescription"] = testDescriptionField.value;

					callback(testObject);

				});


		},
		Constants: {
			"TestHeaderFieldId": "{3699A67C-A4BC-486D-9A37-9E4554AE46CA}",
			"TestDescriptionFieldId": "{E85C0CA0-75F2-4FB7-B2B5-03D8B97EBCCA}"
		},
		TestData: function () {
			var content;

			return content;
		},
		SaveTestData: function () {
			var self = this;

			//Get the data from the textboxes 
			self.TestData.itemFieldTestHeader = self.MyHeaderTextBox.Value;
			self.TestData.itemFieldTestDescription = self.MyDescriptionTextBox.Value;
			
			self.UpdateSitecoreData(function (readyState, status, responseText) {
				//Failure
				if (readyState === 4 && (this.status === 404 || status === 500)) {
					self.StatusAlertConfirmationDialog["HeaderText"] = "Save - error";
					self.StatusAlertConfirmationDialog["Title"] = "An error occurred while saving, please contact support";
					self.StatusAlertConfirmationDialog["IconType"] = "error";
					self.StatusAlertConfirmationDialog["Message"] = responseText;
					self.StatusAlertConfirmationDialog.show();
					self.StatusAlertConfirmationDialog.IsVisible = true;

				}

				//Success
				if (readyState === 4 && this.status !== 404 && status !== 500) {
					self.BindTestData();
				}


			});


		},
		UpdateSitecoreData: function (callback) {
			var self = this;

			var request = new XMLHttpRequest();

			var url = self.StringFormat("/sitecore/api/ssc/item/{0}?database={1}&language={2}&version={3}",
				self.TestData.id,
				self.TestData.database,
				self.TestData.language,
				self.TestData.version);

			request.open("PATCH", url);

			request.setRequestHeader('Content-Type', 'application/json');

			var body = {
				'TestHeader': self.TestData.itemFieldTestHeader,
				'TestDescription': self.TestData.itemFieldTestDescription
			};

			request.send(JSON.stringify(body));

			request.onreadystatechange = function () {

				return callback(this.readyState, this.status, this.responseText);

			};
		},
		StringFormat: function () {
			var s = arguments[0];
			for (var i = 0; i < arguments.length - 1; i++) {
				var reg = new RegExp("\\{" + i + "\\}", "gm");
				s = s.replace(reg, arguments[i + 1]);
			}
			return s;
		}
		
	});
})(Sitecore.Speak);

If you noticed there is a call to ConfirmSave when the Save button is clicked which will popup a ConfirmationDialog.

ConfirmSave: function () {
	var self = this;
	self.UpdateTestDataConfirmationDialog.show();
	self.UpdateTestDataConfirmationDialog.IsVisible = true;
}

If you are wondering about the self.UpdateTestDataConfirmationDialog.IsVisible = true, yes the show() method is enough to pop up the dialog. But I need to know that the dialog is visible, that’s why I’m using the IsVisible attribute.

Finally in the initialized function we are listening to the UpdateTestDataConfirmationDialog’s close event. If OK button is clicked we will call the SaveSearch.

self.UpdateTestDataConfirmationDialog.on("close",
function (data) {

	if (data === "ok") {

		self.SaveSearch();
 
	}
	self.UpdateTestDataConfirmationDialog.IsVisible = false;
},
this);

That’s it and keep doing some good SPEAK stuff out there.

That’s all for now folks πŸ™‚

Put JSON data in your SearchDataSource and bind it to SearchableDroplist – Sitecore SPEAK

droplist

Hello guys, I have been working with SPEAK for a while and some of the controls I’ve been using a lot are:
SearchDataSource and SearchableDropList(SPEAK 2 control)

The SearchableDropList is a droplist with search, the datasources you can use are:
QueryDataSource
SearchDataSource

If you have less than 100 items then QueryDataSource is OK(since it’s using Sitecore Query or Sitecore Fast Query) but if you have more, then use the SearchDataSource(using good old index).

An extremely cool thing regarding SearchDataSource is that you can add JSON data to it. Just create a JSON array and add it like this:

(function (Speak) {

	Speak.pageCode({


		initialized: function () {
			
			var self = this;
			
			self.BindJsonDataToMyCoolSearchDataSource();


		},
        BindJsonDataToMyCoolSearchDataSource: function() {
			var self = this;

			var customJsonData = [];

			for (var i = 0; i < 10; i++) {

				var data = {};
				data["Value"] = i;
				data["Text"] = i + " some text"; 
				customJsonData.push();
			}


			self.MyCoolSearchDataSource.Items = customJsonData;

		}

	});
})(Sitecore.Speak);

To add the JSON data we just set the Items property on the SearchDataSource:
self.MyCoolSearchDataSource.Items = customJsonData;

Then in your SpeakLayout you will connect the SearchDataSource to your control, in this case the SearchableDropList:
speaksearch
To set the datasource for the SearchableDropList, we will use DynamicData. Here we point(bind) to the SearchDataSource:
{Binding MyCoolSearchDataSource.Items}

Next we will set DisplayFieldName to the JSON field “Text” and finally we set ValueFieldName to the JSON field “Value”.

Now you you will have a searchable droplist where you can search in your custom JSON data:
droplist

By the way the new version(Version 2) of Sitecore Rocks is so much faster and it has some very new nice features. It truly rocks πŸ˜‰

That’s it and keep doing some good SPEAK stuff out there

That’s all for now folks πŸ™‚

How to trigger an OK click on a Sitecore SPEAK ConfirmationDialog

dialog

This is a really quick post but I feel I have to share this with you guys.

I have been doing a lot of SPEAK lately and from time to time I end up struggling with something that should be really easy to do(and yes it is easy when you finally find the solution).

Anyways I had to trigger an OK click on a ConfirmationDialog(SPEAK 2 component) in SPEAK.
speak

In this case the ConfirmationDialog should be closed with the Enter key by trigger the OK button. Let’s take a look on the PageCode:

(function (Speak) {

	Speak.pageCode({


		initialized: function () {
			
			var self = this;
			
			self.MyConfirmationDialog.on("close",
				function (data) {

					if (data === "ok") {

						//Do some stuff
					}

				},
				this);
			

			jQuery(document).keypress(function (e) {

				if (e.which === 13) {

					if (self.MyConfirmationDialog.IsVisible) {
						self.MyConfirmationDialog.CloseClick = "ok";
						self.MyConfirmationDialog.hide();
					}

				}
			});


		}
	});
})(Sitecore.Speak);

To close the ConfirmationDialog we would call the hide method – self.MyConfirmationDialog.hide().

The trickier part was to tell what button should be clicked/triggered and that took a while for me to figure out.
In CloseClick you need to set what button you want to trigger, like this self.MyConfirmationDialog.CloseClick = “ok”

That’s it and keep doing some good SPEAK stuff out there

That’s all for now folks πŸ™‚

Authorize your Web API controllers in Sitecore style

webapifilters01
I would like to share with you guys how easy it is to limit access to your Web API actions or controllers. Recently when I was working with a SPEAK component I had to make a Web API controller that only allowed users with a certain role(The Admin user will always have access).

So as always I looked at how Sitecore did and I just loved it πŸ™‚

They are using Authentication filters.

Authentication filters let you set an authentication scheme for individual controllers or actions. That way, your app can support different authentication mechanisms for different HTTP resources.

To apply an authentication filter to a controller, decorate the controller class with the filter attribute.

What we need is a filter to authenticate if user is part of a specific role.
I took a glance at on one of the filters Sitecore did, AuthorizedReportingUserFilter.

using Sitecore.Globalization;
using Sitecore.Xdb.Configuration;
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace Sitecore.Cintel.Endpoint.Plumbing
{
  public class AuthorizedReportingUserFilter : AuthorizationFilterAttribute
  {
    public override void OnAuthorization(HttpActionContext actionContext)
    {
      if ((Context.User.IsAdministrator || Context.User.IsInRole("sitecore\\analytics reporting")) && (XdbSettings.Enabled && Context.User.IsAuthenticated))
        return;
      string message = Translate.Text("Unauthorized Access");
      actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, message);
      base.OnAuthorization(actionContext);
    }
  }
}

As you guys can see, in OnAuthorization that is where all the magic happens. In this case a check is made if user is part of analytics reporting and is authenticated.

It’s almost what we need. So what to do? Let’s make our own filter, like this:

using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using Sitecore;
using Sitecore.Globalization;

namespace Sandbox.Website.Code.HttpFilters
{
  public class AuthorizedCustomRoleFilter : AuthorizationFilterAttribute
  {
    private readonly string _role;

    public AuthorizedCustomRoleFilter (string role)
    {
        _role = role;
    }

    public override void OnAuthorization(HttpActionContext actionContext)
    {
       if (Context.User.IsAdministrator || Context.User.IsInRole($"sitecore\\{_role}") && Context.User.IsAuthenticated)
       return;

       string message = Translate.Text("Unauthorized Access");
       actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, message);
       base.OnAuthorization(actionContext);
    }
  }
}

As you guys can see we have a parameter in the construct, that one allows us to add whatever role you want.

Here is an example where we use the new filter on a Web API controller.

namespace Sandbox.Website.Code.WebAPI.Controllers
{
  [AuthorizedCustomRoleFilter("Author")]
  public class MyController : ApiController
  {

So start doing a bunch of http filters for your web api controllers.

That’s all for now folks πŸ™‚

Map your rendering parameters to your Glass.Mapper viewmodels in Sitecore

HeroImage

I’ve been having(and still) the great pleasure to work with Glass.Mapper for Sitecore and I must say it makes your life so much easier as a developer. Mike Edwards, the founder has done one heck of a job πŸ™‚

I really like the interface model approach where you let an interface represent a sitecore item.

This is a quick post regarding rendering parameters and mapping them to a viewmodel.

In Glass.Mapper when using(inheriting) the GlassController you will find some very nifty methods like GetDataSourceItem and GetRenderingParameters.

Let’s say you have following rendering parameters in Sitecore:
RenderingParams

This is how it will be represented as an interface

[SitecoreType(TemplateId = "{here is a template id}", AutoMap = true)]
public interface IVisibilityRenderingParameters
{
	bool VisibleOnDesktop { get; set; }

	bool VisibleOnTablet { get; set; }

	bool VisibleOnMobile { get; set; }
}

To get the rendering parameters in your Action Result you just have to call the GetRenderingParameters method(You need of course inherit the GlassController to your controller).

  IVisibilityRenderingParameters renderingParameters = GetRenderingParameters<IVisibilityRenderingParameters>();

That is very nice and so easy to use.

Now what I would like to do is to add(map) the parameters to my interface viewmodel. Here is the viewmodel representing a news spot item:

[SitecoreType(AutoMap = true)]
public interface INewsSpotModel : IVisibilityRenderingParameters
{
	string Header { get; set; }
		
	string Subheader { get; set; }

	Image BackgroundImage { get; set; }

		
}

As you can see I’m inheriting the IVisibilityRenderingParameters.

What will happen when I call the GetDataSourceItem(which is also from the GlassController) for the viewmodel INewsSpotModel?

  INewsSpotModel model = GetDataSourceItem<INewsSpotModel>();

Well the tree properties from IVisibilityRenderingParameters will not be set, but…

What if we could do something like this and get the data from the rendering parameters to the viewmodel

  INewsSpotModelmodel = GetDataSourceItemWithRenderingParameters<INewsSpotModel, IVisibilityRenderingParameters>();

I’ve added a generic method to our controller(best would be if you had a base controller which of course needs to inherit the GlassController).

using AutoMapper;
using Glass.Mapper.Sc.Web.Mvc;

namespace Sandbox.Website
{
	public class MyBaseController : GlassController
	{

		/// <summary>
		/// Map rendering parameters to datasource item interface.
		/// </summary>
		/// <typeparam name="TModel">Datasource item interface</typeparam>
		/// <typeparam name="TRenderingParameters">Rendering parameters interface</typeparam>
		/// <param name="isLazy"></param>
		/// <param name="inferType"></param>
		/// <returns></returns>
		protected TModel GetDataSourceItemWithRenderingParameters<TModel, TRenderingParameters>(bool isLazy = false,
			bool inferType = false) where TModel : class where TRenderingParameters : class
		{
			TModel model = GetDataSourceItem<TModel>(isLazy, inferType);

			TRenderingParameters parameters = GetRenderingParameters<TRenderingParameters>();

			//If no parameters
			if (parameters == null)
				return model;

			Mapper.Initialize(cfg => cfg.CreateMap<TRenderingParameters, TModel>());

			model = Mapper.Map(parameters, model);

			return model;
		}
	}

}

We will get the viewmodel(TModel) item using the GetDataSourceItem method and to get the rendering parameters(TRenderingParameters) we use the GetRenderingParameters method.

Now to map the rendering parameters to the viewmodel we will use AutoMapper. In the Mapper.Initialize method we will set the mapping configurations and it’s all generic πŸ™‚

 Mapper.Initialize(cfg => cfg.CreateMap<TRenderingParameters, TModel>());

The actual mapping happens in this line:

 model = Mapper.Map(parameters, model);

To make it even prettier we could move the mapping configuration(and initializing) to a dependency injection container. But then we will have to specify what viewmodel is using/inheriting rendering parameter model and so on.

I’m not sure if we could use something from Glass.Mapper instead, I mean there is AutoMap().

That’s all for now folks πŸ™‚

Real-time in Sitecore.Habitat with SignalR and MongoDB

RealTimeYo

Personalizing content has always been a big fascination of mine and I just love Sitecore’s very elegant solution by using the rules engine. Thanks to this very cool tool the editors can do the following:

Rule-based personalization
Rule-based personalization uses logic-based rules to determine the content that is displayed on a webpage. For example, you can set rules based on the IP address or physical location of your visitors, the keywords they use to reach your site, their mobile device, or the goals that they achieve on your website to determine the content that is displayed.

Adaptive personalization

Adaptive personalization is a feature that dynamically changes the content of your website based on the visitor’s behavior during a visit. Adaptive personalization uses visitor profiles and pattern-card matching to dynamically adapt the content shown to visitors in real time. You can set adaptive personalization rules in the Rules Set Editor.

Historical personalization
You can use rules that personalize content based on a contact’s historical or past behavior, rather than their actions from the current session. The Key Behavior Cache contains information about a contact’s recent activities across all channels. The Contact Behavior Profile contains information about a contact’s past profile matches.

You can create and implement personalization rules based on the information in the Key Behavior Cache and Contact Behavior Profile, enabling you to provide contacts with content that is relevant based on their past behavior, rather than just their current interaction with your brand.

We just need one more thing to make the personalization even more interesting – Personalization in real-time!
How can we do that? By using SignalR of course πŸ™‚ (There are of course other ways to do this but SignalR has a special place in my heart)

There are a number of Sitecore “frameworks” out there but there is one who stands out – Sitecore.Habitat. Thanks to the Modular Architecture approach everything is clean. Anders Laub made a great post about this – The groundbreaking Sitecore Habitat

If you guys are going to the Sitecore Symposium 2016 New Orleans, then you must attend on the Habitat Master Class. The course is held by mr Habitat himself – Thomas Eldblom πŸ™‚

First we need to setup the SignalR and hook it up with MongoDB, that is what this post will be about. For some time ago I did a post about real-time in Sitecore, Real-time in Sitecore using SignalR and MongoDB. I’ve decided to migrate the old code to the best ever Sitecore “framework” – Sitecore.Habitat.

Here is how the project look like after the migration. It’s a typical “Foundation” project, since it probably will be used by “Feature” projects.
I’ve highlighted the ones that I will take up in the post.
Realtime fΓΌr die Leute
Check it out on the GitHub – GoranHalvarsson/Habitat

web.config.transform

In order to make SignalR work properly we need to do some changes/updates in webconfig.
This guy was indeed tricky, working with Xdt transform is not easy but thanks to this little puppy it made my life so much easier – Web.config Transformation Syntax for Web Project Deployment Using Visual Studio.

<configuration  xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">

  <appSettings xdt:Transform="InsertIfMissing">
    <add key="ValidationSettings:UnobtrusiveValidationMode" value="None" xdt:Transform="InsertIfMissing" xdt:Locator="Match(key)"/>
  </appSettings>
  <system.web>
	<httpRuntime targetFramework="4.5" requestValidationMode="2.0" xdt:Transform="SetAttributes(targetFramework,requestValidationMode)" />
  </system.web>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly xdt:Transform="InsertIfMissing" xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='MongoDB.Bson')">
        <assemblyIdentity name="MongoDB.Bson" publicKeyToken="f686731cfb9cc103" culture="neutral" />
        <codeBase version="2.2.4.26" href="bin\MongoDB.Bson.dll" />
        <codeBase version="1.10.0.62" href="bin\Sitecore\MongoDB.Bson.dll" />
      </dependentAssembly>
      <dependentAssembly xdt:Transform="InsertIfMissing" xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='MongoDB.Driver')">
        <assemblyIdentity name="MongoDB.Driver" publicKeyToken="f686731cfb9cc103" culture="neutral" />
        <codeBase version="2.2.4.26" href="bin\MongoDB.Driver.dll" />
        <codeBase version="1.10.0.62" href="bin\Sitecore\MongoDB.Driver.dll" />
      </dependentAssembly>
      <dependentAssembly  xdt:Transform="InsertIfMissing" xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='Microsoft.AspNet.SignalR.Core')">
        <assemblyIdentity name="Microsoft.AspNet.SignalR.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-2.2.0.0" newVersion="2.2.0.0" />
      </dependentAssembly>
      <dependentAssembly xdt:Transform="InsertIfMissing" xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='Microsoft.Owin')">
        <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly xdt:Transform="InsertIfMissing" xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='Microsoft.Owin.Security')">
        <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
      </dependentAssembly>
      <dependentAssembly xdt:Transform="Replace" xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='Newtonsoft.Json')">
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" />
        <bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>

</configuration>

Lets break it down, we will start with the changes to make SignalR work properly.

The request validation feature in ASP.NET provides a certain level of default protection against cross-site scripting (XSS) attacks. In ASP.NET 4, by default, request validation is enabled for all requests. As a result, request validation errors might now occur for requests that previously did not trigger errors. To revert to the behavior of the ASP.NET 2.0 request validation feature we need to add requestValidationMode=”2.0″ in the httpRuntime.

To prevent the Websocket error, 500, which means that the server does not support websockets. In this specific case server means the server (IIS) supports WebSockets but ASP.NET version of your application which hosts SignalR does not support websockets. In the httpRuntime set targetframework to 4.5.

Finally the UnobtrusiveValidationMode setting is really not necessary since this is for webforms only, but hey lets put that one there too πŸ™‚

  <appSettings xdt:Transform="InsertIfMissing">
    <add key="ValidationSettings:UnobtrusiveValidationMode" value="None" xdt:Transform="InsertIfMissing" xdt:Locator="Match(key)"/>
  </appSettings>
  <system.web>
	<httpRuntime targetFramework="4.5" requestValidationMode="2.0" xdt:Transform="SetAttributes(targetFramework,requestValidationMode)" />
  </system.web>

Next part is the dll’s. Since I’m using the 2.0 .Net Driver for MongoDB with Async support but Sitecore is using the previous version of the driver. We need to do the following in config:

  <dependentAssembly xdt:Transform="InsertIfMissing" xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='MongoDB.Bson')">
    <assemblyIdentity name="MongoDB.Bson" publicKeyToken="f686731cfb9cc103" culture="neutral" />
    <codeBase version="2.2.4.26" href="bin\MongoDB.Bson.dll" />
    <codeBase version="1.10.0.62" href="bin\Sitecore\MongoDB.Bson.dll" />
  </dependentAssembly>
  <dependentAssembly xdt:Transform="InsertIfMissing" xdt:Locator="Condition(./_defaultNamespace:assemblyIdentity/@name='MongoDB.Driver')">
    <assemblyIdentity name="MongoDB.Driver" publicKeyToken="f686731cfb9cc103" culture="neutral" />
    <codeBase version="2.2.4.26" href="bin\MongoDB.Driver.dll" />
    <codeBase version="1.10.0.62" href="bin\Sitecore\MongoDB.Driver.dll" />
  </dependentAssembly>

You will also have to create a Sitecore folder in your bin folder in the project, here you will place the “old” dll’s that Sitecore is using – MongoDB.Bson.dll and MongoDB.Driver.dll.

There will also be some gulp scripts that will copy over the specific dll’s when it’s time to publish to the website:

gulp.task("Realtime-Copy-MongoDB-dll's", function () {
    console.log("Copying mongodb assemblies to website");
    var root = "./src/Foundation/Realtime/code/bin/Sitecore";
    var binFiles = root + "/*.{dll,pdb}";
    var destination = config.websiteRoot + "/bin/Sitecore/";
   
    console.log("copying to " + destination);


    return gulp.src(binFiles, { base: root })
      .pipe(gulp.dest(destination));
});

gulp.task("Realtime-Copy-Newtonsoft-dll", function () {
    console.log("Copying newtonsoft assemblies to website");
    var root = "./src/Foundation/Realtime/code/bin";
    var binFiles = root + "/Newtonsoft.Json.{dll,pdb,xml}";
    var destination = config.websiteRoot + "/bin/";

    console.log("copying to " + destination);


    return gulp.src(binFiles, { base: root })
      .pipe(gulp.dest(destination));
});

gulp.task("Realtime-Xml-Transform", function () {
    return gulp.src("./src/Foundation/Realtime/**/code/*.csproj")
      .pipe(foreach(function (stream, file) {
          return stream
            .pipe(debug({ title: "Applying transform realtime:" }))
            .pipe(msbuild({
                targets: ["ApplyTransform"],
                configuration: config.buildConfiguration,
                logCommand: false,
                verbosity: "normal",
                maxcpucount: 0,
                toolsVersion: 14.0,
                properties: {
                    WebConfigToTransform: config.websiteRoot + "\\web.config"
                }
            }));
      }));

});

The last one is to make sure that we run the xml transform for the project.

RealtimeHub.cs

This is the brain of the SignalR, the hub will take care of incoming(javascript calls from the client) and outgoing(calling javascript methods on the client) calls.

It’s very well described in my older post: Create a real-time connection.
You can check out the the code on GitHub – RealtimeHub.cs

Realtime.js

This is the heart of the SignalR, the javascript will feed and listen to the “brain”(hub).

It’s also very well described in my older post: Gather visitor data.
You can check out the the code on GitHub – Realtime.js

RegisterSignalrProcessor.cs

The pipeline will replace the old startup class. We will need to mark it with OwinStartup(SignalR requires Owin).

using Microsoft.Owin;
using VisionsInCode.Foundation.Realtime.Pipelines;

[assembly: OwinStartup(typeof(RegisterSignalrProcessor))]
namespace VisionsInCode.Foundation.Realtime.Pipelines
{
  using Microsoft.AspNet.SignalR;
  using Owin;
  using Sitecore.Diagnostics;
  using Sitecore.Pipelines;
  using VisionsInCode.Foundation.Realtime.Infrastructure;
  using VisionsInCode.Foundation.Realtime.Repositories;

  public class RegisterSignalrProcessor
  {
    public void Configuration(IAppBuilder app)
    {
      Log.Info("OwinStartup has started", this);

      
      GlobalHost.HubPipeline.AddModule(new ErrorHandlingHubPipelineModule());


      GlobalHost.DependencyResolver.Register(
            typeof(RealtimeHub),
            () => new RealtimeHub(new RealtimeVisitorRepository(), new GeoCoordinateRepository(), new HubContextService(), new GeocoderService()));

      app.MapSignalR();

    }

    public virtual void Process(PipelineArgs args)
    {
      Log.Info("Pipeline RegisterSignalrProcessor called", this);

    }

  }
}

You can check out the the code on GitHub – RegisterSignalrProcessor.cs

Foundation.Realtime.config

And finally the config patch file:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
  <sitecore>
    <settings>
      <setting name="IgnoreUrlPrefixes">
        <patch:attribute name="value">/sitecore/default.aspx|/trace.axd|/webresource.axd|/sitecore/shell/Controls/Rich Text Editor/Telerik.Web.UI.DialogHandler.aspx|/sitecore/shell/applications/content manager/telerik.web.ui.dialoghandler.aspx|/sitecore/shell/Controls/Rich Text Editor/Telerik.Web.UI.SpellCheckHandler.axd|/Telerik.Web.UI.WebResource.axd|/sitecore/admin/upgrade/|/layouts/testing|/sitecore/service/xdb/disabled.aspx|/signalr|/signalr/hubs</patch:attribute>
      </setting>
    </settings>
    <pipelines>
      <initialize>
        <processor type="VisionsInCode.Foundation.Realtime.Pipelines.RegisterSignalrProcessor, VisionsInCode.Foundation.Realtime"
       patch:before="processor[@type='Sitecore.Mvc.Pipelines.Loader.InitializeRoutes, Sitecore.Mvc']" ></processor>
      </initialize>
      <mvc.getPageRendering>
        <processor
          patch:before="*[@type='Sitecore.Mvc.Pipelines.Response.GetPageRendering.GetLayoutRendering, Sitecore.Mvc']"
          type="Sitecore.Foundation.Assets.Pipelines.GetPageRendering.AddAssets, Sitecore.Foundation.Assets">
          <defaultAssets hint="raw:AddAsset">
            <asset type="JavaScript" file="/Scripts/Realtime/jquery.signalR-2.2.1.js" location="Body" />
            <asset type="JavaScript" file="/signalr/hubs" location="Body" />
            <asset type="JavaScript" file="/Scripts/Realtime/ClientTracker.js" location="Body" />
            <asset type="JavaScript" file="/Scripts/Realtime/Realtime.js" location="Body" />
          </defaultAssets>
        </processor>
      </mvc.getPageRendering>
    </pipelines>
  </sitecore>
</configuration>

Lets break it down.
We need to stop Sitecore to capture SignalR requests for that we need to update IgnoreUrlPrefixes by adding signalr|/signalr/hubs:

    <settings>
      <setting name="IgnoreUrlPrefixes">
        <patch:attribute name="value">/sitecore/default.aspx|/trace.axd|/webresource.axd|/sitecore/shell/Controls/Rich Text Editor/Telerik.Web.UI.DialogHandler.aspx|/sitecore/shell/applications/content manager/telerik.web.ui.dialoghandler.aspx|/sitecore/shell/Controls/Rich Text Editor/Telerik.Web.UI.SpellCheckHandler.axd|/Telerik.Web.UI.WebResource.axd|/sitecore/admin/upgrade/|/layouts/testing|/sitecore/service/xdb/disabled.aspx|/signalr|/signalr/hubs</patch:attribute>
      </setting>
    </settings> 

Next is to place the “startup” pipeline in the correct “call order”:

      <initialize>
        <processor type="VisionsInCode.Foundation.Realtime.Pipelines.RegisterSignalrProcessor, VisionsInCode.Foundation.Realtime"
       patch:before="processor[@type='Sitecore.Mvc.Pipelines.Loader.InitializeRoutes, Sitecore.Mvc']" ></processor>
      </initialize>

Finally add the javascripts in the correct order.

      <mvc.getPageRendering>
        <processor
          patch:before="*[@type='Sitecore.Mvc.Pipelines.Response.GetPageRendering.GetLayoutRendering, Sitecore.Mvc']"
          type="Sitecore.Foundation.Assets.Pipelines.GetPageRendering.AddAssets, Sitecore.Foundation.Assets">
          <defaultAssets hint="raw:AddAsset">
            <asset type="JavaScript" file="/Scripts/Realtime/jquery.signalR-2.2.1.js" location="Body" />
            <asset type="JavaScript" file="/signalr/hubs" location="Body" />
            <asset type="JavaScript" file="/Scripts/Realtime/ClientTracker.js" location="Body" />
            <asset type="JavaScript" file="/Scripts/Realtime/Realtime.js" location="Body" />
          </defaultAssets>
        </processor>
      </mvc.getPageRendering>

You can check out the the code on GitHub – Foundation.Realtime.config

Ops, I almost forgot we also need to set the connection string to the new mongodb database, signalr, in the connectionStrings.config:

<add name="signalr" connectionString="mongodb://localhost:27017/habitat_local_signalr" />

There will be a second post where we will use SignalR to change content on the fly(in real-time).

That’s all for now folks πŸ™‚

Build an App with Xamarin.Forms using Sitecore Mobile SDK for Xamarin – Putting it all together

StartHero

Xamarin is a true joy to work with, it’s easy and it’s all in.Net. To work with Xamarin is like being in heaven πŸ™‚
To combine it with Sitecore makes it even better, now you can keep the content from the app in Sitecore and manage it from there. Sitecore can also personalize the content for the app – how cool is that πŸ™‚

This is the third blogpost in a series on how to create an Xamarin Cross App using Sitecore Mobile SDK for Xamarin.

Here are the previous posts:
Build an App with Xamarin.Forms using Sitecore Mobile SDK for Xamarin – Getting started
Build an App with Xamarin.Forms using Sitecore Mobile SDK for Xamarin – Website and Navigation : Part 2

In this third and last post I will show you how the cross app was built.

Application – startup
Application is the startup class, her we will set the MainPage property which is where to set the initial page for the app.
It also exposes Lifecycle methods such as OnStart, OnSleep, and OnResume as well as modal navigation events. We will also setup the IOC here, I’ve choosen Autofac for this. πŸ™‚
And finally we will set the global styles here and it’s all in wonderful Xaml.

First we will have the HabitatAppModule class and this is where we will register services, viewmodels and pages. I will just show you a part of the class.

public class HabitatAppModule: Module
{
	protected override void Load (ContainerBuilder builder)
	{
		base.Load (builder);

		builder.RegisterType<SQLiteConnectionService> ()
			.As<ISQLiteConnectionService> ()
			.SingleInstance ();

		builder.RegisterType<SettingsRepository> ()
			.As<ISettingsRepository> ()
			.SingleInstance ();

        builder.RegisterType<SplashPageViewModel> ();
        builder.RegisterType<SplashPage> ();

		

HabitatAppModule on the GitHub

Next will be the Bootstrapper class, here we will call the HabitatAppModule and the container.
We will also set the MainPage by using a static property from the Application(startup class). We will start with a splash page, to make sure everything is loaded properly before we show the users the “actual” app. πŸ™‚

using Autofac;
using HabitatApp.Views.Pages;
using Xamarin.Forms;

public static class Bootstrapper
{
	public static void Run()
	{

		ContainerBuilder builder = new ContainerBuilder();
		builder.RegisterModule<HabitatAppModule>();
		HabitatApp.App.AppInstance.Container = builder.Build();

		HabitatApp.App.AppInstance.MainPage = HabitatApp.App.AppInstance.Container.Resolve<SplashPage> ();


	}
}

Bootstrapper on the GitHub

And the Application class itself, lets start with the codebehind. As you can see we call the Bootstrapper.Run to start it up.

using System;
using Xamarin.Forms;
using Autofac;

public partial class App : Application
{

	public IContainer Container;

	public static App AppInstance; 

	public App ()
	{
		AppInstance = this;

		InitializeComponent();

		Bootstrapper.Run();

	}

	protected override void OnStart ()
	{
		// Handle when your app starts
	}

	protected override void OnSleep ()
	{
		// Handle when your app sleeps
	}

	protected override void OnResume ()
	{
		// Handle when your app resumes
	}
}

App.xaml.cs on the GitHub

The xaml part of the Application. As I mentioned before this is where we will set all global styles like themes, fonts, colors etc. You can also set platform specific styles – how cool is that πŸ™‚
And finally we will have some ValueConverters.

<Application xmlns="http://xamarin.com/schemas/2014/forms" 
	xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
	xmlns:conv="clr-namespace:HabitatApp.Converters;assembly=HabitatApp"  
	x:Class="HabitatApp.App">
	<Application.Resources>
		<ResourceDictionary>
			<OnPlatform x:TypeArguments="Font" Android="40" iOS="40" WinPhone="Large" x:Key="HeaderFontSize" />
			<OnPlatform x:TypeArguments="Color" Android="White" iOS="White" WinPhone="White" x:Key="PrimaryTextColor" />
			<OnPlatform x:TypeArguments="Color" Android="Black" iOS="Black" WinPhone="Black" x:Key="ArticleTextColor" />
			<OnPlatform x:TypeArguments="Color" Android="#2E384E" iOS="#2E384E" WinPhone="#2E384E" x:Key="HabitatBackgroundColor" />
			<OnPlatform x:TypeArguments="Color" Android="White" iOS="White" WinPhone="White" x:Key="HabitatMenuColor" />
			<OnPlatform x:TypeArguments="Color" Android="#c8244f" iOS="#c8244f" WinPhone="#c8244f" x:Key="HabitatRedColor" />

			<conv:CachedMediaToImageSourceConverter x:Key="imageConverter" />
			<conv:HtmlConverter x:Key="htmlConverter" />
			<conv:HtmlSourceConverter x:Key="htmlSourceConverter" />
			<conv:BooleanNegationConverter x:Key="boolNegationConverter" />

			<Style x:Key="HeaderStyle" TargetType="Label">
				<Setter Property="TextColor" Value="{StaticResource PrimaryTextColor}" />
				<Setter Property="FontSize" Value="40" />
				<Setter Property="FontFamily" Value="HelveticaNeue-CondensedBlack" />
			</Style>
			<Style x:Key="HabitatWhiteHeaderStyle" TargetType="Label">
				<Setter Property="TextColor" Value="{StaticResource PrimaryTextColor}" />
				<Setter Property="FontSize" Value="40" />
				<Setter Property="FontFamily" Value="HelveticaNeue-CondensedBlack" />
			</Style>
			<Style x:Key="HabitatRedHeaderStyle" TargetType="Label">
				<Setter Property="TextColor" Value="{StaticResource HabitatRedColor}" />
				<Setter Property="FontSize" Value="40" />
				<Setter Property="FontFamily" Value="HelveticaNeue-CondensedBlack" />
			</Style>
			<Style x:Key="HabitatWhiteSubtitleStyle" TargetType="Label">
				<Setter Property="TextColor" Value="{StaticResource PrimaryTextColor}" />
				<Setter Property="FontSize" Value="20" />
				<Setter Property="FontFamily" Value="HelveticaNeue" />
			</Style>
			<Style x:Key="HabitatRedSubtitleStyle" TargetType="Label">
				<Setter Property="TextColor" Value="{StaticResource HabitatRedColor}" />
				<Setter Property="FontSize" Value="20" />
				<Setter Property="FontFamily" Value="HelveticaNeue" />
			</Style>
			<Style x:Key="ArticleHeaderStyle" TargetType="Label">
				<Setter Property="TextColor" Value="{StaticResource ArticleTextColor}" />
				<Setter Property="FontSize" Value="30" />
				<Setter Property="FontFamily" Value="HelveticaNeue" />
			</Style>
			<Style x:Key="ArticleTextStyle" TargetType="Label">
				<Setter Property="TextColor" Value="{StaticResource HabitatBackgroundColor}" />
				<Setter Property="FontSize" Value="20" />
				<Setter Property="FontFamily" Value="HelveticaNeue" />
			</Style>
			<Style x:Key="ArticleLightTextStyle" TargetType="Label">
				<Setter Property="TextColor" Value="{StaticResource PrimaryTextColor}" />
				<Setter Property="FontSize" Value="15" />
				<Setter Property="FontFamily" Value="HelveticaNeue" />
			</Style>
			<Style x:Key="HabitatValidationTextStyle" TargetType="Label">
		        <Setter Property="HorizontalOptions" Value="Start" />
		        <Setter Property="VerticalOptions" Value="Center" />
		        <Setter Property="FontSize" Value="Micro" />
		        <Setter Property="FontAttributes" Value="Italic" />
      		</Style>
		
		</ResourceDictionary>
	</Application.Resources>
</Application>

App.xaml on the GitHub

So what is a ValueConverter class? See it as an helper class which is used in xaml pages if you want to databind two properties that have incompatible types. Then you need a piece of code in between that converts the value from source to target type and back. This piece of code is called ValueConverter.

For instance the CachedMediaToImageSourceConverter class, it is needed when to present images on the xaml pages. The images are stored as blobs in the local database(SQlite). A typical call to the value converter class, CachedMediaToImageSourceConverter, could look something like this:

<Image Source="{Binding Media, Converter={StaticResource imageConverter}}" Aspect="AspectFill"> 

Here is the CachedMediaToImageSourceConverter class. In the example above a viewmodel with the property “Media” which contains a byte array.
The value converter class will then convert the byte array to an image.

using Xamarin.Forms;
using System;
using System.Globalization;
using HabitatApp.Models;
using System.IO;
using Xamarin.Forms.Xaml;


public class CachedMediaToImageSourceConverter : IMarkupExtension, IValueConverter
{

	public  object Convert (object value, Type targetType, object parameter, CultureInfo culture)
	{
		CachedMedia cachedMedia = (CachedMedia)value;

		if (cachedMedia == null)
			return null;    

		if (cachedMedia.MediaData != null)
			return ImageFromBytes (cachedMedia);

		return ImageFromUrl (cachedMedia.Url);

	}

	private ImageSource ImageFromBytes (CachedMedia cachedMedia)
	{
		return ImageSource.FromStream (() => new MemoryStream (cachedMedia.MediaData));
	}

	private ImageSource ImageFromUrl (string url)
	{

		Uri outUri;

		if (Uri.TryCreate (url, UriKind.Absolute, out outUri))
			return ImageSource.FromUri (outUri);

		return ImageSource.FromResource (url);
	}


	public object ConvertBack (object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
	{
		throw new NotImplementedException ();
	}


	public object ProvideValue (IServiceProvider serviceProvider)
	{
		return this;
	}

}

You can find the rest of the value converters here

Pages – The Xaml pages
The best of it all when it comes to Xamarin.Forms is of course the “forms” – the xaml pages.
How does it work in our app?
Instead of going through all pages/views in the app, let’s have a look at some of them. How about the ListParentPage.xaml?
SitecoreListParentpage
This page will list a “page item’s” children (Sitecore).

In codebehind on the xaml page the constructor will instantiate the viewmodel – ListParentPageViewModel.
Here we will also set the BindingContext property so we from the xaml page can bind to the viewmodel. But as you can see we have very little logic here, it’s all in the viewmodel. Everything is clean πŸ™‚

using Xamarin.Forms;
using Autofac;
using HabitatApp.ViewModels.Pages;
using Xamarin.Forms.Xaml;

[XamlCompilation (XamlCompilationOptions.Compile)]
public partial class ListParentPage : ContentPage
{
	private readonly ListParentPageViewModel _listParentPageViewModel;

	public ListParentPage() : this(App.AppInstance.Container.Resolve<ListParentPageViewModel>())
	{

	}

	public ListParentPage (ListParentPageViewModel listParentPageViewModel)
	{
		InitializeComponent ();

		_listParentPageViewModel = listParentPageViewModel;

		_listParentPageViewModel.ConnectedToPage = this;

		BindingContext = _listParentPageViewModel;

		//We need it for Android
		MediaItemsListView.ItemSelected += (s, e) => {
			MediaItemsListView.SelectedItem = null; 
		};

	}


	protected override async void OnDisappearing ()
	{

		base.OnDisappearing ();
	}
} 

Lets have a quick look at the viewmodel – ListParentPageViewModel.
It contains a number of observable properties for the XAML page and it’s all about binding πŸ™‚

using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Sitecore.MobileSDK.API.Items;
using System.Windows.Input;
using Xamarin.Forms;
using HabitatApp.Services;
using HabitatApp.Models;
using HabitatApp.Extensions;
using HabitatApp.Repositories;

public class ListParentPageViewModel : ViewModelBase
{
	private readonly IListItemService _listItemService;
	private readonly INavigationService _navigationService;
	private readonly ICachedMediaRepository _cachedMediaRepository;


	public ListParentPageViewModel (IListItemService listItemService, INavigationService navigationService, ICachedMediaRepository cachedMediaRepository)
	{
		_listItemService = listItemService;
		_navigationService = navigationService;
		_cachedMediaRepository = cachedMediaRepository;

	}

	private CachedMedia _media;

	public CachedMedia ContentMedia {
		get {
			return _media;
		}
		set { SetProperty (ref _media, value); }

	}

	private string _contentHeader = string.Empty;

	public string ContentHeader {
		get {
			return _contentHeader;
		}
		set { SetProperty (ref _contentHeader, value); }
	}

	private string _contentSummary = string.Empty;

	public string ContentSummary {
		get {
			return _contentSummary;
		}
		set { SetProperty (ref _contentSummary, value); }
	}

	private ObservableCollection<Tuple<ListItem,ListItem>> _listItems = new ObservableCollection<Tuple<ListItem,ListItem>> ();

	public ObservableCollection<Tuple<ListItem,ListItem>> ListItems {
		get {
			return _listItems;
		}
		set { 
			SetProperty (ref _listItems, value); 
		}
	}

	private ObservableCollection<ListItem> _list = new ObservableCollection<ListItem> ();

	public ObservableCollection<ListItem> ListOfItems {
		get {
			return _list;
		}
		set { 
			SetProperty (ref _list, value); 
		}
	}

	private Command _linkSelectedCommand;
	public ICommand LinkSelectedCommand
	{
		get
		{
			if (_linkSelectedCommand == null)
			{
				_linkSelectedCommand = new Command (async (parameter) =>  {
					string itemId = (string)parameter;

					if(string.IsNullOrWhiteSpace(itemId))
						return;

					await _navigationService.NavigateToPageByItemId(ConnectedToPage, itemId);

				});
			}

			return _linkSelectedCommand;
		}
	}

	/// <summary>
	/// Entering page
	/// </summary>
	/// <returns>The async.</returns>
	public async override Task LoadAsync ()
	{

		SetBusy ("Loading");

		PageData pageData = base.PageContext;

		await SetData (pageData);

		ClearBusy ();


	}

	private async Task SetData(PageData pageData){
		
		ISitecoreItem item = pageData.ItemContext.FirstOrDefault ();

		base.Title = item.GetValueFromField (Constants.Sitecore.Fields.PageContent.Title);

		ContentHeader = item.GetValueFromField(Constants.Sitecore.Fields.PageContent.Title);
		ContentSummary = item.GetValueFromField(Constants.Sitecore.Fields.PageContent.Summary);
		ContentMedia =  await _cachedMediaRepository.GetCache(item.GetImageUrlFromMediaField (Constants.Sitecore.Fields.PageContent.Image));

		IEnumerable<ListItem> listItems = await _listItemService.GenerateListItemsFromChildren(pageData.DataSourceFromChildren);

		ListItems = listItems.ToList().AsPairsSafe ().ToObservableCollection ();


	}


}

If you noticed in the screendump, the page lists a number of images – side by side. For that we will have a list of TUPLE items:

ObservableCollection<Tuple<ListItem,ListItem>>

To get the list of items we will call the ListItemService
Here is the method we are using:

public async Task<IEnumerable<ListItem>> GenerateListItemsFromChildren(ScItemsResponse itemsResponse)
{

	List<ListItem> list = new List<ListItem> ();


	for (int i = 0; i < itemsResponse.ResultCount; i++) {

		ISitecoreItem item = itemsResponse[i];

		if (item == null)
			continue;

		ListItem listlItem = new ListItem{ 
			Header = item.GetValueFromField (Constants.Sitecore.Fields.PageContent.Title),
			Text = item.GetValueFromField (Constants.Sitecore.Fields.PageContent.Summary),
			NavigationItem = item.Id,
			NavigationText = item.GetValueFromField (Constants.Sitecore.Fields.PageContent.Title),
			SitecoreItem = item,
			Media =  await _cachedMediaRepository.GetCache(item.GetImageUrlFromMediaField(Constants.Sitecore.Fields.PageContent.Image))	

		};

		list.Add (listlItem);

	}

	return list;
}

Then we will use an extension method to convert the list to a “tuple” list:

ListItems = listItems.ToList().AsPairsSafe ().ToObservableCollection ();

Here is the method in IEnumerableExtensions

public static IEnumerable<Tuple<T, T>> AsPairsSafe<T>(this List<T> list)
{
	int index = 0;

	while (index < list.Count())
	{
		if (index + 1 >= list.Count())
			yield break;

		yield return new Tuple<T,T>(list[index++],  list[index++]);
	}

}

Now finally, let us all have a look at the XAML page – ListParentPage.xaml. Xaml is great and working with bindings is a bliss πŸ™‚ (I really miss that in ASP.Net MVC).
If you look at the code where the backgroundcolor is set – BackgroundColor=”{DynamicResource HabitatBackgroundColor}”. Here we use the global styles from the Application.xaml I showed you earlier.

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
	xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
	x:Class="HabitatApp.Views.Pages.ListParentPage" 
	xmlns:control="clr-namespace:HabitatApp.Views.Controls;assembly=HabitatApp" 
	Title="{Binding Title}" 
	Icon="{Binding Icon}"
	x:Name="rootPage" 
	xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms" 
	BackgroundColor="{DynamicResource HabitatBackgroundColor}">
	<ListView x:Name="MediaItemsListView" 
		BackgroundColor="{DynamicResource HabitatBackgroundColor}" 
		ItemsSource="{Binding ListItems}" 
		SeparatorVisibility="None" 
		HorizontalOptions="FillAndExpand" 
		Header="{Binding}" 
		RowHeight="200" 
		CachingStrategy="RecycleElement">
		<ListView.HeaderTemplate>
			<DataTemplate>
				<Grid>
					<Grid.RowDefinitions>
						<RowDefinition Height="200" />
						<RowDefinition Height="*" />
					</Grid.RowDefinitions>
					<Grid Grid.Row="0" Padding="10,0,10,0">
						<Image Aspect="AspectFill" Source="{Binding ContentMedia, Converter={StaticResource imageConverter}}" />
						<StackLayout Padding="10,10,10,10">
							<StackLayout Opacity="0.7" BackgroundColor="{DynamicResource HabitatBackgroundColor}" Padding="10" HorizontalOptions="FillAndExpand" VerticalOptions="EndAndExpand" Orientation="Vertical">
								<Label x:Name="headerLabel" VerticalOptions="EndAndExpand" Text="{Binding ContentHeader}" Style="{DynamicResource HabitatWhiteHeaderStyle}" />
							</StackLayout>
						</StackLayout>
					</Grid>
					<StackLayout Padding="10,0,10,10" Grid.Row="1" HorizontalOptions="Start" VerticalOptions="EndAndExpand" Orientation="Horizontal">
						<Label x:Name="subtitleLabel" VerticalOptions="StartAndExpand" Style="{DynamicResource ArticleLightTextStyle}" HorizontalOptions="StartAndExpand" Text="{Binding ContentSummary, Converter={StaticResource htmlConverter}}" />
					</StackLayout>
				</Grid>
			</DataTemplate>
		</ListView.HeaderTemplate>
		<ListView.ItemTemplate>
			<DataTemplate>
				<control:ViewCellNonSelectable>
					<Grid Padding="10,0,10,0">
						<Grid.RowDefinitions>
							<RowDefinition Height="200" />
						</Grid.RowDefinitions>
						<Grid.ColumnDefinitions>
							<ColumnDefinition Width="*" />
							<ColumnDefinition Width="*" />
						</Grid.ColumnDefinitions>
						<Grid Grid.Row="0" Grid.Column="0" Padding="0,0,0,5">
							<ffimageloading:CachedImage Opacity="0.9"
								Aspect="AspectFill" 
								DownsampleWidth="100" 
								DownsampleHeight="100" 
								Source="{Binding Item1.Media, Converter={StaticResource imageConverter}}">
							</ffimageloading:CachedImage>
							<StackLayout Padding="10">
								<StackLayout.GestureRecognizers>
										<TapGestureRecognizer Command="{Binding Source={x:Reference rootPage}, Path=BindingContext.LinkSelectedCommand}" CommandParameter="{Binding Item1.NavigationItem}" />
								</StackLayout.GestureRecognizers>
								<Label VerticalOptions="EndAndExpand" Text="{Binding Item1.Header}" Style="{DynamicResource ArticleLightTextStyle}" />
							</StackLayout>
						</Grid>
						<Grid Grid.Row="0" Grid.Column="1" Padding="0,0,0,5">
							<ffimageloading:CachedImage Opacity="0.9"
								Aspect="AspectFill" 
								DownsampleWidth="100" 
								DownsampleHeight="100" 
								Source="{Binding Item2.Media, Converter={StaticResource imageConverter}}">
							</ffimageloading:CachedImage>
							<StackLayout Padding="10">
								<StackLayout.GestureRecognizers>
										<TapGestureRecognizer Command="{Binding Source={x:Reference rootPage}, Path=BindingContext.LinkSelectedCommand}" CommandParameter="{Binding Item2.NavigationItem}" />
								</StackLayout.GestureRecognizers>
								<Label VerticalOptions="EndAndExpand" Text="{Binding Item2.Header}" Style="{DynamicResource ArticleLightTextStyle}" />
							</StackLayout>

						</Grid>
					</Grid>
				</control:ViewCellNonSelectable>
			</DataTemplate>
		</ListView.ItemTemplate>
	</ListView>
</ContentPage>  

To list the images we use a listview.
As datasource(ItemsSource) to the listview we will BIND the ListItems property(from the viewmodel).
We also have a header in the listview – ListView.HeaderTemplate.

In the ListView.ItemTemplate element we will render the images, side by side. To show the image we will use a ValueConverter(convert blob to image)

 
Source="{Binding Item1.Media, Converter={StaticResource imageConverter}}"> 

In order to make the images to load faster and the listview to scroll smothly, I used a very nice component called FFImageLoading

And when the users touch/tap an image we will navigate them to next page. In order to make it work we will use TapGestureRecognizer.

<StackLayout.GestureRecognizers>
	<TapGestureRecognizer Command="{Binding Source={x:Reference rootPage}, Path=BindingContext.LinkSelectedCommand}" CommandParameter="{Binding Item1.NavigationItem}" />
</StackLayout.GestureRecognizers>

TapGestureRecognizer will then call the LinkSelectedCommand in the viewmodel.

public ICommand LinkSelectedCommand
{
	get
	{
		if (_linkSelectedCommand == null)
		{
			_linkSelectedCommand = new Command (async (parameter) =>  {
				string itemId = (string)parameter;

				if(string.IsNullOrWhiteSpace(itemId))
					return;

				await _navigationService.NavigateToPageByItemId(ConnectedToPage, itemId);

			});
		}

		return _linkSelectedCommand;
	}
}

And here we call the NavigationService to navigate to “selected” page.

There are more stuff I would like to go through with you guys but the post will be to long and we all know what happens if a post is to long – no one will read it 😦

I will have to end here, I think there will be a fourth post in this series. I need to show you how cool it is to work with Behaviors in Xamarin.

That’s all for now folks πŸ™‚