Contrôleurs de formulaire
Disponible depuis la version 5.x de Dodock
Introduction
Un contrôleur de formulaire est une classe JavaScript qui définit le comportement d'un type de document lors de la saisie et de l'affichage. Il s'agit de l'équivalent moderne et structuré des anciens scripts clients (Client Scripts), regroupant les gestionnaires d'événements (onload, validate, refresh, etc.) dans une classe réutilisable et extensible.
Les applications comme Dokos ou ERPNext lient traditionnellement ces contrôleurs en haut d'un script de formulaire. Dodock introduit désormais une API dédiée pour les enregistrer proprement et s'assurer qu'ils sont bien chargés au bon moment dans le cycle de vie du formulaire.
Pourquoi un nouveau mécanisme
Les limites de l'approche historique
Avant cette évolution, les applications liaient un contrôleur de classe au formulaire en l'étendant en haut du script de type de document :
extend_cscript(cur_frm.cscript, new erpnext.stock.DeliveryNoteController({ frm: cur_frm }));
Cette approche présentait deux inconvénients majeurs :
- Pas de portée
frm: le script s'exécute sans objetfrmdans la portée,cur_frmest donc le seul moyen d'y accéder. - Gestionnaires d'événements ignorés : le branchement dans un gestionnaire
setupne fonctionne pas, car l'événementtrigger("setup")collecte les gestionnaires à l'ancienne avant que ceux de la classe ne puissent être enregistrés. La méthodesetup()du contrôleur n'est donc jamais déclenchée.
Une API explicite et fiable
La fonction frappe.ui.form.set_controller(doctype, ControllerClass) résout ces problèmes en enregistrant la classe auprès du gestionnaire de scripts. À l'initialisation du formulaire, après l'exécution des scripts de formulaire et des scripts clients, le ScriptManager branche automatiquement le contrôleur pour le type de document concerné, avant que l'événement setup ne soit déclenché.
Si un script a déjà lié une sous-classe du contrôleur — par exemple pour étendre un contrôleur d'application depuis un doctype_js personnalisé — ce branchement est conservé et prioritaire.
Prérequis
- Une application avec un script de formulaire (
<doctype>.jsoudoctype_jsdanshooks.py). - Une classe de contrôleur dont le constructeur accepte
{ frm }.
Enregistrer un contrôleur
Signature
frappe.ui.form.set_controller(doctype, ControllerClass)
doctype(chaîne) — le nom interne (anglais) du type de document, par exemple"Delivery Note". Le libellé traduit ne fonctionne pas.ControllerClass(classe) — la classe de contrôleur à lier à ce type de document.
Exemple
frappe.ui.form.set_controller("Delivery Note", erpnext.stock.DeliveryNoteController);
ERPNext utilise cette instruction à la fin de erpnext/stock/doctype/delivery_note/delivery_note.js. Elle enregistre DeliveryNoteController comme contrôleur du type de document Delivery Note. Le gestionnaire de scripts se chargera de l'instancier et de le lier au formulaire au moment opportun.
Fonctionnement interne
set_controller mémorise la classe dans frappe.ui.form.controllers[doctype]. La méthode ScriptManager.bind_controller() l'instancie avec new ControllerClass({ frm }) et la fusionne dans frm.cscript par extend_cscript. Elle ne fait rien si frm.cscript est déjà une instance de la classe (test instanceof), donc une sous-classe déjà liée est conservée.
Cycle de vie du branchement
L'ordre précis d'exécution garantit que les contrôleurs sont en place avant que les événements ne se déclenchent :
- Scripts de formulaire — les scripts déclarés sur le type de document s'exécutent.
- Scripts clients — les scripts clients configurés via l'interface s'appliquent.
- Branchement du contrôleur — le
ScriptManagerinstancie et branche le contrôleur enregistré pour le type de document, à moins qu'une sous-classe n'ait déjà été liée par un script. - Événement
setup— l'événementsetupest déclenché sur le formulaire. Les méthodessetup()du contrôleur sont désormais appelées.
Cette séquence permet aux développeurs de remplacer l'usage de cur_frm par frm dans la portée des méthodes du contrôleur, et garantit que les méthodes héritées fonctionnent comme prévu.
Héritage et extensions
Le mécanisme respecte l'héritage des contrôleurs. Si une application ou un script personnalisé étend un contrôleur existant :
class MonBonDeLivraisonPersonnalise extends erpnext.stock.DeliveryNoteController {
refresh(frm) {
super.refresh(frm);
// Comportement supplémentaire
}
}
Et le lie explicitement avant que le ScriptManager ne s'exécute, le branchement personnalisé est conservé. Le contrôleur de base enregistré via set_controller ne remplace pas cette sous-classe.
Types de documents concernés
Dodock utilise set_controller pour lier le contrôleur frappe.model.DocTypeController sur deux types de documents techniques :
- Type de document (
DocType) — le formulaire de configuration des types de documents. - Formulaire personnalisé (
Customize Form) — l'interface de personnalisation des formulaires.
Ces branchements internes illustrent l'adoption de l'API au cœur du framework et servent de référence pour les applications tierces.
Migration depuis l'ancienne approche
Si vous maintenez une application ou un script personnalisé qui utilise extend_cscript pour lier un contrôleur, vous pouvez migrer progressivement :
Avant :
extend_cscript(cur_frm.cscript, new erpnext.stock.DeliveryNoteController({ frm: cur_frm }));
Après :
frappe.ui.form.set_controller("Delivery Note", erpnext.stock.DeliveryNoteController);
La migration vous permet de :
- Supprimer la dépendance à
cur_frmdans le branchement. - Garantir que la méthode
setup()du contrôleur est bien déclenchée. - Simplifier la lecture et la maintenance du code d'extension.
Bonnes pratiques
- Un seul contrôleur par type de document — n'enregistrez qu'une seule classe par
doctype. Pour étendre un contrôleur, créez une sous-classe et branchez-la explicitement si nécessaire. - Placez l'enregistrement au plus haut niveau — déclarez
set_controllerdans le script de formulaire de l'application, pas dans un gestionnaire d'événements. - Ne référencez pas
cur_frm— utilisez l'argumentfrmpassé aux méthodes du contrôleur. L'API est conçue pour rendrecur_frmsuperflu. - Testez les sous-classes — si votre application étend un contrôleur d'une autre application, vérifiez que le branchement de votre sous-classe survit bien au branchement automatique du contrôleur de base.