// Run immediately when script loads, not waiting for DOM (function() { console.log('Form Enhancement Script Loaded - Running immediately...'); // If DOM is already ready, run now if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { initializeScript(); initializeTabListeners(); }); } else { // DOM is already loaded, run immediately initializeScript(); initializeTabListeners(); } // Function to initialize tab listeners for Elementor tabs function initializeTabListeners() { // console.log('Initializing tab listeners...'); // Wait a bit for Elementor to be ready setTimeout(function() { // Select all Elementor tab titles const tabTitles = document.querySelectorAll('.elementor-tab-title'); if (tabTitles.length > 0) { console.log('Found', tabTitles.length, 'Elementor tabs'); tabTitles.forEach(function(tab) { // Remove existing listeners to prevent duplicates tab.removeEventListener('click', handleTabClick); // Add click listener tab.addEventListener('click', handleTabClick); }); } else { // console.log('No Elementor tabs found, retrying in 1 second...'); // Retry after 1 second if tabs not found setTimeout(initializeTabListeners, 1000); } }, 500); } // Function to handle tab clicks function handleTabClick(event) { console.log('Tab clicked:', event.target.textContent.trim()); // Wait for the tab content to be visible setTimeout(function() { console.log('Re-initializing script for new tab...'); initializeScript(); }, 300); } function initializeScript() { console.log('Initializing Form Enhancement Script...'); // Vehicle data with images, passengers, and suitcases const vehicleData = { 'Luxury Sedan (3 passengers)': { // Updated to match CF7 option values passengers: 3, suitcases: 3, image: 'https://www.royalcarriages.com/wp-content/uploads/2025/08/sedan-1.jpg' }, 'Mercedes S Class Sedan (3 passengers)': { passengers: 3, suitcases: 3, image: 'https://www.royalcarriages.com/wp-content/uploads/2025/08/S-class-2.png' }, 'Luxury Suburban (6 passengers)': { passengers: 6, suitcases: 6, image: 'https://www.royalcarriages.com/wp-content/uploads/2025/09/2025-suburban-ck10906-1lt-gba-trimselector.jpg' }, 'Luxury Escalade (6 passengers)': { passengers: 6, suitcases: 6, image: 'https://www.royalcarriages.com/wp-content/uploads/2025/08/escalade-5.png' }, 'Stretch Limousine (10 passengers)': { passengers: 10, suitcases: 4, image: 'https://www.royalcarriages.com/wp-content/uploads/2025/08/stretch-white-limousine-6.jpg' }, 'Passenger Van (10 passengers)': { passengers: 10, suitcases: 10, image: 'https://www.royalcarriages.com/wp-content/uploads/2025/08/passenger-vans-9.jpg' }, 'Stretch Hummer Limousine (18 passengers)': { passengers: 18, suitcases: 6, image: 'https://www.royalcarriages.com/wp-content/uploads/2025/08/stretch-hummer-limousine-7.jpg' }, 'Stretch Escalade Limousine (18 passengers)': { passengers: 18, suitcases: 8, image: 'https://www.royalcarriages.com/wp-content/uploads/2025/08/stretch-escalade-limousine-8.jpeg' }, 'Limo Bus (20 passengers)': { passengers: 20, suitcases: 10, image: 'https://www.royalcarriages.com/wp-content/uploads/2025/08/20-passenger-limo-bus-11.jpg' }, 'Mini Bus (25 - 30 passengers)': { passengers: 30, suitcases: 30, image: 'https://www.royalcarriages.com/wp-content/uploads/2025/08/shuttle-buses-12.jpg' }, 'Limo Bus (30 passengers)': { passengers: 30, suitcases: 20, image: 'https://www.royalcarriages.com/wp-content/uploads/2025/08/30-Pass-Limo-Bus.png' }, 'Luxury Mercedes Sprinter Van (14 passengers)': { passengers: 14, suitcases: 14, image: 'https://www.royalcarriages.com/wp-content/uploads/2025/08/mercedes-sprinter-10.jpg' }, 'Luxury Executive Shuttle Bus (40 passengers)': { passengers: 40, suitcases: 40, image: 'https://www.royalcarriages.com/wp-content/uploads/2025/09/Luxury-Executive-Shuttle-Bus-Royal-Carriages.png' }, 'Charter Bus / Motor Coach (55 passengers)': { passengers: 55, suitcases: 55, image: 'https://www.royalcarriages.com/wp-content/uploads/2025/08/charter-bus-motor-coach-16.jpeg' } }; // Wait for Contact Form 7 to fully render the form function waitForCF7Elements(callback, maxAttempts = 50) { let attempts = 0; function checkElements() { attempts++; // Check for CF7-generated elements const typeOfVehicleSelect = document.querySelector('select[name="type-of-vehicle"]'); const passengerSelect = document.querySelector('select[name="number-of-passengers"]'); const suitcaseSelect = document.querySelector('select[name="number-of-suitcases"]'); const pickupDateInput = document.querySelector('input[name="pickup-date"]'); if (typeOfVehicleSelect && passengerSelect && suitcaseSelect && pickupDateInput) { console.log('CF7 elements found, initializing scripts...'); callback(); } else if (attempts < maxAttempts) { setTimeout(checkElements, 100); } else { console.warn('CF7 elements not found after maximum attempts'); // Try to run anyway in case some elements exist callback(); } } checkElements(); } // Initialize all functionality after CF7 elements are ready waitForCF7Elements(function() { initializeAllFeatures(); // Run placeholder initialization again after a short delay to catch late CF7 rendering setTimeout(() => { console.log('Running delayed placeholder initialization...'); initializeSelectPlaceholders(); }, 500); // Also run when CF7 form events fire if (typeof jQuery !== 'undefined') { jQuery(document).on('wpcf7mailsent wpcf7mailfailed wpcf7submit wpcf7invalid wpcf7spam', function() { setTimeout(() => { console.log('Re-running placeholders after CF7 event...'); initializeSelectPlaceholders(); }, 100); }); } }); function initializeAllFeatures() { preloadVehicleImages(); initializeGooglePlaces(); initializeVehicleSelection(); initializeDatePicker(); initializeSelectPlaceholders(); initializeValidation(); } // Preload all vehicle images for instant display function preloadVehicleImages() { console.log('Preloading vehicle images...'); const imagePromises = []; Object.keys(vehicleData).forEach(vehicleName => { const img = new Image(); const promise = new Promise((resolve, reject) => { img.onload = () => { console.log(`Loaded: ${vehicleName}`); resolve(vehicleName); }; img.onerror = () => { console.warn(`Failed to load: ${vehicleName}`); reject(vehicleName); }; }); img.src = vehicleData[vehicleName].image; imagePromises.push(promise); }); // Log when all images are loaded Promise.allSettled(imagePromises).then(results => { const loaded = results.filter(r => r.status === 'fulfilled').length; const failed = results.filter(r => r.status === 'rejected').length; console.log(`Vehicle images preloaded: ${loaded} loaded, ${failed} failed`); }); } // Google Places Autocomplete functionality (unchanged - uses preserved IDs) let pickupValidSelection = false; let dropoffValidSelection = false; function initializeGooglePlaces() { // Wait for Google Places API to be available function waitForGoogleAPI(callback, maxAttempts = 50) { let attempts = 0; function checkAPI() { attempts++; if (typeof google !== 'undefined' && google.maps && google.maps.places) { console.log('Google Places API is ready!'); callback(); } else if (attempts < maxAttempts) { console.log(`Waiting for Google Places API... attempt ${attempts}`); setTimeout(checkAPI, 100); } else { console.error('Google Places API failed to load after maximum attempts'); } } checkAPI(); } waitForGoogleAPI(function() { setupGooglePlaces(); }); } function setupGooglePlaces() { console.log('Setting up Google Places autocomplete...'); const service = new google.maps.places.AutocompleteService(); const placesService = new google.maps.places.PlacesService(document.createElement('div')); function setupCustomAutocomplete(inputElement, fieldType) { if (!inputElement) return; let selectedPrediction = null; // Create dropdown container const dropdown = document.createElement('div'); dropdown.className = 'custom-pac-container'; dropdown.style.cssText = ` position: absolute; z-index: 9999; background: white; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); max-height: 300px; overflow-y: auto; display: none; width: 100%; font-family: Arial, sans-serif; `; inputElement.parentNode.style.position = 'relative'; inputElement.parentNode.appendChild(dropdown); // Input event handler (rest of Google Places logic - unchanged) let debounceTimer; inputElement.addEventListener('input', function() { clearTimeout(debounceTimer); const query = this.value.trim(); // Reset selection flag when user types - this means manual typing if (fieldType === 'pickup') { pickupValidSelection = false; } else { dropoffValidSelection = false; } // Clear any validation styling while typing inputElement.classList.remove('field-required-highlight', 'field-valid'); hideAddressError(inputElement); if (query.length < 1) { dropdown.style.display = 'none'; return; } debounceTimer = setTimeout(() => { service.getPlacePredictions({ input: query, types: ['geocode'] // Includes addresses, airports, and establishments }, (predictions, status) => { console.log('Google Places API Response:', { status, predictions, query }); dropdown.innerHTML = ''; if (status === google.maps.places.PlacesServiceStatus.OK && predictions) { dropdown.style.display = 'block'; dropdown.innerHTML = '
Loading addresses...
'; let processedCount = 0; const totalPredictions = predictions.slice(0, 5).length; const items = []; predictions.slice(0, 5).forEach((prediction, index) => { placesService.getDetails({ placeId: prediction.place_id, fields: ['address_components', 'formatted_address'] }, (place, detailStatus) => { processedCount++; if (detailStatus === google.maps.places.PlacesServiceStatus.OK) { // Create address display item const item = document.createElement('div'); item.className = 'custom-pac-item'; item.style.cssText = ` padding: 12px 16px; border-bottom: 1px solid #f0f0f0; cursor: pointer; font-size: 14px; transition: background-color 0.2s ease; `; item.innerHTML = `
${place.formatted_address}
`; // Click handler item.addEventListener('click', () => { inputElement.value = place.formatted_address; dropdown.style.display = 'none'; // Validate that address has house number and street const addressValidation = validateAddressComponents(place.address_components, place.formatted_address); if (fieldType === 'pickup') { pickupValidSelection = addressValidation.isValid; } else { dropoffValidSelection = addressValidation.isValid; } // Show validation result if (!addressValidation.isValid) { showAddressError(inputElement, addressValidation.message); } else { showAddressValid(inputElement); } }); // Hover effects item.addEventListener('mouseenter', () => { item.style.backgroundColor = '#f8f9fa'; }); item.addEventListener('mouseleave', () => { item.style.backgroundColor = 'white'; }); items.push({ index, item }); } // Update dropdown when all predictions are processed if (processedCount === totalPredictions) { dropdown.innerHTML = ''; if (items.length === 0) { dropdown.innerHTML = '
No addresses found
'; } else { items.sort((a, b) => a.index - b.index); items.forEach(({ item }) => { dropdown.appendChild(item); }); } } }); }); } else { console.error('Google Places API Error:', { status, query }); dropdown.style.display = 'block'; dropdown.innerHTML = `
API Error: ${status}
`; setTimeout(() => { dropdown.style.display = 'none'; }, 3000); } }); }, 100); }); // Hide dropdown on blur inputElement.addEventListener('blur', function() { setTimeout(() => { dropdown.style.display = 'none'; }, 150); }); } // Setup autocomplete for address fields - find ALL forms including roundtrip const pickupInputs = document.querySelectorAll('#search_input, input[name="address"], [id*="search_input"], [id*="pickup"], #roundtrippick_input, #roundtripreturnpick_input'); const dropoffInputs = document.querySelectorAll('#drop_input, input[name="destination"], [id*="drop_input"], [id*="dropoff"], #roundtripdrop_input, #roundtripreturndrop_input'); console.log('Found', pickupInputs.length, 'pickup address inputs'); console.log('Found', dropoffInputs.length, 'dropoff address inputs'); pickupInputs.forEach(function(input) { if (input) { setupCustomAutocomplete(input, 'pickup'); } }); dropoffInputs.forEach(function(input) { if (input) { setupCustomAutocomplete(input, 'dropoff'); } }); console.log('Google Places Autocomplete initialized for CF7 form'); } // Address validation functions function validateAddressComponents(addressComponents, formattedAddress = '') { if (!addressComponents || addressComponents.length === 0) { return { isValid: false, message: 'Please select a valid address from the dropdown.' }; } let hasStreetNumber = false; let hasRoute = false; let isSpecialPlace = false; // Debug: Log all component types to console console.log('Address components:', addressComponents.map(c => ({ name: c.long_name, types: c.types }))); console.log('Formatted address:', formattedAddress); // Check formatted address for airport-related keywords const formattedLower = formattedAddress.toLowerCase(); if (formattedLower.includes('airport') || formattedLower.includes('terminal') || formattedLower.includes('intl') || formattedLower.includes('international') || formattedLower.includes('regional') || formattedLower.includes('airfield') || formattedLower.includes('airway') || formattedLower.includes('aviation')) { console.log('Airport detected in formatted address'); isSpecialPlace = true; } // Check for all types that should be considered valid addressComponents.forEach(component => { const types = component.types; const name = component.long_name.toLowerCase(); // Regular address components if (types.includes('street_number')) { hasStreetNumber = true; } if (types.includes('route')) { hasRoute = true; // Check if route name indicates airport/special location if (name.includes('airport') || name.includes('terminal') || name.includes('concourse') || name.includes('gate') || name.includes('departure') || name.includes('arrival')) { isSpecialPlace = true; } } // Special places that should always be valid if (types.includes('airport') || types.includes('establishment') || types.includes('point_of_interest') || types.includes('transit_station') || types.includes('bus_station') || types.includes('train_station') || types.includes('subway_station') || types.includes('hospital') || types.includes('university') || types.includes('school') || types.includes('shopping_mall') || types.includes('park') || types.includes('tourist_attraction') || types.includes('lodging')) { isSpecialPlace = true; } }); // Special places (airports, hotels, etc.) are always valid if (isSpecialPlace) { console.log('Valid special place detected'); return { isValid: true, message: '' }; } // For regular addresses, require street number and route if (!hasStreetNumber && !hasRoute) { return { isValid: false, message: 'Address must include both house number and street name.' }; } else if (!hasStreetNumber) { return { isValid: false, message: 'Address must include a house number, street name or Special place like Airport address.' }; } else if (!hasRoute) { return { isValid: false, message: 'Address must include a street name.' }; } console.log('Valid regular address detected'); return { isValid: true, message: '' }; } function showAddressError(inputElement, message) { // Remove any existing error hideAddressError(inputElement); // Add error styling inputElement.classList.add('field-required-highlight'); inputElement.classList.remove('field-valid'); // Create and show error message const errorDiv = document.createElement('div'); errorDiv.className = 'field-error-message address-error-message'; errorDiv.style.display = 'block'; errorDiv.textContent = message; inputElement.parentNode.appendChild(errorDiv); } function hideAddressError(inputElement) { // Remove error styling and messages inputElement.classList.remove('field-required-highlight'); // Remove error message const errorMessage = inputElement.parentNode.querySelector('.address-error-message'); if (errorMessage) { errorMessage.remove(); } } function showAddressValid(inputElement) { // Only add valid styling when explicitly called (after dropdown selection) inputElement.classList.remove('field-required-highlight'); inputElement.classList.add('field-valid'); hideAddressError(inputElement); } // Vehicle selection with CF7-compatible selectors function initializeVehicleSelection() { // Use CF7-generated select elements - find ALL forms const vehicleSelects = document.querySelectorAll('select[name="type-of-vehicle"]'); if (vehicleSelects.length === 0) { console.warn('No vehicle select elements found'); return; } console.log('Found', vehicleSelects.length, 'vehicle select elements'); vehicleSelects.forEach(function(vehicleSelect, index) { // Remove existing listeners to prevent duplicates vehicleSelect.removeEventListener('change', vehicleSelect._changeHandler); // Create a change handler for this specific select vehicleSelect._changeHandler = function() { const selectedVehicle = this.value; // Find the vehicle image elements within the same form/container const formContainer = this.closest('form') || this.closest('.wpcf7') || this.closest('.elementor-tab-content'); let vehicleImageDiv, vehicleImg, vehiclePassengers; if (formContainer) { vehicleImageDiv = formContainer.querySelector('#vehicleImage, .vehicleImage, [id*="vehicleImage"]'); vehicleImg = formContainer.querySelector('#vehicleImg, .vehicleImg, [id*="vehicleImg"]'); vehiclePassengers = formContainer.querySelector('#vehiclePassengers, .vehiclePassengers, [id*="vehiclePassengers"]'); } else { // Fallback to document-wide search with index const allVehicleImageDivs = document.querySelectorAll('#vehicleImage, .vehicleImage, [id*="vehicleImage"]'); const allVehicleImgs = document.querySelectorAll('#vehicleImg, .vehicleImg, [id*="vehicleImg"]'); const allVehiclePassengers = document.querySelectorAll('#vehiclePassengers, .vehiclePassengers, [id*="vehiclePassengers"]'); vehicleImageDiv = allVehicleImageDivs[index]; vehicleImg = allVehicleImgs[index]; vehiclePassengers = allVehiclePassengers[index]; } if (selectedVehicle && vehicleData[selectedVehicle]) { const vehicle = vehicleData[selectedVehicle]; // Show vehicle image and details if (vehicleImg) vehicleImg.src = vehicle.image; if (vehiclePassengers) vehiclePassengers.textContent = vehicle.passengers; if (vehicleImageDiv) vehicleImageDiv.style.display = 'block'; // Update passenger and suitcase dropdowns for this specific form updatePassengerOptions(vehicle.passengers, formContainer); updateSuitcaseOptions(vehicle.suitcases, formContainer); // For roundtrip forms, also update return trip fields updateReturnTripFields(vehicle.passengers, vehicle.suitcases, formContainer); } else { // Hide vehicle display if (vehicleImageDiv) vehicleImageDiv.style.display = 'none'; // Reset dropdowns to full range for this specific form updatePassengerOptions(55, formContainer); updateSuitcaseOptions(100, formContainer); // For roundtrip forms, also reset return trip fields updateReturnTripFields(55, 100, formContainer); } }; // Add the event listener vehicleSelect.addEventListener('change', vehicleSelect._changeHandler); }); console.log('Vehicle selection system initialized for CF7 form'); } // Initialize select field placeholders function initializeSelectPlaceholders() { // Define all select fields and their placeholder texts const selectFields = [ { selector: 'select[name="pickup-time"]', placeholder: 'Select Time' }, { selector: 'select[name="drop-time"]', placeholder: 'Select Time' }, { selector: 'select[name="type-of-service"]', placeholder: 'Select Service' }, { selector: 'select[name="type-of-vehicle"]', placeholder: 'Select Vehicle' }, { selector: 'select[name="number-of-passengers"]', placeholder: 'Select Passengers' }, { selector: 'select[name="number-of-suitcases"]', placeholder: 'Select Suitcases' } ]; // Set placeholders for all select fields - find ALL forms selectFields.forEach(field => { const selectElements = document.querySelectorAll(field.selector); if (selectElements.length > 0) { console.log(`Found ${selectElements.length} elements for ${field.selector}`); selectElements.forEach((selectElement, index) => { console.log(`Processing ${field.selector} #${index + 1}:`, selectElement); console.log('Current options:', Array.from(selectElement.options).map(opt => ({ value: opt.value, text: opt.textContent }))); // Find the blank option (should be first option with empty value) let blankOption = selectElement.querySelector('option[value=""]'); if (!blankOption) { // If no blank option exists, create one at the beginning blankOption = document.createElement('option'); blankOption.value = ''; blankOption.textContent = field.placeholder; selectElement.insertBefore(blankOption, selectElement.firstChild); console.log(`Created placeholder option for ${field.selector} #${index + 1}`); } else { // Update existing blank option blankOption.textContent = field.placeholder; console.log(`Updated placeholder option for ${field.selector} #${index + 1}`); } // Force the select to show placeholder by resetting selectedIndex selectElement.selectedIndex = 0; // Also trigger a change event to update any dependencies const changeEvent = new Event('change', { bubbles: true }); selectElement.dispatchEvent(changeEvent); }); } else { console.warn(`Select element not found: ${field.selector}`); } }); console.log('Select field placeholders initialized for CF7 form'); } // Update passenger options with CF7-compatible selector function updatePassengerOptions(maxPassengers, formContainer = null) { let passengerSelect; if (formContainer) { passengerSelect = formContainer.querySelector('select[name="number-of-passengers"]'); } else { // Fallback to all passenger selects const passengerSelects = document.querySelectorAll('select[name="number-of-passengers"]'); if (passengerSelects.length > 0) { // Update all passenger selects if no specific container passengerSelects.forEach(select => updateSinglePassengerSelect(select, maxPassengers)); return; } } if (!passengerSelect) return; updateSinglePassengerSelect(passengerSelect, maxPassengers); } function updateSinglePassengerSelect(passengerSelect, maxPassengers) { const currentValue = passengerSelect.value; // Clear existing options and add placeholder passengerSelect.innerHTML = ''; // Add options up to max passengers for (let i = 1; i <= maxPassengers; i++) { const option = document.createElement('option'); option.value = i.toString().padStart(2, '0'); option.textContent = i.toString().padStart(2, '0'); passengerSelect.appendChild(option); } // Restore previous value if still valid if (currentValue && parseInt(currentValue) <= maxPassengers) { passengerSelect.value = currentValue; } } // Update suitcase options with CF7-compatible selector function updateSuitcaseOptions(maxSuitcases, formContainer = null) { let suitcaseSelect; if (formContainer) { suitcaseSelect = formContainer.querySelector('select[name="number-of-suitcases"]'); } else { // Fallback to all suitcase selects const suitcaseSelects = document.querySelectorAll('select[name="number-of-suitcases"]'); if (suitcaseSelects.length > 0) { // Update all suitcase selects if no specific container suitcaseSelects.forEach(select => updateSingleSuitcaseSelect(select, maxSuitcases)); return; } } if (!suitcaseSelect) return; updateSingleSuitcaseSelect(suitcaseSelect, maxSuitcases); } function updateSingleSuitcaseSelect(suitcaseSelect, maxSuitcases) { const currentValue = suitcaseSelect.value; // Clear existing options and add placeholder suitcaseSelect.innerHTML = ''; // Add options up to max suitcases for (let i = 1; i <= maxSuitcases; i++) { const option = document.createElement('option'); option.value = i.toString().padStart(2, '0'); option.textContent = i.toString().padStart(2, '0'); suitcaseSelect.appendChild(option); } // Restore previous value if still valid if (currentValue && parseInt(currentValue) <= maxSuitcases) { suitcaseSelect.value = currentValue; } } // Update return trip fields for roundtrip forms function updateReturnTripFields(maxPassengers, maxSuitcases, formContainer = null) { let returnPassengerSelect, returnSuitcaseSelect; if (formContainer) { returnPassengerSelect = formContainer.querySelector('select[name="return-number-of-passengers"]'); returnSuitcaseSelect = formContainer.querySelector('select[name="return-number-of-suitcases"]'); } else { // Fallback to all return trip selects const returnPassengerSelects = document.querySelectorAll('select[name="return-number-of-passengers"]'); const returnSuitcaseSelects = document.querySelectorAll('select[name="return-number-of-suitcases"]'); returnPassengerSelects.forEach(select => updateSinglePassengerSelect(select, maxPassengers)); returnSuitcaseSelects.forEach(select => updateSingleSuitcaseSelect(select, maxSuitcases)); return; } // Update return trip passenger select if found if (returnPassengerSelect) { updateSinglePassengerSelect(returnPassengerSelect, maxPassengers); } // Update return trip suitcase select if found if (returnSuitcaseSelect) { updateSingleSuitcaseSelect(returnSuitcaseSelect, maxSuitcases); } } // Date picker initialization with CF7-compatible selector function initializeDatePicker() { const pickupDateInputs = document.querySelectorAll('input[name="pickup-date"], input[name="return-pickup-date"]'); if (pickupDateInputs.length === 0 || typeof AirDatepicker === 'undefined') { console.warn('Date picker elements or AirDatepicker not available'); return; } console.log('Found', pickupDateInputs.length, 'pickup date inputs'); const today = new Date(); pickupDateInputs.forEach(function(pickupDateInput) { // Skip if already initialized if (pickupDateInput._airDatepicker) { return; } new AirDatepicker(pickupDateInput, { minDate: today, dateFormat: 'MM-dd-yyyy', autoClose: true, isMobile: false, toggleSelected: false, onSelect: function({date, formattedDate, datepicker}) { // Trigger validation when a date is selected if (typeof jQuery !== 'undefined') { const $input = jQuery(pickupDateInput); // Manually trigger the validation function $input.removeClass('field-required-highlight').addClass('field-valid'); $input.siblings('.field-error-message').hide(); // Also trigger a blur event to ensure validation runs setTimeout(() => { $input.trigger('blur'); }, 10); } }, locale: { days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], daysMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], today: 'Today', clear: 'Clear', dateFormat: 'MM-dd-yyyy', timeFormat: 'HH:mm', firstDay: 0 } }); }); console.log('Date picker initialized for CF7 form'); } // Validate that address was selected from dropdown (not manually typed) function validateManualAddress(inputElement, fieldName) { const address = inputElement.value.trim(); if (!address) return; // Empty is handled by required field validation // Check if this was a valid selection from dropdown if ((fieldName === 'address' && pickupValidSelection) || (fieldName === 'destination' && dropoffValidSelection)) { hideAddressError(inputElement); // Valid selection return; } // If there's text but it wasn't selected from dropdown, show error if (address) { showAddressError(inputElement, 'Please select an address from the dropdown suggestions.'); } } // Form validation for CF7 forms function initializeValidation() { // Wait for jQuery if using CF7 if (typeof jQuery !== 'undefined') { jQuery(document).ready(function($) { // CF7 form validation logic function validateRequiredField(field) { var isRequired = field.hasClass('wpcf7-validates-as-required') || field.attr('aria-required') === 'true' || field.prop('required'); var isEmpty = false; var errorMessage = 'This field is required.'; var isValid = true; if (field.is('select')) { var value = field.val(); isEmpty = value === '' || value === null; // Check if a disabled placeholder option is selected if (!isEmpty) { var selectedOption = field.find('option:selected'); if (selectedOption.length && selectedOption.prop('disabled')) { isEmpty = true; errorMessage = 'Please select a valid option.'; } } } else { isEmpty = field.val().trim() === ''; } // Check if field is required and empty if (isRequired && isEmpty) { field.addClass('field-required-highlight').removeClass('field-valid'); showErrorMessage(field, errorMessage); return false; } // If not empty, validate format for specific field types if (!isEmpty) { // Email validation if (field.hasClass('wpcf7-validates-as-email') || field.attr('type') === 'email') { var emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailPattern.test(field.val().trim())) { field.addClass('field-required-highlight').removeClass('field-valid'); showErrorMessage(field, 'Please enter an email address.'); return false; } } } // Field is valid field.removeClass('field-required-highlight'); hideErrorMessage(field); if (!isEmpty) { field.addClass('field-valid'); } return true; } function showErrorMessage(field, message) { var errorDiv = field.siblings('.field-error-message'); if (errorDiv.length === 0) { errorDiv = $('
' + message + '
'); field.parent().append(errorDiv); } else { errorDiv.text(message); } errorDiv.show(); } function hideErrorMessage(field) { field.siblings('.field-error-message').hide(); } // Event listeners for CF7 form elements $('.wpcf7-form input, .wpcf7-form select, .wpcf7-form textarea').on('blur', function() { validateRequiredField($(this)); // Additional validation for address fields const fieldName = $(this).attr('name'); if (fieldName === 'address' || fieldName === 'destination') { validateManualAddress(this, fieldName); } }); $('.wpcf7-form input, .wpcf7-form select, .wpcf7-form textarea').on('focus', function() { $(this).removeClass('field-required-highlight field-valid'); hideErrorMessage($(this)); }); console.log('CF7 form validation initialized'); }); } } } // End initializeScript function })(); // End immediate execution // Load Air Datepicker if not already loaded if (typeof AirDatepicker === 'undefined') { const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/air-datepicker@3.5.3/air-datepicker.js'; document.head.appendChild(script); }