/**
* AgentForms WordPress Plugin — AJAX Submission Handler
*
* Handles form submission, multi-step navigation, conditional field visibility,
* and client-side validation. All server-side logic remains on agentforms.io.
*
* Loaded by includes/shortcode.php via wp_enqueue_script().
*/
(function () {
'use strict';
// ── Conditional field engine ──────────────────────────────────────
function evaluateCondition(condition, allData) {
if (!condition) return true;
const targetValue = allData[condition.field] || '';
const op = condition.operator || 'eq';
const compareValue = String(condition.value || '').trim();
const targetStr = String(targetValue).trim();
if (op === 'filled') return Boolean(targetStr);
if (op === 'empty') return !Boolean(targetStr);
if (['gt', 'lt', 'gte', 'lte'].includes(op)) {
const t = parseFloat(targetStr);
const c = parseFloat(compareValue);
if (isNaN(t) || isNaN(c)) return false;
switch (op) {
case 'gt': return t > c;
case 'lt': return t < c;
case 'gte': return t >= c;
case 'lte': return t <= c;
}
}
switch (op) {
case 'eq': return targetStr === compareValue;
case 'neq': return targetStr !== compareValue;
case 'contains': return targetStr.includes(compareValue);
case 'not_contains': return !targetStr.includes(compareValue);
case 'in': return targetStr.split(',').includes(compareValue);
case 'not_in': return !targetStr.split(',').includes(compareValue);
default: return true;
}
}
function updateFieldVisibility(form) {
const data = getFormData(form);
form.querySelectorAll('[data-af-condition]').forEach(function (fieldWrapper) {
const conditionStr = fieldWrapper.getAttribute('data-af-condition');
if (!conditionStr) return;
let condition;
try {
condition = JSON.parse(conditionStr);
} catch (e) {
return;
}
const visible = evaluateCondition(condition, data);
fieldWrapper.style.display = visible ? '' : 'none';
});
}
// ── Form data collection ──────────────────────────────────────────
function getFormData(form) {
const data = {};
const formData = new FormData(form);
for (let [key, value] of formData.entries()) {
if (key.startsWith('_fr_')) continue; // honeypot
// Handle radio/checkbox — only include checked values
const input = form.querySelector('[name="' + key + '"]');
if (input && input.type === 'checkbox' && !input.checked) continue;
if (input && input.type === 'radio' && !input.checked) continue;
data[key] = value;
}
return data;
}
// ── Multi-step navigation ─────────────────────────────────────────
function initMultiStep(form) {
const steps = form.querySelectorAll('.agentforms-step');
if (steps.length <= 1) return;
let currentStep = 0;
const backBtn = form.querySelector('.agentforms-btn-back');
const nextBtn = form.querySelector('.agentforms-btn-next');
const submitBtn = form.querySelector('.agentforms-btn-submit');
const progressBar = form.querySelector('.agentforms-progress-fill');
const stepDots = form.querySelectorAll('.agentforms-step-dot');
const stepTitle = form.querySelector('.agentforms-step-title');
function goToStep(index) {
steps.forEach(function (step, i) {
step.style.display = i === index ? '' : 'none';
});
if (stepDots[index]) {
stepDots.forEach(function (dot, i) {
dot.classList.toggle('active', i <= index);
if (i < index) dot.classList.add('completed');
else dot.classList.remove('completed');
});
}
if (progressBar) {
progressBar.style.width = ((index + 1) / steps.length * 100) + '%';
}
if (stepTitle) {
stepTitle.textContent = 'Step ' + (index + 1) + ' of ' + steps.length;
}
backBtn.style.display = index > 0 ? '' : 'none';
if (index === steps.length - 1) {
nextBtn.style.display = 'none';
submitBtn.style.display = '';
} else {
nextBtn.style.display = '';
submitBtn.style.display = 'none';
}
currentStep = index;
}
if (nextBtn) {
nextBtn.addEventListener('click', function () {
if (currentStep < steps.length - 1) goToStep(currentStep + 1);
});
}
if (backBtn) {
backBtn.addEventListener('click', function () {
if (currentStep > 0) goToStep(currentStep - 1);
});
}
}
// ── Client-side validation ────────────────────────────────────────
function validateField(fieldEl) {
const key = fieldEl.closest('.agentforms-field')?.getAttribute('data-af-field');
if (!key) return null;
const validationStr = fieldEl.closest('.agentforms-field')?.getAttribute('data-af-validation');
if (!validationStr) return null;
let validations;
try {
validations = JSON.parse(validationStr);
} catch (e) {
return null;
}
const value = fieldEl.value || '';
for (const rule of validations) {
if (rule === 'required' && !value.trim()) {
const errorMsg = fieldEl.closest('.agentforms-field')?.getAttribute('data-af-error') || 'This field is required.';
return errorMsg;
}
const parts = rule.split(':');
const ruleName = parts[0];
const ruleValue = parts.slice(1).join(':');
switch (ruleName) {
case 'minLength':
if (value.length < parseInt(ruleValue)) {
return fieldEl.closest('.agentforms-field')?.getAttribute('data-af-error') || 'Minimum ' + ruleValue + ' characters required.';
}
break;
case 'maxLength':
if (value.length > parseInt(ruleValue)) {
return fieldEl.closest('.agentforms-field')?.getAttribute('data-af-error') || 'Maximum ' + ruleValue + ' characters allowed.';
}
break;
case 'min':
if (!isNaN(parseFloat(value)) && parseFloat(value) < parseFloat(ruleValue)) {
return fieldEl.closest('.agentforms-field')?.getAttribute('data-af-error') || 'Must be at least ' + ruleValue + '.';
}
break;
case 'max':
if (!isNaN(parseFloat(value)) && parseFloat(value) > parseFloat(ruleValue)) {
return fieldEl.closest('.agentforms-field')?.getAttribute('data-af-error') || 'Must be at most ' + ruleValue + '.';
}
break;
}
}
return null;
}
function showError(fieldEl, message) {
const wrapper = fieldEl.closest('.agentforms-field');
if (!wrapper) return;
const errorEl = wrapper.querySelector('.agentforms-field-error');
if (errorEl) {
errorEl.textContent = message;
errorEl.style.display = '';
}
fieldEl.classList.add('af-input-error');
}
function clearError(fieldEl) {
const wrapper = fieldEl.closest('.agentforms-field');
if (!wrapper) return;
const errorEl = wrapper.querySelector('.agentforms-field-error');
if (errorEl) {
errorEl.textContent = '';
errorEl.style.display = 'none';
}
fieldEl.classList.remove('af-input-error');
}
// ── Main submission handler ───────────────────────────────────────
function initForm(form) {
const submitUrl = form.getAttribute('data-af-submit-url');
const successMsg = form.getAttribute('data-af-success') || 'Thank you! Your response has been received.';
if (!submitUrl) return;
// Init multi-step if applicable
initMultiStep(form);
// Real-time validation on input
form.querySelectorAll('.agentforms-input, .agentforms-textarea, .agentforms-select').forEach(function (input) {
input.addEventListener('input', function () {
clearError(input);
updateFieldVisibility(form);
});
input.addEventListener('change', function () {
clearError(input);
updateFieldVisibility(form);
});
});
form.addEventListener('submit', function (e) {
e.preventDefault();
// Client-side validation
const inputs = form.querySelectorAll('.agentforms-input, .agentforms-textarea, .agentforms-select');
let hasError = false;
inputs.forEach(function (input) {
const error = validateField(input);
if (error) {
showError(input, error);
hasError = true;
} else {
clearError(input);
}
});
if (hasError) return;
// Show loading
const loadingEl = form.querySelector('.agentforms-loading');
const errorMsgEl = form.querySelector('.agentforms-error-msg');
const successEl = form.querySelector('.agentforms-success');
const submitRow = form.querySelector('.agentforms-submit-row, .agentforms-step-actions');
if (loadingEl) loadingEl.style.display = '';
if (errorMsgEl) errorMsgEl.style.display = 'none';
if (successEl) successEl.style.display = 'none';
if (submitRow) submitRow.style.display = 'none';
// Collect data
const data = getFormData(form);
// Submit via fetch
fetch(submitUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
body: JSON.stringify(data),
})
.then(function (response) {
return response.json().then(function (body) {
return { status: response.status, body: body };
});
})
.then(function (result) {
if (result.status >= 200 && result.status < 300 && result.body.success) {
// Success
if (successEl) {
var msgEl = successEl.querySelector('.agentforms-success-message');
if (msgEl) msgEl.textContent = successMsg;
successEl.style.display = '';
}
} else {
// Error
var errorText = result.body && result.body.error ? result.body.error : 'Submission failed. Please try again.';
if (errorMsgEl) {
var errorTextEl = errorMsgEl.querySelector('.agentforms-error-text');
if (errorTextEl) errorTextEl.textContent = errorText;
errorMsgEl.style.display = '';
}
if (submitRow) submitRow.style.display = '';
}
})
.catch(function (err) {
var errorText = 'Network error. Please check your connection and try again.';
if (errorMsgEl) {
var errorTextEl = errorMsgEl.querySelector('.agentforms-error-text');
if (errorTextEl) errorTextEl.textContent = errorText;
errorMsgEl.style.display = '';
}
if (submitRow) submitRow.style.display = '';
})
.finally(function () {
if (loadingEl) loadingEl.style.display = 'none';
});
});
// Initial visibility check
updateFieldVisibility(form);
}
// ── Init on DOM ready ─────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.agentforms-form').forEach(initForm);
});
// Expose for programmatic use (e.g., after AJAX content loads)
window.AgentForms = {
initForm: initForm,
init: function () {
document.querySelectorAll('.agentforms-form').forEach(initForm);
},
};
})();