Build a custom calendar module in Drupal 8

 

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.

 

Comments

Hello

This is very interesting, but for people starting with D8, could you provide the source ? It's hard to understand what goes in which file....

Yes, I understand.

Currently you can only get the source from the sandbox project.

All the sources presented here are implemented in the module called "ek_projects".

From there you can get a better understanding of the module structure and files.

We will try in the meantime to provide a more complete codes here.

Thank you.

Thanks for your fast answer.

 

I've downloaded again the sandbox project. Done a search in all files, there is not any reference to fullcalendar (I desperatly search samples that implement it), it seems to use the standard calendar. Perhaps is there another sandbox ?

Thanks anyway !

There is nothing in the source about this calendar, could you at least show us the source of this module so we can see where the files go ? :)

Sorry, being short of time, we haven't provided a fully working source files.

If you want to replicate, you can see in the structure below where all the files are placed in the module structure. All scripts in the structure can be derived from the post above, the "fullcalendar" library being a contributed javascript library that we used.

MyModule

Add new comment

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.