Monday, 21 September 2009

MVC/Jquery Calendar

One of the main pluses of working with the ASP.Net MVC framework is of course the separation of concerns it promotes with the split between the controller and the view in the UI. This is not only apparent in the standard method of having the controller obtain the data required for display, and passing this to a view for display as XHTML. The same clear separation of purpose can be made use of when the output of the view is other formats too.

In a recent project I've had need to represent a calendar of events - a simple side bar to the website that displays a calendar view with days that have events associated with them highlighted. Clicking on the day displays the list of events below.

In implementing this feature I made use of the controller and view pattern in three distinct ways.
  1. Having a controller render an XHTML page view
  2. Having a controller render JSON data for use from an AJAX request.
  3. Having a controller render a partial view to support an AJAX page update.
Application Structure

The back-end of this MVC application consisted of a database table of events, accessed via a stored procedure and an ADO.Net repository layer. My controller made calls to obtain data in the form of custom business entities from this repository layer via a service layer.

The controller consists of three methods as illustrated in listing 1.

Listing 1: Controller methods


    1  /// <summary>


    2  /// Renders calendar partial view


    3  /// </summary>


    4  /// <returns></returns>


    5  public ActionResult Calendar()


    6  {


    7      return View();


    8  }


    9 


   10  /// <summary>


   11  /// Renders events for month in JSON format (for AJAX page injection)


   12  /// </summary>


   13  /// <param name="year">Year</param>


   14  /// <param name="month">Month</param>


   15  /// <returns></returns>


   16  public JsonResult ListAsJson(int year, int month)


   17  {


   18      IList<Event> events = eventsService.GetEventList(year, month, 0);


   19      return this.Json(events);


   20  }


   21 


   22  /// <summary>


   23  /// Returns partial view with events for selected day (for AJAX page injection)


   24  /// </summary>


   25  /// <param name="year">Year</param>


   26  /// <param name="month">Month</param>


   27  /// <param name="day">Day</param>


   28  /// <returns></returns>


   29  public ActionResult EventsForDay(int year, int month, int day)


   30  {


   31      IList<Event> events = eventsService.GetEventList(year, month, day);


   32      return View(events);


   33  }




The first, Calendar(), is pretty much as a simple as a controller method gets. Initially I had this retrieving the event data and passing it to the view, but when I realised I wanted to go the AJAX route for display as the calendar is paged from month to month, I decided not to retrieve any data for the initial display. So this simply renders the view.

As the calendar is displayed on most pages of the website, it's called via a RenderAction method coded into the site's master page.

The second ListAsJson() retrieves the events for the supplied year and month and renders them in JSON format. This format, which is native JavaScript of course, can be used directly in the client side code that makes an AJAX call as the months are selected in the calendar.

Finally, EventsForDay() similarly retrieves event data, this time for a specified day. The event data is supplied to a partial view which consists of not a whole XHTML template, but just a fragment. This is then injected into the page using JavaScript.

Listing 2: View XHTML


    1 <div id="calendar-render"></div>


    2 


    3 <script src="<%= Url.Content("~/js/calendar.js") %>" type="text/javascript"></script>


    4 


    5 <div id="calendar-events-display">


    6     <h3></h3>   


    7     <div id="calendar-events-display-events"></div>


    8 </div>




As the major part of the work is via AJAX calls from JavaScript, the view is very simple. Really it just contains a couple of placeholders for the calendar control and event list to be rendered.

Listing 3: JavaScript (jquery) AJAX code


    1 var selectableDates;


    2 var months = new Array(13);


    3 months[0] = "January";


    4 months[1] = "February";


    5 months[2] = "March";


    6 months[3] = "April";


    7 months[4] = "May";


    8 months[5] = "June";


    9 months[6] = "July";


   10 months[7] = "August";


   11 months[8] = "September";


   12 months[9] = "October";


   13 months[10] = "November";


   14 months[11] = "December";


   15 


   16 //Hook-up the jquery datepicker


   17 $(document).ready(function() {


   18     var today = new Date();


   19     getEventsForMonth(today.getUTCFullYear(), today.getMonth() + 1);


   20 });


   21 


   22 //Set up the datepicker


   23 function initializeDatePicker(startDate) {


   24 


   25     //Check if current month - if so use current day rather than 1st of the month


   26     var today = new Date();


   27     if (startDate.getUTCFullYear() == today.getUTCFullYear() && startDate.getMonth() == today.getMonth())


   28         startDate = today;


   29 


   30     $("div#calendar-render").datepicker('destroy'); // Destroy if previously created so render will call again when events wired up


   31     $("div#calendar-render").datepicker({


   32         defaultDate: startDate,


   33         beforeShowDay: renderDay,


   34         onSelect: showEvents


   35     }).datepicker("option", { onChangeMonthYear: getEventsForMonth });


   36 }


   37 


   38 //Render a day as selectable if there's an event on that day


   39 function renderDay(date) {


   40     return [isDateSelectable(date), ""];


   41 }


   42 


   43 //Checks to see if date to be rendered is in the list of selectable dates


   44 function isDateSelectable(date) {


   45     for (var i = 0; i < selectableDates.length; i++) {


   46         if (selectableDates[i].getDate() == date.getDate())


   47             return true;


   48     }


   49     return false;


   50 }


   51 


   52 //Makes AJAX call to get events list for month and populates the selectableDates array


   53 function getEventsForMonth(year, month, inst) {


   54     $("div#calendar-events-display").hide();


   55     selectableDates = new Array();


   56     $.getJSON("/Events/ListAsJson?year=" + year + "&month=" + month, null, function(data) {


   57         var i = 0;


   58         $.each(data, function(index, evt) {


   59             selectableDates[i] = extractDateFromJSONResponse(evt.Start);


   60             i++;


   61         });


   62         initializeDatePicker(new Date(year, month - 1, 1));


   63     });


   64 }


   65 


   66 //Extracts date object from JSON formatted date response


   67 function extractDateFromJSONResponse(jsonDate) {


   68     return new Date(parseInt(jsonDate.substr(jsonDate.indexOf("/Date(") + 6, jsonDate.length - 8)));


   69 }


   70 


   71 //Show events for selected day


   72 function showEvents(dateText, inst) {


   73     var d = new Date(dateText);


   74     var day = d.getDate();


   75     var month = d.getMonth() + 1;


   76     var year = d.getUTCFullYear();


   77     $("div#calendar-events-display").hide();


   78     $("div#calendar-events-display h3").html(day + " " + months[month - 1] + " " + year);


   79     $.get("/Events/EventsForDay?year=" + year + "&month=" + month + "&day=" + day, null, function(data, textStatus) {


   80         $("div#calendar-events-display-events").html(data);


   81         $("div#calendar-events-display-events div:nth-child(odd)").addClass("alt");


   82         $("div#calendar-events-display-events div div:nth-child(odd)").removeClass("alt");


   83         $("div#calendar-events-display").show("fast");


   84     });       


   85 }




The JavasScript code itself makes use of the Jquery UI DatePicker that can be custom styled to provide a very neat client side calendar control. It provides events such as for day rendering and navigating from month to month, that I've hooked into in order to make the AJAX requests to retrieve the events for each month, and for rendering the listing of events for a day when a particular date is selected.

1 comment:

  1. Great Post and Seems to be a wonderful starting point for displaying monthly calendars.

    We've been trying to display a calendar for our Local School Board to keep the community informed.
    Would it be possible for you to supply a sample solution to kickstart the process?

    If so can you let me know how to get it?
    My Email is Bill@Gerold.com

    Thanks in advance

    ReplyDelete