CLNDR.js ~ A jQuery Calendar Plugin

Here's The Deal

CLNDR is a jQuery plugin for creating calendars. Unlike most calendar plugins, this one doesn't generate markup. Instead you provide an Underscore.js HTML template and in return CLNDR gives you a wealth of data to use within it.

CLNDR is free of opinions about markup and style: it is the high-speed chassis on which you build the vehicle of your dreams.

For a little backstory and a soft introduction to the concepts in CLNDR, check out this article: Calendars & Re- Re-inventing the Wheel.

Here's How You Use It

Just call clndr() on the container.


// this will use clndr's default template, which you probably don't want.
$('#calendar').clndr();

// so instead, pass in your template as a string!
$('#calendar').clndr({
  template: $('#calendar-template').html()
});

// there are a lot of options. the rabbit hole is deep.
$('#calendar').clndr({
  template: $('#calendar-template').html(),
  events: [
    { date: '2013-09-09', title: 'CLNDR GitHub Page Finished', url: 'http://github.com/kylestetz/CLNDR' }
  ],
  clickEvents: {
    click: function(target) {
      console.log(target);
    },
    onMonthChange: function(month) {
      console.log('you just went to ' + month.format('MMMM, YYYY'));
    }
  },
  doneRendering: function() {
    console.log('this would be a fine place to attach custom event handlers.');
  }
});
        

How does it work?

CLNDR takes your template and injects some data into it. The data contains everything you need to create a calendar. Here's what you get:


daysOfTheWeek: ['S', 'M', 'T', 'W', 'T', 'F', 'S']
numberOfRows: 5
days: [
  {
    day: '1',
    classes: 'day today event',
    id: 'calendar-day-2013-09-01',
    events: [ ],
    date: moment('2013-09-01')
  },
  ...
]
month: 'September'
year: '2013'
eventsThisMonth: [ ],
extras: { }
        

Most notable is the days array, which contains an entire grid. We can loop through it in our template to make all of the markup:


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

Automatic Mouse Events

CLNDR looks for next and previous buttons in your template. These buttons trigger the calendar to be redrawn with a new month. By default the classes are clndr-next-button and clndr-previous-button, however we can specify our own by passing in some options.


<div class="clndr-controls">
  <div class="clndr-previous-button">‹</div>
  <div class="month"><%= month %></div>
  <div class="clndr-next-button">›</div>
</div>
<% _.each(days, function(day) { %>
    <div class="<%= day.classes %>"><%= day.day %></div>
<% }); %>
        

Event Sorting

CLNDR takes an array of event objects and weaves them into the days array in your template. This means you can pass in as many events as you want and CLNDR will make sure they show up when you need them. In addition to exposing all of the data in your event object, the class event will be added to the days.classes string for easy styling.

If you're looking to get all the events for the month in one array, you've got eventsThisMonth as well. Here's the markup for the first calendar on this page:


<div class="clndr-grid">
  <div class="days-of-the-week clearfix">
    <% _.each(daysOfTheWeek, function(day) { %>
      <div class="header-day"><%= day %></div>
    <% }); %>
  </div>
  <div class="days clearfix">
    <% _.each(days, function(day) { %>
      <div class="<%= day.classes %>" id="<%= day.id %>">
        <span class="day-number"><%= day.day %></span>
      </div>
    <% }); %>
  </div>
</div>
<div class="event-listing">
  <div class="event-listing-title">EVENTS THIS MONTH</div>
  <% _.each(eventsThisMonth, function(event) { %>
      <div class="event-item">
        <div class="event-item-name"><%= event.name %></div>
        <div class="event-item-location"><%= event.location %></div>
      </div>
    <% }); %>
</div>
        

Tell me about the Options.

All of the available options:


$('.parent-element').clndr({
  // the template: this could be stored in markup as a <script type="text/template"&rt;</script&rt;
  // or pulled in as a string
  template: clndrTemplate,
  // start the week off on Sunday (0), Monday (1), etc. Sunday is the default.
  weekOffset: 0,
  // determines which month to start with using either a date string or a moment object.
  startWithMonth: "YYYY-MM-DD" or moment(),
  // an array of day abbreviations. If you have moment.js set to a different language,
  // it will guess these for you! If for some reason that doesn't work, use this...
  // the array MUST start with Sunday (use in conjunction with weekOffset to change the starting day to Monday)
  daysOfTheWeek: ['D', 'L', 'M', 'M', 'J', 'V', 'S'],
  // callbacks!
  clickEvents: {
    // fired whenever a calendar box is clicked.
    // returns a 'target' object containing the DOM element, any events, and the date as a moment.js object.
    click: function(target){ },
    // fired when a user goes forward a month. returns a moment.js object set to the correct month.
    nextMonth: function(month){ },
    // fired when a user goes back a month. returns a moment.js object set to the correct month.
    previousMonth: function(month){ },
    // fired when a user goes back OR forward a month. returns a moment.js object set to the correct month.
    onMonthChange: function(month){ }
    // fired when a user goes to the current month/year. returns a moment.js object set to the correct month.
    today: function(month){ },
  },
  // the target classnames that CLNDR will look for to bind events. these are the defaults.
  targets: {
    nextButton: 'clndr-next-button',
    previousButton: 'clndr-previous-button',
    todayButton: 'clndr-today-button',
    day: 'day',
    empty: 'empty'
  },
  // an array of event objects
  events: [],
  // if you're supplying an events array, dateParameter points to the field in your event object containing a date string. It's set to 'date' by default.
  dateParameter: 'date',
  // show the numbers of days in months adjacent to the current month (and populate them with their events). defaults to true.
  showAdjacentMonths: true,
  // when days from adjacent months are clicked, switch the current month.
  // fires nextMonth/previousMonth/onMonthChange click callbacks. defaults to false.
  adjacentDaysChangeMonth: false,
  // a callback when the calendar is done rendering. This is a good place to bind custom event handlers.
  doneRendering: function(){ },
  // anything you want access to in your template
  extras: { }
  // if you want to use a different templating language, here's your ticket.
  // Precompile your template (before you call clndr), pass the data from the render function
  // into your template, and return the result. The result must be a string containing valid markup.
  // More under 'Template Rendering Engine' below.
  render: function(data){
    return '<div class="html data as a string"></div>';
  }
});
        

I want the instance so I can do stuff with it.

You can save your clndr instance to a variable and mess with it later:


// Create a Clndr and save the instance as theCalendarInstance
var theCalendarInstance = $('#myCalendar').clndr();

// Go to the next month
theCalendarInstance.forward();

// Go to the previous month
theCalendarInstance.back();

// Set the month using a number from 0-11 or a month name
theCalendarInstance.setMonth(0);
theCalendarInstance.setMonth('February');

// Go to the next year
theCalendarInstance.nextYear();

// Go to the previous year
theCalendarInstance.previousYear();

// Set the year
theCalendarInstance.setYear(1997);

// Change the events. Note that this triggers a re-render of the calendar.
theCalendarInstance.setEvents(newEventsArray);

// Add events. Note that this triggers a re-render of the calendar.
theCalendarInstance.addEvents(additionalEventsArray);
        

Want to play with an instance of CLNDR right now? Open the developer console and poke at the variable clndr, which controls the first calendar on this page.

Many Thanks

CLNDR is made possible because of the work of many awesome projects: jQuery, Underscore.js, and Moment.js. CLNDR is developed by Kyle Stetz and is used and supported by the team at P'unk Avenue.