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: 547]

Tags:

ASP.NET | MVC

Detailed Download Counter 1.1 for BlogEngine.NET 2.0

by Geert 4. February 2011 14:04

Update from March 18th, 2011:

GENiALi e-mailed me with an issue that an exception was thrown at startup because the extension was unable to parse a boolean. He sent me a fix which is now included in 1.2.


As you might have noticed, BlogEngine.NET 2.0 is released recently. Quite a while ago, I wrote an extension for the 1.x version of BlogEngine called Detailed Download Counter. For what it’s worth, it was quite “successful”, at least for me Glimlach

During the upgrade, I decided to drop several extensions I had used blindly over the years. I could drop most of them, but the knowledge of how many times a file has been download cannot be expressed in money.

Therefore, I gently decided to make the old version compatible with the new version of BlogEngine and share it with you:

DetailedDownloadCounter-1.1.zip (7.00 kb) [Downloads: 676]

DetailedDownloadCounter-1.2.zip (7.07 kb) [Downloads: 611]

Tags:

ASP.NET | C#

BlogEngine.NET extension: Detailed Download Counter

by Geert 1. January 2009 23:09

Lately, I have been posting some source code and I wanted to know how many times the source code was downloaded. I searched the internet and quickly found an Extensions page on the official BlogEngine.NET website. I noticed there were several different download counters available. I will list them here shortly with the cons and the pros.

1) Counter Extension by RTur

  • Stores every download (so advanced statistics are available)
  • The overview is not an overview (counts per file are not available)

2) Simply Download Counter by Al Nyveldt

  • Download count is displayed in the blog posts itself
  • No settings available to determine who can view the download count
  • Downloads are not stored separate (only the number of downloads per file)

3) File Download Tracker by Chris Blakenship

  • It shows a good overview which file is downloaded how many times
  • Downloads are not stored seapare (only the last download date and the number of downloads per file)

None of these counters could provide the functionality I really wanted. I simply wanted a counter that allowed me to decide at run-time whether I wanted anonymous users to be able to view the number of downloads. Also, I wanted a more structured way of storing and viewing the downloads per file. Since the original extensions didn't too look difficult, I decided to dive into the world of BlogEngine.NET extensions.

What I have developed is a solution that fits my needs (and probably others as well). The Detailed Download Counter saves the information per file, but also stores the timestamp, user agent and user host. This gives anyone the ability to create more advanced statistics. For the moment, I have only implemented a grid which simply shows an overview of the files downloaded and the details of a file. To get a grip of the current implementation, please view the image below:

To install the Detailed Download Counter extension, simply extract the zip file to the root or your BlogEngine.NET application. 

DetailedDownloadCounter.zip (6.69 kb) [Downloads: 1128]

kick it on DotNetKicks.com

A Detailed Download Counter that can be easily used with BlogEngine.NET

Tags:

ASP.NET | C#

ASP.NET Content Management System

by Geert 21. September 2008 08:30

Sometimes, you are surfing the web looking for something that you simply can't find. Quite some time ago, I had this problem. I have developed the website CatenaLogic.com including it's store myself. However, everything was fixed and changing some text required me to completely rebuild and upload the website. This was a horrible task. At the time of development (about 2 years ago), I already looked for some ASP.NET content management systems (from now on I'll call it CMS), but only found DotNetNuke (and some other projects). However, the downside of DNN is that the learning curve is very steep and the integration is much too hard. Turning an existing website into DNN is (at least for me) a horrible task.

About 4 months ago, I decided to upgrade the website to implement better support for multiple payment methods, and I started searching the internet again for an ASP.NET CMS. The reason I don't write it myself is because I don't want to do the job that a lot of people did before me. Then I found it, the golden grail:

N2 CMS

N2 CMS is an open source content management system that lets the developer decide how to do the integration. Simply said, N2 CMS simple stores the data for you and you can retrieve it from the database via the N2 interface. This is the minimum you have to do to use N2 CMS. However, there is also a fully supported templates project that provides a lot of basic controls to build a simple website. For a great example, visit the N2 CMS Templates Page.

N2 CMS is also available on CodePlex.

kick it on DotNetKicks.com

N2 Content Management System, a great open-source content management system for ASP.NET

Tags:

ASP.NET

ASP.NET Web Application types not found

by Geert 17. June 2008 09:43

Introduction 
When I was converting a Web site to a ASP.NET Web Application project in Visual Studio 2008, all my classes in the App_Code folder were not recognized any longer.

After some small investigation, it seemed that App_Code cannot be used in an ASP.NET Web Application project in Visual Studio (2005/2008). So, I put all the classes, before located in App_Code, to subfolders of the project and added the corresponding namespaces.

Problem
However, I was still getting the following compile error:

Error 129 The type or namespace name 'MyClass' could not be found (are you missing a using directive or an assembly reference?)

After googling for this problem, I still couldn't find the problem. Then I thought: wait, are the classes even being compiled? No, the classes were actually not compiled. Somehow, the classes were set as content.

Fix
Select the file that is not being compiled in the solution explorer and view its properties (hit F4). Make sure the Build Action is set to Compile, and not to Content (which was the case with my project).

kick it on DotNetKicks.com

Fix for unknown types in ASP.NET Web Application projects

Tags:

ASP.NET | C#


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!