MVC EditorTemplates with nullable DateTime values

by Geert 6. March 2011 23:35

Recently, I was working on a website that required the user to input date, date/time and time. I noticed that at first, the code was messed up and partly coded in the masterpage (some javascript). Then, I decided to create an editor template for them. Sounds easy, right? Well, it’s not!

Requirements

My requirement for the editor templates was that they would look like this:

image

The user can select the date via the jQuery date picker. Then, it can also change the time via comboboxes. When the values are cleared, the date is removed from the date box and the time is reset to 0:00 (h:mm).

Initial idea

Easy I thought. I created an editor template like this:

   1: @model DateTime?
   2:  
   3: @using MyMvcApp.Properties
   4:  
   5: <div id="dateTimePicker_@(ViewData.ModelMetadata.PropertyName)">
   6:     <script type="text/javascript" language="javascript">
   1:  
   2:         //<![CDATA[
   3:         $(document).ready(function () {
   4:             var $div = $('#dateTimePicker_@(ViewData.ModelMetadata.PropertyName)');
   5:  
   6:             $div.find('.date').datepicker({ altFormat: 'dd-mm-yy' });
   7:         });
   8:  
   9:         function clearDateTimePicker_@(ViewData.ModelMetadata.PropertyName)() {
  10:             var $div = $('#dateTimePicker_@(ViewData.ModelMetadata.PropertyName)');
  11:  
  12:             $div.find('.date').val('');
  13:             $div.find('.hour').val('00');
  14:             $div.find('.minute').val('00');
  15:         }
  16:         //]]>
  17:     
</script>
   7:  
   8:     @* Date - should equal DatePicker.cshtml *@
   9:     @Html.TextBox("Value.Date", Model.HasValue ? Model.Value.Date.ToString() : string.Empty, new { @class = "date" })
  10:     <img alt="@Resources.SelectDate" src="../../../images/calendar.png" class="calendarIcon" />
  11:  
  12:     @* Time - should equal TimePicker.cshtml *@
  13:     @Html.DropDownList("Value.Hour", new SelectList(new[] { "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23" }, Model.HasValue ? Model.Value.Hour.ToString("D2") : "00"), 
  14:         null, new { style = "width: auto; margin-left: 5px;", @class = "hour" })
  15:     :
  16:     @{
  17:         List<string> availableMinutes = new List<string>();
  18:         for (int minute = 0; minute < 60; minute += 1)
  19:         {
  20:             availableMinutes.Add(minute.ToString("D2"));
  21:         }
  22:  
  23:         @Html.DropDownList("Value.Minute", new SelectList(availableMinutes, Model.HasValue ? Model.Value.Minute.ToString("D2") : "00"), 
  24:             null, new { style = "width: auto;", @class = "minute" });
  25:     }
  26:     <img alt="@Resources.SelectTime" src="../../../images/icon_clock_2.gif" style="margin-right: 5px" />
  27:     <input type="button" value="@Resources.Clear" class="ui-state-default" onclick="javascript:clearDateTimePicker_@(ViewData.ModelMetadata.PropertyName)()" />
  28: </div>

As you can see, I tried to edit the values of the model (input controls for Model.Value.Property). However, this never worked, and all I got was a null value in my http post action of my controller. I scratched my head, scratched it again, and then decided it was time to call in the help of the wise guys at StackOverflow. After a week, still no answer. I started loosing weight because I couldn’t sleep at night having nightmares about this issue. There is a solution for my issue, right?

Final idea (and solution)

After a week, I was really surprised that nobody could (or would) answer my question. I also posted the question with the #mvc and #razor hashtags on twitter, no response either. After an evening in a bar, I suddenly got the great (but yet simple) idea to store all values in a local hidden input. Then, I could use javascript to modify the data on the client side, and then post the hidden control (including date and time) as a whole. This way, null values would be supported, but I would still be able to separately edit the data (so the user can’t make errors by typing a time wrong).

DatePicker

Allows a user to pick (and clear) a date.

Usage

   1: @Html.EditorFor(model => model.MyDate, "DatePicker")

Source

   1: @model DateTime?
   2:  
   3: @using Narcast.Server.Properties
   4:  
   5: <div id="datePicker_@(ViewData.ModelMetadata.PropertyName)">
   6:     <script type="text/javascript" language="javascript">
   1:  
   2:         //<![CDATA[
   3:         $(document).ready(function () {
   4:             var $div = $('#datePicker_@(ViewData.ModelMetadata.PropertyName)');
   5:  
   6:             $div.find('.date').datepicker({ altFormat: 'dd-mm-yy' });
   7:         });
   8:  
   9:         function clearDatePicker_@(ViewData.ModelMetadata.PropertyName)() {
  10:             var $div = $('#datePicker_@(ViewData.ModelMetadata.PropertyName)');
  11:  
  12:             $div.find('.date').val('');
  13:  
  14:             updateData_@(ViewData.ModelMetadata.PropertyName)();
  15:         }
  16:  
  17:         function updateData_@(ViewData.ModelMetadata.PropertyName)() {
  18:             var $div = $('#datePicker_@(ViewData.ModelMetadata.PropertyName)');
  19:  
  20:             var $date = $div.find('.date').val();
  21:  
  22:             var $data = '';
  23:  
  24:             if ($date != '')
  25:             {
  26:                 $data = $date;
  27:             }
  28:  
  29:             $div.find('.data').val($data);
  30:         }
  31:         //]]>
  32:     
</script>
   7:  
   8:     @* Hidden field holding the client data *@
   9:     @Html.Hidden("", Model.HasValue ? Model.Value.ToShortDateString() : string.Empty, new { @class = "data" })
  10:  
  11:     <input type="text" class="date" value="@(Model.HasValue ? Model.Value.ToShortDateString() : string.Empty)" 
  12:         onchange="javascript:updateData_@(ViewData.ModelMetadata.PropertyName)()" />
  13:     <img alt="@Resources.SelectDate" src="../../../images/calendar.png" class="calendarIcon" />
  14:  
  15:     <input id="clearDate" type="button" value="@Resources.Clear" class="ui-state-default" onclick="javascript:clearDataPicker_@(ViewData.ModelMetadata.PropertyName)()" />
  16: </div>

TimePicker

Allows a user to pick (and clear) a time. When the values are cleared, the time is reset to 0:00 (h:mm). The date is today, so as a post value, a date with the time will be posted.

Usage

   1: @Html.EditorFor(model => model.PauseTime, "TimePicker")

Source

   1: @model DateTime?
   2:            
   3: @using Narcast.Server.Properties
   4:  
   5: <div id="timePicker_@(ViewData.ModelMetadata.PropertyName)">
   6:     <script type="text/javascript" language="javascript">
   1:  
   2:         //<![CDATA[
   3:         function clearTimePicker_@(ViewData.ModelMetadata.PropertyName)() {
   4:             var $div = $('#timePicker_@(ViewData.ModelMetadata.PropertyName)');
   5:  
   6:             $div.find('.hour').val('00');
   7:             $div.find('.minute').val('00');
   8:  
   9:             updateData_@(ViewData.ModelMetadata.PropertyName)();
  10:         }
  11:  
  12:         function updateData_@(ViewData.ModelMetadata.PropertyName)() {
  13:             var $div = $('#timePicker_@(ViewData.ModelMetadata.PropertyName)');
  14:  
  15:             var $date = '@(DateTime.Now.Date.ToShortDateString())';
  16:             var $hour = $div.find('.hour').val();
  17:             var $minute = $div.find('.minute').val();
  18:  
  19:             var $data = '';
  20:  
  21:             if ($date != '')
  22:             {
  23:                 $data = $date + ' ' + $hour + ':' + $minute;
  24:             }
  25:  
  26:             $div.find('.data').val($data);
  27:         }
  28:         //]]>
  29:     
</script>
   7:  
   8:     @* Hidden field holding the client data *@
   9:     @Html.Hidden("", Model.HasValue ? Model.Value.ToShortTimeString() : string.Empty, new { @class = "data" })
  10:  
  11:     <select class="hour" style="width: auto; margin-left: 5px;" onchange="javascript:updateData_@(ViewData.ModelMetadata.PropertyName)()">
  12:     @{
  13:         for (int hour = 0; hour < 24; hour += 1)
  14:         {
  15:             <text><option @{ if (Model.HasValue &amp;&amp; Model.Value.Hour == hour) { &lt;text>selected="selected"</text> }}>@(hour.ToString("D02"))</option></text>
  16:         }        
  17:     }
  18:     </select>
  19:  
  20:     :
  21:  
  22:     <select class="minute" style="width: auto; margin-left: 5px;" onchange="javascript:updateData_@(ViewData.ModelMetadata.PropertyName)()">
  23:     @{
  24:         for (int minute = 0; minute < 60; minute += 1)
  25:         {
  26:             <text><option @{ if (Model.HasValue &amp;&amp; Model.Value.Minute == minute) { &lt;text>selected="selected"</text> }}>@(minute.ToString("D02"))</option></text>
  27:         }        
  28:     }
  29:     </select>
  30:     <img alt="@Resources.SelectTime" src="../../../images/icon_clock_2.gif" style="margin-right: 5px" />
  31:  
  32:     <input id="clearDate" type="button" value="@Resources.Clear" class="ui-state-default" onclick="javascript:clearTimePicker_@(ViewData.ModelMetadata.PropertyName)()" />
  33: </div>

DateTimePicker

Allows a user to pick (and clear) a date in combination with time. If only a time is selected, null will be posted since this editor requires both a time and date.

Usage

   1: @Html.EditorFor(model => model.StartTime, "DateTimePicker")

Source

   1: @model DateTime?
   2:  
   3: @using Narcast.Server.Properties
   4:  
   5: <div id="dateTimePicker_@(ViewData.ModelMetadata.PropertyName)">
   6:     <script type="text/javascript" language="javascript">
   1:  
   2:         //<![CDATA[
   3:         $(document).ready(function () {
   4:             var $div = $('#dateTimePicker_@(ViewData.ModelMetadata.PropertyName)');
   5:  
   6:             $div.find('.date').datepicker({ altFormat: 'dd-mm-yy' });
   7:         });
   8:  
   9:         function clearDateTimePicker_@(ViewData.ModelMetadata.PropertyName)() {
  10:             var $div = $('#dateTimePicker_@(ViewData.ModelMetadata.PropertyName)');
  11:  
  12:             $div.find('.date').val('');
  13:             $div.find('.hour').val('00');
  14:             $div.find('.minute').val('00');
  15:  
  16:             updateData_@(ViewData.ModelMetadata.PropertyName)();
  17:         }
  18:  
  19:         function updateData_@(ViewData.ModelMetadata.PropertyName)() {
  20:             var $div = $('#dateTimePicker_@(ViewData.ModelMetadata.PropertyName)');
  21:  
  22:             var $date = $div.find('.date').val();
  23:             var $hour = $div.find('.hour').val();
  24:             var $minute = $div.find('.minute').val();
  25:  
  26:             var $data = '';
  27:  
  28:             if ($date != '')
  29:             {
  30:                 $data = $date + ' ' + $hour + ':' + $minute;
  31:             }
  32:  
  33:             $div.find('.data').val($data);
  34:         }
  35:         //]]>
  36:     
</script>
   7:  
   8:     @* Hidden field holding the client data *@
   9:     @Html.Hidden("", Model.HasValue ? Model.Value.ToShortTimeString() : string.Empty, new { @class = "data" })
  10:  
  11:     @* Date - should equal DatePicker.cshtml *@
  12:     <input type="text" class="date" value="@(Model.HasValue ? Model.Value.ToShortDateString() : string.Empty)" 
  13:         onchange="javascript:updateData_@(ViewData.ModelMetadata.PropertyName)()" />
  14:     <img alt="@Resources.SelectDate" src="../../../images/calendar.png" class="calendarIcon" />
  15:  
  16:     @* Time - should equal TimePicker.cshtml *@
  17:     <select class="hour" style="width: auto; margin-left: 5px;" onchange="javascript:updateData_@(ViewData.ModelMetadata.PropertyName)()">
  18:     @{
  19:         for (int hour = 0; hour < 24; hour += 1)
  20:         {
  21:             <text><option @{ if (Model.HasValue &amp;&amp; Model.Value.Hour == hour) { &lt;text>selected="selected"</text> }}>@(hour.ToString("D02"))</option></text>
  22:         }        
  23:     }
  24:     </select>
  25:  
  26:     :
  27:  
  28:     <select class="minute" style="width: auto; margin-left: 5px;" onchange="javascript:updateData_@(ViewData.ModelMetadata.PropertyName)()">
  29:     @{
  30:         for (int minute = 0; minute < 60; minute += 1)
  31:         {
  32:             <text><option @{ if (Model.HasValue &amp;&amp; Model.Value.Minute == minute) { &lt;text>selected="selected"</text> }}>@(minute.ToString("D02"))</option></text>
  33:         }        
  34:     }
  35:     </select>
  36:     <img alt="@Resources.SelectTime" src="../../../images/icon_clock_2.gif" style="margin-right: 5px" />
  37:  
  38:     <input type="button" value="@Resources.Clear" class="ui-state-default" onclick="javascript:clearDateTimePicker_@(ViewData.ModelMetadata.PropertyName)()" />
  39: </div>

For your convenience, I also added all the editors together in a single zip file:

NullableDateTimeEditorTemplates.zip (5.11 kb) [Downloads: 546]

Tags:

ASP.NET | MVC

Comments (1) -

Bram
Bram Belgium
3/9/2011 3:57:03 PM #

Thanks, works like a charm. You just saved me a lot of time! Smile

Pingbacks and trackbacks (1)+

Comments are closed

About the Author

Geert van Horrik is an independent freelance software developer since January 1st, 2007. Since then he was been working on several projects from C++ to C# (WPF, Silverlight, ASP.NET, etc). Currently he loves to write his software using WPF (or Silverlight if WPF isn't an option).

Lately, Geert is spending a lot of time on Catel, a free open-source MVVM Framework for WPF and Silverlight. Actually, it's more than "just" an MVVM Framework, it's a complete application library!