Business objects hooks

This document describes the business object hooks that can be implemented to put some additional business logic to your business objects.

None of these hooks needs to be implemented, simple business objects can only rely on configuration.

You need to implement one or several of these hooks if you want to apply out some dynamic business logic that goes beyond what can be configured.

Note that other mechanisms exists to add some business logic to your business objects using advanced configuration such as:

See this document for details on expressions.

Hooks are very powerful as you can write any needed code, but you need to be careful on the choice of the hook to put your logic in. The choice depends on the nature of the considered business logic:

The hooks examples below are given using the scripted Java (rhino engine) syntax. In such scripts the this variable correspond to the business object itself, it must be explicitly used (it can't be implicit like in Java code). These code examples can easily be transposed to legacy Java hooks code.

Some very common and useful code examples are given in the basic code examples document.

Object definition and right-related hooks

Post load hook

The postLoad hook is called once when the object definition is loaded. It can therefore be used to modify the static object definition.

By static we mean the definition settings that will remain the same all along the user session (i.e. not the dynamic ones that may be updated in other hooks)

For instance it can be used to:

Example:

MyObject.postLoad= function() {
    // In this example we set a restrictive search spec on object (to validated records only) if the user is in a specified group
    if (this.getGrant().hasResponsibility("MYGROUP")
        this.setDefaultSearchSpec(this.getStatusField().getColumn() + " = 'VALIDATED'");
};

Access rights enabling/disabling hooks

The isOpenEnable, isCreateEnable, isCopyEnable, isUpdateEnable and isDeleteEnable hooks allow to dynamically enable/disable open, create, copy, update, delete rights on the object.

They are called for each record (except for isCreateEnable). The row parameter passed to these hooks is the current record for which the hook is called.

These hooks do not allow to override the granted rights of the users, they just allow to restrict these rights depending on more complex business rules.

Example:

MyObject.isCreateEnable = function() {  
    // In this example we check the status of parent object to allow/disallow creation  
    var p = this.getParentObject();
    if (p && p.getName() == "MyParentObject")
        return p.getStatus() == "VALIDATED";
};

MyObject.isUpdateEnable = function(row) {
    // In this example update is allowed if a specified field has a true value  
    return Tool.isTrue(row[this.getFieldIndex("objField1")]);
};

MyObject.isDeleteEnable = function(row) {
    // In this example we apply the same rule as for update
    return this.isUpdateEnable(row);
};

Custom action processing right enabling/disabling hook

The isActionEnable hook has a similar use as above right hooks but for custom actions.

It can be called either globally (row is null in this case) for global actions or for each record.

As above, this hook does not allow to override the granted rights of the users on custom actions, it just allow to restrict the right depending on more complex business rules.

Example:

MyObject.isActionEnable(row, action) {
    // In this example the custom action is allowed depending on the value of a given object field
    if (action == "myCustomAction")
        return Tool.isTrue(row[this.getFieldIndex("objField1")]);
    return true; // This can be omitted as default return value is true
};

See this document for details on how to implement custom actions.

Publication processing right enabling/disabling hook

The isPrintTemplateEnable hook has a similar use as above right hooks but for publications.

Example:

MyObject.isPrintTemplateEnable(row, printtmpl) {
    // In this example the publication is allowed depending on the value of a given object field
    if (printtmpl == "myPrintTemplate")
        return Tool.isTrue(row[this.getFieldIndex("objField1")]);
    return true; // This can be omitted as default return value is true
};

See this document for details on how to implement publications.

State transitions hook

The isStateTransitionEnable hook allows to dynamically enable/disable a state transition.

This hook is called when building the list of possible state transition. It may be useful to implement specific state transition condition rules.

Example:

MyObject.isStateTransitionEnable = function(fromStatus, toStatus) {
    // In this example above the transition between `PENDING` state and `VALIDATED` statuses is dynamically allowed to users of `MYGROUP`:
    if (fromStatus == "PENDING" && toStatus == "VALIDATED")
        return this.getGrant().hasResponsibility("MYGROUP");
};

Current states transition

The current transition can be tested during the save process on UI:

MyObject.postSave = function() {
   var tran = this.getCurrentTransition();
   if (tran && tran.getName()=="Transition-A-B-1") {
      // do something
   }
   else if (tran && tran.getName()=="Transition-A-B-2") {
      // do something else
   }
};

Set the current transition to determine the callback in case of on several granted transitions from A to B:

MyObject.foo = function() {
    // save a transition from A to B
    if (this.getFieldValue("myStatus")=="A") {
        this.setFieldValue("myStatus", "B");
        // Use the callback of transition-A-B-2 (and not transition-A-B-1)
        this.setCurrentTransition("Transition-A-B-2");
        this.save();
    }
}

If the granted transition from A to B is unique, the current transition is not required, by default Simplicite will use the first granted transition from A to B.

Panel hook

The canReference hook allows to show/hide linked object panel.

Example:

MyObject.canReference = function(objectName, fieldName) {
    // In this example the MyChildObject's panel is shown only if the user does not belong to MYGROUP
    return (objectName == "MyChildObject" && !this.getGrant().hasResponsibility("MYGROUP"));        
};

Bulk update hook

The canUpdateAll hook allows to dynamically enable/disable the data bulk update.

Example:

MyObject.canUpdateAll = function(fieldName) {
    // In this example, the bulk update feature is allowed to users who does not belong to MYGROUP
    return (!this.getGrant().hasResponsibility("MYGROUP"));
};

Data history hook

The isHistoric hook allows to dynamically restrict the standard historization. By default, this method return true when the business object has been designed with the historic property. Above, the data bulk update is allowed to user who does not belong to MYGROUP.

Example:

MyObject.isHistoric = function() {
    // In this example an historization is done only when the object's status had changed
    return (this.getStatus() != this.getOldStatus());

};

Data preparation hooks

These data preparation hooks are Object UI-oriented hooks because they are called before displaying a page for read or write (create, update, delete) of an object item.

These hooks are useful for dynamically changing the behavior of an object in a particular use context (e.g. changing one field as updatable depending on the value of another field, forcing a default field values at creation, etc.).

Create, copy, update, and delete preparation hooks

The initCreate, initCopy, initUpdate and initDelete hooks are called each time you open a form to create, copy, update or delete.

They allow to define the properties of attributes, hide, initialize them, put them in read-only, etc. just before the form is displayed.

Examples:

MyObject.initCreate = function() {
    this.getField("objField1").setUpdatable(true);
    this.getField("objField2").setUpdatable(this.getGrant().hasResponsibility("MYGROUP"));
};

MyObject.initUpdate = function() {
    var s = this.getField("objStatus").getValue();
    this.getField("objField1").setUpdatable(s == "PENDING" || s == "VALIDATED");    
};

MyObject.initCopy = function() {
    this.initUpdate();
};

List preparation hook

The initList hook is called each time a list is displayed.

It allows to define the properties of attributes, hide, initialize them, put them in read-only, etc. just before the list is displayed.

Example:

MyObject.initList = function(parent) {
    this.getField("objField1").setUpdatable(true);
    this.getField("objField2").setUpdatable(false);
};

Search preparation hook

The initSearch hook is called before a search form is displayed.

It allows to set field filters for example, etc. just before the search page is displayed.

Example:

MyObject.initSearch = function() {
    this.getField("objField1").setFilter("is null or <1000");
    this.getField("objLogin").setFilter(this.getGrant().getLogin());    
};

Other preparation hooks

The initExport, initCrosstab, initGraph, initAgenda, initPrintTemplate hooks are called before displaying the result of an export, a pivot table, a chart, an agenda, a publication.

They allow to define field filters for example, field values, etc. just before the result is displayed.

Data manipulation hooks

Pre and post validation hooks

These preValidate and postValidate hooks are called before and after the generic data validation is made by the engine.

The generic validation is made before saving a record (creation or update). It only checks the compliance of submitted date in regards to the object definition (e.g. it checks the type of the fileds, checks value of the mandatory fields, apply regular expression checks, ...).

If you have some additional validation logic to add to your business object, such as setting a mandatory field default value before validation or checking a validated value against a more advance business logic (e.g. check that an order quantity is higher than a previously ordered quantity).

Information, warning and/or error messages may be returned (only one message or several).
Only error message(s) prevents the actual saving of the record.

Examples:

MyObject.preValidate = function() {
    if (this.isNew())
        this.getField("objField1").setValue(this.getFieldValue("objField2"));
};

MyObject.postValidate = function() {
    if (this.getField("objQuantity").getInt(0) <= 0)
        return Message.formatError("ERR_TEST", null, "objQuantity");
};

In the above example, the error messages code (ERR_TEST) corresponds to a static text configured in the TEXT list.

Note that pre/postValidate hooks can also return a list of messages instead of a single message like in the above examples:

MyObject.preValidate = function() {
    var msgs = new ArrayList();

    msgs.add(Message.formatError("ERR_TEST0")); // Global error message
    msgs.add(Message.formatInfo("ERR_TEST1", null, "objField1")); // Field error message
    msgs.add(Message.formatWarning("WRN_TEST1", null, "objField2")); // Field warning message

    return msgs;
};

Pre and post selection hooks

The preSelect and postSelect hooks are called before/after selecting the object data (in a list they are called for each list items).

They can be used to implement some business rules to set some field values for example.

Example:

MyObject.preSelect = function(rowId, copy) {
    // If the data is selected for a copy set a field with particular value 
    if (copy)
        this.getField("objField1").setValue("value");
};

Pre and post creation, update, deletion hooks

The preCreate, preUpdate, preDelete, postCreate, postUpdate, postDelete hooks are called before/after creating, updating, deleting the object data.

The preSave hook is called just after the preCreate or preUpdate hooks (see bellow).

The postSave hook is called just after the postCreate or postUpdate hooks (se bellow).

These hooks can be used to implement some business rules to set some field values (that needs to be done after validation) or just to prevent saving in some particular cases, etc.

In case of creation, the technical row_id field is not yet set (this is actually done in the create() core method). The default value for row id is 0 at this step.

The pre delete hook is called before deletion. No validation is processed before deletion. Pre and post save are not executed either in this case.

Pre delete hook can be used to implement some business logic to allow or prevent saving in some particular cases, etc. When it returns a non null single error code, no actual deletion is done.

Post delete hook can be used to implement some business rules after the object is actually deleted.

Note: Cascade deletion of child object is not supposed to be coded as this behavior is configurable at link level.

Example 1:

MyObject.preCreate = function() {   
    // Get a system param sequence next value
    this.setFieldValue("objRefField", "REF"+this.getGrant().getNextSystemParamValue("MYSEQUENCEPARAM"));    
};

Note: for this simple case, the same result could be obtained using te following default value expression of the objRefField field: [EXPR:"REF"+[GRANT].getNextSystemParamValue("MYSEQUENCEPARAM")]

Example 2:

MyOrder.preCreate = function() {    
    // Generate a unique number use as an id. For example an Order number for a Client.
    var client = this.getField("orderClientId");  // foreign key
    var number = this.getField("orderNumber");
    var n = this.getGrant().getNextValueForColumnWhere(this.getTable(), number.getColumn(), client.getColumn()+" = "+client.getValue());
    number.setValue(n);     
};

Note: to generate unique codes based on the row ID the right approach is to configure a default value expression on your field with an expression like [EXPR:Tool.format("ABC-%05d", Long.valueOf([ROWID]))] (in this example the field gets ABC-00123 as value at the creation of a record with row ID 123)

Pre and post save hooks

The preSave and postSave hooks are called before/after saving the object data.

In all cases, the preSave hook is called after a preUpdate or a preCreate hook.

In all cases, the postSave hook is called after a postUpdate or postCreate hook.

These hooks can be used to implement some business rules to set some field values (that needs to be done after validation) or just to prevent saving in some particular cases, etc.

Example:

MyObject.postSave = function() {
    // Update a data of a linked object after
    if (this.getOldStatus() == "VALIDATED" && this.getStatus() == "DELIVERED") {
        var obj = this.getGrant().getTmpObject("MyLinkedObject");
        obj.select(this.getField("objMyLinkedObjectMyObjectId").getValue());
        obj.getField("otherObjField1").setValue("value");
        try {
            new BusinessObjectTool(obj).validateAndSave();
        } catch(e) {
            console.error(e.javaException ? e.javaException.getMessage() : e);
        }
    }
};

Pre and post search hooks

The preSearch and postSearch hooks are called before/after searching the object data: before/after the search core method is called.

Pre search hook is called to add specific filters or order the result: list, pivot table, graph, publication or export.

Post search hook is called after search to add specific code for instance to evaluate simple calculated fields, reorder or remove records.

Examples:

MyObject.preSearch = function() {
    this.getField("objField1").setFilter("is null or <1000");
    this.getField("objField2").setOrder(1);
    this.getField("objField3").setOrder(-2);
};

MyObject.postSearch = function(rows) {
    var fieldIndex = this.getFieldIndex("objField1");
    for (var i = 0; rows && i < rows.size(); i++) {
        var row = rows.get(i);
        row[i] = "Value #" + i;     
    }
    return rows;
};

Pre and post bulk update and bulk delete hooks

The preUpdateAll, postUpdateAll, preDeleteAll and postDeleteAll hooks are called before/after a data bulk update or bulk delete.

These hooks are called to add specific behaviors before/after a bulk update/delete.

Example:

MyObject.preUpdateAll = function() {
    this.getField("objField1").setFilter("is null or <1000");
    this.getField("objField2").setOrder(1);
    this.getField("objField3").setOrder(-2);    
};

Pre and post export hooks

The preImport and postImport hooks are called before/after a data is imported.

These hooks are called to add specific behaviors before/after an import.

Examples:

MyObject.preImport = function() {
    if (this.getFieldValue("objField1") == "value 1") {
        this.setFieldValue("objField2", "value 2");
        this.setFieldValue("objField3", "value 3");
    }   
};

MyObject.postImport = function() {
    // Send an alert if a value is imported 
    var a = this.getAlert("MYALERT", Alert.TYPE_INFO);
    if (a && this.getField("objField1").isEmpty()) a.send(this);    
};

Pre and post alerts hooks

The preAlert and postAlert hooks are called before/after the alert is sending.

The preAlert hook can be used to change the alert just before sending (change the core message and/or add specific recipients).

Example:

MyObject.preAlert = function(alert) {
    alert.setSubject("ENU", "Dear [bill_last_name]");
    alert.setContent("ENU", "Your bill of [bill_amount] ...");
    alert.addRecipient("john@domain.com", Alert.RECIP_TO);   
};

The postAlert hook can be used to implement some business logic just after sending.

Example:

MyObject.postAlert = function(alert) {
    this.getField("objField4").setValue("Mail sent !");
};

Send alert with custom attachments

It is possible to send one alert from any other hook and to add specific attachments:

MyObject.postSave = function() {
    var g = this.getGrant();
    // Get the alert definition (with subject, body, recipients... or null if the alert is disabled)
    var alert = this.getAlert("MyAlert", Alert.TYPE_INFO);
    if (alert) {
        // Add attachments from a child object with a document field
        var i, doc, list, att = new ArrayList();
        var a = g.getTmpObject("MyObjectAttachment");
        a.resetFilters();
        a.setFieldFilter("MyObject_FK", this.getRowId());
        list = a.search();
        for (i=0; i<list.size(); i++) {
            a.setValues(list.get(i));
            doc = a.getField("MyDocField").getDocumentFromDB(g);
            if (doc) {
                console.log("debug attach: "+doc.toString());
                att.add(doc);
            }
        }
        // Send with custom attachments
        alert.send(this, att);
    }
}

Other hooks

Short label hook

The getUserKeyLabel hook can be used to override a business object record's default "short" label (the one which is displayed on form titles, on indexed search results, on treeviews, ...)

Example:

MyObject.getUserKeyLabel = function(row) {
    // Override the record label on treeviews
    if (this.isTreeviewInstance())
        return row[this.getFieldIndex("myFirstLabelField")] + " - " + row[this.getFieldIndex("mySecondLabelField");
    else
    {
        if (!row) // Form
            return this.getFieldValue("myFormLabelField");
        else // List
            return row[this.getFieldIndex("myListLabelField")];
    }
}

Style hook

It is possible to set style (for instance a CSS class) on a field based on business logic:

MyObject.getStyle = function(f, row) {
    if (f.getName() == "myField")
        return this.getFieldValue("myOtherField") == "V1" ? "greenbg" : "redbg";
};

Helps hook

As of version 3.2 MAINTENANCE06 the getHelp and getCtxHelp hooks can be use to dynamically create/update main help and contextual helps:

MyObject.getHelp = function(help) {
    if (help)
        return "<p>This is the main help:</p><pre>" + help + "</pre>";
};
MyObject.getCtxHelp = function(ctx, help) {
    if (help)
        return "This is the contextual help (" + ctx + " context):</p><pre>" + help + "</pre>";
};

Using history

If a MyObject business object is historized, there is an additional business object names MyObjectHistoric that stores the values of each record.

A new record is created each time one of the common fields of MyObject and MyObjectHistoric is changed.

To access the historic records of a given record you can use:

var h = this.getGrant().getTmpObject(this.getHistoricName()); // Get historic object
h.resetFilters();
h.getField("row_ref_id").setFilter(this.getRowId()); // Filter on current row ID
h.getField("row_idx").setOrder(-1); // Reverse order on history index
h.search(false);
(...)

Redirection

It is possible to open a given "abstract" father object (e.g. Vegetable) record as its corresponding specialised child object (e.g. Carrot or Cabbage) record by implementing a father-child redirect hook.

Example:

 Vegetable.getTargetObject = function(rowId, row) {
    if (rowId.equals(ObjectField.DEFAULT_ROW_ID)) return null; // No redirection at creation
    if (!row && this.select(rowId)) row = this.getValues();
    var target = null;
    if (row) {
        var type = row[this.getFieldIndex("vegetableType")];
        if (type=="CARROT") target = "Carrot";
        else if (type=="CABBAGE") target = "Cabbage";
    }
    if (!target) return null; // Unknown type, no redirection
    var t = ScriptInterpreter.getStringArray(3);
    t[0] = target; // target object
    t[1] = "the_"+target; // main target instance
    t[2] = rowId; // target row Id (same in this inheritance case)
    return t;
};

This mechanism can also be used to do redirection between objects that don't have a father-child relationship.