Sitecore Corner

Sitecore Tips and Tricks


Glass Mapper and Custom Time Field

Every Sitecore developer struggled with the problem of implementing a time field and many developers found this article which provides good solution to the problem. The field works well, uses Sitecore built-in timepicker and is easy to implement. The huge problem comes when trying to use the custom time field with one of the most used Sitecore ORMs – Glass Mapper. This arises from the fact that the time picker uses TimeSpan to store its value and Glass doesn’t support TimeSpan values out-of-the-box. Fortunately there are 2 possible solutions to the problem – creating Glass Abstract Data Mapper (in order to support TimeSpan values) or modifying the Custom Time Field (in order for it to work with DateTime which is supported by the default Glass Date Time Handler).

Creating an Abstract Data Mapper Approach

Creating and registering an AbstractSitecoreFieldMapper for Glass is pretty straightforward. The only implementations required are GetFieldValue, SetFieldValue. The other important part is to call the base constructor with Type (or Type array) which is used by Glass to know which types are handled by the custom mapper.

You can find the implementation of the AbstractSitecoreFieldMapper bellow.


namespace Sandbox.GlassExtensions
{
    using Glass.Mapper.Sc;
    using Glass.Mapper.Sc.DataMappers;
    using Sitecore;
    using System;

    public class SitecoreFieldTimeHandler : AbstractSitecoreFieldMapper
    {
        public SitecoreFieldTimeHandler() :
            base(typeof (TimeSpan))
        {

        }

        public override object GetFieldValue(string fieldValue,
            Glass.Mapper.Sc.Configuration.SitecoreFieldConfiguration config, SitecoreDataMappingContext context)
        {
            return DateUtil.ParseTimeSpan(fieldValue, TimeSpan.Zero);
        }

        public override string SetFieldValue(object value,
            Glass.Mapper.Sc.Configuration.SitecoreFieldConfiguration config, SitecoreDataMappingContext context)
        {
            if (value is TimeSpan)
            {
                TimeSpan time = (TimeSpan)value;
                return time.ToString();
            }

            throw new NotSupportedException("The value is not of type System.TimeSpan");
        }
    }
}

After the handler is implemented it needs to be registered in App_Start/GlassMapperScCustom.cs. Keep in mind this code is only valid if you are using Glass with Castle Windsor (via Glass.Mapper.SC.CastleWindsor). If you are using a custom implementation with different dependency injection framework – adjust the registration.


public static void CastleConfig(IWindsorContainer container)
{
	var config = new Config();

	 container.Register(
		        Component.For<AbstractDataMapper>()
		            .ImplementedBy<SitecoreFieldTimeHandler>()
		            .LifestyleCustom<NoTrackLifestyleManager>());

	container.Install(new SitecoreInstaller(config));
}

And that is it !

If you are not happy with TimeSpans for storing time – feel free to implement you custom data type to store the value.

NOTE: If you are using TDS Code Generation with GlassV3 Templates you will need to update GlassV3Item.tt GetGlassFieldType method. Example of what should be added is shown bellow.

case "[TIMEFIELDNAME]":
   return "TimeSpan";

Modifying the Custom Time Field Approach

This approach uses the default Glass Date Time Handler (The code can be found here) and introduces small modifications to the Custom Time Field.

The modified code can be found bellow.


namespace Sandbox.TimeField
{
    using Sitecore;
    using Sitecore.Data.Items;
    using Sitecore.Diagnostics;
    using Sitecore.Shell.Applications.ContentEditor;
    using Sitecore.Web.UI.HtmlControls;
    using Sitecore.Web.UI.Sheer;
    using System;

    public sealed class GlassTimeField : Input, IContentField
    {
        private TimePicker _picker;

        public string ItemID
        {
            get { return GetViewStateString("ItemID"); }
            set
            {
                Assert.ArgumentNotNull(value, "value");
                SetViewStateString("ItemID", value);
            }
        }

        public string RealValue
        {
            get { return GetViewStateString("RealValue"); }
            set
            {
                Assert.ArgumentNotNull(value, "value");
                SetViewStateString("RealValue", value);
            }
        }

        public GlassTimeField()
        {
            Class = "scContentControl";
            Change = "#";
            Activation = true;
        }

        public override void HandleMessage(Message message)
        {
            Assert.ArgumentNotNull(message, "message");
            base.HandleMessage(message);
            string name;
            if (message["id"] != ID || (name = message.Name) == null)
                return;
            if (name == "contentdate:today")
            {
                Now();
            }
            else
            {
                if (name != "contentdate:clear")
                    return;
                ClearField();
            }
        }

        public string GetValue()
        {
            return
                (System.DateTime.MinValue +
                 DateUtil.ParseTimeSpan(_picker == null ? RealValue : _picker.Value, TimeSpan.Zero))
                    .ToString("yyyyMMddTHHmmss");
        }

        public void SetValue(string value)
        {
            Assert.ArgumentNotNull(value, "value");
            RealValue = value;
            if (_picker == null)
                return;
            _picker.Value = value.Length == 15 ? DateUtil.IsoDateToDateTime(value).ToString("t") : value;
        }

        protected override Item GetItem()
        {
            return Client.ContentDatabase.GetItem(ItemID);
        }

        protected override bool LoadPostData(string value)
        {
            if (!base.LoadPostData(value))
                return false;
            _picker.Value = value ?? string.Empty;
            return true;
        }

        protected override void OnInit(EventArgs e)
        {
            SetViewStateBool("Showtime", true);
            _picker = new TimePicker();
            _picker.ID = ID + "_picker";
            Controls.Add(_picker);
            if (!string.IsNullOrEmpty(RealValue))
                _picker.Value = RealValue.Length == 15
                    ? DateUtil.IsoDateToDateTime(RealValue).ToString("t")
                    : RealValue;
            _picker.OnChanged += (EventHandler) ((param0, param1) => SetModified());
            _picker.Disabled = Disabled;
            base.OnInit(e);
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            ServerProperties["Value"] = ServerProperties["Value"];
            ServerProperties["RealValue"] = ServerProperties["RealValue"];
        }

        protected override void SetModified()
        {
            base.SetModified();
            if (!TrackModified)
                return;

            Sitecore.Context.ClientPage.Modified = true;
        }

        private void ClearField()
        {
            SetRealValue(string.Empty);
        }

        private void SetRealValue(string realvalue)
        {
            if (realvalue != RealValue)
                SetModified();
            RealValue = realvalue;
            _picker.Value = realvalue;
        }

        private void Now()
        {
            SetRealValue(DateUtil.IsoNowTime);
        }
    }
}

The modified methods are GetValue, SetValue and OnInit. The idea is to force the Time Field to work with Sitecore`s IsoDateTimeFormat. A slightly dirty approach – but it does the job.

Happy Glass Mapping ! 🙂

 



One response to “Glass Mapper and Custom Time Field”

  1. Nice walkthrough – I have worked around it using my ‘Delegate’ solution (you can see it here http://wp.me/p4MqmH-a), the mapper solution would have been my first approach otherwise.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: