/**
* ISTANY Forms Module v1.0
* Vanilla JS | fetch() | WP REST API
* Formulaires : contact, guide, multistep devis
* Multi-location : chaque formulaire utilise data-location
*/
(function () {
‘use strict’;
// ─── Config (injectée par PHP via window.ISTANY_FORM) ───────────────────────
var CFG = window.ISTANY_FORM || {};
var ENDPOINT = CFG.endpoint || ‘/wp-json/istany/v1/contact’;
var NONCE = CFG.nonce || »;
// ─── Validation ─────────────────────────────────────────────────────────────
function clearErrors(scope) {
scope.querySelectorAll(‘.istany-error’).forEach(function (el) { el.remove(); });
scope.querySelectorAll(‘.istany-field-error’).forEach(function (el) {
el.classList.remove(‘istany-field-error’);
});
}
function showFieldError(field, message) {
field.classList.add(‘istany-field-error’);
var err = document.createElement(‘span’);
err.className = ‘istany-error’;
err.setAttribute(‘role’, ‘alert’);
err.textContent = message;
field.parentNode.insertBefore(err, field.nextSibling);
}
function showGlobalError(container, message) {
var existing = container.querySelector(‘.istany-error-global’);
if (existing) existing.remove();
var err = document.createElement(‘p’);
err.className = ‘istany-error istany-error-global’;
err.setAttribute(‘role’, ‘alert’);
err.textContent = message;
container.insertBefore(err, container.firstChild);
}
function isValidEmail(value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}
function validateFields(fields) {
var valid = true;
Array.prototype.forEach.call(fields, function (field) {
if (!field.required) return;
if (!field.value.trim()) {
showFieldError(field, ‘Ce champ est obligatoire.’);
valid = false;
} else if (field.type === ’email’ && !isValidEmail(field.value.trim())) {
showFieldError(field, ‘Adresse e-mail invalide.’);
valid = false;
}
});
return valid;
}
// ─── Multistep : stepper ────────────────────────────────────────────────────
function updateStepper(form, step) {
for (var i = 1; i <= 3; i++) {
var el = form.querySelector('[data-stepper-step="' + i + '"]');
if (!el) continue;
el.classList.remove('active', 'done');
if (i < step) el.classList.add('done');
else if (i === step) el.classList.add('active');
}
}
function getCurrentStep(form) {
var active = form.querySelector('[data-step].active');
return active ? parseInt(active.dataset.step, 10) : 1;
}
function goToStep(form, step) {
var current = getCurrentStep(form);
var from = form.querySelector('[data-step="' + current + '"]');
var to = form.querySelector('[data-step="' + step + '"]');
if (from) from.classList.remove('active');
if (to) to.classList.add('active');
updateStepper(form, step);
}
// ─── Multistep : cartes cliquables ──────────────────────────────────────────
function bindCards(form) {
form.querySelectorAll('.ms-choice:not(.ms-multi)').forEach(function (card) {
card.addEventListener('click', function () {
var group = card.dataset.group;
form.querySelectorAll('.ms-choice[data-group="' + group + '"]').forEach(function (c) {
c.classList.remove('selected');
});
card.classList.add('selected');
});
});
form.querySelectorAll('.ms-choice.ms-multi').forEach(function (card) {
card.addEventListener('click', function () { card.classList.toggle('selected'); });
});
}
// ─── Collecte des données ───────────────────────────────────────────────────
function collectData(form) {
var data = {
location: form.dataset.location || '',
form_id: form.id || '',
form_type: form.dataset.formType || 'contact',
nonce: NONCE,
website: '',
prenom: '',
nom: '',
email: '',
tel: '',
date: '',
lieu: '',
message: '',
style: '',
prestation: ''
};
// Honeypot
var hp = form.querySelector('[name="website"]');
if (hp) data.website = hp.value;
// Champs nommés standards
['prenom', 'nom', 'email', 'tel', 'date', 'lieu', 'message'].forEach(function (name) {
var el = form.querySelector('[name="' + name + '"]');
if (!el) {
// Fallback pour les IDs du multistep (msPrenom, msEmail…)
var camel = name.charAt(0).toUpperCase() + name.slice(1);
el = form.querySelector('#ms' + camel);
}
if (el) data[name] = el.value.trim();
});
// Cartes sélectionnées
var prests = [];
form.querySelectorAll('.ms-choice.selected:not(.ms-multi)').forEach(function (c) {
if (c.dataset.group === 'style') data.style = c.dataset.value;
});
form.querySelectorAll('.ms-choice.ms-multi.selected').forEach(function (c) {
prests.push(c.dataset.value);
});
data.prestation = prests.join(', ');
return data;
}
// ─── Soumission AJAX ────────────────────────────────────────────────────────
function submitForm(form, submitBtn, onSuccess) {
var origLabel = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.textContent = 'Envoi en cours…';
fetch(ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(collectData(form))
})
.then(function (res) {
return res.json().then(function (json) { return { ok: res.ok, json: json }; });
})
.then(function (result) {
if (result.ok && result.json.success) {
onSuccess(result.json.message || '');
} else {
submitBtn.disabled = false;
submitBtn.textContent = origLabel;
var nav = submitBtn.closest('.ms-nav') || submitBtn.parentNode;
showGlobalError(nav, result.json.message || 'Une erreur est survenue. Veuillez réessayer.');
}
})
.catch(function () {
submitBtn.disabled = false;
submitBtn.textContent = origLabel;
var nav = submitBtn.closest('.ms-nav') || submitBtn.parentNode;
showGlobalError(nav, 'Erreur réseau. Veuillez réessayer.');
});
}
// ─── Init : formulaire multistep ────────────────────────────────────────────
function initMultistepForm(form) {
bindCards(form);
// Navigation (délégation d'événements)
form.addEventListener('click', function (e) {
var btn = e.target.closest('[data-action]');
if (!btn || btn.type === 'submit') return;
var action = btn.dataset.action;
var step = parseInt(btn.dataset.step, 10);
if (action === 'next') goToStep(form, step + 1);
if (action === 'prev') goToStep(form, step - 1);
});
// Soumission
form.addEventListener('submit', function (e) {
e.preventDefault();
clearErrors(form);
if (!validateFields(form.querySelectorAll('[required]'))) return;
var submitBtn = form.querySelector('[type="submit"]');
submitForm(form, submitBtn, function () {
var ms3 = form.querySelector('[data-step="3"]');
var success = form.querySelector('#msSuccess');
if (ms3) ms3.classList.remove('active');
if (success) success.classList.add('active');
});
});
}
// ─── Init : formulaire simple ────────────────────────────────────────────────
function initSimpleForm(form) {
form.addEventListener('submit', function (e) {
e.preventDefault();
clearErrors(form);
if (!validateFields(form.querySelectorAll('[required]'))) return;
var submitBtn = form.querySelector('[type="submit"]');
submitForm(form, submitBtn, function (message) {
form.style.display = 'none';
var box = document.createElement('div');
box.className = 'istany-success';
box.textContent = message || 'Merci ! Nous vous répondrons sous 48 heures.';
form.parentNode.insertBefore(box, form.nextSibling);
});
});
}
// ─── Auto-initialisation ────────────────────────────────────────────────────
function init() {
document.querySelectorAll('form[data-location]').forEach(function (form) {
if (form.dataset.formType === 'multistep-devis') {
initMultistepForm(form);
} else {
initSimpleForm(form);
}
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
}());