Écrire des fonctions 4D
Un bloc atteint votre code de quatre façons différentes, avec quatre signatures différentes. Se tromper de signature est la cause la plus fréquente du cas « ça ne fait rien et ça ne dit rien ».
1. Un gestionnaire d'événement
Function togglePublish($vo_POST : Object)->$vo_WebResponse : cs.bspkComponent.WebFormController
$vo_WebResponse:=cs.bspkComponent.WebFormController.new()
…
$vo_WebResponse.sendAlert("success"; BSPK_Translate("Toast_done"))
C'est le type de retour qui rend la fonction visible dans le panneau, pas son nom. Placez-la dans une dataclass, une classe d'entité ou une classe projet qui fait extends WebFormController : le répartiteur accepte aussi bien ds que cs (voir Les événements et le POST). Nommez-la en camelCase.
2. Remplir un menu déroulant ou un groupe de cases à cocher
Function getMyValuesWF($vo_POST : Object)->$vo_Return : Object
Un nom qui se termine par WF, et exactement cette signature. Aucune exception, aucune tolérance : un écart donne un champ vide, sans erreur. Lancez BSPK_REFRESH_STORAGE après en avoir ajouté une.
3. Un hook avant enregistrement
Il se configure sur le bloc formulaire, dans vt_HookBeforeSave. Si vous n'avez pas écrit les arguments, le framework les ajoute lui-même :
$vt_Expression+="(vo_POST;This)"
La signature est donc :
Function hookBeforeSaveContact($vo_POST : Object; $vo_WFC : cs.bspkComponent.WebFormController)
Pour bloquer l'enregistrement, passez $vo_WFC.vb_Continue:=False et envoyez un message. Trois pièges, tous silencieux :
- Ne réaffectez jamais
$vo_WFC: affectez ses propriétés. Remplacer l'objet fait perdre la référence de l'appelant, et votre refus n'arrive jamais. - Le paramètre s'appelle
vo_Param, au singulier, là où d'autres mécanismes utilisent le pluriel. vt_RowPkn'existe pas dans ce contexte : ne comptez pas dessus.
Notez l'ordre : le hook s'exécute après le contrôle de sécurité. Les contraintes de format (vt_PregMatch, champ obligatoire, type de saisie) ont donc déjà été appliquées. Un test de format placé ici n'est jamais atteint pour une valeur que le schéma a déjà refusée.
4. Les formules évaluées pendant le rendu
conditionalDisplay, le vt_FieldFormula d'une colonne, vt_FirstRequest, la fonction d'une répétition : ce sont des expressions, exécutées par EXECUTE FORMULA dans le contexte de la page. Elles ne reçoivent pas de paramètres : elles lisent Entity et les autres variables process.
Pour une colonne de listbox qui renvoie du HTML (une icône à plusieurs états, par exemple), la fonction se place dans la classe Entity, pas dans la dataclass, et renvoie du HTML brut.
Les attributs calculés ont besoin de leurs propres fonctions
Un attribut calculé sur lequel on fait une recherche a besoin d'une Function query, et un attribut calculé sur lequel on trie a besoin d'une Function orderBy. Sans elles, 4D se rabat sur un parcours séquentiel, ou ne peut pas trier du tout. C'est l'explication habituelle d'une listbox qui n'est lente que lorsqu'elle est filtrée.
Les événements ORDA
store() et delete() sont des alias de save() et drop() ; la logique a sa place dans les événements d'entité. Quatre choses à savoir :
- le statut est
$event.status.success, passaveStatus; - écrire dans
savingn'a aucun effet ; METHOD GET CODElit la version en mémoire, pas le fichier sur disque : elle confirmera sans broncher un code que vous venez de modifier mais pas encore rechargé ;- jamais d'
ALERTcôté serveur : la boîte de dialogue bloque le process, et personne n'est là pour la fermer.
Les événements d'entité (validateSave, validateDrop…) existent à partir de 4D 21. En 20 R10, le seul point de surcharge est le delete() propre à BSPK.
Refuser un enregistrement depuis validateSave : l'objet d'erreur doit être PLAT
Un événement du modèle de données se déclenche sur tous les chemins d'écriture : .save(), .store(), l'import, la synchronisation, l'API Claude et l'ATL de bureau. C'est donc le seul endroit où un garde-fou tient en dehors du web. Un contrôle placé dans une fonction de dataclass appelée par les écrans BWEB ne protège que ces écrans.
Function event validateSave($event : Object) : Object
If (<refusal condition>)
return {errCode: 1; message: "…"; seriousError: True}
End if
| Propriété | Rôle |
|---|---|
errCode | Code d'erreur entier |
message | Message affiché |
seriousError | True = exception visible ; False = échec de validation silencieux |
extraDescription | Objet libre, facultatif |
Renvoyer Null, ou ne rien renvoyer, laisse l'enregistrement se faire.
Piège : toute autre forme est ignorée sans un mot.
{errors: [{message: "…"}]}, une forme plausible et proche de ce que renvoient d'autres API 4D, ne bloque rien : l'événement s'exécute, le refus est calculé, le message est construit, et l'enregistrement est quand même sauvegardé. Le garde-fou a l'air en place et ne protège rien.
Validez un garde-fou en provoquant le refus, puis vérifiez deux choses : le message s'affiche, et la valeur est inchangée dans la base, pas seulement à l'écran.
Où placer le code
| Objectif | Fichier |
|---|---|
| Agir sur un enregistrement | <TABLE>Entity.4dm |
| Agir sur la table ou sur une sélection | <TABLE>.4dm / <TABLE>Selection.4dm |
| Orchestrer sans enregistrement naturel | une classe qui fait extends WebFormController |
Pour les tables de l'hôte, la classe d'entité active est celle de l'hôte (Project/Sources/Classes). Les copies placées dans Resources/Misc/DataClasses du composant sont des gabarits d'installation : les modifier ne change rien à l'exécution.
Un fichier de dataclass a trois zones, écrites par trois acteurs différents
Class extends BSPKEntity
/*** START BSPKENTITY ***/
… zone 1: the generic base …
/*** END BSPKENTITY ***/
… zone 2: BWeb-specific …
/********* YOUR CODE AFTER THIS ********/
… zone 3: your code …
| Zone | Écrite par | Remarques |
|---|---|---|
| 1 — entre les marqueurs | BSPK_UTIL_UPDATE_DATACLASS | Réinjecte store, delete, updateFromObject… à chaque passage. Une fonction que vous redéfinissez est renommée xxxBspk |
2 — jusqu'à YOUR CODE AFTER THIS | Launcher_FC, depuis Resources/Misc/DataClasses/<Class>.4dm | Copiée dans l'hôte à l'installation |
| 3 — après le marqueur | Vous | Préservée quand la zone 2 est recopiée |
Les confondre fait soit perdre du code à l'installation, soit en accumuler des copies. Une classe qui ne porte que la base n'a rien à faire dans Resources/Misc/DataClasses : elle est générée d'elle-même.
Voir aussi
- Les événements et le POST : la répartition, et ce que contient
$vo_POST - Le langage 4D :
Num(Null), variables typées, objets partagés

