Adding a dynamic calendar to HarpJS

Another day, another HarpJS recipe. Can you tell what I’m excited about lately? For today’s demo, I’ve built a simple dynamic calendar for HarpJS. There are probably many different ways to handle this (you could simply embed a Google Calendar as I describe here), but here is how I solved it.

I decided that I’d use the FullCalendar jQuery plugin for the display of the calendar. I’ve used it before with a ColdFusion demo and I like how easy it is use. I also knew it supported loading events from an Ajax source. HarpJS lets you build more than just dynamic HTML pages. You can build dynamic XML and JSON files as well. This recipe describes how you can generate XML for a RSS feed. A similar method could be used to generate JSON as well.

I began by creating an events folder. Inside of this folder I created a few files to represent my events. I’m not doing anything fancy with that so I won’t bother sharing the code. I then created a _data.json file. This is what will drive the JSON feed.

{
	"a":{
		"title":"Event A",
		"date":"2/14/2014",
		"allDay":true
	},
	"b":{
		"title":"Event B",
		"date":"2/13/2014 2:00 PM"
	},
	"events":{
		"layout":false
	}
}

The first two items represent my events. Title and date should make sense. I’ll explain allDay in a second. The final item represents the file that will generate my JSON feed. By default Harp wraps your pages in layout templates. But for a JSON feed that layout would break the code that parses it. By adding layout:false I’m telling Harp to not wrap that file with the regular site layout. (This causes another small problem and I’ll discuss that more at the end.)

Next – I created my front end. This is taken straight from the FullCalendar sample files so it isn’t too exciting, but here it is:

<link href='/fullcalendar/fullcalendar.css' rel='stylesheet' />
<link href='/fullcalendar/fullcalendar.print.css' rel='stylesheet' media='print' />
<script src='/lib/moment.min.js'></script>
<script src='/lib/jquery.min.js'></script>
<script src='/lib/jquery-ui.custom.min.js'></script>
<script src='/fullcalendar/fullcalendar.min.js'></script>
<script>

	$(document).ready(function() {
	
		$('#calendar').fullCalendar({
			header: {
				left: 'prev,next today',
				center: 'title',
				right: 'month,agendaWeek,agendaDay'
			},
			editable: true,
			timezone:"America/Chicago",
			events: {
				url: '/events/events.json',
				error: function() {
					$('#script-warning').show();
				}
			},
			loading: function(bool) {
				$('#loading').toggle(bool);
			}
		});
		
	});

</script>


<div id='script-warning'>
	crap broke.
</div>

<div id='loading'>loading...</div>

<div id='calendar'></div>

The only thing here really interesting is the url option in full calendar. Note that I’m pointing to /events/events.json. My real file is events/events.json.ejs. Harp will serve it up without the EJS extension and even automatically use the right content type based on the JSON extension. Cool! Now let’s look at the code.

[
	<% 
	var events = Object.keys(public.events._data);
	for(var i=0; i<events.length; i++) { 
		if(events[i] !== "events") {
			event = public.events._data[events[i]];
	%>
		{
			"title":"<%- event.title %>",
			"start":"<%- event.date %>",
			"url":"/events/<%- events[i] %>.html"
			<% if(event.allDay) { %>
			,"allDay":<%- event.allDay %>
			<% } %>
		}
			<% if(i+2 < events.length) { %>,<% } %>
	<% 
		}
	} 
	%>
]

Ok, not the prettiest code, but let’s break it down. I begin by getting the keys from my data and looping over it. I skip the events key – remember, it is there only so that I can disable layout. For each event I output the title and date. The start label is used there because that’s what FullCalendar wants. Speaking of – FullCalendar also requires that you tell it when an event is over the entire day. If you don’t, it makes a guess as to a time. That’s why I included allDay in my JSON data and you can see me using it here. Finally, I use a bit of logic to determine if I’m at the end of my loop. If I’m not, I output a comma.

So yeah – this is ugly – and it was pretty easy for me to break it. The output was pretty – but the code was gross. I then figured out that it was silly for me to create JSON when the server could do it instead. Here is version two:

<%
var eventData = [];
var events = Object.keys(public.events._data);
for(var i=0; i<events.length;i++) {
	if(events[i] !== "events") {
		var eventOb = {};
		event = public.events._data[events[i]];
		eventOb.title = event.title;
		eventOb.start = event.date;
		eventOb.url = "/events/" + events[i] + ".html";
		if(event.allDay) {
			eventOb.allDay = event.allDay;	
		}
		eventData.push(eventOb);
	}
}

%>

<%- JSON.stringify(eventData) %>

A heck of a lot cleaner, right? And then I end with a simple call to JSON.stringify. Much simpler. And that’s it! Here is a screen shot of the calendar displaying the events powered by HarpJS and the dynamic JSON feed.

I’ve attached a copy of the source code to this, and a compiled version of it, as an attachment to this blog entry. One small little note. I really didn’t like that I had to include the layout disable thing inside of my _data.json. I tried moving my events.json.ejs to the root of my project but found that I couldn’t disable the layout there. I filed a bug report for the issue and once it is fixed (or maybe I did something wrong) I’ll probably move that script to root and leave my data a bit more “pure”. I’m being picky, but you get the idea. An alternative would be to use a substructure in the data file, but then I’d lose the “you are on this page I’ll automatically copy these values” feature of Harp. I could live with that as well.

Download attached file.