Using DOJO when creating a custom visitor group

  • Jun 07, 2015
  • EPiServer
  • DOJO
  • Visitor Groups
  • Google Visualization
  • |

In my previous blog post I explained how you can show content based on the visitor’s current weather. I also mentioned it’s possible to use DOJO for the rendering of the input parameters. Now I want to explain how this work with an example.

EPiServer standard delivers some visitor groups that can be used by content editors. One of these visitor groups is ‘Geographic Location’ which can be used to show content based on the visitor’s current location. For this example I’m using the same idea, so showing content based on the visitor’s current location. The only difference is the rendering of the input parameter field for the country. In the standard visitor group of EPiServer it’s possible to select a region with dropdown as shown in the image below.

 

For this example I’m using a world map for selecting one or more countries.

 

As you can see a world map is now used where multiple countries can be selected. When a country is selected the country get’s a grey background. When the country is clicked again the selection is removed. I will start explaining how this work.

 

Visitor group criterion settings

 

As mentioned earlier the input parameter field is build with DOJO. Below the CountryCriterionSettings class. The Countries property is decorated with the DojoWidget attribute. This attribute contains a reference to the DOJO widget that will render the input field.

public class CountryCriterionSettings : CriterionModelBase
        {
            [DojoWidget(WidgetType = "app.fields.CountriesSelectField")]
            public string Countries { get; set; }
     
            public override ICriterionModel Copy()
            {
                return ShallowCopy();
            }
        }
Dojo widget

 

For the world map I’m using the Google Visualization API. In the postCreate method first the Google Jsapi script tag is added to the HTML. After this script is loaded then the Google Visualization API script is loaded. When a region is selected in the map the method onRegionSelected is called. In this method the selected region is added to a local array variable. When the content editor saves the visitor group criterion the _getValueAttr method is called. This method returns a JSON object containing the selected regions.

define([
        "dojo",
        "dojo/_base/array",
        "dojo/_base/declare",
        "dojo/json",
     
        "dijit/registry",
        "dijit/_Widget",
        "dijit/_TemplatedMixin",
        "dijit/_Container",
        "dojox/layout/TableContainer",
        "dijit/form/ValidationTextBox",
        "dojo/text!./templates/CountriesSelectFieldTemplate.html"
    ],
    function (
        dojo,
        array,
        declare,
        json,
     
        registry,
        _Widget,
        _TemplatedMixin,
        _Container,
        TableContainer,
         ValidationTextBox,
        template
    ) {
        return declare("app.fields.CountriesSelectField", [
            _Widget,
            _TemplatedMixin,
            _Container], {
                templateString: template,
                chart: null,
                chartOptions: { tooltip: { trigger: 'focus' }, backgroundColor: 'transparent', defaultColor: '#555b64', datalessRegionColor: '#ffffff' },
                chartData: [['Country', '']],
                postCreate: function () {
                    this.loadScript();
                },
                _getValueAttr: function () {
                    var arr = [];    
                    if (this.chartData != null) {
                        array.forEach(this.chartData, function (item, i) {
                            if (i > 0) { // skip the first item in the array
                                arr.push(item[0]);
                            }
                        });
                    }
                    return json.stringify(arr);
                },
                _setValueAttr: function (val) {
                    if (val != null) {
                        var arr = json.parse(val);
                        array.forEach(arr, dojo.hitch(this, function(item, i) {
                            this.chartData.push([ item, item ]);
                        }));
                    }
                },
     
                loadScript: function() {
     
                    var script = document.createElement('script');
                    script.type = 'text/javascript';
                    script.src = "https://www.google.com/jsapi";
                    script.onload = dojo.hitch(this, this.scriptOnLoad);
                    document.body.appendChild(script);
                },
                scriptOnLoad: function() {
                    if (google) {
                        google.load("visualization", "1", {
                            packages: ["corechart"],
                            callback: dojo.hitch(this, function () {
                                var data = google.visualization.arrayToDataTable(this.chartData);
     
                                this.chart = new google.visualization.GeoChart(this.container);
                                 
                                this.chart.draw(data, this.chartOptions);
                                google.visualization.events.addListener(this.chart, 'regionClick', dojo.hitch(this,this.onRegionSelected));
                            })
                        });
                    }
                },
                onRegionSelected: function(e) {
                    var region = e.region;
     
                    var index = -1;
                    $.each(this.chartData, function (i) {
                        if (this[0] == region) {
                            index = i;
                            return;
                        }
                    });
                    if (index != -1) {
                        this.chartData.splice(index, 1);
     
                    } else {
                        this.chartData.push([region, region]);
                    }
                    this.chart.draw(google.visualization.arrayToDataTable(this.chartData), this.chartOptions);
                }
            }
        );
    });
Visitor group criterion

 

The CountryVisitorCriterion class inherits from GeographicCriterionBase class of EPiServer. This base class contains functionality for retrieving the visitor’s location by IP address. The IsMatch method compares the Country property value (DOJO widget) with the current country of the visitor. If there is a match the method will return true, indicating that the visitor can view the content.

[VisitorGroupCriterion(
            Category = "Time and Place Criteria",
            DisplayName = "Countries",
            Description = "Check the visitor's current country")]
        public class CountryVisitorCriterion : GeographicCriterionBase<CountryCriterionSettings>
        {
            protected override bool IsMatch(IGeolocationResult location, Capabilities capabilities)
            {
                if (!string.IsNullOrEmpty(Model.Countries))
                {
                    var countries = Newtonsoft.Json.JsonConvert.DeserializeObject<string[]>(Model.Countries);
     
                    return countries.Any(c => c.Equals(location.CountryCode, StringComparison.InvariantCultureIgnoreCase));
                }
                return false;
            }
        }
IP address resolver

 

The problem with running this example locally is that the IP address of the visitor is ::0. The GeographicCriterionBase class internally uses the IClientIPAddressResolver interface for retrieving the IP address of the visitor. The implementation for this interface (DefaultClientIPAddressResolver) calls the UserHostAddress property of the HttpContext.Request object. This property returns ::0 as IP address when the code is run locally. So the result for the criterion is always false when running locally, because no location can be found. I solved this by creating my own implementation of the IClientIPAddressResolver and register this with StructureMap. This custom implementation returns a valid IP address when the code is run locally.

public class CustomClientIPAddressResolver : IClientIPAddressResolver
        {
            public IPAddress ResolveAddress(HttpContextBase httpContext)
            {
                IPAddress pAddress;
                if (httpContext.Request.UserHostAddress.Equals("::1"))  // Website is running localhost so define hardcoded a valid IP address
                {
                    return IPAddress.Parse("0.0.0.0");
                }
                else if (IPAddress.TryParse(httpContext.Request.UserHostAddress, out pAddress))
                {
                    return pAddress;
                }
                return IPAddress.None;
            }
        }

You can find the full source on my GitHub account.

Comments