/** * WP BnB Contact Form 7 Integration * * Handles dynamic form behavior for booking forms. * * @package Magdev\WpBnb */ (function() { 'use strict'; const WpBnbCF7 = { config: window.wpBnbCF7 || {}, /** * Initialize all CF7 integration features. */ init: function() { this.initBuildingRoomFilter(); this.initDateValidation(); this.initCapacityValidation(); this.initAvailabilityCheck(); this.initPriceDisplay(); }, /** * Filter rooms dropdown when building is selected. */ initBuildingRoomFilter: function() { document.querySelectorAll('[data-bnb-building-select]').forEach(function(buildingSelect) { const form = buildingSelect.closest('form'); const roomSelect = form ? form.querySelector('[data-bnb-room-select]') : null; if (!roomSelect) return; // Store all options for filtering const allOptions = Array.from(roomSelect.querySelectorAll('option, optgroup')); const originalHTML = roomSelect.innerHTML; buildingSelect.addEventListener('change', function() { const selectedBuilding = buildingSelect.value; // Show all options if no building selected if (!selectedBuilding) { roomSelect.innerHTML = originalHTML; roomSelect.dispatchEvent(new Event('change')); return; } // Filter options by building roomSelect.innerHTML = ''; // Add placeholder option const placeholder = document.createElement('option'); placeholder.value = ''; placeholder.textContent = WpBnbCF7.config.i18n?.selectRoom || '-- Select Room --'; roomSelect.appendChild(placeholder); allOptions.forEach(function(el) { if (el.tagName === 'OPTGROUP') { // Check if any options in this optgroup match const matchingOptions = Array.from(el.querySelectorAll('option')).filter(function(opt) { return opt.dataset.building === selectedBuilding; }); if (matchingOptions.length > 0) { const clonedGroup = el.cloneNode(false); matchingOptions.forEach(function(opt) { clonedGroup.appendChild(opt.cloneNode(true)); }); roomSelect.appendChild(clonedGroup); } } }); // Trigger change to update dependent fields roomSelect.dispatchEvent(new Event('change')); }); }); }, /** * Validate and link check-in/check-out dates. */ initDateValidation: function() { document.querySelectorAll('[data-bnb-checkin]').forEach(function(checkinInput) { const form = checkinInput.closest('form'); const checkoutInput = form ? form.querySelector('[data-bnb-checkout]') : null; if (!checkoutInput) return; // Set minimum check-in to today const today = WpBnbCF7.formatDate(new Date()); if (!checkinInput.getAttribute('min') || checkinInput.getAttribute('min') < today) { checkinInput.setAttribute('min', today); } checkinInput.addEventListener('change', function() { if (checkinInput.value) { // Set checkout minimum to checkin + 1 day const minCheckout = new Date(checkinInput.value); minCheckout.setDate(minCheckout.getDate() + 1); checkoutInput.setAttribute('min', WpBnbCF7.formatDate(minCheckout)); // Clear checkout if it's now invalid if (checkoutInput.value && checkoutInput.value <= checkinInput.value) { checkoutInput.value = ''; } // Trigger availability check WpBnbCF7.triggerAvailabilityCheck(form); } }); checkoutInput.addEventListener('change', function() { if (checkoutInput.value && checkinInput.value) { if (checkoutInput.value <= checkinInput.value) { alert(WpBnbCF7.config.i18n?.invalidDateRange || 'Check-out must be after check-in'); checkoutInput.value = ''; return; } // Trigger availability check WpBnbCF7.triggerAvailabilityCheck(form); } }); }); }, /** * Validate guest count against room capacity. */ initCapacityValidation: function() { document.querySelectorAll('[data-bnb-guests]').forEach(function(guestsInput) { const form = guestsInput.closest('form'); const roomSelect = form ? form.querySelector('[data-bnb-room-select]') : null; if (!roomSelect) return; const validateCapacity = function() { const selectedOption = roomSelect.selectedOptions[0]; const capacity = parseInt(selectedOption?.dataset.capacity || 99, 10); const guests = parseInt(guestsInput.value || 0, 10); // Update max attribute guestsInput.setAttribute('max', capacity); // Show warning if over capacity const wrapper = guestsInput.closest('.wpcf7-form-control-wrap'); let warning = wrapper ? wrapper.querySelector('.wp-bnb-capacity-warning') : null; if (guests > capacity) { if (!warning && wrapper) { warning = document.createElement('span'); warning.className = 'wp-bnb-capacity-warning'; wrapper.appendChild(warning); } if (warning) { warning.textContent = (WpBnbCF7.config.i18n?.capacityExceeded || 'Maximum %d guests for this room').replace('%d', capacity); } } else if (warning) { warning.remove(); } }; roomSelect.addEventListener('change', validateCapacity); guestsInput.addEventListener('change', validateCapacity); guestsInput.addEventListener('input', validateCapacity); }); }, /** * Initialize AJAX availability checking. */ initAvailabilityCheck: function() { // Find forms with availability display document.querySelectorAll('.wp-bnb-availability-status').forEach(function(statusEl) { const form = statusEl.closest('form'); if (form) { form._availabilityStatus = statusEl; } }); }, /** * Trigger availability check for a form. * * @param {HTMLFormElement} form Form element. */ triggerAvailabilityCheck: function(form) { const roomSelect = form.querySelector('[data-bnb-room-select]'); const checkinInput = form.querySelector('[data-bnb-checkin]'); const checkoutInput = form.querySelector('[data-bnb-checkout]'); const statusEl = form._availabilityStatus || form.querySelector('.wp-bnb-availability-status'); const priceEl = form.querySelector('.wp-bnb-price-display'); if (!roomSelect || !checkinInput || !checkoutInput) return; const roomId = roomSelect.value; const checkIn = checkinInput.value; const checkOut = checkoutInput.value; if (!roomId || !checkIn || !checkOut) { if (statusEl) statusEl.innerHTML = ''; if (priceEl) priceEl.innerHTML = ''; return; } // Show loading state if (statusEl) { statusEl.innerHTML = '' + (WpBnbCF7.config.i18n?.checking || 'Checking availability...') + ''; } // Make AJAX request WpBnbCF7.ajax('wp_bnb_get_availability', { room_id: roomId, check_in: checkIn, check_out: checkOut }) .then(function(response) { if (statusEl) { if (response.available) { let html = '' + (WpBnbCF7.config.i18n?.available || 'Room is available!') + ''; statusEl.innerHTML = html; } else { statusEl.innerHTML = '' + (WpBnbCF7.config.i18n?.unavailable || 'Room is not available for these dates') + ''; } } // Update price display if (priceEl && response.available && response.price_formatted) { priceEl.innerHTML = '' + (WpBnbCF7.config.i18n?.estimatedTotal || 'Estimated Total') + ': ' + '' + response.price_formatted + ' ' + '(' + response.nights + ' ' + (WpBnbCF7.config.i18n?.nights || 'nights') + ')'; } else if (priceEl) { priceEl.innerHTML = ''; } }) .catch(function(error) { console.error('Availability check failed:', error); if (statusEl) { statusEl.innerHTML = ''; } }); }, /** * Initialize price display updates. */ initPriceDisplay: function() { const self = this; document.querySelectorAll('.wp-bnb-price-display').forEach(function(priceEl) { const form = priceEl.closest('form'); if (!form) return; const updatePrice = self.debounce(function() { const roomSelect = form.querySelector('[data-bnb-room-select]'); const checkinInput = form.querySelector('[data-bnb-checkin]'); const checkoutInput = form.querySelector('[data-bnb-checkout]'); if (!roomSelect?.value || !checkinInput?.value || !checkoutInput?.value) { priceEl.innerHTML = ''; return; } self.ajax('wp_bnb_calculate_price', { room_id: roomSelect.value, check_in: checkinInput.value, check_out: checkoutInput.value }) .then(function(response) { priceEl.innerHTML = '' + (self.config.i18n?.estimatedTotal || 'Estimated Total') + ': ' + '' + response.price_formatted + ' ' + '(' + response.nights + ' ' + (self.config.i18n?.nights || 'nights') + ')'; }) .catch(function() { priceEl.innerHTML = ''; }); }, 500); // Bind to relevant field changes form.querySelectorAll('[data-bnb-room-select], [data-bnb-checkin], [data-bnb-checkout]') .forEach(function(input) { input.addEventListener('change', updatePrice); }); }); }, /** * Make AJAX request. * * @param {string} action AJAX action name. * @param {object} data Request data. * @return {Promise} */ ajax: function(action, data) { data = data || {}; const formData = new FormData(); formData.append('action', action); formData.append('nonce', this.config.nonce || ''); Object.keys(data).forEach(function(key) { if (data[key] !== null && data[key] !== undefined) { formData.append(key, data[key]); } }); return fetch(this.config.ajaxUrl || '/wp-admin/admin-ajax.php', { method: 'POST', body: formData, credentials: 'same-origin' }) .then(function(response) { return response.json(); }) .then(function(responseData) { if (!responseData.success) { throw new Error(responseData.data?.message || 'Request failed'); } return responseData.data; }); }, /** * Format date as YYYY-MM-DD. * * @param {Date} date Date object. * @return {string} */ formatDate: function(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return year + '-' + month + '-' + day; }, /** * Debounce function. * * @param {function} func Function to debounce. * @param {number} wait Milliseconds to wait. * @return {function} */ debounce: function(func, wait) { let timeout; return function() { const context = this; const args = arguments; clearTimeout(timeout); timeout = setTimeout(function() { func.apply(context, args); }, wait); }; } }; // Initialize on DOM ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { WpBnbCF7.init(); }); } else { WpBnbCF7.init(); } // Re-initialize on CF7 form reset document.addEventListener('wpcf7reset', function(event) { setTimeout(function() { WpBnbCF7.init(); }, 100); }); // Export to window for external access window.WpBnbCF7 = WpBnbCF7; })();