(function() {
'use strict';
let swiperInstances = {};
function loadSwiper() {
return new Promise((resolve) => {
// Check if Swiper is already loaded
if (window.Swiper) {
resolve();
return;
}
// Load Swiper CSS
const swiperCSS = document.createElement('link');
swiperCSS.rel = 'stylesheet';
swiperCSS.href = 'https://cdnjs.cloudflare.com/ajax/libs/Swiper/11.0.5/swiper-bundle.min.css';
document.head.appendChild(swiperCSS);
// Load Swiper JS
const swiperJS = document.createElement('script');
swiperJS.src = 'https://cdnjs.cloudflare.com/ajax/libs/Swiper/11.0.5/swiper-bundle.min.js';
swiperJS.onload = () => resolve();
document.head.appendChild(swiperJS);
});
}
// Widget configuration
const WIDGET_CONFIG = {
apiBase: window.API_BASE || '/api',
propertyId: window.PROPERTY_ID,
theme: 'light'
};
// Widget state
let widgetData = {
property: null,
unitPlans: [],
selectedPlan: null,
filters: {
bedrooms: 'all',
priceRange: 'all',
sortBy: 'price'
}
};
// Utility functions
function createElement(tag, className, innerHTML) {
const element = document.createElement(tag);
if (className) element.className = className;
if (innerHTML) element.innerHTML = innerHTML;
return element;
}
function formatPrice(price) {
if (!price) return 'Contact for pricing';
return `$${price.toLocaleString()}`;
}
function formatPriceRange(min, max) {
if (!min && !max) return 'Contact for pricing';
if (min && max && min !== max) return `${formatPrice(min)} - ${formatPrice(max)}`;
return `Starting at ${formatPrice(min || max)}`;
}
// API functions
async function fetchPropertyData() {
try {
const response = await fetch(`${WIDGET_CONFIG.apiBase}/widget/property/${WIDGET_CONFIG.propertyId}`);
if (!response.ok) throw new Error('Failed to fetch property data');
return await response.json();
} catch (error) {
console.error('Error fetching property data:', error);
return null;
}
}
async function trackApplicationClick(planId) {
try {
await fetch(`${WIDGET_CONFIG.apiBase}/widget/track-application`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
property_id: WIDGET_CONFIG.propertyId,
plan_id: planId,
referral_source: 'widget'
})
});
} catch (error) {
console.error('Error tracking application click:', error);
}
}
// Widget rendering functions
function renderPropertyHeader(property) {
const header = createElement('div', 'property-header');
// Banner (if enabled)
if (property.banner_enabled && property.banner_text) {
const banner = createElement('div', 'property-banner');
banner.style.backgroundColor = property.banner_background_color || '#28a745';
banner.style.color = property.banner_text_color || '#ffffff';
banner.innerHTML = `
${property.banner_text}
`;
header.appendChild(banner);
}
// Property info
const info = createElement('div', 'property-info');
info.innerHTML = `
${property.property_logo ? `

` : ''}
${property.property_name}
${property.property_address}, ${property.property_city}, ${property.property_state} ${property.property_zip}
${property.property_description ? `
${property.property_description}
` : ''}
`;
header.appendChild(info);
return header;
}
function renderFilters() {
const filters = createElement('div', 'widget-filters');
filters.innerHTML = `
`;
// Add event listeners
filters.querySelector('#bedrooms-filter').addEventListener('change', (e) => {
widgetData.filters.bedrooms = e.target.value;
renderUnitPlans();
});
filters.querySelector('#sort-filter').addEventListener('change', (e) => {
widgetData.filters.sortBy = e.target.value;
renderUnitPlans();
});
return filters;
}
function renderUnitPlan(plan) {
const planElement = createElement('div', 'unit-plan');
const primaryImage = plan.media.find(m => m.is_featured);
planElement.innerHTML = `
${primaryImage ? `

` : '
No Image Available
'}
${plan.plan_name}
${plan.bedrooms === 0 ? 'Studio' : `${plan.bedrooms} Bed`}
${plan.bathrooms} Bath
${plan.square_feet ? `${plan.square_feet} sq ft` : ''}
${formatPriceRange(plan.rent_range_min || plan.base_rent, plan.rent_range_max)}
${plan.availability_count > 0 ?
`
${plan.availability_count} units available
` :
'
Contact for availability
'
}
${plan.availability_notes ? `
${plan.availability_notes}
` : ''}
`;
return planElement;
}
function renderUnitPlans() {
const container = document.querySelector('.unit-plans-container');
if (!container) return;
// Filter and sort unit plans
let filteredPlans = [...widgetData.unitPlans];
// Filter by bedrooms
if (widgetData.filters.bedrooms !== 'all') {
const bedroomCount = parseInt(widgetData.filters.bedrooms);
filteredPlans = filteredPlans.filter(plan => {
if (bedroomCount === 4) return plan.bedrooms >= 4;
return plan.bedrooms === bedroomCount;
});
}
// Sort plans
filteredPlans.sort((a, b) => {
switch (widgetData.filters.sortBy) {
case 'price':
const priceA = a.rent_range_min || a.base_rent || 0;
const priceB = b.rent_range_min || b.base_rent || 0;
return priceA - priceB;
case 'price-desc':
const priceDescA = a.rent_range_max || a.base_rent || 0;
const priceDescB = b.rent_range_max || b.base_rent || 0;
return priceDescB - priceDescA;
case 'bedrooms':
return a.bedrooms - b.bedrooms;
case 'sqft':
return (b.square_feet || 0) - (a.square_feet || 0);
default:
return 0;
}
});
// Clear and render
container.innerHTML = '';
filteredPlans.forEach(plan => {
container.appendChild(renderUnitPlan(plan));
});
if (filteredPlans.length === 0) {
container.innerHTML = 'No units match your criteria.
';
}
}
async function renderGalleryModal(plan) {
// Load Swiper if not already loaded
await loadSwiper();
const modalId = `gallery-modal-${plan.plan_id}`;
const modal = createElement('div', 'plan-modal');
modal.innerHTML = `
${plan.media.length > 0 ? `
${plan.media.map(media => `
`).join('')}
${plan.media.length > 1 ? `
${plan.media.map(media => `
`).join('')}
` : ''}
` : '
No images available
'}
`;
document.body.appendChild(modal);
// Initialize Swiper after modal is added to DOM
if (plan.media.length > 0) {
setTimeout(() => {
// Initialize thumbnail swiper if more than 1 image
let galleryThumbs = null;
if (plan.media.length > 1) {
galleryThumbs = new Swiper(`.gallery-thumbs-${plan.plan_id}`, {
spaceBetween: 10,
slidesPerView: 4,
freeMode: true,
watchSlidesProgress: true,
breakpoints: {
320: {
slidesPerView: 3,
},
640: {
slidesPerView: 4,
},
768: {
slidesPerView: 5,
}
}
});
swiperInstances[`thumbs-${plan.plan_id}`] = galleryThumbs;
}
// Initialize main swiper
const gallerySwiper = new Swiper(`.gallery-swiper-${plan.plan_id}`, {
spaceBetween: 10,
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
pagination: {
el: '.swiper-pagination',
clickable: true,
},
thumbs: galleryThumbs ? {
swiper: galleryThumbs,
} : null,
loop: plan.media.length > 1,
keyboard: {
enabled: true,
}
});
swiperInstances[`main-${plan.plan_id}`] = gallerySwiper;
}, 100);
}
// Close modal when clicking outside
modal.addEventListener('click', (e) => {
if (e.target === modal) {
closePlanModal();
}
});
}
async function renderPlanModal(plan) {
await loadSwiper();
const modal = createElement('div', 'plan-modal');
modal.innerHTML = `
${plan.media.length > 0 ? `
${plan.media.map(media => `
`).join('')}
${plan.media.length > 1 ? `
${plan.media.map(media => `
`).join('')}
` : ''}
` : '
No images available
'}
Pricing
${formatPriceRange(plan.rent_range_min || plan.base_rent, plan.rent_range_max)}
${plan.deposit_amount ? `
Deposit: ${formatPrice(plan.deposit_amount)}
` : ''}
${plan.application_fee ? `
Application Fee: ${formatPrice(plan.application_fee)}
` : ''}
Availability
${plan.availability_count > 0 ?
`${plan.availability_count} unit${plan.availability_count > 1 ? 's' : ''} available` :
'Contact for availability'
}
${plan.availability_notes ? `
${plan.availability_notes}
` : ''}
${plan.description ? `
` : ''}
${plan.amenities && plan.amenities.length > 0 ? `
Amenities
${plan.amenities.map(amenity => `
`).join('')}
` : ''}
${plan.appliances && plan.appliances.length > 0 ? `
Appliances
${plan.appliances.map(appliance => `
`).join('')}
` : ''}
`;
document.body.appendChild(modal);
if (plan.media.length > 0) {
setTimeout(() => {
// Initialize thumbnail swiper if more than 1 image
let detailThumbs = null;
if (plan.media.length > 1) {
detailThumbs = new Swiper(`.detail-thumbs-${plan.plan_id}`, {
spaceBetween: 10,
slidesPerView: 4,
freeMode: true,
watchSlidesProgress: true,
breakpoints: {
320: { slidesPerView: 3 },
640: { slidesPerView: 4 },
768: { slidesPerView: 5 },
1024: { slidesPerView: 6 }
}
});
swiperInstances[`detail-thumbs-${plan.plan_id}`] = detailThumbs;
}
const detailSwiper = new Swiper(`.detail-swiper-${plan.plan_id}`, {
spaceBetween: 0,
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
pagination: {
el: '.swiper-pagination',
clickable: true,
dynamicBullets: true,
},
thumbs: detailThumbs ? {
swiper: detailThumbs,
} : null,
loop: plan.media.length > 1,
keyboard: {
enabled: true,
},
effect: 'fade',
fadeEffect: {
crossFade: true
}
});
swiperInstances[`detail-main-${plan.plan_id}`] = detailSwiper;
}, 100);
}
// Close modal when clicking outside
modal.addEventListener('click', (e) => {
if (e.target === modal) {
closePlanModal();
}
});
}
// Global functions (attached to window for onclick handlers)
window.showPlanDetails = function(planId) {
const plan = widgetData.unitPlans.find(p => p.plan_id === planId);
if (plan) {
renderPlanModal(plan);
}
};
window.showGallery = function(planId) {
const plan = widgetData.unitPlans.find(p => p.plan_id === planId);
if (plan) {
renderGalleryModal(plan);
}
};
window.closePlanModal = function() {
const modal = document.querySelector('.plan-modal');
if (modal) {
// Destroy Swiper instances to prevent memory leaks
Object.keys(swiperInstances).forEach(key => {
if (swiperInstances[key]) {
swiperInstances[key].destroy();
delete swiperInstances[key];
}
});
modal.remove();
}
};
window.applyNow = function(planId) {
const plan = widgetData.unitPlans.find(p => p.plan_id === planId);
if (plan && widgetData.property) {
// Track the application click
trackApplicationClick(planId);
// Redirect to application URL
const applicationUrl = widgetData.property.default_application_url;
if (applicationUrl) {
window.open(applicationUrl, '_blank');
} else {
alert('Please contact the property for application information.');
}
}
};
// Widget styles
function injectStyles() {
const primaryColor = widgetData.property?.widget_primary_color || '#007bff';
const secondaryColor = widgetData.property?.widget_secondary_color || '#6c757d';
const fontFamily = widgetData.property?.widget_font_family || 'Arial, sans-serif';
const styles = `
`;
document.head.insertAdjacentHTML('beforeend', styles);
}
// Initialize widget
async function initWidget() {
const container = document.getElementById('widget-container');
if (!container) {
console.error('Widget container not found');
return;
}
// Show loading
container.innerHTML = 'Loading property information...
';
// Fetch data
const data = await fetchPropertyData();
if (!data) {
container.innerHTML = 'Failed to load property information. Please try again later.
';
return;
}
widgetData.property = data.property;
widgetData.unitPlans = data.unitPlans;
// Inject styles
injectStyles();
// Render widget
const widget = createElement('div', 'property-availability-widget');
// Property header
widget.appendChild(renderPropertyHeader(widgetData.property));
// Filters
widget.appendChild(renderFilters());
// Unit plans container
const unitPlansContainer = createElement('div', 'unit-plans-container');
widget.appendChild(unitPlansContainer);
// Replace container content
container.innerHTML = '';
container.appendChild(widget);
// Initial render of unit plans
renderUnitPlans();
}
// Start the widget when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initWidget);
} else {
initWidget();
}
})();