ASP.NET MVC Outlook styled appointment form with JQuery date and time picker

30 Jun
June 30, 2013

Developers new to ASP.NET MVC often struggle with the transition to JQuery from the built in webforms controls many of them are used to.   The discipline of binding controls such as drop down lists in a model or controller can also trip up developers used to modifying an aspx file to populate a control inline or with an ObjectDataSource.

This article will show you how to create a form that allows users to enter events and appointments, with events able to span multiple days and be all day or at set times.  We’re going to store the date in a single field in the Model, but seperate the date and time components in the View and use Jquery to improve the user experience.

1. Create the Model

The first thing we’re going to do is to create a simple Event model to hold our event data:

namespace Project.Models
{
    public class Event
    {
       public Event()
        {
            StartDate = System.DateTime.Now;
            EndDate = System.DateTime.Now;
        }

        [Key]
        public int EventID { get; set; }

        [Required(ErrorMessage = "Start Date is required.")]
        [DataType(DataType.DateTime)]
        [DisplayName("Start Date")]
        public DateTime StartDate { get; set; }

        [Required(ErrorMessage = "End Date is required.")]
        [DataType(DataType.DateTime)]
        [DisplayName("End Date")]
        public DateTime EndDate { get; set; }

        [Required(ErrorMessage = "Event Name is required.")]
        [MaxLength(100, ErrorMessage = "Event Name cannot be longer than 100 characters.")]
        public string EventName { get; set; }

        public bool AllDayEvent { get; set; }
    }
}

This should be fairly straightforward, we’re using Data Annotations to validate the Model properties.  In the Event contructor we set the start and end date of the event to today, as the default value for creating a new event.

2. Create a ViewModel

Our event model by itself isn’t sufficient for our form.  Our form to create a new event contains 2 drop down lists, one for the start time and one for the end time: Create event form In order to populate the drop down lists with all the times, we’re going to use a ViewModel.  The ViewModel consists of the Event object, a holder for the start and end event times, and the list of all the times that are the options in the drop down lists. Create a ViewModels folder, and add a new class called EventCreateViewModel.cs with the following:

using Project.Models;

namespace Project.ViewModels
{
    public class EventCreateViewModel
    {
        public EventCreateViewModel()
        {
            StartTime = "9:00 AM";
            EndTime = "10:00 AM";
        }
        [DisplayName("Start Time")]
        public string StartTime { get; set; }

        [DisplayName("End Time")]
        public string EndTime { get; set; }

        public Event Event { get; set; }
        public IEnumerable<SelectListItem> Times { get; set; }
    }
}

3. Create the Event Controller GET method

The event controller for our form consists of two methods, a Create GET and a a Create POST. Here is the Create GET method, which populates the ViewModel we previously created:

public ActionResult Create()
{
    // Create a new instance of our View model
    var Model = new EventCreateViewModel();

    // Initialise a new Event - this will set the start and end dates to today
    Model.Event = new Event();
    Model.Times = new[]
    {
        new SelectListItem{ Text="12:00 AM", Value = "12:00 AM" },
        new SelectListItem{ Text="12:30 AM", Value = "12:30 AM" },
        new SelectListItem{ Text="1:00 AM", Value = "1:00 AM" },
        // etc etc ...
    };
    return View(Model);

}

We’ll come back to the POST method, let’s first get the view up and running.

4. Create a View for the event  form

For the view, we’re going to use Twitter Boostrap which is a great front end development Framework to get a site up and running quickly.  The first iteration of the create.cshtml file simply displays the Model elements on the page:

@model Project.ViewModels.EventCreateViewModel

<!DOCTYPE html>
<html lang="en">
<head>
    <link href="/Content/css/bootstrap.css" rel="stylesheet" />
</head>

@using (Html.BeginForm())
{
    <div class="container-fluid">
        <fieldset>
            <legend>Add a new Event</legend>

            <div class="editor-label">
                Event Name
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(model => model.Event.EventName, new { @class = "wide" })
            </div>
            <div class="editor-field">
                <table>
                    <tr>
                        <td style="width: 80px">Start time</td>
                        <td style="width: 100px">@Html.EditorFor(m => m.Event.StartDate)</td>
                        <td>
                            @Html.DropDownListFor(x => x.StartTime, Model.Times, new { @id = "ddlStartTime" })
                        </td>
                        <td style="text-align: left; width: 140px">
                            @Html.CheckBoxFor(m => m.Event.AllDayEvent, new { @id = "chkAllDayEvent" })
                            All day event
                        </td>
                    </tr>
                    <tr>
                        <td style="width: 80px; vertical-align: middle">End time</td>
                        <td>@Html.EditorFor(m => m.Event.EndDate)</td>
                        <td>
                            @Html.DropDownListFor(x => x.EndTime, Model.Times, new { @id = "ddlEndTime"})
                        </td>
                        <td></td>
                    </tr>
                </table>
            </div> 
            <div class="text-center">
                <input type="submit" value="Add Event" class="btn btn-large" />
            </div>
        </fieldset>
    </div>
}
<script type="text/javascript" src="/scripts/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="/scripts/bootstrap.js"></script>

Note how we’ve bound the Times from our ViewModel into a drop down list that store it’s value in the StartTime property and consists of the Times array:

@Html.DropDownListFor(x => x.StartTime, Model.Times, new { @id = "ddlStartTime" })

However before this code can work,  you need to create a new Helper for your dates.

5. Creating a custom date helper

If you have a property in your model of type ‘Date’ then when you display it in your view, the standard syntax is to use the EditorFor method:

@Html.EditorFor(x => x.Created)

Unfortunately this doesn’t let you add HTML Attributes such as class or ID, which we need for our Datepicker.  An alternative is to use the TextBoxFor method:

@Html.TextBoxFor(x => x.Created, new { @class = "datepicker" }) 

But the problem here is that you can’t use DataAnnotations to format your date to remove the time.

So we get around this by creating our own custom helper for Dates.  Create a file called date.cshtml in the /Views/Shared/EditorTemplates/ folder with the following code:

@model DateTime

@Html.TextBox("", Model.ToShortDateString(), new { @class = "datepicker" })

Now you can use the EditorFor function and your dates will be displayed in the ShortDateformat, and support the datepicker which we’re about to integrate.

6. Integrating Bootstap Datepicker

There are many Jquery date pickers you can use, and my personal favourite is a fork of Stefan Petre’s Bootstrap Datepicker by @eternicode.  Eternicode has added a few nice features, most importantly the auto close on click function and an event that fires when the date is changed.  Full details and download links here. To integrate it, simply add the datapicker.css and datepicker.js files to your project and create.cshtml file, then ad the following Javascript:

<script type="text/javascript">
$(".datepicker").datepicker(
    {
        weekStart: 1,
        format: 'dd/mm/yyyy',
        gotoCurrent: true,
        autoclose: true,
        todayBtn: "linked",
        keyboardNavigation: false,
        autoclose: true,
        todayHighlight: true
    });
</script>

Of course, feel free to modify the options to suit your project.

7.  Create the Event Controller POST method

The POST method combines the date and time from our ViewModel and saves it in the database:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(EventCreateViewModel model)
{
    // Get the time portion of our date/time from our drop down lists
    DateTime startTime = Convert.ToDateTime(model.StartTime);
    DateTime endTime = Convert.ToDateTime(model.EndTime);

    // Create a new date based on the date from our date picker, and time from our drop down lists:
    model.Event.StartDate = new DateTime(model.Event.StartDate.Year, model.Event.StartDate.Month, model.Event.StartDate.Day, startTime.Hour, startTime.Minute, startTime.Second);
    model.Event.EndDate = new DateTime(model.Event.EndDate.Year, model.Event.EndDate.Month, model.Event.EndDate.Day, endTime.Hour, endTime.Minute, endTime.Second);

    // Show an error if the end date is before the start date
    if (model.Event.StartDate > model.Event.EndDate)
    {
        ModelState.AddModelError(string.Empty, "The end time has to be after the start time.");
    }
    if (ModelState.IsValid)
    {
        // Save the database record
        db.Events.Add(model.Event);
        db.SaveChanges();
        TempData["success"] = "Your event was successfully added.";
        return RedirectToAction("Index");
    }
    // Validation failed, redisplay the form
    return View(model);
}

8.  Finishing Touches

To finish up, we’re going to improve the user experience with some JQuery. Firstly, if the user selects the All Day option, then diable the time controls:

$("#chkAllDayEvent").click(function () {
    $('#ddlEndTime').attr("disabled", $(this).is(':checked'));
    $('#ddlStartTime').attr("disabled", $(this).is(':checked'));
});

Secondly, when the user sets a Start Date, set the End Date to be the same:

$('#Event_StartDate').on('changeDate', function(e){
    $('#Event_EndDate').val($(this).val())
});

Finally. if the user sets a Start Time, set the End Time to be an hour later”

$("#ddlStartTime").change(function () {
    var selectedValue = $(this).val();

    if (selectedValue == "11:00 PM") {
        $('#ddlEndTime').val("12:00 AM");
    }
    else if (selectedValue == "11:30 PM") {
        $('#ddlEndTime').val("12:30 AM");
    }
    else {
        var startTime = selectedValue.split(':');
        var endHours = parseInt(startTime[0]) + 1;
        endHours = Math.min(Math.max(endHours, 1), 24);
        $('#ddlEndTime').val(endHours + ':' + startTime[1]);
    }

});

And that’s it!

Tags: ,
© Copyright - Evonet