A simple automatic time zone selector in Episerver

There are many times when you want editors to specify a start date and time and need to capture the relevant time zone (this is especially pertinent for things like events), one option is to persist the editors local time zone automatically when they enter a date (like this example). However, it's also a fairly common desire to allow editors to select a specific time zonewhich is what this blog post covers.

The easiest way to do this would be to implement the ISelectionFactory interface and use the SelectOne attribute (this is all covered in the Episerver documentation). However, because there's many possible time zones it would be nice to use Episerver's built-in auto-suggestion via the ISelectionQuery interface (which ultimately makes the data available as part of the SelectionQueryStore REST store). However, the problem with this is that the TimeZoneInfo Id typically has a space in it which means the REST store throws a lot of 404s.

This code example foregoes the use of the REST store passes the time zone data directly to the Dojo widget, it also automatically pre-selects a time zone based on the user's locale via the Internationalization API.

The first thing we need to create is the selection attribute:

[AttributeUsage(AttributeTargets.Property)]
public sealed class TimeZoneSelectionAttribute : Attribute, IMetadataAware
{
    public void OnMetadataCreated(ModelMetadata metadata)
    {
        var timeZones = TimeZoneInfo.GetSystemTimeZones();
        var items = new List<object> {new {name = string.Empty, id = string.Empty, iana = string.Empty }};


        foreach (var timeZone in timeZones)
        {
            items.Add(new
                {name = timeZone.DisplayName, id = timeZone.Id, iana = TZConvert.WindowsToIana(timeZone.Id)});
        }

        var clientEditingClass = metadata as ExtendedMetadata;

        if (clientEditingClass == null)
        {
            return;
        }

        clientEditingClass.EditorConfiguration.Add("style", "width: 300px");
        clientEditingClass.ClientEditingClass = "alloy/TimeZoneComboBox";
        clientEditingClass.CustomEditorSettings["uiType"] = clientEditingClass.ClientEditingClass;
        clientEditingClass.CustomEditorSettings["uiWrapperType"] = "flyout";
        clientEditingClass.EditorConfiguration["data"] = items;
    }
}

This resolves the system time zones and passes them down to Dojo, it makes use of the TimeZoneConverter NuGet package to get the IANA time zone (which is what the Internationalization API returns).

Next, we can create the custom Dojo widget, extending dijit.form.FilteringSelect:

define("alloy/TimeZoneComboBox", [
        "dojo/_base/declare",
        "dojo/store/Memory",
        "dijit/form/FilteringSelect"],
    function (
        declare,
        Memory,
        FilteringSelect) {
        return declare([FilteringSelect], {
            required: false,
            queryExpr: "*${0}*",
            autoComplete: false,

            postMixInProperties: function () {
                var store = new Memory({
                    data: this.data
                });

                this.set("store", store);

                this.inherited(arguments);
            },

            _setValueAttr: function (/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue, /*item?*/ item) {
                // If no value has been set select a time zone automatically
                if (value === null) {
                    var result = this.data.find(timeZone => {
                        return timeZone.iana === Intl.DateTimeFormat().resolvedOptions().timeZone;
                    });

                    if (result) {
                        value = result.id;
                    }
                }

                this.inherited(arguments);
            },

            destroy: function () {
                // Remove the validation message in case it has been displayed.
                this.displayMessage();

                this.inherited(arguments);
            }
        });
    });

This takes the time zone data from the back-end and registers it as a Memory store which is used to drive the FilteringSelect. Finally, if no time zone has been selected (i.e. the page or block has just been created) it selects the users time zone automatically.

Finally, we can use the time zone selection attribute like so:

[Required]
[TimeZoneSelection]
[Display(Name = "Time Zone")]
public virtual string TimeZone { get; set; 

This blog post hopefully shows how (with only a minimum amount of code) it's possible to extend a Dojo component in Episerver and add some new functionality. What it does is pretty simplebut I think it shows the extensibility and customizability of the Episerver editor UI.

Comments

There are zero comments 😢