Thursday 12 November 2015

Using FXM with single page applications

I have recently been doing some work with FXM and as part of that a large portion of that work has been with using FXM on single page applications(SPA). While the Sitecore FXM  api provides functions for pushing events it doesn't provide a function for refreshing the context of the application as a whole. The following is the approach that I have used on a recent project. 

I should firstly caveat the above with the fact that this is based on Sitecore 8.0 Update 5 and there is a fair chance that this will approach will have been superseded in the future. 

If you set FXM up on a site hosting a single page application by default you will be able to send down content and track interactions on the first screen. But when the users start transitioning through screens, you will find that content targeted to those screens doesn't appear, content from the previous screen won't be removed and events targeting the second screen won't fire. From my investigations this appear to com about when the SPA updates or removes the DOM elements and FXM is not aware of the transition. So the question is how can we make FXM aware of the transition?

The first aspect is understanding how your SPA transitions. One of the key requirements however is that after the transition is complete that FXM will be able to understand the current state. This essentially means are there any page filter rules in Sitecore that can observe the current state.
The simplest of these to observe is a change in the URL, which if your SPA supports deep linking or the "back" button in the browser should be a part of the SPA.

Once you know how it transitions the next step is determine when it has transitioned, this is going to vary from SPA to SPA and you may need to work with the team developing the SPA to get some help. There are some approaches that are more common then others however:
  • There is often an even raised by the SPA when it transitions and with a bit of JavaScript debugging yo can probably discover it
  • A lot of the SPA frameworks provide and even similar to the above
  • Failing all else if the URL has change the location in the address bar has changed as well, so you can always poll it for changes
Now that we can observe the changes in state and we can filter our FXM content/actions to match that state we need to refresh the FXM context. At this stage the below will work, but I am hoping for a more refined approach in later versions of FXM. Essentially we need to remove FXm from the page and re-add it afterwards. This has the effect of tricking FXM into thinking it is responding to a new page view. To make this add/remove easier I recommend adding an id to the script tag in the beacon script when adding it to your external site.

        $( "#fxmScriptId" ).remove()
        $('body').append('<script id="fxmScriptId" src="//your-site/bundle/beacon" />' )

Please keep in mind the method that you use to reintroduce the beacon script to the page, as you need it to evaluate the JavaScript and some approaches won't do this.

If you have the above running you should see your tracking and content being served, but what you are probably also seeing is that the content from previous screens are not being removed. This is happening because we are just removing and re-adding the FXM beacon to the page and no one is doing a clean up. 

To get around this I recommend encapsulating all of your FXM content in a class that you can target during a transition. Once you have a way of targeting that content you can then remove during the transitions, similar to the below. One thing to keep in mind however is that this is best suited to the "insert after" and "insert before" approaches to injecting content, as opposed to the "replace" approach as it will not allows you to restore the content that has been replaced.

        $( "#fxmScriptId" ).remove()
        $( ".fxm-content" ).remove()
        $('body').append('<script id="fxmScriptId" src="//your-site/bundle/beacon" />' )

Now that we have all of the following wired up, when you transitions states through your SPA you should have both tracking and content matching the state of your SPA.

Wednesday 25 March 2015

Chrome doesn't always expire session cookies and what it means for Sitecore DMS

So I was working through an issue with a customer recently and we couldn't work out why a certain behavior was happening and why it appeared to be happening on some browsers and not others. After a bit of digging we discovered that it was to do with the way Chrome manages session cookies.  After solving the specific issue the conversation turned to what does this mean for DMS tracking, particularly with the uptake of chrome in the market.

To put it in context Chrome is becoming pretty popular on the web according to http://gs.statcounter.com/ it is currently sitting at about 49% globally. So any change in the behavior of Chrome can dramatically change the way we need to build solutions.

A session cookie, according to webopedia.com, is: "a cookie that is erased when the user closes the Web browser. The session cookie is stored in temporary memory and is not retained after the browser is closed." This means that traditionally we have been able to set these cookies and assume that they will have expired after the user has closed their browser and won't be present when they come back to the site.

The change that Chrome introduced is it introduced a "Continue where you left off" piece of functionality. When this mode is enabled it aims to provide users the ability to exactly restore their browser to the state it was when they closed it. This includes reinstating any session cookies they had set during their last session. The other point of interest is that this appears to be the default setting on Chrome for mobile.

Now I'm not going to get into the debate of whether its a feature or a bug, or what the security aspects of it are, I'm only interested in how we account for this in the solutions we build.

To put this in context I will use the Sitecore DMS session tracking cookie as an example. Note that I haven't confirmed what versions of Chrome or Sitecore are affected by this, so if you run into this issue please contact Sitecore support. I do know that the issue doesn't exist with Sitecore 7.5 and up as the xDb does not use the same cookie structures as the DMS. Also note that between my writing of this post and when you might be reading it, it is likely that an official patch has been released by Sitecore, so please check release notes or contact Sitecore support.

The Sitecore DMS uses a cookie dropped on a users machine to identify requests that come from a specific session. The information gathered based on the cookie relates to value per visit, page view tracking, time on site, etc. Essentially it is a pretty important piece of information and demonstrates how a seemingly small change in the browser landscape can have unforeseen consequences on web applications.

The way of preventing this from occurring with your cookies is to essentially stop using session cookies and to use a sliding timeout for your cookies instead. The example below is a Sitecore related patch, but the logic can be applied in non-Sitecore related implementations.

In the DMS the visit cookie is set in a place that's not easy to get a hook for, so the easiest way of setting the desired expiry time on the cookie is to update the cookie once the DMS has set it. I have chosen to set this at the end of the "renderLayout" pipeline, which ensures that the DMS has set its values already and won't override anything. It also means that the code will be invoked on every request, which provides the effect of setting a sliding window. The code is as follows:

public class ForceScVisitCookieExpiration
{
    public void Process(PipelineArgs args)
    {
        var context = HttpContext.Current;
        var sessionCookie = context.Request.Cookies.Get("SC_ANALYTICS_SESSION_COOKIE");
        if (sessionCookie != null)
        {
            var timeout = Sitecore.Configuration.Settings.GetIntSetting( "SitecoreVisitExpirationTimeInMinutes", 60);

            sessionCookie.Expires = DateTime.Now.AddMinutes(timeout); 
            context.Response.SetCookie(sessionCookie);
        }
    }
}

All that the code does is retrieve the existing value for the cookie from the request (only needed if you don't have access to where the cookie is being set) and then sets an expiration time for the cookie. This means that the cookie will expire at the designated time, meaning that if a user closes their browser for that period of time, when they come back the cookie will be gone.

The upshot of all of this is that people need to be aware of this behavior in Chrome and that they need to update their solutions to cater for it, whether we like it or not.

Tuesday 17 February 2015

The rich page/experience editor

As we move down the path of personalisation in Sitecore we are finding that our editors are spending more and more time in the "Page/Experience Editor" and less time in the "Content Editor". This means that editors are starting to expect to be able to do all of their editing in the "Experience Editor", and why shouldn't they? We are rendering the page in edit mode, we have full control, lets use it.

Below is a simple example of what can be done using a carousel as an example. Now put aside your concerns about carousels (http://shouldiuseacarousel.com/), this is designed as a thought provoking exercise aimed at getting you to think about how you can make life better for you content editors within your own project domain, not as a way of building a carousel.

The reason that a carousel is useful as a thought provoker is that it has some characteristics that don't make it feel like an easy fit for the experience editor. These include
  • Unknown number of slides
  • The order of content in a carousel is important - the first slide gets more views then the last
So without further adieu lets show you what the experience editor experience could be like


Notice the new buttons? This style of functionality has typically been implemented using edit frames and there is a lot of content there around it (e.g. http://www.cmsbestpractices.com/how-to-properly-use-sitecore-edit-frames/), but  we can also include these buttons directly into your site and and simplify the editing experience.

Now what have I actually done to get this running, well there are two main things, the first being accept that you are going to need to do different things in edit mode to delivery mode. This is as simple as adding an if(or multiple ifs) to your view layer to check what the current page mode is.


The next step is to understand what commands are available to you and how you can additional commands. As mentioned before, traditionally these commands would be implemented using the edit frames functionality and the commands that I am using are exactly the same. 

If you were to configure an edit frame or sub layout with a series of commands and inspect it you would see something like the following.


Essentially each edit frame button/command is a JavaScript click event with a command, webedit:new, and an item id associated with it. Knowing that Sitecore uses this approach to triggering commands we can build our own renderings taking this into account. So going back to the buttons in my carousel, each button essentially has the same JavaScript rendered from an MVC helper as demonstrated below.

namespace Website.Helpers
{
    using System.Web;
    using Sitecore.Data.Items;

    public class PageEditorHelpers
    {
        public HtmlString NewItemButton(Item parentItem, string buttonText)
        {
            return new HtmlString(string.Format(@"<a href=""#"" class=""button"" onclick=""javascript:Sitecore.PageModes.PageEditor.postRequest('webedit:new(id={1})')"">{0}</a>", buttonText, parentItem.ID));
        }        
        
        public HtmlString DeleteItemButton(Item parentItem, string buttonText)
        {
            return new HtmlString(string.Format(@"<a href=""#"" class=""button"" onclick=""javascript:Sitecore.PageModes.PageEditor.postRequest('webedit:delete(id={1})')"">{0}</a>", buttonText, parentItem.ID));
        }        
        
        public HtmlString MoveItemUpButton(Item parentItem, string buttonText)
        {
            return new HtmlString(string.Format(@"<a href=""#"" class=""button"" onclick=""javascript:Sitecore.PageModes.PageEditor.postRequest('item:moveup(id={1})')"">{0}</a>", buttonText, parentItem.ID));
        }        
        
        public HtmlString MoveItemDownButton(Item parentItem, string buttonText)
        {
            return new HtmlString(string.Format(@"<a href=""#"" class=""button"" onclick=""javascript:Sitecore.PageModes.PageEditor.postRequest('item:movedown(id={1})')"">{0}</a>", buttonText, parentItem.ID));
        }

        public HtmlString SortItemsButton(Item parentItem, string buttonText)
        {
            return new HtmlString(string.Format(@"<a href=""#"" class=""button"" onclick=""javascript:Sitecore.PageModes.PageEditor.postRequest('webedit:sortcontent(id={1})')"">{0}</a>", buttonText, parentItem.ID));
        }
    }
}

Once we have the helper setup it's easy to include them in the markup and customise the experience for our editors.

Now keep in mind that this was a thought provoking exercise and you can create any custom command you like, or add any code you like. What can you do for your editors? The ideas become endless and all of those custom UI's you build for you public users can now also be used to enhance the experience for you editors.

What happens when you cast an IQueryable to an IEnumerable

Recently I was digging through some code and came across an issue where the "Where" expressions I was applying in code were not being run against the database. After a bit of digging it turned out to be related to an implicit cast of an IQueryable<T> to and IEnumerable<T>. This cast affects the query generation process with every clause added after the cast being run in memory.

So how about an example to demonstrate it and make it clearer. Looking at the two unit tests below, what would you expect the resulting SQL query to be? Keep in mind the IQueryable inherits from IEnumerable, so the casting is valid.

    public class UnitTest1
    {
        [TestMethod]
        public void RunQueryAsIQueryable()
        {
            IQueryable<Record> records = new ModelContext().Records;
            var result = records.Take(10).ToArray();
        }

        [TestMethod]
        public void RunQueryAsIEnumerable()
        {
            IQueryable<Record> records = new ModelContext().Records;
            var result = ((IEnumerable<Record>)records).Take(10).ToArray();
        }
    }

Here are the results from SQL profiler

As IQueryable
    SELECT TOP (10) 
    [c].[Id] AS [Id], 
    [c].[Name] AS [Name]
    FROM [dbo].[Records] AS [c]

As IEnumerable
    SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name]
    FROM [dbo].[Records] AS [Extent1]

As you can see the IQueryable "Take/Top" clause is run against the database, where as the IEnumerable "Take" clause is not. Now consider the following example.

    public class UnitTest1
    {
        [TestMethod]
        public void RunHybridQuery()
        {
            IQueryable<Record> records = new ModelContext().Records;
            var result = ((IEnumerable<Record>)records.Where(record => record.Name.StartsWith("something"))).Take(10).ToArray();
        }   
    }

    SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name]
    FROM [dbo].[Records] AS [Extent1]
    WHERE [Extent1].[Name] LIKE N'something%'

As you can see with this example everything up until the cast(note the top 10 is missing) runs against the database.

This "unexpected" behavior is actually by design and relates to the reason why we have the two interfaces, as opposed to just IEnumerable. An IEnumerable is something that is enumerated, i.e. looped over. An IQueryable on the other hand represents something that can be queried, generally by a query language.

Seems pretty self explanatory, the confusion that comes in is when we use the LINQ syntax to mix these two concepts into a single statement. Because of the fluent nature of the LINQ API it can often be hard to know exactly what the parameter and return types of each method in your chain are and what the actual query being run is(without profiling). So the lesson here, be careful and don't rely on LINQ to do all of your work for you, it is only as smart as the person who wrote it.

Tuesday 6 January 2015

Creating a Clicks report for Sitecore Email Experience manager (ECM)

Recently I was out on site with a client working through an ECM implementation and some questions came up around the reports that are provided out of the box. The reports that are provided out of the box are all looking at the value of a user interaction with an email, as opposed to whether they clicked a link in an email or not. While this provide you a much more rounded picture of how a user interact with an email and what value you can associate with a particular send a lot of people are still expecting the traditional "Click" report that you get from other email platforms.

So, I decided to build one.

The first thing to note is the ECM has been built to be extended and that it already collects all of the information to produce a click report. I recommend looking at the following to see how you can customise ECM:

Now that you have the background on where to customise ECM and where it stores its data, its just a matter of pulling the pieces together to build your own report.


I have open sourced an implementation on my git repository over at https://bitbucket.org/ctekempel/ecmreports and hope to add some more detail about the implementation when I get some time, but thought I would put it up now just in case I don't. Once you pull down the git repo you will need to:

  • Compile and copy across the appropriate files
  • Deserialize all of the items in the "items" folder
  • Add a report to each of the task types report pages in the ECM:
    • /sitecore/system/Modules/SPEAK/EmailCampaign/TaskPages/AdHockTaskPage/Reports
    • /sitecore/system/Modules/SPEAK/EmailCampaign/TaskPages/PeriodicalTaskPage/Reports
    • /sitecore/system/Modules/SPEAK/EmailCampaign/TaskPages/TrickleTaskPage/Reports

  • To add the report you need to add a “/sitecore/layout/Renderings/Email Campaign/Speak/ObjectDetailListObserver” control to the page and then set its datasource to “/sitecore/system/Modules/SPEAK/EmailCampaign/Controls/List views/Custom ECM Reports/Clicks”

If you have any questions before I get this post fully updated, drop a note in the comments and I will get back to it ASAP.

Also if anyone has ideas for other reports, or wants to contribute to it, please reach out.

How to disable "Add Users" in Sitecore's Platform DXP

 I have seen a few posts going around the community asking how to disable the "Add Users" button in Sitecore Platform DXP.   This ...