Files
wp-bnb/assets/js/cf7-integration.js
magdev 28350aabfa
All checks were successful
Create Release Package / build-release (push) Successful in 1m1s
Implement Phase 7: Contact Form 7 Integration (v0.7.0)
Add custom CF7 form tags for booking requests:
- [bnb_building_select] - Building filter dropdown
- [bnb_room_select] - Room selection with capacity data
- [bnb_date_checkin/checkout] - Date pickers with validation
- [bnb_guests] - Guest count with capacity limits

Features:
- Server-side validation for all fields
- Real-time AJAX availability checking
- Automatic price calculation display
- Booking creation on form submission
- Guest record creation/linking
- Custom mail tags for CF7 templates

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 16:30:30 +01:00

376 lines
11 KiB
JavaScript

/**
* 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 = '<span class="wp-bnb-checking">' + (WpBnbCF7.config.i18n?.checking || 'Checking availability...') + '</span>';
}
// 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 = '<span class="wp-bnb-available">' + (WpBnbCF7.config.i18n?.available || 'Room is available!') + '</span>';
statusEl.innerHTML = html;
} else {
statusEl.innerHTML = '<span class="wp-bnb-unavailable">' + (WpBnbCF7.config.i18n?.unavailable || 'Room is not available for these dates') + '</span>';
}
}
// Update price display
if (priceEl && response.available && response.price_formatted) {
priceEl.innerHTML = '<span class="wp-bnb-price-label">' + (WpBnbCF7.config.i18n?.estimatedTotal || 'Estimated Total') + ':</span> ' +
'<span class="wp-bnb-price-amount">' + response.price_formatted + '</span> ' +
'<span class="wp-bnb-nights">(' + response.nights + ' ' + (WpBnbCF7.config.i18n?.nights || 'nights') + ')</span>';
} 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 = '<span class="wp-bnb-price-label">' + (self.config.i18n?.estimatedTotal || 'Estimated Total') + ':</span> ' +
'<span class="wp-bnb-price-amount">' + response.price_formatted + '</span> ' +
'<span class="wp-bnb-nights">(' + response.nights + ' ' + (self.config.i18n?.nights || 'nights') + ')</span>';
})
.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;
})();