Skip to content
Snippets Groups Projects
Commit 35220bfb authored by Mingsong Hu's avatar Mingsong Hu
Browse files

Initial commit.

parents
No related branches found
No related tags found
No related merge requests found
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Installation
* Configuration
* Features
INTRODUCTION
------------
This module provide a block to display a calendar powered by FullCalendar 5.
It created a block called 'FullCalendar block' which accepts event source as a json (URL) feed.
* For a full description of the module visit:
https://www.drupal.org/project/fullcalendar_block
* To submit bug reports and feature suggestions, or to track changes visit:
https://www.drupal.org/project/issues/fullcalendar_block
REQUIREMENTS
------------
This module requires Drupal FullCalendar library from https://github.com/drupal-spider/drupal-fullcalendar .
INSTALLATION
------------
* Install via Composer.
The required library will be downloaded automatically. You can install this module as a normal installation process of Composer.
* Install via zip file.
The required library is not included in the zip file. You need to downoad it from https://github.com/drupal-spider/drupal-fullcalendar/archive/refs/tags/5.10.2.zip and decompress all files to /libraries/drupal-fullcalendar/ under your Drupal root folder.
FEATURES:
-------------
* FullCalendar 5.
* Fully configurable via the block setting.
* Support simple recurring events out of box.
\ No newline at end of file
{
"name": "drupal/fullcalendar_block",
"type": "drupal-module",
"description": "Provide a block for calendar powered by FullCalendar JS.",
"keywords": ["Drupal"],
"license": "MIT",
"homepage": "http://drupal.org/project/fullcalendar_block",
"minimum-stability": "dev",
"authors": [
{
"name": "Mingsong Hu",
"homepage": "https://www.drupal.org/u/mingsong",
"role": "Maintainer"
}
],
"support": {
"issues": "http://drupal.org/project/issues/fullcalendar_block",
"source": "http://cgit.drupalcode.org/fullcalendar_block"
},
"required": {
"drupal-ckeditor-libraries-group/fakeobjects": "^4.5.11"
}
}
name: FullCalendar Block
type: module
description: 'Provide the ability to present contents in a calendar block rendered by FullCalendar JS.'
package: Block
core_version_requirement: ^9 || ^10
dependencies:
- drupal:block
- drupal:datetime
fullcalendar:
remote: https://fullcalendar.io/
version: '5.10.2'
license:
name: MIT
url: https://github.com/fullcalendar/fullcalendar/blob/master/LICENSE.txt
gpl-compatible: true
css:
theme:
/libraries/drupal-fullcalendar/main.min.css: { minified: true }
js:
/libraries/drupal-fullcalendar/main.min.js: { minified: true }
js/fullcalendar_block.js: {}
dependencies:
- core/drupal
- core/jquery
- core/once
- core/drupalSettings
- core/drupal.dialog.ajax
<?php
/**
* Implements hook_theme().
*/
function fullcalendar_block_theme($existing, $type, $theme, $path): array {
return [
'fullcalendar_block' => [
'variables' => [
'block_index' => NULL,
],
],
];
}
\ No newline at end of file
/**
* @file
* Entity Reference Tree JavaScript file.
*/
// Codes run both on normal page loads and when data is loaded by AJAX (or BigPipe!)
// @See https://www.drupal.org/docs/drupal-apis/javascript-api/javascript-api-overview
(function ($, Drupal, once) {
Drupal.behaviors.buildCalendarBlock = {
attach: function (context, settings) {
var dialogWidth = [400];
var dialogType = ['modal'];
var dialogOpen = [1];
// Redirect to the URL
function gotoURL(url) {
let eventURL = new URL(url, location.origin);
if (eventURL.origin !== "null") {
window.location = url;
}
}
// Event click callback
function eventClick(info) {
info.jsEvent.preventDefault();
let blockIndex = parseInt(this.el.getAttribute("calendar-block-index"));
if (info.event.url && dialogOpen[blockIndex] === "1") {
let ajaxSettings = {
url: info.event.url,
dialogType: dialogType[blockIndex],
dialog: { width: dialogWidth[blockIndex] },
};
let openDialog = Drupal.ajax(ajaxSettings).execute();
openDialog.done(function() {
// The modal dialog open successfully.
}).fail(function() {
// The Ajax modal couldn't open.
// Open in a new tab instead.
gotoURL(info.event.url);
});
}
else if (info.event.url) {
gotoURL(info.event.url);
}
}
// Function to build calendar objects.
function buildCalendars() {
$('.fullcalendar-block')
.each(function() {
let blockIndex = parseInt(this.getAttribute("calendar-block-index"));
let blockSettings = drupalSettings.fullCalendarBlock[blockIndex];
var calendarOptions = JSON.parse(blockSettings.calendar_options);
dialogOpen[blockIndex] = blockSettings.dialog_open;
dialogWidth[blockIndex] = blockSettings.dialog_width;
dialogType[blockIndex] = 'modal';
// Bind the event click handler.
calendarOptions.eventClick = eventClick;
var calendar = new FullCalendar.Calendar(this, calendarOptions);
calendar.render();
});
}
once('buildCalendarBlock', 'html', context).forEach( function () {
// Build all calendars.
buildCalendars();
})
}
}
})(jQuery, Drupal, once);
\ No newline at end of file
<?php
namespace Drupal\fullcalendar_block\Plugin\Block;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a FullCalendar Block.
*
* @Block(
* id = "fullcalendar_block",
* admin_label = @Translation("FullCalendar block"),
* category = @Translation("Calendar"),
* )
*/
class FullCalendarBlock extends BlockBase {
protected static $blockIndex = 0;
/**
* {@inheritdoc}
*/
public function build() {
$block_index = self::$blockIndex++;
$config = $this->getConfiguration();
$event_url = $config['event_source'] ?? '';
$initial_view = $config['initial_view'] ?? 'dayGridMonth';
$header_start = $config['header_start'] ?? 'prev,next today';
$header_center = $config['header_center'] ?? 'title';
$header_end = $config['header_end'] ?? 'timeGridWeek,timeGridDay';
$dialog_open = $config['open_dialog'] ?? 1;
$dialog_width = $config['dialog_width'] ?? 800;
$advanced_settings = $config['advanced'] ?? '';
// Fullcalendar options.
$calendar_options = [
'initialView' => $initial_view,
'events' => $event_url,
'headerToolbar' => [
'start' => $header_start,
'center' => $header_center,
'end' => $header_end,
],
];
if (!empty($advanced_settings)) {
$calendar_options = array_merge($calendar_options, json_decode($advanced_settings, true));
}
$block_content = [
'#theme' => 'fullcalendar_block',
'#block_index' => $block_index,
'#attached' => [
'library' => [
'fullcalendar_block/fullcalendar',
],
],
];
$block_content['#attached']['drupalSettings']['fullCalendarBlock'][$block_index] = [
'calendar_options' => json_encode($calendar_options),
'dialog_open' => $dialog_open,
'dialog_width' => $dialog_width,
];
return $block_content;
}
/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state) {
$form = parent::blockForm($form, $form_state);
$config = $this->getConfiguration();
$form['event_source'] = [
'#type' => 'textfield',
'#title' => $this->t('Event source URL'),
'#description' => $this->t('The URL where the calendar events data feeds'),
'#default_value' => $config['event_source'] ?? '',
];
$form['initial_view'] = [
'#type' => 'textfield',
'#title' => $this->t('Initial View'),
'#description' => $this->t('The initial view of the calendar'),
'#default_value' => $config['initial_view'] ?? 'dayGridMonth',
];
// Header toolbar settings.
$form['header_toolbar'] = [
'#type' => 'details',
'#title' => $this->t('Header Toolbar'),
'#description' => $this->t('The header toolbar settings.'),
];
$form['header_toolbar']['header_start'] = [
'#type' => 'textfield',
'#title' => $this->t('Start of the header toolbar'),
'#description' => $this->t('Start area will normally be on the left. if RTL, will be on the right'),
'#default_value' => $config['header_start'] ?? 'prev,next today',
];
$form['header_toolbar']['header_center'] = [
'#type' => 'textfield',
'#title' => $this->t('Center of the header toolbar'),
'#description' => $this->t('The default value is title if leave this empty'),
'#default_value' => $config['header_center'] ?? 'title',
];
$form['header_toolbar']['header_end'] = [
'#type' => 'textfield',
'#title' => $this->t('End of the header toolbar'),
'#description' => $this->t('The default value is Week and Day view if leave this empty'),
'#default_value' => $config['header_end'] ?? 'timeGridWeek,timeGridDay',
];
// Click event settings.
$form['click_event'] = [
'#type' => 'details',
'#title' => $this->t('Click event settings.'),
];
$form['click_event']['open_dialog'] = [
'#type' => 'radios',
'#title' => $this->t('Click on an event'),
'#default_value' => 1,
'#options' => [
0 => $this->t('Open in a new tab'),
1 => $this->t('Open in a dialog'),
],
'#default_value' => $config['open_dialog'] ?? 1,
'#attributes' => [
//define static data condition attribute so we can easier select it
'data-condition' => 'field-open-dialog',
],
];
$form['click_event']['dialog_width'] = [
'#type' => 'textfield',
'#title' => $this->t('Dialog width'),
'#default_value' => $config['dialog_width'] ?? 800,
'#size' => '5',
'#states' => [
//show this textfield only if 'open in a dialog' is selected above
'visible' => [
':input[data-condition="field-open-dialog"]' => ['value' => 1],
],
],
];
// Advanced settings.
$form['advanced'] = [
'#type' => 'details',
'#title' => $this->t('Advanced settings.'),
];
$form['advanced']['addition'] = [
'#type' => 'textarea',
'#title' => $this->t('Advanced settings in JSON format.'),
'#default_value' => $config['advanced'] ?? '',
'#description' => $this->t('It must be in valid JSON format.<br/>See %doc for available options.', array(
'%doc' => Link::fromTextAndUrl($this->t('Fullcalendar Document'), Url::fromUri('https://fullcalendar.io/docs#toc', array('attributes' => array('target' => '_blank'))))->toString(),
)),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state) {
parent::blockSubmit($form, $form_state);
$values = $form_state->getValues();
$this->configuration['event_source'] = $values['event_source'];
$this->configuration['initial_view'] = $values['initial_view'];
$this->configuration['header_start'] = $values['header_toolbar']['header_start'];
$this->configuration['header_center'] = $values['header_toolbar']['header_center'];
$this->configuration['header_end'] = $values['header_toolbar']['header_end'];
$this->configuration['open_dialog'] = $values['click_event']['open_dialog'];
$this->configuration['dialog_width'] = $values['click_event']['dialog_width'];
$this->configuration['advanced'] = $values['advanced']['addition'];
}
}
\ No newline at end of file
{#
/**
* Default theme implementation for blocks to output a Fullcalendar.
*
* Available variables:
* - block_index: Block index
*
* @see template_preprocess_block()
*
* @ingroup themeable
*/
#}
<div class="fullcalendar-block" calendar-block-index="{{ block_index }}"></div>'
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment