Aller au contenu principal
Version : 21 BETA

Events

Historique
ReleaseModifications
21Added events: validateSave / saving / afterSave / validateDrop / dropping / afterDrop
20 R10touched event added

ORDA events are functions that are automatically invoked by ORDA each time entities and entity attributes are manipulated (added, deleted, or modified). You can write very simple events, and then make them more sophisticated.

You cannot directly trigger event function execution. Events are called automatically by ORDA based on user actions or operations performed through code on entities and their attributes.

Compatibility note

ORDA events in the datastore are equivalent to triggers in the 4D database. However, actions triggered at the 4D database level using the 4D classic language commands or standard actions do not trigger ORDA events.

Vue d’ensemble

Event level

A event function is always defined in the Entity class.

It can be set at the entity level and/or the attribute level (it includes computed attributes). In the first case, it will be triggered for any attributes of the entity; on the other case, it will only be triggered for the targeted attribute.

For the same event, you can define different functions for different attributes.

You can also define the same event at both attribute and entity levels. The attribute event is called first and then the entity event.

Execution in remote configurations

Usually, ORDA events are executed on the server.

In client/server configuration however, the touched() event function can be executed on the server or the client, depending on the use of local keyword. A specific implementation on the client side allows the triggering of the event on the client.

note

ORDA constructor() functions are always executed on the 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. It means that you have to make sure the server can "see" that an attribute has been touched to trigger the event (see below).

Summary table

The following table lists ORDA events along with their rules.

EvénementNiveauFunction name(C/S) Executed onCan stop action by returning an error
Entity instantiationEntityconstructor()clientnon
Attribute touchedAttributevent touched <attrName>()Depends on local keywordnon
Entityevent touched()Depends on local keywordnon
Before saving an entityAttributvalidateSave <attrName>()serveroui
EntityvalidateSave()serveroui
When saving an entityAttributsaving <attrName>()serveroui
Entitysaving()serveroui
After saving an entityEntityafterSave()servernon
Before dropping an entityAttributvalidateDrop <attrName>()serveroui
EntityvalidateDrop()serveroui
When dropping an entityAttributdropping <attrName>()serveroui
Entitydropping()serveroui
After dropping an entityEntityafterDrop()servernon
note

The constructor() function is not actually an event function but is always called when a new entity is instantiated.

event parameter

Event functions accept a single event object as parameter. When the function is called, the parameter is filled with several properties:

Nom de propriétéDisponibilitéTypeDescription
"kind"ToujoursStringEvent name: "touched", "validateSave", "saving", "afterSave", "validateDrop", "dropping", "afterDrop"
attributeNameOnly for events implemented at attribute level ("validateSave", "saving", "validateDrop", "dropping")StringNom de l'attribut (ex. "firstname")
dataClassNameToujoursStringNom du verre de données (ex. "Company")
"savedAttributes"Only in afterSave()Collection of StringNames of attributes properly saved
"droppedAttributes"Only in afterDrop()Collection of StringNames of attributes properly dropped
"saveStatus"Only in afterSave()String"success" if the save was successful, "failed" otherwise
"dropStatus"Only in afterDrop()String"success" if the drop was successful, "failed" otherwise

Error object

Some event functions can return an error object to raise an error and stop the running action.

When an error occurs in an event, the other events are stopped at the first raised error and the action (save or drop) is also stopped. This error is sent before other potential errors like stamp has changed, entity locked, etc.

Error object properties

PropriétéTypeDescriptionSet by the developer
errCodeIntegerSame as for Last errors commandOui
messageTextSame as for Last errors commandOui
extraDescriptionObjectFree information to set upOui
seriousErrorBooleanUsed only with validate events (see below). Will insert a specific status value in the save() or drop() function:
  • If true: dk status serious validation error
  • If false: dk status validation failed
  • Yes (default is false)
    componentSignatureTextAlways "DBEV"Non
    • The errors are stacked in the errors collection property of the Result object returned by the save() or drop() functions.
    • In case of an error triggered by a validate event, the fatalError property allows you to insert a specific status and its associated statusText in the Result object returned by the save() or drop() functions:
      • If false: status gets dk status validation failed and statusText gets "Mild Validation Error". Such errors do not require a try catch and are not stacked in the errors returned by the Last errors command.
      • If true: status gets dk status serious validation error and statusText gets "Serious Validation Error". Such errors require a try catch and are not stacked in the errors returned by the Last errors command. They are raised at the end of the event and reach the client requesting the save/drop action (REST client for example).
    • In case of an error triggered by a saving/dropping event, when an error object is returned, the error is always raised as a serious error (dk status serious error) whatever the seriousError property value.

    Event function description

    Function event touched

    Syntaxe

    {local} Function event touched($event : Object)
    {local} Function event touched <attributeName>($event : Object)
    // code

    This event is triggered each time a value is modified in the entity.

    • If you defined the function at the entity level (first syntax), it is triggered for modifications on any attribute of the entity.
    • If you defined the function at the attribute level (second syntax), it is triggered only for modifications on this attribute.

    This event is triggered as soon as the 4D Server / 4D engine can detect a modification of attribute value which can be due to the following actions:

    • in client/server with the local keyword or in 4D single-user:
      • the user sets a value on a 4D form,
      • the 4D code makes an assignment with the := operator. The event is also triggered in case of self-assignment ($entity.attribute:=$entity.attribute).
    • in client/server without the local keyword: some 4D code that makes an assignment with the := operator is executed on the server.
    • in client/server without the local keyword, in Qodly application and remote datastore: the entity is received on 4D Server while calling an ORDA function (on the entity or with the entity as parameter). It means that you might have to implement a refresh or preview function on the remote application that sends an ORDA request to the server and triggers the event.
    • with the REST server: the value is received on the REST server with a REST request ($method=update)

    The function receives an event object as parameter.

    If this function throws an error, it will not stop the undergoing action.

    note

    This event is also triggered:

    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 query function 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 query function 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.

    The function receives an event object as parameter.

    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.

    note

    It is not recommended to update the entity within this function (using This).

    Exemple

    In this example, the user is not allowed to save a product with a margin lower than the average. In case of an invalid price attribute, you return an error object and thus, stop the save action.

    // ProductsEntity class
    Function event validateSave margin($event : Object) : Object

    var $result : Object
    var $marginAverage : Real

    $marginAverage:=ds.Products.query("category= :1"; This.category).average("margin")

    If (This.margin<$marginAverage)
    $result:={\
    errCode: 1; \
    message: "The margin of this product ("+String(This.margin)+") is under the average"; \
    extraDescription: {\
    info: "For the "+This.category+" category the margin average is: "+String($marginAverage)};\
    fatalError: 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.

    The function receives an event object as parameter.

    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.

    note

    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 product is saved, some information is logged to an external system which may be unavailable.

    Function event saving($event : Object) : Object

    var $result; $status : Object
    var $log : cs.Entity
    var $remote : 4D.DataStoreImplementation

    Try
    $remote:=Open datastore({hostname: "events@acme.com"}; "logs")
    $log:=$remote.Logs.new()
    $log.productId:=This.ID
    $log.stamp:=Timestamp
    $log.event:="Created by "+Current user()
    $status:=$log.save()
    Catch
    $result:={\
    errCode: Last errors.last().errCode;\
    message: Last errors.last().message; \
    extraDescription: {info: "The external Logs can't be reached"}}
    End try

    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.

    The function receives an event object as parameter.

    • To avoid infinite loops, calling a save() on the current entity (through This) in this function is not allowed. It will raise an error.
    • Throwing an error object is not supported by this function.

    Exemple 1

    If an error occurred in the above saving event, the product is recorded in the ProductsInFailure dataclass so an employee can review it later.

    // ProductsEntity class
    Function event afterSave($event : Object)

    var $failure : cs.ProductsInFailureEntity
    var $status : Object

    // $event.status.errors is filled if the error comes from a validateSave event
    If (($event.status.success=False) && ($event.status.errors=Null))
    $failure:=ds.ProductsInFailure.new()
    $failure.name:=This.name
    $failure.category:=This.category
    $failure.costPrice:=This.costPrice
    $failure.retailPrice:=This.retailPrice
    $failure.reason:="Error during the save action"
    $failure.stamp:=Timestamp
    $status:=$failure.save()
    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.

    The function receives an event object as parameter.

    This event is triggered by the following features:

    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 1

    Products can be deleted only if they have been flagged TO DELETE.

        //ProductsEntity class
    Function event validateDrop status($event : Object) : Object

    If (This.status != "TO DELETE")

    var $result:= New object()
    $result.errCode:=1
    $result.message:="The record can't be deleted"
    $result.extraDescription:={attribute; $event.attributeName; info: "The status must be TO DELETE"}
    $result.fatalError:=False
    return $result
    End if

    Exemple 2

    The user can delete products if they are flagged as "TO DELETE" and if their creation year is < current year -3.

        //ProductsEntity class
    Function event validateDrop($event : Object) : Object

    var $yearOffSet : Integer
    $yearOffSet:=Year of(Current date)-3

    If ((This.status != "TO DELETE") || (Year of(This.creationDate) >= $yearOffSet))
    var $result:=New object()
    $result.errCode:=1
    $result.message:="The record can't be deleted"
    $result.extraDescription:={info: "The status must be TO DELETE and the creation year must be lower than " + String($yearOffSet)}
    $result.fatalError:=False
    return $result
    End if

    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.

    The function receives an event object as parameter.

    This event is triggered by the following features:

    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().

    note

    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 1

    When dropping an order with totalPrice >= 500, a log file is updated.

        //OrderEntity class
    Function event dropping totalPrice ($event : Object)

    var $log : cs.LogEntity
    var $status: Object

    If (This.totalPrice >= 500)

    $log:=ds.Log.new()
    $log.orderID:=This.ID
    $log.orderPrice:=This.totalPrice
    $log.event:="Drop"
    $log.creationDate:=Current date()
    $status:=$log.save()

    If($status.success=False)
    throw ({errCode: 1; message: "Error while updating the log file"})
    End if
    End if

    Exemple 2

    When a product is dropped, a log file is updated.

        //ProductsEntity class
    Function event dropping ($event : Object)

    var $log : cs.LogEntity
    var $status: Object

    $log:=ds.Log.new()
    $log.productID:=This.ID
    $log.productPrice:=This.price
    $log.event:="Drop"
    $log.creationDate:=Current date()
    $status:=$log.save()

    If($status.success=False)
    throw ({errCode: 1; message:"Error while updating the log file"})
    End if

    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.

    The function receives an event object as parameter.

    • To avoid infinite loops, calling a drop() on the current entity (through This) in this function is not allowed. It will raise an error.
    • Throwing an error object is not supported by this function.
    note

    The dropped entity is referenced by This and still exists in memory.

    Exemple 1

    Send a mail to the customer with the details of the dropped order.

        //OrderEntity class
    Function event afterDrop ($event : Object)

    var $oAuth2 : cs.NetKit.OAuth2Provider
    var $google : cs.NetKit.Google

    //$param contains clientId, secretId...
    $oAuth2:=cs.NetKit.OAuth2Provider.new($param)
    $google:=cs.NetKit.Google.new($oAuth2; {mailType: "JMAP"})

    //Email creation
    $email:=New object
    $email.from:="youremail@gmail.com"
    $email.to:="destinationmail@mail.com"
    $email.subject:="Your order is cancelled"
    $email.textBody:="Products numbers: " + This.products.number.join("-")

    //Email sending
    $status:=$google.mail.send($email)

    Exemple 2

    Create an action to do because there were errors in the dropping() event.

        //ProductEntity class
    Function event afterDrop ($event : Object)

    var $action: cs.ActionEntity
    var $status: Object

    // The drop action failed
    If($event.dropStatus = "failed")
    $action:=ds.Action.new()
    $action.label:=Last errors.first().message //message is "Error while dropping product XXX"
    $action.status:="TO CHECK"
    $status:=$action.save()
    End if