1/** 2 * @file 3 * Fullcalendar View plugin JavaScript file. 4 */ 5 6// Jquery wrapper for drupal to avoid conflicts between libraries. 7(function ($, Drupal, drupalSettings) { 8 var initialLocaleCode = 'en'; 9 // Dialog index. 10 var dialogIndex = 0; 11 // Dialog objects. 12 var dialogs = []; 13 // Date entry clicked. 14 var slotDate; 15 16 /** 17 * @see https://fullcalendar.io/docs/v4/eventSourceSuccess 18 */ 19 function eventSourceSuccessRenderingBackground(content) { 20 for(let i = 0; i < content.length; i++){ 21 content[i].rendering = 'background'; 22 } 23 } 24 25 /** 26 * Event render handler 27 */ 28 function eventRender (info) { 29 let viewIndex = parseInt(this.el.getAttribute('data-calendar-view-index')); 30 let viewSettings = drupalSettings.fullCalendarView[viewIndex]; 31 32 if (info.el.classList.contains('fc-bgevent')) { 33 info.el.setAttribute('title', Drupal.checkPlain(info.event.title)); 34 } 35 36 // Event title html markup. 37 let eventTitleEle = info.el.getElementsByClassName('fc-title'); 38 if(eventTitleEle.length > 0) { 39 eventTitleEle[0].innerHTML = info.event.title; 40 } 41 // Event list tile html markup. 42 let eventListTitleEle = info.el.getElementsByClassName('fc-list-item-title'); 43 if(eventListTitleEle.length > 0) { 44 if (info.event.url) { 45 eventListTitleEle[0].innerHTML = '<a href="' + encodeURI(info.event.url) + '">' + info.event.title + '</a>'; 46 } 47 else { 48 eventListTitleEle[0].innerHTML = info.event.title; 49 } 50 } 51 // Modal popup 52 if (viewSettings.dialogModal) { 53 if ($(info.el).is('a')) { 54 $(info.el).addClass('use-ajax'); 55 if (!viewSettings.dialogCanvas) { 56 $(info.el).attr('data-dialog-type', 'modal'); 57 } 58 else { 59 $(info.el).attr('data-dialog-type', 'dialog'); 60 $(info.el).attr('data-dialog-renderer', 'off_canvas'); 61 } 62 $(info.el).attr('data-dialog-options', viewSettings.dialog_modal_options); 63 $(info.el).attr('href', $(info.el).attr('href').replaceAll('&', '&')); 64 } 65 else { 66 $(info.el).find('a').each(function(){ 67 if (!viewSettings.dialogCanvas) { 68 $(this).attr('data-dialog-type', 'modal'); 69 } 70 else { 71 $(this).attr('data-dialog-type', 'dialog'); 72 $(this).attr('data-dialog-renderer', 'off_canvas'); 73 } 74 $(this).attr('data-dialog-options', viewSettings.dialog_modal_options); 75 $(this).addClass('use-ajax'); 76 $(this).attr('href', $(this).attr('href').replaceAll('&', '&')); 77 }); 78 } 79 } 80 } 81 /** 82 * Event resize handler 83 */ 84 function eventResize(info) { 85 const end = info.event.end; 86 const start = info.event.start; 87 let strEnd = ''; 88 let strStart = ''; 89 let viewIndex = parseInt(this.el.getAttribute("data-calendar-view-index")); 90 let viewSettings = drupalSettings.fullCalendarView[viewIndex]; 91 const formatSettings = { 92 month: '2-digit', 93 year: 'numeric', 94 day: '2-digit', 95 hour: '2-digit', 96 minute: '2-digit', 97 second: '2-digit', 98 timeZone: 'UTC', 99 locale: 'sv-SE' 100 }; 101 // define the end date string in 'YYYY-MM-DD' format. 102 if (end) { 103 // The end date of an all-day event is exclusive. 104 // For example, the end of 2018-09-03 105 // will appear to 2018-09-02 in the calendar. 106 // So we need one day subtract 107 // to ensure the day stored in Drupal 108 // is the same as when it appears in 109 // the calendar. 110 if (end.getUTCHours() == 0 && end.getUTCMinutes() == 0 && end.getUTCSeconds() == 0) { 111 end.setDate(end.getDate() - 1); 112 } 113 // String of the end date. 114 strEnd = FullCalendar.formatDate(end, formatSettings); 115 } 116 // define the start date string in 'YYYY-MM-DD' format. 117 if (start) { 118 strStart = FullCalendar.formatDate(start, formatSettings); 119 } 120 const title = info.event.title.replace(/(<([^>]+)>)/ig,"");; 121 const msg = Drupal.t('@title end is now @event_end. Do you want to save this change?', { 122 '@title': title, 123 '@event_end': strEnd 124 }); 125 126 if ( 127 viewSettings.updateConfirm === 1 && 128 !confirm(msg) 129 ) { 130 info.revert(); 131 } 132 else { 133 /** 134 * Perform ajax call for event update in database. 135 */ 136 jQuery 137 .post( 138 drupalSettings.path.baseUrl + 139 "fullcalendar-view-event-update", 140 { 141 eid: info.event.extendedProps.eid, 142 entity_type: viewSettings.entityType, 143 start: strStart, 144 end: strEnd, 145 start_field: viewSettings.startField, 146 end_field: viewSettings.endField, 147 token: viewSettings.token 148 } 149 ) 150 .done(function(data) { 151 if (data !== '1') { 152 alert("Error: " + data); 153 info.revert(); 154 } 155 }); 156 } 157 } 158 159 // Day entry click call back function. 160 function dayClickCallback(info) { 161 slotDate = info.dateStr; 162 } 163 164 // Event click call back function. 165 function eventClick(info) { 166 slotDate = null; 167 info.jsEvent.preventDefault(); 168 let thisEvent = info.event; 169 let viewIndex = parseInt(this.el.getAttribute("data-calendar-view-index")); 170 let viewSettings = drupalSettings.fullCalendarView[viewIndex]; 171 let des = thisEvent.extendedProps.des; 172 // Show the event detail in a pop up dialog. 173 if (viewSettings.dialogWindow) { 174 let dataDialogOptionsDetails = {}; 175 if ( des == '') { 176 return false;
177 } 178 179 const jsFrame = new JSFrame({ 180 parentElement:info.el,//Set the parent element to which the jsFrame is attached here 181 }); 182 // Position offset. 183 let posOffset = dialogIndex * 20; 184 // Dialog options. 185 let dialogOptions = JSON.parse(viewSettings.dialog_options); 186 dialogOptions.left += posOffset + info.jsEvent.pageX; 187 dialogOptions.top += posOffset + info.jsEvent.pageY; 188 dialogOptions.title = dialogOptions.title ? dialogOptions.title : thisEvent.title.replace(/(<([^>]+)>)/ig,""); 189 dialogOptions.html = des; 190 //Create window 191 dialogs[dialogIndex] = jsFrame.create(dialogOptions); 192 193 dialogs[dialogIndex].show(); 194 dialogIndex++; 195 Drupal.ajax.bindAjaxLinks(document.body); 196 197 return false; 198 } 199 // Open a new window to show the details of the event. 200 if (thisEvent.url) { 201 let eventURL = new URL(thisEvent.url, location.origin); 202 if (eventURL.origin === "null") { 203 // Invalid URL. 204 return false; 205 } 206 if (viewSettings.openEntityInNewTab) { 207 // Open a new window to show the details of the event. 208 window.open(thisEvent.url); 209 return false; 210 } 211 else { 212 // Open in same window 213 window.location.href = thisEvent.url; 214 return false; 215 } 216 } 217 218 return false; 219 } 220 221 // Event drop call back function. 222 function eventDrop(info) { 223 const end = info.event.end; 224 const start = info.event.start; 225 let strEnd = ''; 226 let strStart = ''; 227 let viewIndex = parseInt(this.el.getAttribute("data-calendar-view-index")); 228 let viewSettings = drupalSettings.fullCalendarView[viewIndex]; 229 const formatSettings = { 230 month: '2-digit', 231 year: 'numeric', 232 day: '2-digit', 233 hour: '2-digit', 234 minute: '2-digit', 235 second: '2-digit', 236 timeZone: 'UTC', 237 locale: 'sv-SE' 238 }; 239 // define the end date string in 'YYYY-MM-DD' format. 240 if (end) { 241 // The end date of an all-day event is exclusive. 242 // For example, the end of 2018-09-03 243 // will appear to 2018-09-02 in the calendar. 244 // So we need one day subtract 245 // to ensure the day stored in Drupal 246 // is the same as when it appears in 247 // the calendar. 248 if (end.getUTCHours() == 0 && end.getUTCMinutes() == 0 && end.getUTCSeconds() == 0) { 249 end.setDate(end.getDate() - 1); 250 } 251 // String of the end date. 252 strEnd = FullCalendar.formatDate(end, formatSettings); 253 } 254 // define the start date string in 'YYYY-MM-DD' format. 255 if (start) { 256 strStart = FullCalendar.formatDate(start, formatSettings); 257 } 258 const title = info.event.title.replace(/(<([^>]+)>)/ig,"");; 259 const msg = Drupal.t('@title start is now @event_start and end is now @event_end - Do you want to save this change?', { 260 '@title': title, 261 '@event_start': strStart, 262 '@event_end': strEnd 263 }); 264 265 if ( 266 viewSettings.updateConfirm === 1 && 267 !confirm(msg) 268 ) { 269 info.revert(); 270 } 271 else { 272 /** 273 * Perform ajax call for event update in database. 274 */ 275 jQuery 276 .post( 277 drupalSettings.path.baseUrl + 278 "fullcalendar-view-event-update", 279 { 280 eid: info.event.extendedProps.eid, 281 entity_type: viewSettings.entityType, 282 start: strStart, 283 end: strEnd, 284 start_field: viewSettings.startField, 285 end_field: viewSettings.endField, 286 token: viewSettings.token 287 } 288 ) 289 .done(function(data) { 290 if (data !== '1') { 291 alert("Error: " + data); 292 info.revert(); 293 } 294 }); 295 296 } 297 } 298 299 function datesRender (info) { 300 Drupal.attachBehaviors(info.el); 301 } 302 function datesDestroy (info) { 303 Drupal.detachBehaviors(info.el); 304 } 305 306 // Build the calendar objects. 307 function buildCalendars() { 308 $('.js-drupal-fullcalendar') 309 .each(function() { 310 let calendarEl = this; 311 let viewIndex = parseInt(calendarEl.getAttribute("data-calendar-view-index")); 312 let viewSettings = drupalSettings.fullCalendarView[viewIndex]; 313 var calendarOptions = JSON.parse(viewSettings.calendar_options); 314 // Switch default view at mobile widths. 315 if (calendarOptions.mobileWidth !== undefined && calendarOptions.defaultMobileView !== undefined && $(window).width() <= calendarOptions.mobileWidth) { 316 calendarOptions.defaultView = calendarOptions.defaultMobileView; 317 } 318 // Bind the render event handler. 319 calendarOptions.eventRender = eventRender; 320 // Bind the resize event handler. 321 calendarOptions.eventResize = eventResize; 322 // Bind the day click handler. 323 calendarOptions.dateClick = dayClickCallback; 324 // Bind the event click handler. 325 calendarOptions.eventClick = eventClick; 326 // Bind the drop event handler. 327 calendarOptions.eventDrop = eventDrop; 328 // Trigger Drupal behaviors when calendar events are updated. 329 calendarOptions.datesRender = datesRender; 330 // Trigger Drupal behaviors when calendar events are destroyed. 331 calendarOptions.datesDestroy = datesDestroy; 332 // Language select element. 333 var localeSelectorEl = document.getElementById('locale-selector-' + viewIndex); 334 335 if (viewSettings.fetchGoogleHolidays === true) { 336 calendarOptions.plugins = calendarOptions.plugins || []; 337 calendarOptions.plugins.push('googleCalendar'); 338 calendarOptions.googleCalendarApiKey = viewSettings.googleCalendarAPIKey; 339 calendarOptions.eventSources = calendarOptions.eventSources || []; 340 341 calendarOptions.eventSources.push({ 342 id: viewSettings.googleCalendarGroup, 343 googleCalendarId: viewSettings.googleCalendarGroup, 344 rendering: viewSettings.renderGoogleHolidaysAsBackgroun
344d ? 'background' : '', 345 success: viewSettings.renderGoogleHolidaysAsBackground ? eventSourceSuccessRenderingBackground : null, 346 }); 347 } 348 349 // Allow passing a default date via query string. 350 const params = (new URL(document.location)).searchParams; 351 const initialDate = params.get('initialDate') 352 if (initialDate) { 353 calendarOptions.defaultDate = initialDate; 354 } 355 356 // Initial the calendar. 357 if (calendarEl) { 358 if (drupalSettings.calendar) { 359 drupalSettings.calendar[viewIndex] = new FullCalendar.Calendar(calendarEl, calendarOptions); 360 } 361 else { 362 drupalSettings.calendar = []; 363 drupalSettings.calendar[viewIndex] = new FullCalendar.Calendar(calendarEl, calendarOptions); 364 } 365 let calendarObj = drupalSettings.calendar[viewIndex]; 366 calendarObj.render(); 367 // Language dropdown box. 368 if (viewSettings.languageSelector) { 369 // build the locale selector's options 370 calendarObj.getAvailableLocaleCodes().forEach(function(localeCode) { 371 var optionEl = document.createElement('option'); 372 optionEl.value = localeCode; 373 optionEl.selected = localeCode == calendarOptions.locale; 374 optionEl.innerText = localeCode; 375 localeSelectorEl.appendChild(optionEl); 376 }); 377 // when the selected option changes, dynamically change the calendar option 378 localeSelectorEl.addEventListener('change', function() { 379 if (this.value) { 380 let viewIndex = parseInt(this.getAttribute("data-calendar-view-index")); 381 drupalSettings.calendar[viewIndex].setOption('locale', this.value); 382 } 383 }); 384 } 385 else if (localeSelectorEl){ 386 localeSelectorEl.style.display = "none"; 387 } 388 389 // Double click event. 390 calendarEl.addEventListener('dblclick' , function(e) { 391 let viewIndex = parseInt(this.getAttribute("data-calendar-view-index")); 392 let viewSettings = drupalSettings.fullCalendarView[viewIndex]; 393 // New event window can be open if following conditions match. 394 // * The new event content type are specified. 395 // * Allow to create a new event by double click. 396 // * User has the permission to create a new event. 397 // * The add form for the new event type is known. 398 if ( 399 slotDate && 400 viewSettings.eventBundleType && 401 viewSettings.dblClickToCreate && 402 viewSettings.addForm !== "" 403 ) { 404 // Open a new window to create a new event (content). 405 window.open( 406 drupalSettings.path.baseUrl + 407 viewSettings.addForm + 408 "?start=" + 409 slotDate + 410 "&start_field=" + 411 viewSettings.startField + 412 "&destination=" + window.location.pathname, 413 "_blank" 414 ); 415 } 416 417 }); 418 } 419 }); 420 } 421 422 // document.ready event does not work with BigPipe. 423 // The workaround is to ckeck the document state 424 // every 100 milliseconds until it is completed. 425 // @see https://www.drupal.org/project/drupal/issues/2794099#comment-13274828 426 var checkReadyState = setInterval(function() { 427 if ( 428 document.readyState === "complete" && 429 $('.js-drupal-fullcalendar').length > 0 430 ) { 431 clearInterval(checkReadyState); 432 // Build calendar objects. 433 buildCalendars(); 434 } 435 }, 100); 436 437 // After an Ajax call, the calendar objects need to rebuild, 438 // to reflect the changes, such as Ajax filter. 439 $( document ).ajaxComplete(function( event, request, settings ) { 440 // Remove the existing calendars except updating Ajax events. 441 if ( 442 drupalSettings.calendar && 443 settings.url !== '/fullcalendar-view-event-update' && 444 settings.url.indexOf('_wrapper_format=drupal_modal') < 0 445 ) { 446 // Rebuild the calendars. 447 drupalSettings.calendar.forEach(function(calendar) { 448 calendar.destroy(); 449 }); 450 //Re-build calendars. 451 buildCalendars(); 452 } 453 }); 454 455})(jQuery, Drupal, drupalSettings);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.