Commit 08c64c25 authored by codebymikey's avatar codebymikey
Browse files

Issue #3280301: Provide various improvements to the module

parent 78634002
Loading
Loading
Loading
Loading
+39 −0
Original line number Diff line number Diff line
block.settings.fullcalendar_block:
  type: block_settings
  label: 'FullCalendar block'
  mapping:
    event_source:
      type: string
      label: 'Event source URL'
    initial_view:
      type: string
      label: 'Initial View'
    header_start:
      type: string
      label: 'Start of the header toolbar'
    header_center:
      type: string
      label: 'Center of the header toolbar'
    header_end:
      type: string
      label: 'End of the header toolbar'
    open_dialog:
      type: 'fullcalendar_block_dialog_type.[%parent.open_dialog]'
      # label: 'Open in a new tab/dialog'
    dialog_width:
      type: integer
      label: 'Dialog width'
    advanced:
      # Translatable text so that you can easily have a different configuration
      # per site.
      # @see config_translation_config_schema_info_alter()
      type: text
      label: 'Advanced settings.'

# The dialog types.
fullcalendar_block_dialog_type.0:
  type: integer
  label: 'Open in a new tab'
fullcalendar_block_dialog_type.1:
  type: integer
  label: 'Open in a dialog'
+5 −1
Original line number Diff line number Diff line
fullcalendar:
libraries.fullcalendar:
  remote: https://fullcalendar.io/
  version: '5.10.2'
  license:
@@ -11,6 +11,9 @@ fullcalendar:
  js:
    /libraries/drupal-fullcalendar/main.min.js: { minified: true }
    /libraries/drupal-fullcalendar/locales-all.min.js: { minified: true }

fullcalendar:
  js:
    js/fullcalendar_block.js: {}
  dependencies:
    - core/drupal
@@ -18,3 +21,4 @@ fullcalendar:
    - core/once
    - core/drupalSettings
    - core/drupal.dialog.ajax
    - fullcalendar_block/libraries.fullcalendar
+66 −2
Original line number Diff line number Diff line
<?php

/**
 * @file
 * Contains Fullcalendar block module functions.
 */

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Site\Settings;

/**
 * Implements hook_theme().
 */
function fullcalendar_block_theme($existing, $type, $theme, $path): array {
function fullcalendar_block_theme($existing, $type, $theme, $path) {
  return [
    'fullcalendar_block' => [
      'variables' => [
@@ -12,3 +20,59 @@ function fullcalendar_block_theme($existing, $type, $theme, $path): array {
    ],
  ];
}

/**
 * Prepares variables for fullcalendar_block templates.
 *
 * Default template: fullcalendar-block.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - block_index: The block index.
 */
function template_preprocess_fullcalendar_block(array &$variables) {
  $variables['attributes']['class'][] = 'fullcalendar-block';
  $variables['attributes']['data-calendar-block-index'] = $variables['block_index'];
}

/**
 * Implements hook_library_info_alter().
 */
function fullcalendar_block_library_info_alter(&$libraries, $extension) {
  if ($extension === 'fullcalendar_block') {
    /*
     * Provide an easy way for developers to switch to non-minified assets
     * during dev by specifying the following in the settings.php:
     * @code
     * $settings['fullcalendar_block.dev_mode'] = TRUE;
     * @endcode
     */
    $dev_mode = Settings::get('fullcalendar_block.dev_mode', FALSE);
    if (!$dev_mode) {
      return;
    }
    // External libraries to use unminimized versions for.
    $elements = ['libraries.fullcalendar'];
    foreach ($elements as $element) {
      foreach (['css.theme', 'js'] as $component) {
        $component_path = explode('.', $component);
        $updated_libraries = [];
        foreach ((array) NestedArray::getValue($libraries[$element], $component_path, $key_exists) as $asset_key => $asset_config) {
          if (empty($asset_config['no_min']) && ($new_asset_key = preg_replace('/\.min\.(css|js)$/', '.$1', $asset_key)) !== $asset_key) {
            // Strip out the "/min/" from the path if it exists.
            $new_asset_key = str_replace('/min/', '/', $new_asset_key);
            if (is_file(DRUPAL_ROOT . $new_asset_key)) {
              // The supposed file exists. Use it.
              $asset_key = $new_asset_key;
              $asset_config['minified'] = FALSE;
            }
          }
          $updated_libraries[$asset_key] = $asset_config;
        }
        if ($key_exists) {
          NestedArray::setValue($libraries[$element], $component_path, $updated_libraries);
        }
      }
    }
  }
}
+34 −0
Original line number Diff line number Diff line
<?php

/**
 * @file
 * Post update hooks for the Fullcalendar Block module.
 */

use Drupal\block\BlockInterface;
use Drupal\Core\Config\Entity\ConfigEntityUpdater;

/**
 * Update the Fullcalendar block configuration schema.
 */
function fullcalendar_block_post_update_update_block_data(&$sandbox = NULL) {
  /** @var \Drupal\Core\Config\Entity\ConfigEntityUpdater $config_entity_updater */
  $config_entity_updater = \Drupal::classResolver(ConfigEntityUpdater::class);

  $callback = function (BlockInterface $block) {
    if ($block->getPluginId() === 'fullcalendar_block') {
      $settings = $block->get('settings');
      if ($settings && !empty($settings['advanced'])) {
        $settings['advanced'] = trim(str_replace(["\r\n", "\r"], "\n", $settings['advanced']));
        $block->set('settings', $settings);
      }
      // Resave the fullcalendar block instance.
      $block->save();
    }

    // Return false since we're updating the block ourselves.
    return FALSE;
  };

  $config_entity_updater->update($sandbox, 'block', $callback);
}
+140 −26
Original line number Diff line number Diff line
@@ -6,15 +6,22 @@
// 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) {
  /**
   * @namespace
   */
  Drupal.fullcalendar_block = {};

  Drupal.fullcalendar_block.instances = {};

  Drupal.behaviors.buildCalendarBlock = {
    attach: function (context, settings) {
      var dialogWidth = [400];
      var dialogType = ['modal'];
      var dialogOpen = [1];
      const dialogWidth = [400];
      const dialogType = ['modal'];
      const dialogOpen = [1];

      // Redirect to the URL
      function gotoURL(url) {
        let eventURL = new URL(url, location.origin);
        const eventURL = new URL(url, location.origin);
        if (eventURL.origin !== "null") {
          window.open(url, '_blank').focus();
        }
@@ -23,14 +30,19 @@
      // 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 = {
        if (!info.event.url) {
          return;
        }

        const blockIndex = this.el.getAttribute('data-calendar-block-index');
        if (dialogOpen[blockIndex] === 1) {
          // Open in a dialog.
          const ajaxSettings = {
            url: info.event.url,
            dialogType: dialogType[blockIndex],
            dialog: { width: dialogWidth[blockIndex] },
            dialog: {width: dialogWidth[blockIndex]}
          };
          let openDialog = Drupal.ajax(ajaxSettings).execute();
          const openDialog = Drupal.ajax(ajaxSettings).execute();

          openDialog.done(function() {
            // The modal dialog open successfully.
@@ -40,36 +52,138 @@
            gotoURL(info.event.url);
          });
        }
        else if (info.event.url) {
        else {
          gotoURL(info.event.url);
        }
      }

      // Function to build calendar objects.
      function buildCalendars() {
        $('.fullcalendar-block')
      function buildCalendars(context, settings) {
        $('.fullcalendar-block', context)
          .each(function() {
            let blockIndex = parseInt(this.getAttribute("calendar-block-index"));
            let blockSettings = drupalSettings.fullCalendarBlock[blockIndex];
            var calendarOptions = JSON.parse(blockSettings.calendar_options);
            const blockIndex = this.getAttribute('data-calendar-block-index');
            this.setAttribute('data-calendar-block-initialized', true);
            const blockSettings = settings.fullCalendarBlock && settings.fullCalendarBlock[blockIndex];
            if (!blockSettings) {
              console.warn('Could not fetch fullcalendar_block settings', blockIndex, this);
              return;
            }
            const calendarOptions = typeof blockSettings.calendar_options === 'string' ? JSON.parse(blockSettings.calendar_options) : $.extend(true, {}, blockSettings.calendar_options);

            if (typeof calendarOptions.events === 'string' && Drupal.url.isLocal(calendarOptions.events)) {
              // Check if there are any GET parameters to send to the internal
              // Drupal path. This should provide better integration with Views exposed filters.
              let queryString = window.location.search || '';

              if (queryString !== '') {
                // Remove the question mark and any Drupal path component,
                // existing fullcalendar search filters if any.
                const startParam = calendarOptions.startParam || 'start';
                let initialDate;
                const specialParams = [
                  // Internal Drupal paths.
                  'q', 'render',
                  // Remove parameters that will be sent by fullcalendar.
                  startParam,
                  calendarOptions.endParam || 'end',
                  calendarOptions.timeZoneParam || 'timeZone',
                ];

                if (typeof URLSearchParams !== 'undefined') {
                  const query = new URLSearchParams(queryString);
                  specialParams.forEach(function(specialParam) {
                    if (specialParam === startParam) {
                      initialDate = query.get(startParam);
                    }
                    query.delete(specialParam);
                  });
                  queryString = query.toString();
                }
                else {
                  // Fallback to strip out query strings.
                  // https://stackoverflow.com/a/1634841
                  const params = queryString.slice(1).split(/[&;]/g);
                  // Reverse iteration as may be destructive.
                  specialParams.forEach(function(specialParam) {
                    const prefix = encodeURIComponent(specialParam);
                    let i;
                    for (i = params.length; i-- > 0;) {
                      // Idiom for string.startsWith.
                      if (params[i] === prefix || (params[i].lastIndexOf(prefix + '=', 0) !== -1)) {
                        initialDate = params[i].split('=')[1];
                        if (initialDate) {
                          initialDate = decodeURIComponent(initialDate);
                        }
                        params.splice(i, 1);
                      }
                    }
                  });
                  queryString = params.join('&');
                }

                // Provide a default initial date if it's specified in the query string.
                if (!calendarOptions.initialDate && initialDate) {
                  const date = new Date(initialDate);
                  if (date instanceof Date && !isNaN(date)) {
                    // Valid date string.
                    calendarOptions.initialDate = initialDate;
                  }
                }

                if (queryString !== '') {
                  // If there is a '?' in events URL, & should be used to add
                  // parameters.
                  queryString = (/\?/.test(calendarOptions.events) ? '&' : '?') + queryString;
                  // Add the current query string to the query.
                  // Useful for taking in exposed filters.
                  calendarOptions.events = calendarOptions.events + queryString;
                }
              }
            }

            dialogOpen[blockIndex] = blockSettings.dialog_open;
            dialogWidth[blockIndex] = blockSettings.dialog_width;
            dialogOpen[blockIndex] = parseInt(blockSettings.dialog_open);
            dialogWidth[blockIndex] = parseInt(blockSettings.dialog_width);
            dialogType[blockIndex] = 'modal';

            // Bind the event click handler.
            calendarOptions.eventClick = eventClick;


            var calendar = new FullCalendar.Calendar(this, calendarOptions);
            const calendar = new FullCalendar.Calendar(this, calendarOptions);
            calendar.render();
            // Store a reference to the calendar block instance.
            Drupal.fullcalendar_block.instances[blockIndex] = {
              element: this,
              index: blockIndex,
              calendar: calendar,
              calendarOptions: calendarOptions,
              blockSettings,
            };
            // Trigger an event to let other modules know when a calendar
            // has been built.
            $(context).trigger('fullcalendar_block.build', [Drupal.fullcalendar_block.instances[blockIndex]]);
          });
      }

      once('buildCalendarBlock', 'html', context).forEach( function () {
        // Build all calendars.
        buildCalendars();
        buildCalendars(context, settings);
      })
    },
    detach: function (context, settings, trigger) {
      if (trigger === 'unload') {
        $('.fullcalendar-block[data-calendar-block-initialized]', context)
          .each(function() {
            // Unload the calendar block instance.
            const blockIndex = this.getAttribute('data-calendar-block-index');
            if (Drupal.fullcalendar_block.instances[blockIndex]) {
              if (Drupal.fullcalendar_block.instances[blockIndex].calendar) {
                Drupal.fullcalendar_block.instances[blockIndex].calendar.destroy();
              }
              delete Drupal.fullcalendar_block.instances[blockIndex];
              delete settings.fullcalendar_block[blockIndex];
            }
          });
      }
    }
  }
})(jQuery, Drupal, once);
Loading