Third & GroveThird & Grove
Sep 8, 2015 - Wes Jones

Drupal Calendar Solution with FullCalendar API module

 

Dynamic calendars are a particularly challenging web application feature to implement. What is desired is the robust functionality of front-end Javascript, driven by the database backend of event data.

For a recent project, we researched all the Javascript calendar libraries out there, and we finally settled on FullCalendar.js for our Drupal calendar solution. It's beautiful, well written, extensible, and has an active community. We've created the FullCalendar API module and contributed it back to the community.

Why a new module?

Wait, isn't there already a FullCalendar module? Yes, that's true, but development has stalled for nearly a year and it's not compatible with the latest versions of jQuery and FullCalendar.js. Furthermore, our approach is very different. The FullCalendar module looks to deeply integrate the FullCalendar library into Drupal's plugin system. We wanted a drop-in API where we can initialize FullCalendar very simply. All the API calls are theme functions, and a simple "preprocess" Javascript event allows you to hook in and customize FullCalendar to your heart's content.

The following is a brief explanation of how to use the module. Included in the module is fullcalendar_api_example, which contains most of these examples. Enable that module on your local site to see some of these calls in action.

theme_fullcalendar_calendar()

Without the module, here's how you'd embed the calendar directly in a Javascript file. Using FullCalendar is as simple as initializing and passing in some options:

 

$('#calendar').fullCalendar({
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek,basicDay'
},
defaultDate: '2015-02-12',
editable: true,
eventLimit: true, // allow "more" link when too many events
events: [
{
title: 'All Day Event',
start: '2015-02-01'
},
{
title: 'Long Event',
start: '2015-02-07',
end: '2015-02-10'
},
]
});

 

In FullCalendar API module, its most basic function, theme_calendar_calendar(), can be used to do exactly the same thing. An array of options is passed and is mapped to:

 

// Array of FullCalendar settings.
$settings = array(
'header' => array(
'left' => 'prev,next today',
'center' => 'title',
'right' => 'month,agendaWeek,agendaDay',
),
'defaultDate' => '2015-02-12',
'editable' => TRUE,
'eventLimit' => TRUE, // allow "more" link when too many events
'events' => array(
array(
'title' => 'All Day Event',
'start' => '2015-02-01',
),
array(
'title' => 'Long Event',
'start' => '2015-02-07',
'end' => '2015-02-10',
),
)
);
return theme('fullcalendar_calendar', array(
'calendar_id' => 'fullcalendar',
'calendar_settings' => $settings,
));

 

theme_fullcalendar_calendar_entity()

Entities can be queried and retrieved, as long as there is a Date field on the entity. Use date_field_map to set the date field machine name.

 

$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'node')
->entityCondition('bundle', 'article');
$result = $query->execute();
if (isset($result['node'])) {
$entity_ids = array_keys($result['node']);
$entities = entity_load('node', $entity_ids);
// Array of custom calendar settings. These override the defaults set
// in the theme function.
$custom_settings = array(
'header' => array(
'left' => 'month,agendaWeek',
'right' => 'prev title next',
),
'weekends' => TRUE,
'firstDay' => 1,
'defaultDate' => date('Y-m-d'),
);
return theme('fullcalendar_calendar_entity', array(
'entities' => $entities,
'date_field_map' => array('article' => 'field_date'),
'calendar_id' => 'fullcalendar-entity',
'calendar_settings' => $custom_settings,
));
}

 

Using a Datasource

The above method works for a quick, small calendar, but what if you have lots of events or even a calendar spanning multiple years? Then it's better performance-wise to use a datasource URL which returns a JSON array of events.

This can be done using theme_fullcalendar_calendar_datasource().

First define your ajax callback using hook menu, making sure that the delivery callback is set to ajax_deliver. For the example, let's say we have a path fullcalendar/json-data.

Then, use the theme function:

 

// Array of FullCalendar settings.
$settings = array(
'header' => array(
'left' => 'prev,next today',
'center' => 'title',
'right' => 'month,agendaWeek,agendaDay',
),
'defaultDate' => '2015-02-12',
'editable' => TRUE,
);
// @see fullcalendar_api_example.ajax.inc.
$datasource_uri = 'fullcalendar/json-data';
return theme('fullcalendar_calendar_datasource', array(
'calendar_id' => 'fullcalendar',
'calendar_settings' => $settings,
'datasource_uri' => $datasource_uri,
));

 

Overriding FullCalendar Methods

The extensibility of FullCalendar lies in its methods. For example, if you want to modify the HTML output of an event, you can implement the method eventAfterRender and add your custom HTML.

The above theme functions will only work on settings that are properties. So, methods have to be overridden in a Javascript file. We've included a custom event that can be hooked into to modify the FullCalendar object just before being rendered:

fullCalendarApiCalendar.preprocess

Include your js file and make sure it loads after fullCalendar. (Consider loading FullCalendar in the header region and your custom script in footer region). Then call the preprocess event and work your magic.

Here's an example that demonstrates how to implement the eventRender method and add extra properties to the link:

 

/**
* @file
* Extend FullCalendar methods.
*/
(function($) {
Drupal.behaviors.EPCalendarFullCalendar = {
attach: function(context, settings) {
$(document).bind('fullCalendarApiCalendar.preprocess', function(event, calendar, calendarSettings) {
calendarId = $(calendar).attr('id');
if (!calendarSettings.defaultView) {
calendarSettings.defaultView = 'month';
}
switch (calendarSettings.defaultView) {
case 'month':
// Month view.
$.extend(calendarSettings, {
eventRender: function(event, element, view) {
// Create a data attribute so we can target all html fragments
// of an event.
var dataTag = event.entityType + '-' + event.bundle + '-' + event.id;
$(element).attr('data-entity', dataTag);
} // end eventRender
}); // end fullcalendar
break;
}
}); // end preprocess
}
}
})(jQuery);

 

 

Once this structure is in place, the sky's the limit!

We hope our module proves useful for your Drupal calendar integration. If you see any bugs, file an issue on the Drupal.org project page and let us know!