Évènements d'entité
Historique
| Release | Modifications |
|---|---|
| 21 | Événements ajoutés : validateSave / saving / afterSave / validateDrop / dropping / afterDrop |
| 20 R10 | ajout événement touched |
Les événements d'entité sont des fonctions qui sont automatiquement invoquées par ORDA chaque fois que des entités et des attributs d'entité sont touchés (ajoutés, supprimés ou modifiés). Vous pouvez écrire des événements très simples, puis les rendre plus sophistiqués.
Vous ne pouvez pas déclencher directement l'exécution d'une fonction d'événement. Les événements sont appelés automatiquement par ORDA en fonction des actions de l'utilisateur ou des opérations effectuées par le code sur les entités et leurs attributs.
Les événements d'entité ORDA dans le magasin de données sont équivalents aux triggers dans la base de données 4D. Cependant, les actions déclenchées au niveau de la base de données 4D à l'aide des commandes du langage classique 4D ou des actions standard ne déclenchent pas les événements ORDA.
Vue d’ensemble
Niveau de l'événement
Une fonction d'événement d'entité est toujours définie dans la classe Entity.
Un événement peut être défini au niveau de l'entité et/ou de l'attribut (y compris les attributs calculés). Dans le premier cas, il sera déclenché pour tous les attributs de l'entité ; dans l'autre cas, il ne sera déclenché que pour l'attribut ciblé.
Pour un même événement, vous pouvez définir différentes fonctions pour différents attributs.
Vous pouvez également définir le même événement au niveau de l'attribut et de l'entité. L'événement attribut est appelé en premier, puis l'événement entité.
Exécution en configuration distante
En général, les événements ORDA sont exécutés sur le serveur.
Cependant, dans une configuration client/serveur, la fonction d'événement touched() peut être exécutée sur le serveur ou le client, en fonction de l'utilisation du mot-clé local. Une implémentation spécifique côté client permet de déclencher l'événement sur le client.
Les fonctions ORDA constructor() sont toujours exécutées sur le client.
With other remote configurations (i.e. Qodly applications, REST API requests, or requests through Open datastore), the touched() event function is always executed server-side. Cela signifie que vous devez vous assurer que le serveur peut "voir" qu'un attribut a été touché pour déclencher l'événement (voir ci-dessous).
Tableau de synthèse
Le tableau suivant liste les événements d'entité ORDA ainsi que leurs règles.
| Evénement | Niveau | Nom de la fonction | (C/S) Exécuté sur | Peut arrêter l'action en renvoyant une erreur |
|---|---|---|---|---|
| Instanciation d'entité | Entity | constructor() | client | non |
| Attribut touched | Attribut | event touched <attrName>() | Dépend du mot-clé local | non |
| Entity | event touched() | Dépend du mot-clé local | non | |
| Avant l'enregistrement d'une entité | Attribut | validateSave <attrName>() | server | oui |
| Entity | validateSave() | server | oui | |
| Pendant l'enregistrement d'une entité | Attribut | saving <attrName>() | server | oui |
| Entity | saving() | server | oui | |
| Après l'enregistrement d'une entité | Entity | afterSave() | server | non |
| Avant la suppression d'une entité | Attribut | validateDrop <attrName>() | server | oui |
| Entity | validateDrop() | server | oui | |
| Pendant la suppression d'une entité | Attribut | dropping <attrName>() | server | oui |
| Entity | dropping() | server | oui | |
| Après la suppression d'une entité | Entity | afterDrop() | server | non |
La fonction constructor() n'est pas en soi une fonction d'événement, mais elle est toujours appelée lorsqu'une nouvelle entité est instanciée.
Paramètre event
Les fonctions d'événement acceptent un seul objet event comme paramètre. Lorsque la fonction est appelée, le paramètre est rempli avec diverses propriétés :
| Nom de propriété | Disponibilité | Type | Description | |
|---|---|---|---|---|
| "kind" | Toujours | String | Nom de l'événement : "touched", "validateSave", "saving", "afterSave", "validateDrop", "dropping", "afterDrop" | |
| attributeName | Uniquement pour les événements définis au niveau des attributs ("validateSave", "saving", "validateDrop", "dropping") | String | Nom de l'attribut (ex. "firstname") | |
| dataClassName | Toujours | String | Nom du verre de données (ex. "Company") | |
| "savedAttributes" | Uniquement dans afterSave() | Collection de chaînes | Noms des attributs correctement enregistrés | |
| "droppedAttributes" | Uniquement dans afterDrop() | Collection de chaînes | Noms des attributs correctement supprimés | |
| "saveStatus" | Uniquement dans afterSave() | String | "success" si l'enregistrement a réussi, "failed" sinon | |
| "dropStatus" | Uniquement dans afterDrop() | String | "success" si la suppression a réussi, "failed" sinon |
Objet error
Certaines fonctions d'événement peuvent renvoyer un objet error pour déclencher une erreur et arrêter l'action en cours.
Lorsqu'une erreur survient dans un événement, les autres événements sont stoppés à la première erreur signalée et l'action (enregistrement pu suppression) est également arrêtée. Cette erreur est envoyée avant d'autres erreurs potentielles telles que stamp has changed, entity locked, etc.
Propriétés de l'objet error
| Propriété | Type | Description | Fixé par le développeur |
|---|---|---|---|
| errCode | Integer | Identique à la commande Last errors | Oui |
| message | Text | Identique à la commande Last errors | Oui |
| extraDescription | Object | Informations libres à définir | Oui |
| seriousError | Boolean | Utilisé uniquement avec les événements de validation (voir ci-dessous). True : crée une erreur critique (imprévisible) et déclenche une exception. Ajoute le statut dk status serious validation errorFalse : crée seulement une erreur silencieuse (prévisible). Ajoute le statut dk status validation failed. | Oui (par défaut : False) |
| componentSignature | Text | Toujours "DBEV" | Non |
- Les erreurs critiques sont empilées dans la collection de la propriété
errorsde l'objet Result renvoyé par les fonctionssave()oudrop(). - Dans le cas d'une erreur déclenchée par un événement validate, la propriété
seriousErrorpermet de choisir le niveau d'erreur à générer :- Si true : une erreur critique est déclenchée et doit être traitée par le code de traitement des erreurs, tel qu'un "try catch". Dans l'objet résultat de la fonction appelante,
statusvautdk status serious validation erroretstatusTextvaut "Serious Validation Error". L'erreur est levée à la fin de l'événement et parvient au client qui demande l'action d'enregistrement/suppression (client REST par exemple). - Si false (défaut) : une erreur silencieuse (prévisible) est générée. Elle ne déclenche aucune exception et n'est pas empilée dans les erreurs retournées par la commande
Last errors. Dans l'objet résultat de la fonction appelante,statusvautdk status validation failedetstatusTextvaut "Mild Validation Error".
- Si true : une erreur critique est déclenchée et doit être traitée par le code de traitement des erreurs, tel qu'un "try catch". Dans l'objet résultat de la fonction appelante,
- Dans le cas d'une erreur déclenchée par un événement saving/dropping, lorsqu'un objet d'erreur est renvoyé, l'erreur est toujours définie comme critique, quelle que soit la valeur de la propriété
seriousError.
Description des fonctions
Function event touched
Syntaxe
{local} Function event touched($event : Object)
{local} Function event touched <attributeName>($event : Object)
// code
Cet événement est déclenché chaque fois qu'une valeur est modifiée dans l'entité.
- Si vous avez défini la fonction au niveau de l'entité (première syntaxe), elle est déclenchée pour des modifications sur n'importe quel attribut de l'entité.
- Si vous avez défini la fonction au niveau de l'attribut (deuxième syntaxe), elle n'est déclenchée que pour les modifications sur cet attribut.
Cet événement est déclenché dès que le moteur de 4D Server / 4D détecte une modification de la valeur de l'attribut qui peut être due aux actions suivantes :
- en client/serveur avec le mot-clé
localou en 4D mono-utilisateur :- l'utilisateur saisit une valeur dans un formulaire 4D,
- le code 4D effectue une assignation avec l'opérateur
:=. L'événement est également déclenché en cas d'auto-assignation ($entity.attribute:=$entity.attribute).
- en client/serveur sans le mot-clé
local: du code 4D effectue une assignation avec l'opérateur:=est exécuté sur le serveur. - en client/serveur sans le mot-clé
local, une application Qodly ou datastore distant : l'entité est reçue sur le serveur 4D lors de l'appel d'une fonction ORDA (sur l'entité ou avec l'entité en tant que paramètre). Cela signifie que vous devrez peut-être mettre en place une fonction refresh ou preview sur l'application distante qui envoie une requête ORDA au serveur et déclenche l'événement. - avec le serveur REST : la valeur est reçue sur le serveur REST avec une requête REST (
$method=update)
La fonction reçoit un objet event en paramètre.
Si cette fonction génère une erreur, elle n'arrêtera pas l'action en cours.
Cet événement est également déclenché :
- lorsque les attributs sont assignés par l'événement
constructor(), - lorsque les attributs sont modifiés via l'Explorateur de données.
Exemple 1
You want to uppercase all text attributes of an entity when it is updated.
//ProductsEntity class
Function event touched($event : Object)
If (Value type(This[$event.attributeName])=Is text)
This[$event.attributeName]:=Uppercase(This[$event.attributeName])
End if
Exemple 2
The "touched" event is useful when it is not possible to write indexed query code in Function query() for a computed attribute.
This is the case for example, when your query function has to compare the value of different attributes from the same entity with each other. You must use formulas in the returned ORDA query -- which triggers sequential queries.
To fully understand this case, let's examine the following two calculated attributes:
Function get onGoing() : Boolean
return ((This.departureDate<=Current date) & (This.arrivalDate>=Current date))
Function get sameDay() : Boolean
return (This.departureDate=This.arrivalDate)
Even though they are very similar, these functions cannot be associated with identical queries because they do not compare the same types of values. The first compares attributes to a given value, while the second compares attributes to each other.
- For the onGoing attribute, the
queryfunction is simple to write and uses indexed attributes:
Function query onGoing($event : Object) : Object
var $operator : Text
var $myQuery : Text
var $onGoingValue : Boolean
var $parameters : Collection
$parameters:=New collection()
$operator:=$event.operator
Case of
: (($operator="=") | ($operator="==") | ($operator="==="))
$onGoingValue:=Bool($event.value)
: (($operator="!=") | ($operator="!=="))
$onGoingValue:=Not(Bool($event.value))
Else
return {query: ""; parameters: $parameters}
End case
$myQuery:=($onGoingValue) ? "departureDate <= :1 AND arrivalDate >= :1" : "departureDate > :1 OR arrivalDate < :1"
// the ORDA query string uses indexed attributes, it will be indexed
$parameters.push(Current date)
return {query: $myQuery; parameters: $parameters}
- For the sameDay attribute, the
queryfunction requires an ORDA query based on formulas and will be sequential:
Function query sameDay($event : Object) : Text
var $operator : Text
var $sameDayValue : Boolean
$operator:=$event.operator
Case of
: (($operator="=") | ($operator="==") | ($operator="==="))
$sameDayValue:=Bool($event.value)
: (($operator="!=") | ($operator="!=="))
$sameDayValue:=Not(Bool($event.value))
Else
return ""
End case
return ($sameDayValue) ? "eval(This.departureDate = This.arrivalDate)" : "eval(This.departureDate != This.arrivalDate)"
// the ORDA query string uses a formula, it will not be indexed
- Using a scalar sameDay attribute updated when other attributes are "touched" will save time:
//BookingEntity class
Function event touched departureDate($event : Object)
This.sameDay:=(This.departureDate = This.arrivalDate)
//
//
Function event touched arrivalDate($event : Object)
This.sameDay:=(This.departureDate = This.arrivalDate)
Example 3 (diagram): Client/server with the local keyword:
Example 4 (diagram): Client/server without the local keyword
Example 5 (diagram): Qodly application
Function event validateSave
Syntaxe
Function event validateSave($event : Object)
Function event validateSave <attributeName>($event : Object)
// code
This event is triggered each time an entity is about to be saved.
- if you defined the function at the entity level (first syntax), it is called for any attribute of the entity.
- if you defined the function at the attribute level (second syntax), it is called only for this attribute. This function is not executed if the attribute has not been touched in the entity.
La fonction reçoit un objet event en paramètre.
This event is triggered by the following functions:
This event is triggered before the entity is actually saved and lets you check data consistency so that you can stop the action if needed. For example, you can check in this event that "departure date" < "arrival date".
To stop the action, the code of the function must return an error object.
It is not recommended to update the entity within this function (using This).
Exemple
In this example, it is not allowed to save a product with a margin lower than 50%. In case of an invalid price attribute, you return an error object and thus, stop the save action.
// ProductsEntity class
//
// validateSave event at attribute level
Function event validateSave margin($event : Object) : Object
var $result : Object
//The user can't create a product whose margin is < 50%
If (This.margin<50)
$result:={errCode: 1; message: "The validation of this product failed"; \
extraDescription: {info: "The margin of this product ("+String(This.margin)+") is lower than 50%"}; seriousError: False}
End if
return $result
Function event saving
Syntaxe
Function event saving($event : Object)
Function event saving <attributeName>($event : Object)
// code
This event is triggered each time an entity is being saved.
- If you defined the function at the entity level (first syntax), it is called for any attribute of the entity. The function is executed even if no attribute has been touched in the entity (e.g. in case of sending data to an external app each time a save is done).
- If you defined the function at the attribute level (second syntax), it is called only for this attribute. The function is not executed if the attribute has not been touched in the entity.
La fonction reçoit un objet event en paramètre.
This event is triggered by the following functions:
This event is triggered while the entity is actually saved. If a validateSave() event function was defined, the saving() event function is called if no error was triggered by validateSave(). For example, you can use this event to create a document on a Google Drive account.
The business logic should raise errors which can't be detected during the validateSave() events, e.g. a network error
During the save action, 4D engine errors can be raised (index, stamp has changed, not enough space on disk).
To stop the action, the code of the function must return an error object.
Exemple
When a file is saved on disk, catch errors related to disk space for example.
// ProductsEntity class
// saving event at attribute level
Function event saving userManualPath($event : Object) : Object
var $result : Object
var $userManualFile : 4D.File
var $fileCreated : Boolean
If (This.userManualPath#"")
$userManualFile:=File(This.userManualPath)
// The user manual document file is created on the disk
// This may fail if no more space is available
Try
$fileCreated:=$userManualFile.create()
Catch
// No more room on disk for example
$result:={/
errCode: 1; message: "Error during the save action for this product"; /
extraDescription: {info: "There is no available space on disk to store the user manual"}/
}
End try
End if
return $result
Function event afterSave
Syntaxe
Function event afterSave($event : Object)
// code
This event is triggered just after an entity is saved in the data file, when at least one attribute was modified. It is not executed if no attribute has been touched in the entity.
This event is useful after saving data to propagate the save action outside the application or to execute administration tasks. For example, it can be used to send a confirmation email after data have been saved. Or, in case of error while saving data, it can make a rollback to restore a consistent state of data.
La fonction reçoit un objet event en paramètre.
- To avoid infinite loops, calling a
save()on the current entity (throughThis) in this function is not allowed. It will raise an error. - Throwing an error object is not supported by this function.
Exemple
If an error occurred in the above saving event, the attribute value is reset accordingly in the afterSave event:
// ProductsEntity class
Function event afterSave($event : Object)
If (($event.status.success=False) && ($event.status.errors=Null))
// $event.status.errors is filled if the error comes from the validateSave event
// The userManualPath attribute has not been properly saved
// Its value is reset
If ($event.savedAttributes.indexOf("userManualPath")=-1)
This.userManualPath:=""
This.status:="KO"
End if
End if
Function event validateDrop
Syntaxe
Function event validateDrop($event : Object)
Function event validateDrop <attributeName>($event : Object)
// code
This event is triggered each time an entity is about to be dropped.
- If you defined the function at the entity level (first syntax), it is called for any attribute of the entity.
- If you defined the function at the attribute level (second syntax), it is called only for this attribute.
La fonction reçoit un objet event en paramètre.
This event is triggered by the following features:
entity.drop()entitySelection.drop()- deletion control rules that can be defined at the database structure level.
This event is triggered before the entity is actually dropped, allowing you to check data consistency and if necessary, to stop the drop action.
To stop the action, the code of the function must return an error object.
Exemple
In this example, it is not allowed to drop a product that is not labelled "TO DELETE". In this case, you return an error object and thus, stop the drop action.
// ProductsEntity class
Function event validateDrop status($event : Object) : Object
var $result : Object
// Products must be marked as TO DELETE to be dropped
If (This.status#"TO DELETE")
$result:={errCode: 1; message: "You can't drop this product"; \
extraDescription: {info: "This product must be marked as To Delete"}; seriousError: False}
End if
return $result
Function event dropping
Syntaxe
Function event dropping($event : Object)
Function event dropping <attributeName>($event : Object)
// code
This event is triggered each time an entity is being dropped.
- If you defined the function at the entity level (first syntax), it is called for any attribute of the entity.
- If you defined the function at the attribute level (second syntax), it is called only for this attribute.
La fonction reçoit un objet event en paramètre.
This event is triggered by the following features:
entity.drop()entitySelection.drop()- deletion control rules that can be defined at the database structure level.
This event is triggered while the entity is actually dropped. If a validateDrop() event function was defined, the dropping() event function is called if no error was triggered by validateDrop().
The business logic should raise errors which cannot be detected during the validateDrop() events, e.g. a network error.
To stop the action, the code of the function must return an error object.
Exemple
Here is an example of dropping event at entity level:
// ProductsEntity class
Function event dropping($event : Object) : Object
var $result : Object
var $userManualFile : 4D.File
$userManualFile:=File(This.userManualPath)
// When dropping a product, its user manual is also deleted on the disk
// This action may fail
Try
If ($userManualFile.exists)
$userManualFile.delete()
End if
Catch
// Dropping the user manual failed
$result:={errCode: 1; message: "Drop failed"; extraDescription: {info: "The user manual can't be dropped"}}
End try
return $result
Function event afterDrop
Syntaxe
Function event afterDrop($event : Object)
// code
This event is triggered just after an entity is dropped.
This event is useful after dropping data to propagate the drop action outside the application or to execute administration tasks. For example, it can be used to send a cancellation email after data have been dropped. Or, in case of error while dropping data, it can log an information for the administrator to check data consistency.
La fonction reçoit un objet event en paramètre.
- To avoid infinite loops, calling a
drop()on the current entity (throughThis) in this function is not allowed. It will raise an error. - Throwing an error object is not supported by this function.
The dropped entity is referenced by This and still exists in memory.
Exemple
If the drop action failed, then the product must be checked manually:
Function event afterDrop($event : Object)
var $status : Object
If (($event.status.success=False) && ($event.status.errors=Null))
//$event.status.errors is filled
//if the error comes from the validateDrop event
This.status:="Check this product - Drop action failed"
$status:=This.save()
End if