Introduction
This article is intended to outline a solution to a common problem, namely a compact events calendar to allow users to see forthcoming events quickly. This was prompted by a re-write of a website into MVC3, which has the need for such a calendar. I am sharing what I have found in here as I could not find a step by step guide for the complete process. To illustrate the features I wish to support, here is the sort of thing I want to achieve:
- Dates with events are selectable, dates without are not.
- If the user hovers over an event date, a brief description or title of the event is shown in a tooltip.
- If the event is clicked, a page outlining the event detail will be displayed.
It a also important to say what this article will not cover, namely how to store the events. By extension the article does not supply a UI for adding events, and only stubbed view event functionality. I want to concentrate on the process of creating the calendar and its interaction with the MVC3 framework, rather than the specifics of data storage and retrieval. In any case, storage of events and the types of data stored are likely to be implementation specific. In place of a backing store I will use stubbed code mimic that of the real-world application this article is really research for.
This is intended as a step-by-step guide about how to implement the calendar, but you will need some knowledge of Javascript, jQuery and MVC3. In particular you may want to review AJAX calls via javascript and how MVC3 actions can handle these, I will highlight the key points as I see them.
Source Code
The source code can be downloaded from:
Download Events_calendar.zip - 277.95 KB
Setting up the Project
I used a basic MVC3 project for the sample code, obviously you can skip these steps if you are happy about adding your own action and view and move straight to “Adding the Basic Datepicker” (though you may need to check you jQuery versions).
If you want to follow along, create a basic MVC3 project: select New Project , Web then MVC3 Application, choosing the Empty version. I will use the Razor syntax for my site and this article, naturally the other engines will support what we are about to do, but Razor is not only my preferred option, it also seems to now be the prevalent choice.
The jQuery and jQuery-ui libraries bundled with VS2010 are rather old, I replaced them with the latest versions at the time of writing, these can be found at http://jqueryui.com/download . On this page I selected all options for ease. The versions I will use are jquery-1.7.1.min.js and jquery-ui-1.8.17.custom.min.js, if you use a different version you may get different results. Note that the “.custom” part of the filename is because of the options I selected, it is still just the standard code. As an aside, there is a useful online tool to let you theme your jQueryUI items, it can be found at http://jQueryui.com/themeroller/ , you can download the jQuery libraries at the same time too.
Next I added a simple page to carry our calendar. To do this in MVC3:
- Add a
HomeController
class to the MVC controllers folder. This should have an Action of Index
which just returns a new View
as per the default generated methods.
- Add a Home folder into the Views folder.
- Add an Index view into directory just created.
- Replaced the jQuery js files with the newer version in the layout.
At this point I did something a little weird. I removed the jQuery script tags from the layout. This is an attempt to put as much of the relevant markup into the Index.cshtml file as possible, so you need only really refer to one file. In a real-world application the normal judgement calls need to be made about where each script tag should be put.
Adding the basic DatePicker
Now we need to add the jQuery Datepicker to the page. This is a very simple process: add a tag as a container for the calendar, make it selectable for jQuery purposes (I added a div
with an id
of datepicker) and then get jQuery to generate the datepicker when loaded in the browser:
<div id="datepicker"></div>
<script src="@Url.Content("~/Scripts/jquery-ui-1.8.17.custom.min.js")" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
$("#datepicker").datepicker();
});
</script>
The results are stylistically underwhelming, but at least we have made a start:
Next we will style the calendar. In the folder \Content\Themes\Base there is a default style called jquery-ui.css which we will use, as it is sufficient for demonstation. If you want to roll your own theme, I recommend the jQuery themeroller mentioned in the introduction, it makes producing a consistent css style much easier. Moving back to the main point, to add the default theme add the following into the head
tag of the _layout.cshtml:
<link href="@Url.Content("~/Content/themes/base/jquery-ui.css")" rel="stylesheet" type="text/css" />
Now we have:
Much Better :)
Making the Calendar Do Something
Currently the calendar does not tell us very much, just the valid dates for the selected month. We need to highlight dates that have events and give a description of the available event if the date is hovered over, all of which is actually one logical block in the relevant jQuery. Finally we need to handle the user selecting a day.
Limiting and Highlighting the selectable dates.
First, we will define a controller action called GetCalendarEvents
which will return a dictionary of day numbers against the title of the event on that day. The month and year will be passed as parameters to this action. The dictionary will be returned by the atcion as JSON, on the client this will be deserialized as an “associative” array. Normally I would make the dictionary key of type int
as we are using the day number of the month for this, but I have actually used string
. This is because a string
key can be serialized without further coding, and we can take advantage of the fact that this is indistinguishable from an int
when it reaches the client. For convenience I have put this method in the home controller, and we will follow this pattern for all event methods in this article, but you may find a dedicated events controller appropriate according to your needs. The code is as follows:
[HttpPost]
public ActionResult GetEventsForMonth(int month, int year)
{
Dictionary<string, string> events = new Dictionary<string, string>();
for (int i = 1; i < 31; i += 2)
{
events.Add((month % 2 == 0 ? i + 1 : i).ToString(), string.Format("event on day {0}/{1}/{2}", i, month, year));
}
Thread.Sleep(500); return Json(events);
}
As mentioned earlier this is stub code, it generates at set of events on even days for even-numbered months and odd days for odd-numbered months. The Thread.Sleep
is to simulate a delay on the database, the reason will become apparent later. Note that I have decorated the method with an HttpPost
attribute, this is the type of call the client script will make.
Next we need to write some jQuery to make the call to the server method. We will do the following:
- Create an empty array to take the results of the call, I have called this
calendarEvents
.
- Define AJAX request details (URL, type and content etc)
- Pass the month and year parameters in the AJAX call
- If the call is successful we set
calendarEvents
. to the results of the AJAX call If not (i.e. on error) we will hide the calendar to prevent a [misleadingly empty] calendar from being shown.
- Finally, we will refresh the calendar.
var calendarEvents = {};
function getEvents(month, year) {
$.ajax({
url: '@Url.Action("GetEventsForMonth")',
type: 'POST',
data: JSON.stringify({ month: month, year: year }),
dataType: 'json',
processdata: false,
contentType: 'application/json; charset=utf-8',
success: function (data) {
calendarEvents = data; },
error: function (xhr, ajaxOptions, thrownError) {
$("#datepicker").datepicker("hide"); },
complete: function (x, y) {
$("#datepicker").datepicker("refresh"); }
});
}
Note the line url: '@Url.Action("GetEventsForMonth")
', it uses Razor to generate the target url, we could have hardcoded this.
This code isn’t called yet, so we must do this to populate the calendar. First, we will add to the date picker generation code a callback function for the beforeShowDay
event ,to highlight the "event" days. This callback sets the selectiability status of the date, its css class and, optionally, tooltip text by returning an array containing these values. In this we have three possible situations:
- If the day is not in the
calendarEvents
array, return [false,’’]
. false
makes the date un-selectable, the empty string clears the css class.
- If the day does exist in the array, but the title for the tooltip is empty, return
[true,’isActive’]
, making the item highlighted and selectable, (but with no tooltip specified).
- If the day does exist in the array, and it has a title (the "value" in the equivalent Dictionary), a tooltip is needed in addition to the code in item 2:
[true,’isActive’, calendarEvents[day]]
. In this code the final parameter adds the text from the array to the tooltip.
We also need to add a callback function to the onChangeMonthYear
event, this will call the AJAX script we defined above if the month or year is changed, refreshing the calendar. The AJAX code forces the refresh when complete, so we do not need to do worry about that here. Finally, after the datepicker has been added, we need to make a call to our AJAX function to perform the initial population.
$(function () {
$("#datepicker").datepicker({
beforeShowDay: function (date) {
var day = date.getDate();
if (day in calendarEvents) {
if (calendarEvents[day] == undefined) {
return [true, 'isActive'];
}
return [true, 'isActive', calendarEvents[day]];
}
return [false, ''];
},
onChangeMonthYear: function (year, month, inst) {
getEvents(month, year); }
});
var calendarDate = $("#datepicker").datepicker("getDate");
getEvents(calendarDate.getMonth() + 1, calendarDate.getFullYear());
});
The only thing that looks a little odd is calendarDate.getMonth() + 1
, the +1 is needed as months are is 0-indexed in this case. Executing the project now renders:
For odd months and, for even , as we’d expect given our stub code:
I haven’t shown it in the images, hovering over the date now pops-up the tooltip with the event title as expected.
Two Small Problems
When tested in Chrome (the browser I have been using for the screenshots) this works but older versions of IE have problems. An error "IE Microsoft JScript runtime error: 'JSON' is undefined" is raised. This is a problem in IE7 and below, which do not have the relevant JSON stuff in its Javascript implementation. It also occurs with IE8 when running in IE7 quirks mode, which may happen with VS2010. If you have developer toolbar installed you can simulate this behaviour iby switching the browser mode to IE7. All you need to do to fix is to reference the JSON2 library which can be downloaded from https://github.com/douglascrockford/JSON-js/blob/master/json2.js, note that you may want to minify it for live. Once placed in the scripts folder and added to the project, add the following into the markup:
<!---->
In case you have not seen it before, the “Comments” (<!—[if lt IE8]>
etc) defines a section that will only be downloaded by versions lower than IE8. Non-IE browsers and IE8 or greater won’t download it, improving response time and server load. If you know your users are using IE8 or better, or non-IE browsers, you do not need this library.
There is also a slight problem when the month is changed. Remember the sleep I put in the controller action to simulate a database delay? While the client is waiting for the response, the month has changed, but the dates with events from the previously selected month are displayed as active until the AJAX call refreshes the datepicker. This is easy to fix, all we need do is clear the array containing the events and when the new month is rendered (raising the onBeforeShowDay
event for each day) there will be no dates to highlight. To do this we add the highlighted line to the onMonthYearChanged
callback in the datepicker definition:
onChangeMonthYear: function (year, month, inst) {
calendarEvents = {};
getEvents(month, year); },
}
Now the dates clear month to month, and are populated once the action returns to the AJAX call which is much neater.
Selecting a Date
All that remains is for the client to be able to click an active date to display the event details. This is relatively easy. We can define an MVC3 action to display an event, and an equivalent view to display it. In the sample code the view and action are both stubs, with just enough code to demonstrate that the date is sent to the server correctly and give a flavour of what might be displayed (in my case the date, an event title and an event description).
public ActionResult Events(DateTime id)
{
ViewBag.Title = string.Format("event on day {0}", id.ToShortDateString());
ViewBag.Date = id;
ViewBag.Content = "This is an event generated by a stub";
return View();
}
As the action is accessible through a simple URL it can be used more generally through the application if needed. Plugging it into the client is now very simple. We need to define a date text format for the datepicker that is more URL-friendly than MM/dd/yyyy as it looks like part of the URL "path". We could use that format by changing the action parameters but then the much sought-after URL user friendliness is lost in my opinion. I have chosen dd-MMM-yyyy as it is unambiguous between American and British date formats and produces a pleasing URL: http://whatever/Home/Events/29-Jan-2012. To finish we add a callback function to the onSelect
event of the datepicker control that opens the url for the selected date:
$("#datepicker").datepicker({
beforeShowDay: function (date) {
var day = date.getDate();
if (day in calendarEvents) {
if (calendarEvents[day] == undefined) {
return [true, 'isActive'];
}
return [true, 'isActive', calendarEvents[day]];
}
return [false, ''];
},
onChangeMonthYear: function (year, month, inst) {
calendarEvents = {};
getEvents(month, year); },
dateFormat: "dd-M-yy",
onSelect: function (dateText, inst) {
window.location = '@Url.Action("Events")/' + dateText; }
});
If the application is run and a selectable date clicked this page of great beauty is produced :)
Points of Interest
Adding a calendar to an MVC3 application isn’t as easy as some UI components, but is not hard either:
- Add a container to take the datepicker in the html markup
- Define a variable to take event dates and titles. This is initialzed as an empty array.
- Define a jQuery AJAX call on the client that gets a list of days where there are events and the event title. When returned ,populate array and refresh the calendar.
- Make a jQeury call to create the datepicker
- Use the
onBeforeShowDate
to make the dates highlighted and selectable according, array and populate the tooltip text.
- Hook in the AJAX call in step 2 to refresh the data if the month/year is changed via the
onChangeMonthYear
event. Clear out the array from step 2 first to make the transition less clumsy.
- Give the datepicker a url-friendly date format an use it to navigate to a page dedicated to showing events.
This method does not take into account multiple events on the same day, but the mechanism could be extended if needed. For the site that prompted me to write this article it happens rarely, so I modified the title in the controller to display each events on its own line om the tooltip, and the opened events page displays all events for that date.
There is relatively little MVC3 work here, much of this code could use this mechanism in other frameworks. The only limitation would be the need for support for the AJAX call and can take a use the URL formatted by the datepicker. Even this last linitation can be worked around.
History
If you edit this article, please keep a running update of any changes or improvements you've made here.
- 29th January: Draft Article
- 2nd February: Corrections to draft, see my message