137 lines
5.4 KiB
JavaScript
137 lines
5.4 KiB
JavaScript
/**
|
|
* Contrôle de format à la saisie (`PTA.modules.fieldFormat`), page Agents.
|
|
*
|
|
* Vérifie l'adresse e-mail et le numéro de téléphone pendant la frappe, dans le
|
|
* formulaire de création comme dans la fiche de modification.
|
|
*
|
|
* COPIE ASSUMÉE DES RÈGLES SERVEUR (App\Core\ContactFormat) : le projet n'a ni
|
|
* bundler ni gabarit partagé PHP/JS, la duplication est le prix du retour
|
|
* immédiat. Elle ne déplace PAS la décision — le serveur revérifie et refuse en
|
|
* 422 quoi qu'affiche l'interface. Toute modification de ContactFormat doit être
|
|
* reportée ici ; l'inverse ne suffit jamais.
|
|
*
|
|
* Le contrôle passe par `setCustomValidity()` plutôt que par un attribut
|
|
* `pattern` : le numéro est comparé APRÈS retrait des séparateurs, ce qu'une
|
|
* expression HTML ne sait pas faire. Le champ invalide bloque donc l'envoi du
|
|
* formulaire par la validation native du navigateur, sans que les modules de
|
|
* formulaire aient à s'en occuper.
|
|
*
|
|
* Les deux champs restent FACULTATIFS : vide vaut valide.
|
|
*/
|
|
(() => {
|
|
'use strict';
|
|
|
|
const PTA = window.PTA;
|
|
if (!PTA) throw new Error('PTA core doit être chargé avant field-format.js');
|
|
|
|
/** Miroir de ContactFormat::EMAIL_PATTERN. */
|
|
const EMAIL_PATTERN = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)*\.[A-Za-z]{2,}$/;
|
|
/** Miroir de ContactFormat::PHONE_FR_PATTERN. */
|
|
const PHONE_FR_PATTERN = /^(?:\+33|0033|0)[1-9]\d{8}$/;
|
|
/** Miroir de ContactFormat::PHONE_INTERNATIONAL_PATTERN. */
|
|
const PHONE_INTERNATIONAL_PATTERN = /^\+[1-9]\d{7,14}$/;
|
|
/** Miroir de ContactFormat::PHONE_SEPARATORS. */
|
|
const PHONE_SEPARATORS = /[\s.\-/()\u00A0\u202F]/g;
|
|
|
|
/** Champs déjà branchés, pour pouvoir les réévaluer sur demande. */
|
|
const bound = new Map();
|
|
|
|
const RULES = {
|
|
email: {
|
|
test: (value) => EMAIL_PATTERN.test(value),
|
|
invalide: 'Adresse e-mail invalide. Format attendu : prenom.nom@exemple.fr',
|
|
valide: 'Adresse e-mail au bon format.',
|
|
},
|
|
phone: {
|
|
test: (value) => {
|
|
const compact = value.replace(PHONE_SEPARATORS, '');
|
|
return PHONE_FR_PATTERN.test(compact) || PHONE_INTERNATIONAL_PATTERN.test(compact);
|
|
},
|
|
invalide: 'Numéro de téléphone invalide. Format attendu : 06 12 34 56 78 ou +33 6 12 34 56 78.',
|
|
valide: 'Numéro de téléphone au bon format.',
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Branche un champ sur sa règle.
|
|
* L'état est recalculé à chaque frappe, mais n'est AFFICHÉ en erreur qu'une
|
|
* fois le champ quitté ou l'envoi tenté : signaler « invalide » au deuxième
|
|
* caractère tapé serait juste et insupportable.
|
|
*/
|
|
function attach(field) {
|
|
if (bound.has(field)) return;
|
|
const rule = RULES[field.dataset.format];
|
|
if (!rule) return;
|
|
|
|
const status = field.parentElement?.querySelector('[data-format-status]')
|
|
|| field.closest('.field')?.querySelector('[data-format-status]');
|
|
let touched = false;
|
|
|
|
function evaluate() {
|
|
const value = field.value.trim();
|
|
// Champ facultatif : vide est valide, et rien n'est affiché.
|
|
const empty = value === '';
|
|
const ok = empty || rule.test(value);
|
|
|
|
field.setCustomValidity(ok ? '' : rule.invalide);
|
|
field.classList.toggle('field-invalid', !ok && touched);
|
|
field.classList.toggle('field-valid', ok && !empty);
|
|
|
|
if (!status) return;
|
|
if (empty) {
|
|
status.textContent = '';
|
|
status.className = 'field-format-status';
|
|
} else if (ok) {
|
|
status.textContent = rule.valide;
|
|
status.className = 'field-format-status field-format-ok';
|
|
} else if (touched) {
|
|
status.textContent = rule.invalide;
|
|
status.className = 'field-format-status field-format-error';
|
|
} else {
|
|
status.textContent = '';
|
|
status.className = 'field-format-status';
|
|
}
|
|
}
|
|
|
|
bound.set(field, () => {
|
|
touched = false;
|
|
evaluate();
|
|
});
|
|
|
|
field.addEventListener('input', evaluate);
|
|
field.addEventListener('blur', () => {
|
|
touched = true;
|
|
evaluate();
|
|
});
|
|
// Envoi refusé par la validation native : le champ fautif s'explique.
|
|
field.addEventListener('invalid', () => {
|
|
touched = true;
|
|
evaluate();
|
|
});
|
|
// Après un form.reset(), aucun événement de saisie n'est émis : sans
|
|
// cela la note du champ resterait celle de l'agent précédent.
|
|
field.form?.addEventListener('reset', () => {
|
|
window.setTimeout(() => {
|
|
touched = false;
|
|
evaluate();
|
|
}, 0);
|
|
});
|
|
evaluate();
|
|
}
|
|
|
|
/**
|
|
* Branche les champs porteurs de `data-format` et remet à zéro l'état de
|
|
* ceux déjà branchés.
|
|
*
|
|
* Rappelable, et RAPPELÉ : la fiche de modification est remplie par
|
|
* programme (module `administration`), ce qui n'émet aucun événement de
|
|
* saisie — sans ce rappel, la note afficherait l'état de l'agent précédent.
|
|
*/
|
|
function init() {
|
|
document.querySelectorAll('[data-format]').forEach(attach);
|
|
bound.forEach((reset) => reset());
|
|
}
|
|
|
|
PTA.modules.fieldFormat = { init, refresh: init };
|
|
})();
|