É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_RowPk n'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, pas saveStatus ;
  • écrire dans saving n'a aucun effet ;
  • METHOD GET CODE lit 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'ALERT cô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
errCodeCode d'erreur entier
messageMessage affiché
seriousErrorTrue = exception visible ; False = échec de validation silencieux
extraDescriptionObjet 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

ObjectifFichier
Agir sur un enregistrement<TABLE>Entity.4dm
Agir sur la table ou sur une sélection<TABLE>.4dm / <TABLE>Selection.4dm
Orchestrer sans enregistrement naturelune 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 parRemarques
1 — entre les marqueursBSPK_UTIL_UPDATE_DATACLASSRéinjecte store, delete, updateFromObject… à chaque passage. Une fonction que vous redéfinissez est renommée xxxBspk
2 — jusqu'à YOUR CODE AFTER THISLauncher_FC, depuis Resources/Misc/DataClasses/<Class>.4dmCopiée dans l'hôte à l'installation
3 — après le marqueurVousPré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