Tuesday, 17 August 2010

A Boolean Date Time Field Template for Dynamic Data 4

Here’s the idea, you want to mark a status of a row with a checkbox and you also need the date time setting one the row to record when the status changed. You normally end up with two fields in your table for this. I decided that in many cases I would only need the date time i.e. if the date time is set then the status has changed is null then not, e.g. to set Order Status to complete set the Complete column to DateTime.Now.

Another reason you may want to use a checkbox to enter a date is to set the date without letting the user enter a custom date.

This turned out to be a little more complicated than I first though and I needed to consult David Ebbo at Microsoft to get the answer. First we’ll deal with the issue I came across that at first was a road block. This is the issue of setting the field to null when the user unchecks the check box.

<%@ Control
    Language="C#" 
    CodeBehind="DateTimeBoolean_Edit.ascx.cs" 
    Inherits="CustomFieldTemplates.DateTimeBoolean_EditField" %>

<asp:CheckBox runat="server" ID="CheckBox1" />
<asp:HiddenField ID="HiddenField1" runat="server" />

Listing 1 – DateTimeBoolean_Edit.ascx

So here in Listing 2 we see the first ExtractValues attempt

protected override void ExtractValues(IOrderedDictionary dictionary)
{
    if (!CheckBox1.Checked)
    {
        // only set to null if unchecked
        dictionary[Column.Name] = String.Empty;
    }
    else if (String.IsNullOrEmpty(HiddenField1.Value))
    {
        // only set a value if there is no current value
        dictionary[Column.Name] = DateTime.Now;
    }
}

Listing 2 – ExtractValues method v1

As you can see if the CheckBox1 is unchecked I set the return value to String.Empty this you would think would do the trick but it doesn't what happens is the value is retained. What confused the matter for me was is if I have a new row or a row with a null value and check it, it sets the value Surprise

It turns out that the reason is that the ExtractValues method is called twice the first time when the field template is initialised and the second time when the updated value is returned. But if look at my logic it returns no value the first time the method is called, but a value is required for the system to know that it is to be set to null the second time it is called Big Grin

So here the full code behind of the Edit template, you will see now that the hidden field is set in the OnDataBound event to make sure we return the correct value the first time ExtractValues is called.

public partial class DateTimeBoolean_EditField : System.Web.DynamicData.FieldTemplateUserControl
{
    protected override void OnDataBinding(EventArgs e)
    {
        base.OnDataBinding(e);

        // keep current value
        HiddenField1.Value = FieldValueString;

        if (FieldValue != null)
            CheckBox1.Checked = true;
        else
            CheckBox1.Checked = false;
    }

    protected override void ExtractValues(IOrderedDictionary dictionary)
    {
        if (!CheckBox1.Checked)
        {
            // only set to null if unchecked
            dictionary[Column.Name] = String.Empty;
        }
        else if (String.IsNullOrEmpty(HiddenField1.Value))
        {
            // only set a value if there is no current value
            dictionary[Column.Name] = DateTime.Now;
        }
        //else
        //    dictionary[Column.Name] = ConvertEditedValue(HiddenField1.Value);
    }

    public override Control DataControl
    {
        get { return CheckBox1; }
    }
}

Listing 3 – DateTimeBoolean_Edit.ascx.cs

And for completeness the read-only version of the field template.

<%@ Control
    Language="C#" 
    CodeBehind="DateTimeBoolean.ascx.cs" 
    Inherits="CustomFieldTemplates.DateTimeBooleanField" %>

<asp:CheckBox ID="CheckBox1" runat="server" Enabled="false" />

Listing 4 – DateTimeBoolean.ascx

public partial class DateTimeBooleanField : System.Web.DynamicData.FieldTemplateUserControl
{
    protected override void OnDataBinding(EventArgs e)
    {
        base.OnDataBinding(e);
        CheckBox1.Checked = !String.IsNullOrEmpty(FieldValueString);
        if (FieldValue != null)
            CheckBox1.ToolTip = FieldValueString;
    }

    public override Control DataControl
    {
        get { return CheckBox1; }
    }
}

Listing 5 – DateTimeBoolean.ascx.cs

You may want to show a date time value instead of the checkbox if so you can just use a copy of the default read-only Date Time field template.

And remember to add a UIHint to set the column to use the new field template

[UIHint("DateTimeBoolean")]
public Nullable<DateTime> ShippedDate { get; set; }

As always happy coding