Monthly Archives: April 2014

Client Tracker with Sitecore DMS

Marketplace

In my previous blog post, Track a visitors coordinates in the Sitecore DMS, I was asked if I could move it into a Sitecore package and release it on the Sitecore Marketplace.

I managed to transform the blog post to a Sitecore module and released it on Sitecore Marketplace – CLIENT TRACKER

What does it do?
Well the main idea was to track visitors/users location(using HTML5 Geolocation API) and store it in the Sitecore DMS but It can also be used to track events/goals.

I will continue working with the module – Soon there will be support for enrolling and moving visitors between States in the Engagement Automation Plans.

How to use it?
Install the package(It contains only files)
/TrackerHandler.ashx
/ClientEventTracker.js
/PageScriptSample.js
/bin/Sitecore.SharedSource.ClientTracker.dll

The package will put TrackerHandler.ashx, ClientEventTracker.js and PageScriptSample.js in the root. I strongly suggest that you move the files to proper folders. PageScriptSample.js is just a sample.

Add the ClientEventTracker.js to the page/s where you want to do the client tracking.

To send the client data you will use a Json dictionary, which supports following parameters:
Coordinates (Track current user/visitor location)
PageEventId (Track event/goal)
PageUrl (The url/page where the event/goal is fired)

It’s important that you name the dictionary to requestParamAndValues. Don’t forget to add the path to the handler, TrackerHandler.ashx.

var pathToHandler = "/CustomHandlers/TrackerHandler.ashx";
var requestParamAndValues = {};
requestParamAndValues["PageEventId"] = dataContainer.data("goal");
requestParamAndValues["PageUrl"] = dataContainer.data("pageurl");

var jsonObject = {};
jsonObject["requestParamAndValues"] = requestParamAndValues;

var analyticsEvent = new AnalyticsPageEvent(jsonObject, pathToHandler);
analyticsEvent.trigger();

Ok, let’s try it then. In following example we want to track if a user/visitor has closed an “alert box” and get the geolocation of the visitor.
Image1

First we create the event/goal in Sitecore.
Image2

Next we proceed to the widget containing the alert box. The data-goal attribute contains the item id for the “Closed Alert box” event and the data-pageurl holds the path to actual page(in order for the tracker to find the correct page row).

<div class="alert alert-info fade in" 
        data-pageurl="<%= HttpContext.Current.Request.Url.PathAndQuery %>"
        data-goal=" <%# Sandbox.Alert.Constants.Analytics.Goals.CloseAlertBox %> ">
        <button type="button" class="close" id="closeAlert" data-dismiss="alert">×</button>
        <asp:PlaceHolder runat="server" Visible="<%# !string.IsNullOrWhiteSpace(this.GetDataSourceOrContextItem().GetString(Sandbox.Spots.Constants.Fields.Alert.AlertHeader)) || Sitecore.Context.PageMode.IsPageEditorEditing %>">
            <h4>
                <sc:Text runat="server" DataSource="<%# this.GetDataSourceOrContextItem().ID %>" Field="<%# Sandbox.Spots.Constants.Fields.Alert.AlertHeader%>" />
            </h4>
        </asp:PlaceHolder>

        <sc:Text runat="server" DataSource="<%# this.GetDataSourceOrContextItem().ID %>" Field="<%# Sandbox.Spots.Constants.Fields.Alert.AlertText%>" />
    </div>

Finally we add the custom/sample javascript for the page.

var SharedSource = SharedSource || {};

jQuery(document).ready(function () {
    SharedSource.ClientTracker.DomReady();
});

SharedSource.ClientTracker = {
    DomReady: function() {

        SharedSource.ClientTracker.Init();

       jQuery("#closeAlert").click(function () {

            SharedSource.ClientTracker.TrackPageEvent();

        });


    },

    Init: function(trackUser) {

        window.pathToHandler = "/CustomHandlers/TrackerHandler.ashx";

        if (navigator)
            navigator.geolocation.getCurrentPosition(geoSuccess, geoError);

        function geoSuccess(p) {

            window.coordinates = p.coords;

            if (trackUser) {
                SharedSource.ClientTracker.TrackUserLocation(); 
            }

        }

        function geoError(error) {
            var message = "";
            switch (error.code) {
            case error.PERMISSION_DENIED:
                message = "This website does not have permission to use " + "the Geolocation API";
                break;
            case error.POSITION_UNAVAILABLE:
                message = "The current position could not be determined.";
                break;
            case error.PERMISSION_DENIED_TIMEOUT:
                message = "The current position could not be determined " + "within the specified timeout period.";
                break;
            }

            if (message == "") {
                var strErrorCode = error.code.toString();
                message = "The position could not be determined due to " + "an unknown error (Code: " + strErrorCode + ").";
            }

            Logging(message);
        };


    },
    TrackUserLocation: function() {

        if (!window.coordinates)
            return;

        var requestParamAndValues = {};
        requestParamAndValues["Coordinates"] = SharedSource.ClientTracker.StringFormat("{0},{1}", window.coordinates.latitude, window.coordinates.longitude, '1');

        var jsonObject = {};
        jsonObject["requestParamAndValues"] = requestParamAndValues;

        var analyticsEvent = new AnalyticsPageEvent(jsonObject, window.pathToHandler);
        analyticsEvent.trigger();

    },
    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;
    },

    Logging: function (message) {
        if (typeof console == "object") {
            console.log(message);
        }
    },

    TrackPageEvent: function() {

        var dataContainer = jQuery(".alert alert-info fade in");

        var requestParamAndValues = {};
        requestParamAndValues["PageEventId"] = dataContainer.data("goal");
        requestParamAndValues["PageUrl"] = dataContainer.data("pageurl");

        if (window.coordinates)
            requestParamAndValues["Coordinates"] = SharedSource.ClientTracker.StringFormat("{0},{1}", window.coordinates.latitude, window.coordinates.longitude, '1');


        var jsonObject = {};
        jsonObject["requestParamAndValues"] = requestParamAndValues;

        var analyticsEvent = new AnalyticsPageEvent(jsonObject,  window.pathToHandler);
        analyticsEvent.trigger();
    }

};

That’s it – The event and the visitors current location will now be registered in the Sitecore DMS.

If you have questions, improvements, finding bugs – Please let me know.

That’s all for now folks 🙂

Advertisements

Track a visitors coordinates in the Sitecore DMS

phoneAndTabletMaxMind is great for tracking visitors – It gives you all kind of information based on the IP address.

Today when more and more users/visitors are using handheld devices with GPS for browsing the internet, I wanted to get the actual location for the visitor using HTML5 Geolocation API
Note: Since it can compromise user privacy, the position is not available unless the user approves it.

In order to get the location/coordinates, following javascript is used (window.coordinates is a global variable):

if (navigator)
    navigator.geolocation.getCurrentPosition(geoSuccess, geoError);

function geoSuccess(p) {
    window.coordinates = p.coords;
}

function geoError(data) {
    //Do some error handling
	
}

I added the geolocation to my previous post, Client side tracking with Sitecore DMS.

var Sandbox = Sandbox || {};
 
jQuery(document).ready(function () {
    Sandbox.Alert.DomReady();
});
 
Sandbox.Alert = {
    DomReady: function () {
 
		Sandbox.Alert.InitSandbox();
 
        jQuery("#closeAlert").click(function () {
 
            Sandbox.Alert.TrackCloseEvent();
 
        });
 
    },
 
	InitSandbox: function () {

        if (navigator)
            navigator.geolocation.getCurrentPosition(geoSuccess, geoError);

        function geoSuccess(p) {
            window.coordinates = p.coords;
        }

        function geoError(data) {
            //Some error handling
        }


    },
 
    TrackCloseEvent: function () {
 
        var dataContainer = jQuery(".alert alert-info fade in");
 
	    var requestParamAndValues = {};
        requestParamAndValues["PageEventId"] = dataContainer.data("goal");
        requestParamAndValues["PageUrl"] = dataContainer.data("pageurl");
 
		if (window.coordinates)
			requestParamAndValues["Coordinates"] = Sandbox.Alert.StringFormat("{0},{1}", window.coordinates.latitude, window.coordinates.longitude, '1');

 
        var jsonObject = {};
        jsonObject["requestParamAndValues"] = requestParamAndValues;
 
        var analyticsEvent = new AnalyticsPageEvent(jsonObject);
        analyticsEvent.trigger();
    },
	
	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;
    }
};

In the aspx page, ClientEventTracker, we need to add some lines of code (TrackerService):

namespace Sandbox.Tracking.Presentation
{
    public partial class ClientEventTracker : System.Web.UI.Page
    {
        protected void Page_Init(object sender, EventArgs e)
        {
            Context.Response.ContentType = "application/javascript";
            TriggerEvent(this.Context);
        }
 
        private static void TriggerEvent(HttpContext context)
        {
 
            string jsonData = context.Request["jsonData"];
 
            InputData inputData = ConvertJsonToObjectService.Convert<InputData>(jsonData);
 
            if (!inputData.ContainsParamkey(InputDataKeys.PageUrl))
                return;
 
            if (!inputData.ContainsParamkey(InputDataKeys.PageEventId))
                return;
 
            Tracker.StartTracking();
            Tracker.CurrentPage.Cancel();
 
	        TrackerService.SetCurrentVisitorCoordinates(inputData.GetValueByKey(InputDataKeys.Coordinates));
 
            TrackerService.RegisterEventToAPage(inputData.GetValueByKey(InputDataKeys.PageEventId), inputData.GetValueByKey(InputDataKeys.PageUrl));
 
            Tracker.Submit();
        }
    }
}

TrackerService is defined here:

namespace Sandbox.Tracking.Infrastructure
{
    public class TrackerService
    {

        public static void RegisterEventToAPage(string eventId, string url)
        {

            if (string.IsNullOrWhiteSpace(eventId))
                return;

            if (string.IsNullOrWhiteSpace(url))
                return;

            if (!Tracker.IsActive)
                return;

            PageEventItem pageEvent = new PageEventItem(Sandbox.Framework.SitecoreEnhancements.SitecoreItemRepository.Get(eventId));

            VisitorDataSet.PagesRow pagesRow = Tracker.CurrentVisit.GetPages().LastOrDefault(p => p.Url.Contains(url));

            if (pagesRow == null)
                return;

            pagesRow.Register(pageEvent);

        }

        public static void SetCurrentVisitorCoordinates(string coordinates)
        {

            GeoCoordinate? geoCoordinate = GeoCoordinateRepository.Get(coordinates);

            if (!geoCoordinate.HasValue)
                return;

            if (!Tracker.IsActive)
                return;

            Tracker.CurrentVisit.Latitude = geoCoordinate.Value.Latitude;
            Tracker.CurrentVisit.Longitude = geoCoordinate.Value.Longitude;

        }

    }
}

GeoCoordinate object:

namespace Sandbox.Tracking.Model
{
    public struct GeoCoordinate
    {
        private readonly double _latitude;
        private readonly double _longitude;

        public double Latitude { get { return _latitude; } }
        public double Longitude { get { return _longitude; } }

        public GeoCoordinate(double latitude, double longitude)
        {
            _latitude = latitude;
            _longitude = longitude;
        }


        public override string ToString()
        {
            return string.Format("{0},{1}", Latitude, Longitude);
        }

        public override bool Equals(Object other)
        {
            return other is GeoCoordinate && Equals((GeoCoordinate)other);
        }

        public bool Equals(GeoCoordinate other)
        {
            return Math.Abs(Latitude - other.Latitude) < double.Epsilon && Math.Abs(Longitude - other.Longitude) < double.Epsilon;
        }

        public override int GetHashCode()
        {
            return Latitude.GetHashCode() ^ Longitude.GetHashCode();
        }

    }
}

GeoCoordinateRepository:

namespace Sandbox.Tracking.Model.Repositories
{
    public class GeoCoordinateRepository
    {

        public static GeoCoordinate? Get(string latitude, string longitude)
        {
            if (string.IsNullOrWhiteSpace(latitude) || string.IsNullOrWhiteSpace(longitude))
                return null;


            string coordinates = string.Concat(latitude, ",", longitude);
            return GeoCoordinateFactory.Create(coordinates);

        }

        public static GeoCoordinate? Get(string coordinates)
        {
            if (string.IsNullOrWhiteSpace(coordinates))
                return null;

            return GeoCoordinateFactory.Create(coordinates);

        }

        public static IEnumerable<GeoCoordinate?> Get(IEnumerable<string> manyCoordinates)
        {
            return manyCoordinates.Select(Get);

        }
    }
}

Finally the GeoCoordinateFactory:

namespace Sandbox.Tracking.Model.Factories
{
    internal class GeoCoordinateFactory
    {
        internal static GeoCoordinate? Create(string coordinates)
        {

            string[] coordinateArray = coordinates.Split(',');

            if (coordinateArray.Length == 0)
                return null;

            if (string.IsNullOrWhiteSpace(coordinateArray[0]) || string.IsNullOrWhiteSpace(coordinateArray[1]))
                return null;

            return new GeoCoordinate(double.Parse(coordinateArray[0], NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture), double.Parse(coordinateArray[1], NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture));


        }
    }
}

That’s all for now folks 🙂