Ajout des vérifications des types dans les formulaires (adresses, email, telephone)
This commit is contained in:
136
assets/js/field-format.js
Normal file
136
assets/js/field-format.js
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 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 };
|
||||
})();
|
||||
Reference in New Issue
Block a user