Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough

Build a custom calendar module in Drupal 8

Parent Feed: 

In this article we will describe how we created the calendar in our back-office management application EK (see demo).

Calendar

For this function, we used the FullCalendar plugin and its dependencies.

1) Create the Drupal 8 library

In a file called MyModule.libraries.yml, insert the following css and js configurations:

MyModule.calendar:
  version: VERSION
  css:
    theme:
      css/ek_calendar.css: {}
      js/cal/fullcalendar/fullcalendar.css: {}
      js/jquery.qtip/jquery.qtip.css: {}
  js:
    js/cal/fullcalendar/lib/moment.min.js: {}
    js/cal/fullcalendar/fullcalendar.js: {}
    js/jquery.qtip/jquery.qtip.min.js: {}
    js/cal/calendar_script.js: {}
  dependencies:
    - core/jquery
    - core/drupal
    - core/drupalSettings
    - core/drupal.ajax
    - core/drupal.dialog
    - core/jquery.ui.datepicker

Note: we also used jQuery qtip to display title pop-up in the calendar, but this not an obligation.

The file calendar_script.js is for custom javascript scripts needed in this implementation.

2) create the routes

In MyModule.routing.yml we will need 2 routes. The first one is the main route to the calendar display function in our countroller; the second one is to pull data displayed in the calendar from an ajax call.

MyModule_calendar:
  path: '/path/calendar'
  defaults:
    _title: 'Calendar'
    _controller: '\Drupal\MyModule\Controller\CalendarController::calendar'
  requirements:
    _permission: 'calendar'

MyModule_calendar_view:
  path: '/MyModule/calendar/view/{id}'
  defaults:
    _controller: '\Drupal\ek_projects\Controller\CalendarController::view'
  requirements:
    _permission: 'calendar'

The id in MyModule_calendar_view route is a key used to indentify the type of data to be retrieved. In our case we display different events from dates of projects status and tasks thus we filter base on event type in our controller (submission, start, deadline, etc...). But this can be adapted to your own case.

3) The form to filter content display

This form is very simple and used to filter the events and trigger the calendar display.

filter

Here is the function buildForm into the SelectCalendar Class

public function buildForm(array $form, FormStateInterface $form_state) {

    $options = [ 0 => t('Calendar'), 1 => t('My tasks') , 2 => t('Projects submission'),    3 => t('Projects validation'), 4 => t('Projects start')],

    $form['select'] = array(

      '#type' => 'select',

      '#id' => 'filtercalendar',

      '#options' => $options,

      '#attributes' => array('title' => t('display options'), 'class' => array()),

      );

        return $form;  

  }

validateForm and submitForm are not used as the form is actually never submitted;

4) Create the controller

In our controller CalendarController.php we have 2 functions that match the above routes: calendar() and view() plus a third function to display the calendar in a dialog box: dialog();

calendar() function is very basic as it only call the dialog box.

  /**
   * AJAX callback handler for Ajax Calendar Dialog
   */
  public function calendar() {
    return $this->dialog(TRUE);
  }

So when using route /MyModule/calendar, the actual response happens in the dialog function:

/**
   * Render dialog in ajax callback.
   *
   * @param bool $is_modal
   *   (optional) TRUE if modal, FALSE if plain dialog. Defaults to FALSE.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse
   *   An ajax response object.
   */
  protected function dialog($is_modal = FALSE) {
 
    $content = $this->formBuilder->getForm('Drupal\MyModule\Form\SelectCalendar');
    $content['content']['#markup'] = "<div id='calendar'></div>";
                        
    $response = new AjaxResponse();
    $title = t('Calendar');
    $l =  \Drupal::currentUser()->getPreferredLangcode();
    $content['#attached']['drupalSettings'] = array('calendarLang' => $l );
    $content['#attached']['library'] = array('core/drupal.dialog.ajax', 'ek_projects/ek_projects.calendar');
    $options = array('width' => '80%');
    
    if ($is_modal) {
      $dialog = new OpenModalDialogCommand($title, $content, $options);
      $response->addCommand($dialog);
    }
    else {
      $selector = '#ajax-text-dialog-wrapper-1';
      $response->addCommand(new OpenDialogCommand($selector, $title, $html));
    }
    return $response;
  }

Note: first the form to filter data is included with $this->formBuilder->getForm('Drupal\MyModule\Form\SelectCalendar');. Then we add a simple div markup in the content that will hold the calendar display generated by the plugin: $content['content']['#markup'] = "<div id='calendar'></div>"; Finally, the Ajax response is built with necessary parameters. The proper core ajax and dialog references in the controller are needed for the dialog to work as expected:

use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Ajax\OpenDialogCommand;


The view() function is where the customisation part is the most relevant. This function collect data to display in a database and send them back in appropriate format. Thus we will only show the basic structure as it may apply to multiple data sources or formats.

  /**
   * AJAX callback handler for task and event display in calendar
   */
  public function view($id) {
      $color_array = array('#CEE3F6','#CEEFF6','#CEF6F0','#CEE3F6','#CEF6D4');

// this array will hold the data to be send back for dispay
      $events=array();
 
      switch($id) {
          
          case 1 :
     
          //1 My tasks
          // query data here and format it
          // sample event value format required by fullCalendar:
          //        
          //      $values = array(
          //          'id' => 'entry id',
          //          'title' => 'string',
          //          'description' => 'string',
          //          'start' => 'date',
          //          'end' => 'date',
          //          'url' => "MyModule/path",
          //          'allDay' => 'True/False',
          //          'className' => "",
          //          'color' => $color_array[$n],  
          //          'textColor' => 'black',
          //      );
          //      array_push($events, $values);
            
              break;     
            case 2 :
              // query data here
              break;
            case 3 :
              // query data here      
              break;
            
            //etc.
        
    }
      return new JsonResponse($events);        
   }

5) Javascript

Here is the code in js/cal/calendar_script.js:

(function ($, Drupal, drupalSettings) {

  Drupal.behaviors.MyModule_calendar = {
    attach: function (context, settings) {
      
        jQuery( "#filtercalendar" )
          .bind( "change", function( event ) {
              jQuery('#loading').show();
              var h = screen.height;
              var w = screen.width;
              var dh = h*0.8;
              var dw = dh;
              var top = (h-dh)/3;
              var left = (w-dw)/2;
              jQuery('.ui-dialog').css({top: top});
              var option = jQuery(this).val();
              display_calendar(option,settings.calendarLang);
          });

    }
  };
 
    
  function display_calendar(e,calendarLang) {
        jQuery('#calendar').fullCalendar( 'destroy' );
        jQuery('#calendar').fullCalendar({
            header: {
                left: 'prev,next today',
                center: 'title',
                right: 'month,agendaWeek,agendaDay'
            },
            eventMouseover:true,
            lang: calendarLang,
            events: {
                url: drupalSettings.path.baseUrl + "MyModule/calendar/view/" + e,
                error: function() {
                    jQuery('#calendar-warning').show();
                }
            },
                        aspectRatio:  1.8,
                        timeFormat: 'H(:mm)',
                        agenda: 'h:mm{ - h:mm}',
                        loading: function(bool) {
                            jQuery('#loading').toggle(bool);
                        },
                        eventRender: function(event, element) {
                                element.qtip({
                                    content: event.description,
                                    target: 'mouse',
                                    adjust: { x: 5, y: 5 }
                                });
                            }
            });
  }  

})(jQuery, Drupal, drupalSettings);

What happen here is that the function display_calendar() which actually trigger the fullCalendar plugin action is bind to the form that filters the data and identified by its id '#filtercalendar'. This function simply call  fullCalendar with necessary options (including the events that are pulled from view()) and display it into the html div markup identified by its id  '#calendar'.

Feel free to comment or suggest other ways of creating a calendar.

Author: 
Original Post: 

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web