1function wfea_render_calendar() { 2 // Safety check - ensure moment.js is loaded before proceeding. 3 // This handles cases where JS optimizers defer scripts and break load order. 4 if (typeof moment === 'undefined') { 5 console.warn('WFEA: moment.js not loaded yet, retrying in 100ms'); 6 setTimeout(wfea_render_calendar, 100); 7 return; 8 } 9 10 // Safety check - ensure FullCalendar is available. 11 if (typeof jQuery.fn.fullCalendar === 'undefined') { 12 console.warn('WFEA: FullCalendar not loaded yet, retrying in 100ms'); 13 setTimeout(wfea_render_calendar, 100); 14 return; 15 } 16 17 // Select all elements with the class name (include Elementor widgets) 18 let elements = jQuery(".wfea.wfea-cal, .wfea.wfea-calendar-list, .wfea-elementor-widget .wfea.wfea-cal, .wfea-elementor-widget .wfea.wfea-calendar-list"); 19 20 console.debug('WFEA: Found ' + elements.length + ' calendar elements on page'); 21 22 // Log instance IDs for debugging 23 elements.each(function (index) { 24 console.debug('WFEA: Calendar ' + index + ' instance ID:', jQuery(this).attr('data-wfea-instance')); 25 }); 26 27 if (elements.length > 0) { 28 // Iterator over all found elements 29 elements.each(function () { 30 let dataAttributes = {}; 31 let element = jQuery(this); // Current element in the loop 32 let inlineData = null; 33 let calOptions = null; 34 35 // For Elementor widgets, check if we're inside a preview container 36 let isElementorWidget = element.closest('.wfea-elementor-widget').length > 0; 37 38 // Check if we have JavaScript variables with our data (primary approach) 39 let eventsVar = element.attr('data-wfea-events-var'); 40 let optionsVar = element.attr('data-wfea-options-var'); 41 42 console.debug('WFEA: Checking for JavaScript variables'); 43 console.debug('WFEA: data-wfea-events-var attribute:', eventsVar); 44 console.debug('WFEA: data-wfea-options-var attribute:', optionsVar); 45 46 // Check if the variables are defined in the window object 47 if (eventsVar) { 48 console.debug('WFEA: JS variable ' + eventsVar + ' exists:', window[eventsVar] !== undefined); 49 } 50 if (optionsVar) { 51 console.debug('WFEA: JS variable ' + optionsVar + ' exists:', window[optionsVar] !== undefined); 52 } 53 54 if (eventsVar && optionsVar && window[eventsVar] && window[optionsVar]) { 55 console.debug('WFEA: Found JavaScript variables for calendar data'); 56 57 inlineData = window[eventsVar]; 58 calOptions = window[optionsVar]; 59 60 console.debug('WFEA: Loaded events count:', Array.isArray(inlineData) ? inlineData.length : 'not an array'); 61 62 renderCalendarWithData(element, calOptions, inlineData); 63 64 // Setup search for both regular and Elementor widgets 65 if (isElementorWidget) { 66 // For Elementor, we need to look for the search form in the parent container 67 let container = element.closest('.wfea-elementor-widget'); 68 setupElementorSearch(container, element, inlineData); 69 } else { 70 setupSearch(element, inlineData); 71 } 72 return; // Skip REST API call 73 } else { 74 console.debug('WFEA: JavaScript variables not found or not ready, falling back to REST API'); 75 } 76 77 // Collect data attributes for REST API 78 jQuery.each(this.attributes, function (index, attribute) { 79 if (attribute.name.startsWith('data-wfea')) { 80 // Extract the attribute name and value 81 let dataName = attribute.name.replace('data-wfea-', ''); 82 let dataValue = attribute.value; 83 84 // Check if the value is not undefined before storing it 85 if (typeof dataValue !== 'undefined') { 86 // Store it in key-value format 87 dataAttributes[dataName] = dataValue; 88 } 89 } 90 }); 91 92 // Convert object to JSON 93 let attributesString = JSON.stringify(dataAttributes); 94 95 // Log that we're falling back to REST API 96 console.debug('WFEA: Using REST API fallback for calendar data'); 97 98 // Call API here (only for editor/admin or when inline data not available) 99 wp.apiFetch({ 100 path: '/widget-for-eventbrite-api/v1/wfea_cal_options', 101 method: 'POST', 102 headers: {'Content-Type': 'application/json'}, 103 body: attributesString 104 }).then(response => { 105 console.debug('WFEA: REST API options response received'); 106 107 // Handle the response and perform rendering 108 let calendar = element.find('.wfea-fullcalendar'); 109 let options = jQuery.extend(response, wfea_extend_fullcalendar(
109)) 110 calendar.fullCalendar(options); 111 let sources = calendar.fullCalendar('getEventSources'); 112 calendar.fullCalendar('removeEventSources'); 113 114 // Prepare new URL with updated query parameters 115 let updatedUrl = sources[0].url.split('/eb_events')[0] + '/eb_events?' + attributesToQuery(dataAttributes); 116 sources[0].url = updatedUrl; 117 118 console.debug('WFEA: REST API events source URL:', updatedUrl); 119 120 calendar.fullCalendar('addEventSource', sources[0]); 121 calendar.fullCalendar('refetchEventSources', sources[0]); 122 }).catch(error => { 123 // Handle any errors 124 console.error('WFEA: Error performing REST API lookup:', error); 125 }); 126 }); 127 } else { 128 // console.log('The ".wfea" element does not exist on the page.'); 129 } 130 131 function renderCalendarWithData(element, calOptions, eventData) { 132 let calendar = element.find('.wfea-fullcalendar'); 133 let options = jQuery.extend(calOptions, wfea_extend_fullcalendar()); 134 135 // Initialize calendar with options 136 calendar.fullCalendar(options); 137 138 // Clear existing events 139 calendar.fullCalendar('removeEventSources'); 140 141 // Add events directly without REST API call 142 calendar.fullCalendar('addEventSource', { 143 events: eventData 144 }); 145 } 146 147 function setupSearch(element, allEventData) { 148 // Find the search form associated with this calendar 149 let instance = element.data('wfea-instance'); 150 let searchForm = jQuery('.wfea-search__form').filter(function () { 151 return jQuery(this).find('input[name="wfea_s_target"]').val() === 'wfea_instance_' + instance; 152 }); 153 154 if (searchForm.length) { 155 // Check if this is cal or cal_list layout 156 let isCalLayout = element.hasClass('wfea-cal'); 157 let isCalListLayout = element.hasClass('wfea-calendar-list'); 158 159 // For both calendar layouts, prevent default form submission and use JS filtering 160 if (isCalLayout || isCalListLayout) { 161 // Remove any existing handlers to prevent duplication 162 searchForm.off('submit.wfeaSearch'); 163 164 // Bind the submit event 165 searchForm.on('submit.wfeaSearch', function (e) { 166 e.preventDefault(); 167 168 // Get search term 169 let searchTerm = jQuery(this).find('input[name="wfea_s"]').val().toLowerCase(); 170 let calendar = element.find('.wfea-fullcalendar'); 171 172 // Filter events based on search term 173 let filteredEvents = allEventData; 174 if (searchTerm) { 175 filteredEvents = allEventData.filter(function (event) { 176 return event.title.toLowerCase().includes(searchTerm) || 177 (event.excerpt && event.excerpt.toLowerCase().includes(searchTerm)) || 178 (event.location && event.location.toLowerCase().includes(searchTerm)) || 179 (event.venue && event.venue.toLowerCase().includes(searchTerm)); 180 }); 181 } 182 183 // Update calendar with filtered events 184 calendar.fullCalendar('removeEventSources'); 185 calendar.fullCalendar('addEventSource', { 186 events: filteredEvents 187 }); 188 189 return false; 190 }); 191 192 // Also bind to the submit button click directly for better compatibility 193 let submitButton = searchForm.find('input[type="submit"]'); 194 submitButton.off('click.wfeaSearch'); 195 submitButton.on('click.wfeaSearch', function (e) { 196 e.preventDefault(); 197 e.stopPropagation(); // Stop event propagation 198 searchForm.trigger('submit.wfeaSearch'); 199 return false;
200 }); 201 } 202 } 203 } 204 205 function setupElementorSearch(container, element, allEventData) { 206 // For Elementor widgets, find the search form inside the container 207 let searchForm = container.find('.wfea-search__form'); 208 209 if (searchForm.length) { 210 // Remove any existing event handlers first (in case of duplicates) 211 searchForm.off('submit.wfeaSearch'); 212 213 // For Elementor widgets, bind to both form submit and button click 214 searchForm.on('submit.wfeaSearch', function (e) { 215 e.preventDefault(); 216 performElementorSearch(searchForm, element, allEventData); 217 return false; 218 }); 219 220 // Also bind to the submit button click directly 221 let submitButton = searchForm.find('input[type="submit"]'); 222 submitButton.off('click.wfeaSearch'); 223 submitButton.on('click.wfeaSearch', function (e) { 224 e.preventDefault(); 225 e.stopPropagation(); // Stop event propagation 226 performElementorSearch(searchForm, element, allEventData); 227 return false; 228 }); 229 230 // Add event delegation for the document to catch any other click 231 jQuery(document).off('click.wfeaButtonSearch', '.wfea-elementor-widget .wfea-search_cal__submit'); 232 jQuery(document).on('click.wfeaButtonSearch', '.wfea-elementor-widget .wfea-search_cal__submit', function (e) { 233 e.preventDefault(); 234 e.stopPropagation(); 235 236 // Find the closest form and calendar 237 let clickedForm = jQuery(this).closest('.wfea-search__form'); 238 let widgetContainer = jQuery(this).closest('.wfea-elementor-widget'); 239 let calendarElement = widgetContainer.find('.wfea.wfea-cal, .wfea.wfea-calendar-list'); 240 241 // Make sure we have the right elements 242 if (clickedForm.length && calendarElement.length) { 243 let eventData = calendarElement.data('wfea-inline-events'); 244 if (eventData) { 245 performElementorSearch(clickedForm, calendarElement, eventData); 246 } 247 } 248 249 return false; 250 }); 251 } 252 } 253 254 // Helper function to perform search on Elementor widgets 255 function performElementorSearch(form, element, allEventData) { 256 // Get search term 257 let searchTerm = form.find('input[name="wfea_s"]').val().toLowerCase(); 258 let calendar = element.find('.wfea-fullcalendar'); 259 260 // Filter events based on search term 261 let filteredEvents = allEventData; 262 if (searchTerm) { 263 filteredEvents = allEventData.filter(function (event) { 264 return event.title.toLowerCase().includes(searchTerm) || 265 (event.excerpt && event.excerpt.toLowerCase().includes(searchTerm)) || 266 (event.location && event.location.toLowerCase().includes(searchTerm)) || 267 (event.venue && event.venue.toLowerCase().includes(searchTerm)); 268 }); 269 } 270 271 // Update calendar with filtered events 272 calendar.fullCalendar('removeEventSources'); 273 calendar.fullCalendar('addEventSource', { 274 events: filteredEvents 275 }); 276 277 // Log for debugging 278 console.log('Elementor search performed with term:', searchTerm, 'found', filteredEvents.length, 'events'); 279 } 280 281 function attributesToQuery(dataAttributes) { 282 let query = Object.keys(dataAttributes) 283 .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(dataAttributes[k])) 284 .join('&'); 285 return query; 286 } 287} 288 289function wfea_extend_fullcalendar() { 290 return { 291 eventAfterAllRender: function (view) { 292 jQuery("[data-eb-id]").wfeaBindLinksToEB(); 293 }, 294 eventRender: function (event, element) { 295 let img = ''; 296 297 element.filter(".fc-event").prop('id', 'wfea-popup-booknow-' + event._id); 298 element.filter(".fc-event").addClass(event.classes); 299 element.filter(".fc-list-item").addClass(event.classes); 300 element.find(".fc-list-item-title").prop('id', 'wfea-popup-booknow-' + event._id); 301 if (false != event.popup_id) { 302 element.filter(".fc-event").prop('id', 'wfea-popup-booknow-' + event._id).attr('data-eb-id', event.popup_id); 303 element.find(".fc-list-item-title").prop('id', 'wfea-popup-booknow-' + event._id).attr('data-eb-id', event.popup_id); 304 } 305 306 element.find(".fc-list-item-title > a").wrapInner("<div class=\"cal-list-text-wrap\"></div>"); 307 if (event.show_inline_content) { 308 element.find(".cal-list-text-wrap").append("<div class=\"cal-list-content-wrap\">" + event.excerpt + "</div>"); 309 } 310 if (event.imageurl) {
311 // Create image markup with lazy loading for visible list items 312 let imgHtml = ""; 313 if (event.imageurl_original) { 314 // Log URLs for debugging 315 console.log('Calendar images - Standard:', event.imageurl); 316 console.log('Calendar images - Original:', event.imageurl_original); 317 318 // When thumb_original is true, use the original image directly (not as srcset) 319 imgHtml = "<div class=\"cal-list-img-wrap\"><img class=\"cal-list\" src='" + 320 event.imageurl_original + "' loading='lazy' alt=''></div>"; 321 } else { 322 // Basic lazy loading (only for visible calendar images) 323 imgHtml = "<div class=\"cal-list-img-wrap\"><img class=\"cal-list\" src='" + 324 event.imageurl + "' loading='lazy' alt=''></div>"; 325 } 326 element.find(".fc-list-item-title > a").append(imgHtml); 327 328 element.find(".fc-list-item-title > a").wrapInner("<div class=\"cal-list-link-wrap\"></div>"); 329 330 // For tooltip/qtip image - use data attributes instead of src to prevent automatic loading 331 if (event.imageurl_original) { 332 // When thumb_original is true, use the original image directly 333 img = "<img class=\"cal-list-qtip\" data-src='" + event.imageurl_original + 334 "' style='width: 50%;float:right;' alt=''>"; 335 336 // Add debug data attributes to help troubleshoot 337 img = img.replace('>', 338 ' data-debug-original="' + event.imageurl_original + '"' + 339 ' data-debug-standard="' + event.imageurl + '">' 340 ); 341 } else { 342 img = "<img class=\"cal-list-qtip\" data-src='" + event.imageurl + 343 "' style='width: 50%;float:right;' alt=''>"; 344 } 345 } 346 let loc = ''; 347 if (event.location) { 348 loc = "<p class='wfea-tip wfea-tip-location'>" + event.location + "</p>" 349 } 350 let loc_title = ''; 351 if (event.location_title) { 352 loc_title = "<h5 class='wfea-tip wfea-tip-title'>" + event.location_title + "</h5>" 353 } 354 let presented_by = ''; 355 if (event.presented_by) { 356 presented_by = "<p class='wfea-tip wfea-tip-presented-by'>" + event.presented_by + "</p>" 357 } 358 let prices = ''; 359 if (event.prices) { 360 prices = "<p class='wfea-tip wfea-tip-prices'>" + event.prices + "</p>" 361 } 362 let availability = ''; 363 if (event.availability) { 364 availability = "<p class='wfea-tip wfea-tip-availabilty'>" + event.availability + "</p>" 365 } 366 let door_time = ''; 367 if (event.door_time) { 368 door_time = "<p class='wfea-tip wfea-tip-door-time'>" + event.door_time + "</p>" 369 } 370 let age_restriction = ''; 371 if (event.age_restriction) { 372 age_restriction = "<p class='wfea-tip wfea-tip-age-restriction'>" + event.age_restriction + "</p>" 373 } 374 375 var help_tip = ` 376 <div class="wfea-help-tip"> 377 <div class="wfea-help-tip-titlebar"> 378 <div class="wfea-help-tip-title" aria-atomic="true">${event.title}</div> 379 </div> 380 <div class="wfea-help-tip-content" aria-atomic="true"> 381 ${loc_title + loc + presented_by + img + prices + availability + door_time + age_restriction + event.excerpt} 382 </div> 383 </div> 384 `; 385 386 if (element.find(".fc-content").length > 0) { 387 element.find(".fc-content").after(help_tip); 388 } 389 390 if (element.find(".fc-list-item-title").length > 0) { 391 element.find(".fc-list-item-title").append(help_tip); 392 } 393 } 394 }; 395} 396 397(function ($) { 398 'use strict'; 399 /* -------- new calendar code -------- */ 400 // dom ready and scripts loaded 401 $(document).ready(function () { 402 403 // Enhanced debugging 404 console.debug('WFEA Calendar initialized with deferred image loading'); 405 406 // Log browser capabilities information 407 console.debug('WFEA Browser info - userAgent:', navigator.userAgent); 408 console.debug('WFEA Browser limitations check:', { 409 'localStorage available': (function () { 410 try { 411 return window.localStorage !== undefined; 412 } catch (e) { 413 return false;
414 } 415 })(), 416 'jQuery version': jQuery.fn.jquery, 417 'Max attribute length test': (function () { 418 try { 419 // Create a test element to check attribute size limits 420 let testEl = jQuery('<div></div>'); 421 let testStr = ''; 422 const chunk = 'X'.repeat(1000); // 1KB chunk 423 let chunks = 0; 424 let maxAttrLength = 0; 425 426 // Try to set increasingly large data attributes 427 try { 428 // Start with 1KB and increase 429 for (let i = 1; i <= 512; i++) { // Up to 512KB 430 testStr += chunk; 431 testEl.attr('data-test-size', testStr); 432 maxAttrLength = testStr.length; 433 chunks = i; 434 435 // If we get past 100KB, that's usually enough for testing 436 if (i >= 100) break; 437 } 438 } catch (e) { 439 // We've hit a limit 440 console.error('Attribute size limit reached at', maxAttrLength, 'bytes'); 441 } 442 443 return { 444 'Maximum attribute size': maxAttrLength + ' bytes', 445 'Number of 1KB chunks': chunks, 446 'Likely supports large data attributes': maxAttrLength >= 65536 447 }; 448 } catch (e) { 449 return 'Error testing attribute sizes: ' + e.message; 450 } 451 })() 452 }); 453 }); 454 455 // For fullcalendar smart tooltips 456 $(document).on('mouseenter', '.fc-event, .fc-list-item', function () { 457 var $event = $(this); 458 var $tip = $event.find('.wfea-help-tip'); 459 460 if ($tip.length) { 461 // 1. Move tip to body to escape overflow:hidden containers 462 $tip.data('original-parent', $tip.parent()); 463 $tip.appendTo('body'); 464 465 // 2. Handle deferred image loading 466 $tip.find('img[data-src]').each(function () { 467 var $img = $(this); 468 if ($img.attr('data-src') && !$img.attr('src')) { 469 $img.attr('src', $img.attr('data-src')); 470 if ($img.attr('data-srcset')) { 471 $img.attr('srcset', $img.attr('data-srcset')); 472 } 473 $img.removeAttr('data-src').removeAttr('data-srcset'); 474 } 475 }); 476 477 // 3. Calculate position 478 var eventRect = $event[0].getBoundingClientRect(); 479 var tipRect = $tip[0].getBoundingClientRect(); 480 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; 481 var scrollTop = window.pageYOffset || document.documentElement.scrollTop; 482 483 // Default position: centered above the event 484 var top = eventRect.top + scrollTop - tipRect.height - 10; // 10px margin 485 var left = eventRect.left + scrollLeft + (eventRect.width / 2) - (tipRect.width / 2); 486 487 // 4. Viewport collision detection and correction (flipping) 488 if (top - scrollTop < 0) { // If it goes above the viewport 489 top = eventRect.bottom + scrollTop + 10; // Position it below 490 } 491 if (left < 0) { // If it goes off the left 492 left = 5; 493 } 494 if (left + tipRect.width > window.innerWidth) { // If it goes off the right 495 left = window.innerWidth - tipRect.width - 5; 496 } 497 498 // 5. Apply position and show the tip 499 $tip.css({ 500 position: 'absolute', 501 top: top + 'px', 502 left: left + 'px', 503 visibility: 'visible', 504 opacity: 1, 505 'z-index': 999999 506 }); 507 } 508 }); 509 510 $(document).on('mouseleave', '.fc-event, .fc-list-item', function() { 511 var $tip = $('body > .wfea-help-tip'); 512 if ($tip.length) { 513 // Hide and return to original parent 514 $tip.css({ 515 visibility: 'hidden', 516 opacity: 0 517 }); 518 var $originalParent = $tip.data('original-parent'); 519 if ($originalParent && $originalParent.length) { 520 $tip.appendTo($originalParent); 521 } 522 } 523 }); 524 525 // Define a function to initialize calendars that will be called after document.ready 526 function wfea_initialize_calendars() { 527 console.debug('WFEA: Delayed initialization to ensure attributes are set'); 528 // Give a small delay to make sure all DOM attributes are set 529 setTimeout(function () {
530 wfea_render_calendar(); 531 }, 100); // 100ms delay should be sufficient 532 } 533 534 $(document).ready(function () { 535 // Use the delayed initialization function 536 wfea_initialize_calendars(); 537 538 // Global click handler for all calendar search buttons (extra fallback) 539 // This handles both Elementor widgets and shortcode renders 540 $(document).on('click', '.wfea-search_cal__submit', function (e) { 541 e.preventDefault(); 542 543 // Find the closest form 544 let form = $(this).closest('form'); 545 if (!form.length) return false; 546 547 let searchTerm = form.find('input[name="wfea_s"]').val().toLowerCase(); 548 let targetInput = form.find('input[name="wfea_s_target"]'); 549 550 if (!targetInput.length) return false; 551 552 // Extract the instance ID to find the associated calendar 553 let instanceTarget = targetInput.val(); 554 let instanceNumber = instanceTarget.replace('wfea_instance_', ''); 555 556 // Find any calendar with matching instance ID 557 let calElement = $('.wfea[data-wfea-instance="' + instanceNumber + '"]'); 558 559 // If not found, try looking in containers 560 if (!calElement.length) { 561 // Try finding in Elementor widget or block containers 562 let container = $(this).closest('.wfea-elementor-widget, .wp-block-widget-for-eventbrite-api-display-eventbrite-calendar'); 563 calElement = container.find('.wfea.wfea-cal, .wfea.wfea-calendar-list'); 564 } 565 566 if (calElement.length) { 567 let eventData = calElement.data('wfea-inline-events'); 568 let calendar = calElement.find('.wfea-fullcalendar'); 569 570 if (eventData && calendar.length) { 571 // Filter events based on search term 572 let filteredEvents = eventData; 573 if (searchTerm) { 574 filteredEvents = eventData.filter(function (event) { 575 return event.title.toLowerCase().includes(searchTerm) || 576 (event.excerpt && event.excerpt.toLowerCase().includes(searchTerm)) || 577 (event.location && event.location.toLowerCase().includes(searchTerm)) || 578 (event.venue && event.venue.toLowerCase().includes(searchTerm)); 579 }); 580 } 581 582 // Update calendar with filtered events 583 calendar.fullCalendar('removeEventSources'); 584 calendar.fullCalendar('addEventSource', { 585 events: filteredEvents 586 }); 587 588 console.log('Global handler - Calendar search performed with term:', searchTerm, 'Instance:', instanceNumber); 589 } 590 } 591 592 return false; 593 }); 594 }); 595 596 /* ---------end new calendar code --------- */ 597 598 599})(jQuery);
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.