/** * Descomplicar® Crescimento Digital * https://descomplicar.pt */ /** * Care Booking Block - Frontend JavaScript * * Provides graceful degradation and enhanced interaction for KiviCare integration * * @package CareBookingBlock */ (function($, config) { 'use strict'; // Global configuration const CareBooking = { config: config || {}, initialized: false, retryCount: 0, observers: [], /** * Initialize the Care Booking frontend functionality */ init: function() { if (this.initialized) { return; } if (this.config.debug) { console.log('Care Booking Block: Initializing frontend scripts'); } this.setupObservers(); this.enhanceExistingElements(); this.setupEventListeners(); this.setupFallbacks(); this.initialized = true; }, /** * Setup MutationObserver to watch for dynamically added content */ setupObservers: function() { if (!window.MutationObserver) { if (this.config.debug) { console.warn('Care Booking Block: MutationObserver not supported'); } return; } const observer = new MutationObserver((mutations) => { let hasNewContent = false; mutations.forEach((mutation) => { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { // Check if new node contains KiviCare content if (this.hasKiviCareContent(node)) { hasNewContent = true; } } }); } }); if (hasNewContent) { this.enhanceNewContent(); } }); // Start observing observer.observe(document.body, { childList: true, subtree: true }); this.observers.push(observer); }, /** * Check if element contains KiviCare content * @param {Element} element * @returns {boolean} */ hasKiviCareContent: function(element) { const selectors = [ this.config.selectors.doctors, this.config.selectors.services, this.config.selectors.forms ].join(', '); return $(element).find(selectors).length > 0 || $(element).is(selectors); }, /** * Enhance existing KiviCare elements on page load */ enhanceExistingElements: function() { this.enhanceLoadingStates(); this.enhanceFormValidation(); this.enhanceFallbackElements(); }, /** * Enhance newly added content */ enhanceNewContent: function() { if (this.config.debug) { console.log('Care Booking Block: Enhancing new content'); } // Add a small delay to ensure DOM is stable setTimeout(() => { this.enhanceExistingElements(); }, 100); }, /** * Setup loading states for better UX */ enhanceLoadingStates: function() { const $forms = $(this.config.selectors.forms); $forms.each((index, form) => { const $form = $(form); // Add loading indicator if (!$form.find('.care-booking-loading').length) { $form.prepend('
'); } // Handle form submissions $form.on('submit', (e) => { this.showLoadingState($form); }); // Handle AJAX requests $(document).on('ajaxStart', () => { if (this.isKiviCareAjax()) { this.showLoadingState($form); } }); $(document).on('ajaxComplete', () => { this.hideLoadingState($form); }); }); }, /** * Show loading state * @param {jQuery} $element */ showLoadingState: function($element) { $element.addClass('care-booking-loading'); $element.find('.care-booking-loading').show(); }, /** * Hide loading state * @param {jQuery} $element */ hideLoadingState: function($element) { $element.removeClass('care-booking-loading'); $element.find('.care-booking-loading').hide(); }, /** * Check if current AJAX request is KiviCare related * @returns {boolean} */ isKiviCareAjax: function() { // This is a simplified check - could be enhanced based on KiviCare's AJAX patterns return window.location.href.indexOf('kivicare') !== -1 || document.body.className.indexOf('kivicare') !== -1; }, /** * Enhance form validation */ enhanceFormValidation: function() { const $forms = $(this.config.selectors.forms); $forms.each((index, form) => { const $form = $(form); $form.on('submit', (e) => { if (!this.validateBookingForm($form)) { e.preventDefault(); return false; } }); // Real-time validation for select fields $form.find('select').on('change', (e) => { this.validateSelectField($(e.target)); }); }); }, /** * Validate booking form * @param {jQuery} $form * @returns {boolean} */ validateBookingForm: function($form) { let isValid = true; const requiredFields = $form.find('select[required], input[required]'); requiredFields.each((index, field) => { const $field = $(field); if (!$field.val() || $field.val() === '0' || $field.val() === '') { isValid = false; this.showFieldError($field, 'This field is required'); } else { this.clearFieldError($field); } }); return isValid; }, /** * Validate individual select field * @param {jQuery} $field */ validateSelectField: function($field) { const value = $field.val(); if ($field.attr('required') && (!value || value === '0' || value === '')) { this.showFieldError($field, 'Please make a selection'); } else { this.clearFieldError($field); } }, /** * Show field error * @param {jQuery} $field * @param {string} message */ showFieldError: function($field, message) { $field.addClass('error'); let $error = $field.siblings('.field-error'); if (!$error.length) { $error = $(''); $field.after($error); } $error.text(message).show(); }, /** * Clear field error * @param {jQuery} $field */ clearFieldError: function($field) { $field.removeClass('error'); $field.siblings('.field-error').hide(); }, /** * Setup fallback elements for graceful degradation */ enhanceFallbackElements: function() { // Add fallback classes to elements that might be blocked $(this.config.selectors.doctors).each((index, element) => { const $element = $(element); if (!$element.hasClass('care-booking-fallback')) { $element.addClass('care-booking-enhanced'); } }); $(this.config.selectors.services).each((index, element) => { const $element = $(element); if (!$element.hasClass('care-booking-fallback')) { $element.addClass('care-booking-enhanced'); } }); }, /** * Setup event listeners */ setupEventListeners: function() { // Handle dynamic doctor selection $(document).on('change', 'select[name="doctor_id"], .doctor-selection', (e) => { this.handleDoctorChange($(e.target)); }); // Handle service selection $(document).on('change', 'select[name="service_id"], .service-selection', (e) => { this.handleServiceChange($(e.target)); }); // Handle retry buttons $(document).on('click', '.care-booking-retry', (e) => { e.preventDefault(); this.retryOperation($(e.target)); }); }, /** * Handle doctor selection change * @param {jQuery} $select */ handleDoctorChange: function($select) { const doctorId = $select.val(); if (this.config.debug) { console.log('Care Booking Block: Doctor changed to', doctorId); } // Clear service selection if doctor changed const $serviceSelect = $select.closest('form').find('select[name="service_id"], .service-selection'); if ($serviceSelect.length) { $serviceSelect.val('').trigger('change'); this.updateServiceOptions($serviceSelect, doctorId); } }, /** * Handle service selection change * @param {jQuery} $select */ handleServiceChange: function($select) { const serviceId = $select.val(); if (this.config.debug) { console.log('Care Booking Block: Service changed to', serviceId); } // Additional service-specific logic can be added here }, /** * Update service options based on selected doctor * @param {jQuery} $serviceSelect * @param {string} doctorId */ updateServiceOptions: function($serviceSelect, doctorId) { if (!doctorId || doctorId === '0') { return; } // This would typically make an AJAX request to get services // For now, we'll rely on KiviCare's existing functionality $serviceSelect.trigger('doctor_changed', [doctorId]); }, /** * Setup fallback mechanisms */ setupFallbacks: function() { if (!this.config.fallbackEnabled) { return; } // Setup automatic retry for failed operations this.setupAutoRetry(); // Setup offline detection this.setupOfflineDetection(); }, /** * Setup automatic retry for failed operations */ setupAutoRetry: function() { $(document).on('ajaxError', (event, jqXHR, ajaxSettings, thrownError) => { if (this.isKiviCareAjax() && this.retryCount < this.config.retryAttempts) { setTimeout(() => { this.retryCount++; if (this.config.debug) { console.log('Care Booking Block: Retrying operation, attempt', this.retryCount); } // Retry the failed request $.ajax(ajaxSettings); }, this.config.retryDelay); } }); }, /** * Setup offline detection */ setupOfflineDetection: function() { $(window).on('online offline', (e) => { const isOnline = e.type === 'online'; if (this.config.debug) { console.log('Care Booking Block: Connection status changed to', isOnline ? 'online' : 'offline'); } if (isOnline) { // Retry any pending operations this.retryPendingOperations(); } else { // Show offline message this.showOfflineMessage(); } }); }, /** * Retry pending operations when back online */ retryPendingOperations: function() { // Implementation would depend on what operations need to be retried if (this.config.debug) { console.log('Care Booking Block: Retrying pending operations'); } }, /** * Show offline message */ showOfflineMessage: function() { const message = ''; if (!$('.care-booking-offline-message').length) { $('body').prepend(message); setTimeout(() => { $('.care-booking-offline-message').fadeOut(); }, 5000); } }, /** * Retry a specific operation * @param {jQuery} $button */ retryOperation: function($button) { const $container = $button.closest('.care-booking-container'); this.showLoadingState($container); // Simulate retry - in practice, this would repeat the failed operation setTimeout(() => { this.hideLoadingState($container); $button.closest('.error-message').fadeOut(); }, 1000); }, /** * Cleanup resources */ destroy: function() { // Remove observers this.observers.forEach(observer => observer.disconnect()); this.observers = []; // Remove event listeners $(document).off('.careBooking'); this.initialized = false; } }; // Initialize when DOM is ready $(document).ready(() => { CareBooking.init(); }); // Handle page unload $(window).on('beforeunload', () => { CareBooking.destroy(); }); // Expose to global scope for debugging if (config && config.debug) { window.CareBooking = CareBooking; } })(jQuery, window.careBookingConfig);