From ae0ad42253fe9a328e9ff181834bc75457065c9f Mon Sep 17 00:00:00 2001 From: Mingsong Hu Date: Wed, 22 Apr 2020 17:10:32 +1000 Subject: [PATCH] Use JSFrame instead of Drupal modal --- fullcalendar_view.libraries.yml | 12 + fullcalendar_view.module | 5 + js/fullcalendar_view.js | 436 +++++++++--------- src/FullcalendarViewPreprocess.php | 33 +- .../views/style/FullCalendarDisplay.php | 27 +- 5 files changed, 290 insertions(+), 223 deletions(-) diff --git a/fullcalendar_view.libraries.yml b/fullcalendar_view.libraries.yml index d60ecb2..aef9478 100644 --- a/fullcalendar_view.libraries.yml +++ b/fullcalendar_view.libraries.yml @@ -49,6 +49,18 @@ libraries.fullcalendar: - fullcalendar_view/libraries.fullcalendar-theme - fullcalendar_view/libraries.rrule +libraries.jsframe: + remote: https://github.com/riversun/JSFrame.js + version: '1.5.16' + license: + name: MIT + url: https://github.com/riversun/JSFrame.js/blob/master/LICENSE + gpl-compatible: true + cdn: + https://cdn.jsdelivr.net/npm/jsframe.js@1.5.16/lib/jsframe.min.js + js: + /libraries/JSFrame/1.5.16/jsframe.min.js: { minified: true } + # Feature libraries. fullcalendar: js: diff --git a/fullcalendar_view.module b/fullcalendar_view.module index 310990e..b8b4e62 100644 --- a/fullcalendar_view.module +++ b/fullcalendar_view.module @@ -51,6 +51,11 @@ function fullcalendar_view_form_node_form_alter(&$form, FormStateInterface $form function fullcalendar_view_library_info_alter(array &$libraries, $module) { if ('fullcalendar_view' == $module) { // Use CDN instead of all local missing libraries. + // JSFrame.js + $cdn_library = _fullcalendar_view_use_cdn_full_path($libraries, 'libraries.jsframe', 'js'); + if ($cdn_library) { + $libraries['libraries.jsframe']['js'] = $cdn_library; + } // RRule js. $cdn_library = _fullcalendar_view_use_cdn_full_path($libraries, 'libraries.rrule', 'js'); if ($cdn_library) { diff --git a/js/fullcalendar_view.js b/js/fullcalendar_view.js index 1a172cc..ee9eec2 100644 --- a/js/fullcalendar_view.js +++ b/js/fullcalendar_view.js @@ -4,10 +4,234 @@ */ (function($, Drupal) { + var calendarObjs = []; + var initialLocaleCode = 'en'; + // Dialog index. + var dialogIndex = 0; + // Dialog objects. + var dialogs = []; + + /** + * Event render handler + */ + function eventRender (info) { + // Event title html markup. + let eventTitleEle = info.el.getElementsByClassName('fc-title'); + if(eventTitleEle.length > 0) { + eventTitleEle[0].innerHTML = info.event.title; + } + // Event list tile html markup. + let eventListTitleEle = info.el.getElementsByClassName('fc-list-item-title'); + if(eventListTitleEle.length > 0) { + eventListTitleEle[0].innerHTML = info.event.title; + } + } + /** + * Event resize handler + */ + function eventResize(info) { + const end = info.event.end; + const start = info.event.start; + let strEnd = ''; + let strStart = ''; + const formatSettings = { + month: '2-digit', + year: 'numeric', + day: '2-digit', + locale: 'sv-SE' + }; + // define the end date string in 'YYYY-MM-DD' format. + if (end) { + // The end day of an event is exclusive. + // For example, the end of 2018-09-03 + // will appear to 2018-09-02 in the calendar. + // So we need one day subtract + // to ensure the day stored in Drupal + // is the same as when it appears in + // the calendar. + end.setDate(end.getDate() - 1); + // String of the end date. + strEnd = FullCalendar.formatDate(end, formatSettings); + } + // define the start date string in 'YYYY-MM-DD' format. + if (start) { + strStart = FullCalendar.formatDate(start, formatSettings); + } + const title = info.event.title.replace(/(<([^>]+)>)/ig,"");; + const msg = Drupal.t('@title end is now @event_end. Do you want to save this change?', { + '@title': title, + '@event_end': strEnd + }); + + if (!confirm(msg)) { + info.revert(); + } + else { + /** + * Perform ajax call for event update in database. + */ + jQuery + .post( + drupalSettings.path.baseUrl + + "fullcalendar-view-event-update", + { + eid: info.event.id, + entity_type: drupalSettings.entityType, + start: strStart, + end: strEnd, + start_field: drupalSettings.startField, + end_field: drupalSettings.endField, + token: drupalSettings.token + } + ) + .done(function(data) { + if (data !== '1') { + alert("Error: " + data); + info.revert(); + } + }); + } + } + + // Day entry click call back function. + function dayClickCallback(info) { + slotDate = info.dateStr; + } + + // Event click call back function. + function eventClick(info) { + slotDate = null; + info.jsEvent.preventDefault(); + // if (drupalSettings.linkToEntity) { + // Open a time slot details in a dialog + if (drupalSettings.dialogWindow) { + let dataDialogOptionsDetails = {}; + let thisEvent = info.event; + if (thisEvent.url == '') { + return false; + } + + const jsFrame = new JSFrame({ + parentElement:document.body,//Set the parent element to which the jsFrame is attached here + }); + // Position offset. + let posOffset = dialogIndex * 20; + // Dialog options. + let dialogOptions = JSON.parse(drupalSettings.dialog_options); + dialogOptions.left += posOffset; + dialogOptions.top += posOffset; + dialogOptions.title = thisEvent.title.replace(/(<([^>]+)>)/ig,""); + dialogOptions.url = thisEvent.url; + //Create window + dialogs[dialogIndex] = jsFrame.create(dialogOptions); + + dialogs[dialogIndex].show(); + dialogIndex++; + + return false; + } + // Open a new window to show the details of the event. + if (thisEvent.url) { + if (drupalSettings.openEntityInNewTab) { + // Open a new window to show the details of the event. + window.open(thisEvent.url); + return false; + } + else { + // Open in same window + return true; + } + } +// } + + return false; + } + + // Event drop call back function. + function eventDrop(info) { + const end = info.event.end; + const start = info.event.start; + let strEnd = ''; + let strStart = ''; + const formatSettings = { + month: '2-digit', + year: 'numeric', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + locale: 'sv-SE' + }; + // define the end date string in 'YYYY-MM-DD' format. + if (end) { + // The end day of an event is exclusive. + // For example, the end of 2018-09-03 + // will appear to 2018-09-02 in the calendar. + // So we need one day subtract + // to ensure the day stored in Drupal + // is the same as when it appears in + // the calendar. + if (end.getHours() == 0 && end.getMinutes() == 0 && end.getSeconds() == 0) { + end.setDate(end.getDate() - 1); + } + // String of the end date. + strEnd = FullCalendar.formatDate(end, formatSettings); + } + // define the start date string in 'YYYY-MM-DD' format. + if (start) { + strStart = FullCalendar.formatDate(start, formatSettings); + } + const title = info.event.title.replace(/(<([^>]+)>)/ig,"");; + const msg = Drupal.t('@title end is now @event_end. Do you want to save this change?', { + '@title': title, + '@event_end': strEnd + }); + + if (!confirm(msg)) { + info.revert(); + } + else { + /** + * Perform ajax call for event update in database. + */ + jQuery + .post( + drupalSettings.path.baseUrl + + "fullcalendar-view-event-update", + { + eid: info.event.id, + entity_type: drupalSettings.entityType, + start: strStart, + end: strEnd, + start_field: drupalSettings.startField, + end_field: drupalSettings.endField, + token: drupalSettings.token + } + ) + .done(function(data) { + if (data !== '1') { + alert("Error: " + data); + info.revert(); + } + }); + + } + } + Drupal.behaviors.fullcalendarView = { attach: function(context, settings) { - var calendarObjs = []; - var initialLocaleCode = 'en'; + /*if (typeof calendarObjs === 'undefined') { + return; + } + else { + if (calendarObjs.length > 0) { + for (let i = 0; i < calendarObjs.length; i++) { + calendarObjs[i].render(); + } + return; + } + }*/ + // Language select element. var localeSelectorEl = document.getElementById('locale-selector'); // Date entry clicked. var slotDate; @@ -82,214 +306,6 @@ }); } - - /** - * Event render handler - */ - function eventRender (info) { - // Event title html markup. - let eventTitleEle = info.el.getElementsByClassName('fc-title'); - if(eventTitleEle.length > 0) { - eventTitleEle[0].innerHTML = info.event.title; - } - // Event list tile html markup. - let eventListTitleEle = info.el.getElementsByClassName('fc-list-item-title'); - if(eventListTitleEle.length > 0) { - eventListTitleEle[0].innerHTML = info.event.title; - } - } - /** - * Event resize handler - */ - function eventResize(info) { - const end = info.event.end; - const start = info.event.start; - let strEnd = ''; - let strStart = ''; - const formatSettings = { - month: '2-digit', - year: 'numeric', - day: '2-digit', - locale: 'sv-SE' - }; - // define the end date string in 'YYYY-MM-DD' format. - if (end) { - // The end day of an event is exclusive. - // For example, the end of 2018-09-03 - // will appear to 2018-09-02 in the calendar. - // So we need one day subtract - // to ensure the day stored in Drupal - // is the same as when it appears in - // the calendar. - end.setDate(end.getDate() - 1); - // String of the end date. - strEnd = FullCalendar.formatDate(end, formatSettings); - } - // define the start date string in 'YYYY-MM-DD' format. - if (start) { - strStart = FullCalendar.formatDate(start, formatSettings); - } - const title = info.event.title.replace(/(<([^>]+)>)/ig,"");; - const msg = Drupal.t('@title end is now @event_end. Do you want to save this change?', { - '@title': title, - '@event_end': strEnd - }); - - if (!confirm(msg)) { - info.revert(); - } - else { - /** - * Perform ajax call for event update in database. - */ - jQuery - .post( - drupalSettings.path.baseUrl + - "fullcalendar-view-event-update", - { - eid: info.event.id, - entity_type: drupalSettings.entityType, - start: strStart, - end: strEnd, - start_field: drupalSettings.startField, - end_field: drupalSettings.endField, - token: drupalSettings.token - } - ) - .done(function(data) { - if (data !== '1') { - alert("Error: " + data); - info.revert(); - } - }); - } - } - - // Day entry click call back function. - function dayClickCallback(info) { - slotDate = info.dateStr; - } - - // Event click call back function. - function eventClick(info) { - slotDate = null; - info.jsEvent.preventDefault(); - if (drupalSettings.linkToEntity) { - // Open a time slot details in a dialog - if (drupalSettings.dialogWindow) { - let dataDialogOptionsDetails = {}; - let thisEvent = info.event; - var modalLink = $(''); - dataDialogOptionsDetails.draggable = true; - dataDialogOptionsDetails.autoResize = false; - dataDialogOptionsDetails.title = thisEvent.title.replace(/(<([^>]+)>)/ig,""); - - modalLink.addClass('use-ajax'); - modalLink.attr('href', thisEvent.url); - modalLink.attr('data-dialog-type', 'dialog'); - modalLink.attr('data-dialog-options', JSON.stringify(dataDialogOptionsDetails)); - modalLink.appendTo($('body')); - - Drupal.attachBehaviors(); - modalLink.trigger('click').remove(); - // The entry element object. - let $thisEntry = $(this); - if (typeof $thisEntry.qtip === "function") { - // Hide the pop tip. - $thisEntry.qtip("hide"); - } - - return false; - } - // Open a new window to show the details of the event. - if (thisEvent.url) { - if (drupalSettings.openEntityInNewTab) { - // Open a new window to show the details of the event. - window.open(thisEvent.url); - return false; - } - else { - // Open in same window - return true; - } - } - } - - return false; - } - - // Event drop call back function. - function eventDrop(info) { - const end = info.event.end; - const start = info.event.start; - let strEnd = ''; - let strStart = ''; - const formatSettings = { - month: '2-digit', - year: 'numeric', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - locale: 'sv-SE' - }; - // define the end date string in 'YYYY-MM-DD' format. - if (end) { - // The end day of an event is exclusive. - // For example, the end of 2018-09-03 - // will appear to 2018-09-02 in the calendar. - // So we need one day subtract - // to ensure the day stored in Drupal - // is the same as when it appears in - // the calendar. - if (end.getHours() == 0 && end.getMinutes() == 0 && end.getSeconds() == 0) { - end.setDate(end.getDate() - 1); - } - // String of the end date. - strEnd = FullCalendar.formatDate(end, formatSettings); - } - // define the start date string in 'YYYY-MM-DD' format. - if (start) { - strStart = FullCalendar.formatDate(start, formatSettings); - } - const title = info.event.title.replace(/(<([^>]+)>)/ig,"");; - const msg = Drupal.t('@title end is now @event_end. Do you want to save this change?', { - '@title': title, - '@event_end': strEnd - }); - - if (!confirm(msg)) { - info.revert(); - } - else { - /** - * Perform ajax call for event update in database. - */ - jQuery - .post( - drupalSettings.path.baseUrl + - "fullcalendar-view-event-update", - { - eid: info.event.id, - entity_type: drupalSettings.entityType, - start: strStart, - end: strEnd, - start_field: drupalSettings.startField, - end_field: drupalSettings.endField, - token: drupalSettings.token - } - ) - .done(function(data) { - if (data !== '1') { - alert("Error: " + data); - info.revert(); - } - }); - - } - } - - } }; })(jQuery, Drupal); diff --git a/src/FullcalendarViewPreprocess.php b/src/FullcalendarViewPreprocess.php index b5e0214..efa2921 100644 --- a/src/FullcalendarViewPreprocess.php +++ b/src/FullcalendarViewPreprocess.php @@ -104,14 +104,14 @@ class FullcalendarViewPreprocess { $start_field_option['settings']['timezone_override'] : date_default_timezone_get(); // Title field machine name. $title_field = (empty($options['title']) || $options['title'] == 'title') ? 'title' : $options['title']; - // Calendar entries linked to entity. +/* // Calendar entries linked to entity. $link_to_entity = FALSE; if (isset($fields[$title_field]->options['settings']['link_to_entity'])) { $link_to_entity = $fields[$title_field]->options['settings']['link_to_entity']; } elseif (isset($fields[$title_field]->options['settings']['link'])) { $link_to_entity = $fields[$title_field]->options['settings']['link']; - } + } */ // Set the first day setting. $first_day = isset($options['firstDay']) ? intval($options['firstDay']) : 0; // Left side buttons. @@ -156,10 +156,18 @@ class FullcalendarViewPreprocess { else { $title = t('Invalid event title'); } + $link_url = strstr($title, 'href="'); + if ($link_url) { + $link_url = substr($link_url, 6); + $link_url = strstr($link_url, '"', true); + } + else { + $link_url = ''; + } $entry = [ 'title' => Xss::filterAdmin($title), 'id' => $entity_id, - 'url' => $current_entity->toUrl()->toString(), + 'url' => $link_url, ]; // Event duration. if (!empty($duration_field) && !empty($fields[$duration_field])) { @@ -304,15 +312,31 @@ class FullcalendarViewPreprocess { 'eventLimit' => true, // Allow "more" link when too many events. 'eventOverlap' => $options['allowEventOverlap'] !== 0, ]; + // Dialog options. + // Other modules can override following options by custom plugin. + // For reference of JSFrame options see: + // https://github.com/riversun/JSFrame.js/ + $dialog_optoins = [ + 'left' => 40, + 'top' => 60, + 'width' => 640, + 'height' => 480, + 'movable' => true, //Enable to be moved by mouse + 'resizable' => true, //Enable to be resized by mouse + ]; // Load the fullcalendar js library. $variables['#attached']['library'][] = 'fullcalendar_view/fullcalendar'; + if ($options['dialogWindow']) { + // Load the JS library for dialog. + $variables['#attached']['library'][] = 'fullcalendar_view/libraries.jsframe'; + } // Pass data to js file. $variables['#attached']['drupalSettings'] = [ 'languageSelector' => $options['languageSelector'], 'updateConfirm' => $options['updateConfirm'], 'dialogWindow' => $options['dialogWindow'], - 'linkToEntity' => $link_to_entity, + // 'linkToEntity' => $link_to_entity, 'eventBundleType' => $event_bundle_type, 'startField' => $start_field, 'endField' => $end_field, @@ -322,6 +346,7 @@ class FullcalendarViewPreprocess { 'token' => $token, 'openEntityInNewTab' => $options['openEntityInNewTab'], 'calendar_options' => json_encode($calendar_options), + 'dialog_options' => json_encode($dialog_optoins), ]; } } diff --git a/src/Plugin/views/style/FullCalendarDisplay.php b/src/Plugin/views/style/FullCalendarDisplay.php index a4dc32e..ad3a84f 100644 --- a/src/Plugin/views/style/FullCalendarDisplay.php +++ b/src/Plugin/views/style/FullCalendarDisplay.php @@ -147,15 +147,6 @@ class FullCalendarDisplay extends StylePluginBase { '#empty_value' => '', '#default_value' => (!empty($this->options['end'])) ? $this->options['end'] : '', ]; - // Field name of rrules. - $form['duration'] = [ - '#title' => $this->t('Event duration field.'), - '#description' => $this->t('The field value should be a string in the format hh:mm:ss.sss, hh:mm:sss or hh:mm. For example, "05:00" signifies 5 hours.'), - '#type' => 'select', - '#options' => $field_names, - '#empty_value' => '', - '#default_value' => (!empty($this->options['duration'])) ? $this->options['duration'] : '', - ]; // Field name of title. $form['title'] = [ '#title' => $this->t('Title Field'), @@ -478,10 +469,28 @@ class FullCalendarDisplay extends StylePluginBase { ':doc-url' => 'https://github.com/jakubroztocil/rrule' ]), '#type' => 'select', + '#empty_value' => '', '#fieldset' => 'recurring', '#options' => $field_names, '#default_value' => (!empty($this->options['rrule'])) ? $this->options['rrule'] : '', ]; + // Field name of rrules. + $form['duration'] = [ + '#fieldset' => 'recurring', + '#title' => $this->t('Event duration field.'), + '#description' => $this->t('For specifying the end time of each recurring event instance. The field value should be a string in the format hh:mm:ss.sss, hh:mm:sss or hh:mm. For example, "05:00" signifies 5 hours.'), + '#type' => 'select', + '#empty_value' => '', + '#options' => $field_names, + '#empty_value' => '', + '#default_value' => (!empty($this->options['duration'])) ? $this->options['duration'] : '', + '#states' => [ + // Only show this field when the 'rrule' is specified. + 'invisible' => [ + [':input[name="style_options[rrule]"]' => ['value' => '']], + ], + ], + ]; // New event bundle type. $form['bundle_type'] = [ -- GitLab