clndr logo
September 10th, 2013

Calendars & Re-, Re-Inventing the Wheel

Kyle Stetz

In the mad shuffle to push front-end browser technology beyond the limits of sanity, we forgot about calendars. The calendar grid is a common design element, sometimes relegated to a "widget" in a sidebar and other times filling up the screen with excruciating detail about the current month. It's useful: the grid is a design pattern we've translated to the web from the old days of printed calendars (remember those?) and it therefore appeals to the intuition of a wide range of users.

So why are they difficult to create?

I'm not here to advocate putting more calendar grids out there. Use them when it feels good. I am, however, here to propose a solution to the mess of creating these calendars over and over again. The landscape is not looking good; there are viable plug-and-play solutions out there, sure, but they come with a lot of opinions. Opinions about style, markup, API, and data structures can be good or even great, but in this case a heap of concrete decisions detracts from our ability to control this very unique circumstance.

What's the deal?

My first project at P'unk Ave involved developing an event system for a client. I had a blast crafting the back-end code and the editing experience, but when it came time to create the calendar widget in the sidebar I got stuck. A very experienced coworker admitted that this happens: it's best to create it from scratch. No way, I thought. There has to be something out there that I can plug in. So I surfed Github, found a few jQuery plugins, tried them out. Eventually I landed on one that looked kind of like the design… Close enough that I could hack it apart.

So I hacked and I hacked. One problem led to another and I realized– far beyond the point of no return– that I should have taken the advice and built it from scratch.

The markup in the plugin I chose was written with javascript strings (clearly it was not meant to be edited). The classnames weren't terribly semantic. It made assumptions about the structure of my data, which led me to rewrite the server API for sending the event information. It functioned right out of the proverbial box, but that didn't diminish the headache of implementing the design.

I realized two things:

  1. Implementation is tied heavily to design.
  2. Calendars are not easy to build.

Yikes.

The implementation of the calendar is tied to the design because when you need element B to light up for an event instead of element A, your javascript (or whatever you're using!) is going to have to know about it. In my research I didn't come across any plugins that let you say "here's the div I want you to attach these event classes to". Along the same lines, you have to generate those date numbers in the boxes that indicate which day the 1st falls on. What element do we put those numbers in? Whichever one the plugin decided they should go in.

Calendars are difficult to build because they involve so much date-crunching. Dates are an unnatural, man-made set of numbers that don't jive well with programmatic math. This leads to a lot of boilerplate, and corners are very tempting to cut when you're working within tight deadlines. Assuming we're creating a month grid that can go forward and backward, it's going to have to be regenerated every time the month is switched. Either we use a templating language or we'll have to do a ton of DOM selecting and replacing.

I have a solution

I've thought hard about this issue. It has kept me up at night. It led me to create another plugin– one that has some loftier concepts than most calendar plugins. It's called CLNDR.js.

CLNDR is a calendar plugin that doesn't have any markup. Instead, the developer supplies an HTML template (using, by default, Underscore.js's awesome templating engine). In return for a template you get a suite of tools for building your calendar.

HTML templates are well-suited to this task because they allow us the flexibility to specify where we want the data to go in our markup. If you're unfamiliar with templates, have a look at this stack overflow discussion.

Take this example, where days is an array of objects our template is given:

days = [ { day: "1" }, { day: "2" }, ... { day: "31" } ]
<% _.each(days, function(day) { %>
    <div class="day"><%= day.day %></div>
<% }); %>

 We just looped through all of the days in the month, making a div for each one with a date in it. What if we had a little more information in our array? What if the month started on a Monday instead of a Sunday? We're trying to make a grid, so how about some classes to tell us whether a grid box is empty or not?

days = [
    { day: "", classes: "empty" },
    { day: "1", classes: "day" }
    ...
]
<% _.each(days, function(day) { %>
    <div class="<%= day.classes %>"><%= day.day %></div>
<% }); %>

 Neat, now we've accounted for empty days. CLNDR does work to supply you with awesome data inside of a template, and the rest is up to you. This is, quite literally, all there is to it. What you see above is valid CLNDR markup.

...But we need to show off all of our upcoming events. What if, when I pass in my massive AJAXed array of upcoming events, they magically appear in the days array?

days = [
    { day: "", classes: "empty", events: [] },
    { day: "1", classes: "day event", events: [ { title: "baseball game", url: "/events/baseball-game" } ] }
    ...
]
<% _.each(days, function(day) { %>
    <div class="<%= day.classes %>">
        <div class="date"><%= day.day %></div>
        <% _.each(day.events, function(event) { %>
            <a href="<%= event.url %>"><%= event.title %></a>
        <% }); %>
    </div>
<% }); %>

 Now our events show up in the calendar view! As a bonus, we've left a nice artifact for other developers, should they do some work in our project. They don't have to memorize what plugin you're using or dig through JS files because your template is a self-documenting piece of code.

Everything CLNDR gives you is semantic; the classes passed to the template are empty, day, and today, and the variables are day, month, year, events, etc.

The set of options you can pass CLNDR covers almost every situation you are going to run into, from "I need to bind custom events every time the calendar renders" to "I need to use a different templating engine" to "My week starts on Wednesday". If it doesn't, let me know and we'll make it work.

It's made possible using Moment.js, an amazing JS library that makes you want to work with dates again.

Into the Future

I'm excited to push CLNDR out into the world. It's already being battle-tested in three of our client projects and it's reaching a point of clarity and maturity that should allow other developers to contribute code. My hope is that we can push the design and interaction of calendars on the web that much further if we don't have to worry about the guts so much.

To read more thorough documentation and get your hands on the code, check out CLNDR's Github. To see it in action, have a look at CLNDR's Github Project Page.