Commit ae0ad422 authored by Mingsong Hu's avatar Mingsong Hu

Use JSFrame instead of Drupal modal

parent f88537b4
...@@ -49,6 +49,18 @@ libraries.fullcalendar: ...@@ -49,6 +49,18 @@ libraries.fullcalendar:
- fullcalendar_view/libraries.fullcalendar-theme - fullcalendar_view/libraries.fullcalendar-theme
- fullcalendar_view/libraries.rrule - 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. # Feature libraries.
fullcalendar: fullcalendar:
js: js:
......
...@@ -51,6 +51,11 @@ function fullcalendar_view_form_node_form_alter(&$form, FormStateInterface $form ...@@ -51,6 +51,11 @@ function fullcalendar_view_form_node_form_alter(&$form, FormStateInterface $form
function fullcalendar_view_library_info_alter(array &$libraries, $module) { function fullcalendar_view_library_info_alter(array &$libraries, $module) {
if ('fullcalendar_view' == $module) { if ('fullcalendar_view' == $module) {
// Use CDN instead of all local missing libraries. // 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. // RRule js.
$cdn_library = _fullcalendar_view_use_cdn_full_path($libraries, 'libraries.rrule', 'js'); $cdn_library = _fullcalendar_view_use_cdn_full_path($libraries, 'libraries.rrule', 'js');
if ($cdn_library) { if ($cdn_library) {
......
...@@ -4,10 +4,234 @@ ...@@ -4,10 +4,234 @@
*/ */
(function($, Drupal) { (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 = { Drupal.behaviors.fullcalendarView = {
attach: function(context, settings) { attach: function(context, settings) {
var calendarObjs = []; /*if (typeof calendarObjs === 'undefined') {
var initialLocaleCode = 'en'; 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'); var localeSelectorEl = document.getElementById('locale-selector');
// Date entry clicked. // Date entry clicked.
var slotDate; var slotDate;
...@@ -82,214 +306,6 @@ ...@@ -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 = $('<a id="fullcalendar-view-dialog"></a>');
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); })(jQuery, Drupal);
...@@ -104,14 +104,14 @@ class FullcalendarViewPreprocess { ...@@ -104,14 +104,14 @@ class FullcalendarViewPreprocess {
$start_field_option['settings']['timezone_override'] : date_default_timezone_get(); $start_field_option['settings']['timezone_override'] : date_default_timezone_get();
// Title field machine name. // Title field machine name.
$title_field = (empty($options['title']) || $options['title'] == 'title') ? 'title' : $options['title']; $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; $link_to_entity = FALSE;
if (isset($fields[$title_field]->options['settings']['link_to_entity'])) { if (isset($fields[$title_field]->options['settings']['link_to_entity'])) {
$link_to_entity = $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'])) { elseif (isset($fields[$title_field]->options['settings']['link'])) {
$link_to_entity = $fields[$title_field]->options['settings']['link']; $link_to_entity = $fields[$title_field]->options['settings']['link'];
} } */
// Set the first day setting. // Set the first day setting.
$first_day = isset($options['firstDay']) ? intval($options['firstDay']) : 0; $first_day = isset($options['firstDay']) ? intval($options['firstDay']) : 0;
// Left side buttons. // Left side buttons.
...@@ -156,10 +156,18 @@ class FullcalendarViewPreprocess { ...@@ -156,10 +156,18 @@ class FullcalendarViewPreprocess {
else { else {
$title = t('Invalid event title'); $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 = [ $entry = [
'title' => Xss::filterAdmin($title), 'title' => Xss::filterAdmin($title),
'id' => $entity_id, 'id' => $entity_id,
'url' => $current_entity->toUrl()->toString(), 'url' => $link_url,
]; ];
// Event duration. // Event duration.
if (!empty($duration_field) && !empty($fields[$duration_field])) { if (!empty($duration_field) && !empty($fields[$duration_field])) {
...@@ -304,15 +312,31 @@ class FullcalendarViewPreprocess { ...@@ -304,15 +312,31 @@ class FullcalendarViewPreprocess {
'eventLimit' => true, // Allow "more" link when too many events. 'eventLimit' => true, // Allow "more" link when too many events.
'eventOverlap' => $options['allowEventOverlap'] !== 0, '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. // Load the fullcalendar js library.
$variables['#attached']['library'][] = 'fullcalendar_view/fullcalendar'; $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. // Pass data to js file.
$variables['#attached']['drupalSettings'] = [ $variables['#attached']['drupalSettings'] = [
'languageSelector' => $options['languageSelector'], 'languageSelector' => $options['languageSelector'],
'updateConfirm' => $options['updateConfirm'], 'updateConfirm' => $options['updateConfirm'],
'dialogWindow' => $options['dialogWindow'], 'dialogWindow' => $options['dialogWindow'],
'linkToEntity' => $link_to_entity, // 'linkToEntity' => $link_to_entity,
'eventBundleType' => $event_bundle_type, 'eventBundleType' => $event_bundle_type,
'startField' => $start_field, 'startField' => $start_field,
'endField' => $end_field, 'endField' => $end_field,
...@@ -322,6 +346,7 @@ class FullcalendarViewPreprocess { ...@@ -322,6 +346,7 @@ class FullcalendarViewPreprocess {
'token' => $token, 'token' => $token,
'openEntityInNewTab' => $options['openEntityInNewTab'], 'openEntityInNewTab' => $options['openEntityInNewTab'],
'calendar_options' => json_encode($calendar_options), 'calendar_options' => json_encode($calendar_options),
'dialog_options' => json_encode($dialog_optoins),
]; ];
} }
} }
......
...@@ -147,15 +147,6 @@ class FullCalendarDisplay extends StylePluginBase { ...@@ -147,15 +147,6 @@ class FullCalendarDisplay extends StylePluginBase {
'#empty_value' => '', '#empty_value' => '',
'#default_value' => (!empty($this->options['end'])) ? $this->options['end'] : '', '#default_value' => (!empty($this->options['end'])) ? $this->options['end'] : '',
]; ];