Revisiting the JavaScript Calendar Control – Part 3
November 25, 2009 12:00 am Comments (0)It’s been some time since I started writing this three part series, and it certainly took a while longer to get done than I expected. A busy work schedule does tend to do that, but if it makes anyone feel better, having left the third part hanging did give my conscience a field trip. So let’s do a quick recap and get on with it
- In part 1, we created a jQuery plugin which displays a simple static calendar.
- In part 2, we added the facility to pick a date, and to switch the month on display.
- In part 3 (this post), we will add some sanity checks, and the ability to display events on given days.
Code and examples
This post contains three examples, which are linked from the relevant sections. If you’re after the script itself, it can be found here. There is also a list of options that can be used when constructing the calendar.
Restricting the selectable dates
In some cases, we want our users to select dates which meet certain conditions. For example, if we’re taking appointments, we may only want people to pick weekdays in the future. If we’re providing an archive of some sort, we only want people to pick dates in the past.
Limiting selections to weekdays
The weekend restriction (or any other restriction based on the day of the week) can be implemented using just one selector. We’re already marking our days with the day of the week, so all we need to do is select all days marked dw0 or dw6, and add the “disabled” class to them:
257: jQuery(“td.dw0, td.dw6″).addClass(“disabled”);
When we’re assigning event handlers, we tell jQuery to skip all cells marked with this class:
269: jQuery(“td:not(.disabled)”, table).click(selectCell);
Restricting selections to a given range
The date range restriction also uses the “disabled” class to tell jQuery to skip dates which are out of range. To determine which dates are out of range, we select the cell matching the boundary of the restriction (for example, if we set the minimum date to 1st November, 2009, we look for a cell with the classes “d1”, “m10” and “y2009”:
189: jQuery(“td.d” + date.getUTCDate(), table)
190: .filter(“.m” + date.getUTCMonth())
191: .filter(“.y” + date.getUTCFullYear())
192: .addClass(type);
As before, we filter using the least common class first.
When we have marked the edge or edges of the range, we then use jQuery’s sibling selection functions to find and mark all cells which are out of range. In the case of the minimum date restriction:
228: startCell
229: .prevAll()
230: .add(startCell.parent().prevAll().children(“td”))
231: .addClass(“disabled”);
The prevAll() function will give us all cells preceding the start cell in its row (i.e. any preceding days in the same week). After that, we’re adding any cells in the preceding rows, which covers all days before the start date. As the cells will be marked disabled, they will not be assigned any on click behaviour.
The end date uses the same technique, but uses the nextAll() function instead to pick days after the end date.
The above only works if the end and start dates are currently on display. If they are not, we simply check whether the first and last days displayed fall into range, and disable the entire calendar if they are not.
This example shows how the calendar works when date ranges are configured. And now, on to the meatier subject of event display.
Displaying events in the calendar
Since we’re allowing people to click around the months freely, It doesn’t make sense to try and pre-load all the event information – pre-loading 10 years worth of information when your user only needs a month is never a good idea. Instead, we can use AJAX, neatly wrapped up in jQuery’s ajax() function, to request the information for a given month when we need it – that is, any time the display month changes.
145: var date = table.data(“date.active”);
146:
147: jQuery.ajax({
148: dataType: “json”,
149: type: table.data(“eventFetchMode”),
150: url: table.data(“eventSource”),
151: data: {
152: month:date.getUTCMonth(),
153: year:date.getUTCFullYear(),
154: calendar: table.prev().attr(“id”)
155: },
156: cache: false,
157: success: displayCalendarEvents
158: });
Quite a lot going on there. We’re telling jQuery to request a JSON object (dataType) from a given url. We’re also telling it to pass certain parameters – month, year, and an id (more on this later – I’m not happy with this yet). Depending on the type value, it will generate a Get or a Post request. When the response is received, it will call the displayCalendarEvents function to process the response.
The actual request happens asynchronously. In other words, when the request is fired, the script doesn’t hang around waiting for an answer. It will just go on its merry way, and execute the callback when it gets an answer.
The callback itself just attaches the event data to the appropriate cells, and assigns an additional click handler to them:
167: for (var index in data.events) {
168: var event = data.events[index];
169: var eventCell = jQuery(“.d” + event.date, table)
170: .filter(“.m” + event.month)
171: .filter(“.y” + event.year);
172:
173: eventCell.addClass(“event”);
174: eventCell.data(“event.data”, event);
175: eventCell
176: .unbind(“click”)
177: .click(table.data(“eventSelectionHandler”))
178: .click(selectCell);
179: }
When adding the event handler, we’re unbinding the click event before we add our handler and restore the normal click handler. This tells our script to run the event selection handler before the normal handler.
Using the default handler for events, our calendar will now pop an alert box when an event cell is clicked.
This example shows how the calendar works with events. Rather than serving json from a dynamic page, as would normally be the case, we’re just serving it a this javascript file, which defines a single event.
Doing more with events
As it is, the calendar doesn’t actually care how the event content returned by the server is structured – you can define the actual event content any way you like. We could, for example, return the content as an object in its own right:
“events”: [
{ "date": 1, "month": 0, "year": 2010, "content": {
"summary" : "Breakfast at Tiffany's",
"location" : "Tiffany's",
"time" : new Date(2010, 0, 1) }}]
In fact, we could take it further and make each content an array of events, allowing us to assign more than one event per day.
To make use of any such event, we’ll need to provide the calendar with a way to understand it, and we can do this by providing an eventSelectionHandler function:
function () {
var event = $(this).data(“event.data”).content;
$(“.vevent .summary”).text(event.summary);
$(“.vevent .location”).text(event.location);
$(“.vevent .dtstart”).text(“”+event.time);
$(“.vevent”).slideDown();
}
This extracts the event content from the cell, and populates some elements with it. The vevent classes are in fact a microformat standard for events, so any microformat-aware browser or device can now read your event. Here’s a screenshot of how the Operator plugin for Firefox handles the event in this example:
Where to go from here
The actual plugin still has some issues which I’m not pleased with, especially in the way that the calendar identifies itself after an ajax call. One alternative I was considering was to use generate some sort of random id at the time the calendar is created.
And that’s all for now
So that’s it for now. The plugin here is actually quite redundant, but building stuff is always a good learning experience, and going back over one’s old work is certainly a humbling one. I hope you found these posts useful or at least interesting; if you have any opinions or advice you’d like to share, I’m all ears.
RSS feed for comments on this post. TrackBack URL
