Custom action examples
Please refer to basic code examples document for naming conventions and logging strategies.
Server-side custom action
A server-side action corresponds to an action with a value in the method field.
This method field contains the name of a server-side Rhino script function or a Java method (depending on the language you are using).
For instance if you set myCustomAction
as action method and if you grant this action to the MyObject
business object,
the platform will look for the server-side Rhino script function or Java method called MyObject.myCustomAction()
and will run it.
Java
public String myCustomAction() {
String rowId = getRowId();
return Message.formatSimpleInfo("Using instance " + getInstanceName() + (!Tool.isEmpty(rowId) ? " and row ID " + rowId : ""));
}
Rhino JavaScript equivalent
MyObject.myCustomAction = function() {
var rowId = this.getRowId();
return Message.formatSimpleInfo("Using instance " + this.getInstanceName() + (!Tool.isEmpty(rowId) ? " and row ID " + rowId : ""));
}
On responsive UI (using ajax services) the action can return:
- a textual message to be displayed in a UI dialog box (error and warning) or in a UI toast box (info)
- a client-side JavaScript statement (a string prefixed with
javascript:
) - an HTTP redirect URL statement (a string prefixed with
redirect:
)
Examples:
return Message.formatSimpleError("It is not permitted");
to simply return an error messagereturn this.javascript("$ui.displayForm(null, 'myObject', " + myRowId + ", {nav:'add'})");
orreturn this.redirect(HTMLTool.getFormURL("myObject", "the_ajax_myObject", myRowId, "nav=add"));
to redirect to the form of another object's recordreturn $ui.displayPrint(null, 'MyPublication', obj, obj.getRowId());
to apply a publication on the current record of the object t etc.
Note: These server-side custom actions are available thru the webservices APIs.
Confirmation with custom fields
The action supports fields in the confirmation dialog:
- If the field belong also to the object: the value is read only (to confirm a value or to preview a document)
- If the field is standalone: the input field is updatable and will be sent to the server side as a String
On server side the action's method will receive the fields within a HashMap
:
Java
public String myCustomAction(Map<String,String> params) {
String value = params!=null? params.get("myActionField"): null;
...
return null;
}
Rhino JavaScript equivalent
Previous V4 syntax supports String
values only thru a Map
:
MyObject.myCustomAction = function(params) {
var myActionField = params!=null ? params.get("myActionField") : null;
...
}
This syntax is deprecated in V5 and must be replaced by a new
V5 syntax to supports ObjectField
directly as follow:
Java
public String myCustomAction(Action a) {
String = getGrant().getLang();
String actionField = action!=null ? action.get(lang, "myActionField").getValue() : null;
DocumentDB myDoc = action!=null ? action.get(lang, "myDocField").getDocument() : null;
File file = myDoc.getUploadFile();
// do something with the document...
// Remove the file from /tmp directory
file.delete();
// ...
}
Rhino JavaScript equivalent
MyObject.myCustomAction = function(action) {
var lang = this.getGrant().getLang();
var myActionField = action!=null ? action.get(lang, "myActionField").getValue() : null;
var myDoc = action!=null ? action.get(lang, "myDocField").getDocument() : null;
var file = myDoc.getUploadFile();
// do something with the document...
// Remove the file from /tmp directory
file.delete();
// ...
}
Java
Previous V4 syntax supports only String
values thru a Map<String,String>
:
public String myCustomAction(Map<String,String> params) {
String myActionField = params!=null ? params.get("myActionField") : null;
// ...
}
This syntax is deprecated but still compatible with V5 and simple fields.
Now V5 syntax supports ObjectField
directly to manipulate value or document:
public String myAction(Action action) {
// String field (text, date...)
String param = action.getConfirmField("myStringField").getValue();
// Document field
ObjectField myDocField = action.getConfirmField("myDocField");
DocumentDB doc = myDocField!=null ? myDocField.getDocument() : null;
AppLog.info(getClass(), "myAction", "DOCUMENT " + doc, getGrant());
if (doc!=null) {
// Uploaded tmp file from UI
java.io.File file = doc.getUploadFile();
AppLog.info(getClass(), "myAction", "DOCUMENT FILE " + file, getGrant());
// do something with the document...
// You must remove the file from /tmp directory when used
file.delete();
}
// ...
}
Call to action with the returned message
Message.formatCallToAction
is available since V5.3.
Custom Action
can be added to the returned message (ex postSave) to ask the user to do something.
- action type must be hidden to be used only in this context
- action is displayed only if the user is granted thru a function
- action will call the front javascript or the back-end method
// Single action
ObjectDB obj = this;
Action action = obj.getAction("MyAction_SendEmail");
return Message.formatCallToAction("WARNING", "Save is OK, do you want to send an email to client?", Message.WARN, null, obj, action);
// Or with several actions
List<Action> list = new ArrayList<>();
list.add(obj.getAction("MyAction_OptionA");
list.add(obj.getAction("MyAction_OptionB");
return Message.formatCallToAction("MYCODE", "What is the best option?", Message.WARN, null, obj, list);
State transition with parameters
When the action is a transition, there is no method invoked but confirmation fields are passed thru an object's parameter named ActionFields
.
This parameters can be read in all common hooks during the state transition (validate / save / update).
Previous V4 implementation:
Java
public String postSave() {
HashMap<String,String> params = (HashMap<String,String>)geObjectParameter("ActionFields");
String myActionField = params!=null ? params.get("myActionField") : null;
...
}
Rhino JavaScript equivalent
MyObject.postSave = function() {
var params = this.geObjectParameter("ActionFields");
var myActionField = params!=null ? params.get("myActionField") : null;
...
}
V5 implementation:
"ActionFields" has been removed from object parameters, the current transition and its Action can be accessed to read values and documents:
public String postSave() {
// Save has been called from a transition ?
FieldStateTransition tran = getCurrentTransition();
Action action = tran!=null ? tran.getAction() : null;
if (action!=null) {
String param = action.getConfirmField("myField").getValue();
DocumentDB doc = action.getConfirmField("myDocField").getDocument();
File file = doc!=null ? doc.getUploadFile() : null;
...
}
...
}
The Action fields can also been accessed in the callback method
of transition with this syntax:
public String myTransitionCallback(Action action) {
if (action!=null) {
String param = action.getConfirmField("myField").getValue();
DocumentDB doc = action.getConfirmField("myDocField").getDocument();
File file = doc!=null ? doc.getUploadFile() : null;
// ...
// Return a message to UI
if ("value1".equals(param))
return Message.formatSimpleInfo(getGrant().T("MY_MESSAGE"));
// Redirect to other form
if ("value2".equals(param)) {
String url = HTMLTool.getFormURL("myObject", "the_ajax_myObject", "123", null);
return HTMLTool.redirectStatement(url);
}
}
return null; // OK
}
The callback method
:
- can be set on the
Transition
or on the relatedAction
(but not both) - will be executed after the DB commit (new status is correctly stored in table) and before the hook
postUpdate
of object - can return a message (info, warning, error) or a statement (redirect, javascript)
Client-side custom action
A client-side action corresponds to an action with a value in the URL field.
Plain URL
When URL contains a plain URL (that can be either absolute or relative), the platform UI will call this URL appending the object
, inst
and row_id
URL parameters.
JavaScript statement pseudo-URL
When URL contains a client-side JavaScript statement pseudo-URL (starting with javascript:
), the platform will run this JavaScript statement after
having substituted the [object]
, [inst]
and [row_id]
tags.
For instance, let's say you have created the default client JavaScript resources SCRIPT
resource for your MyObject
business object like this:
var MyObject = (function() {
function test(inst, row_id) {
return $ui.alert({ content: "Using instance " + inst + (row_id ? " and row ID " + row_id : "") });
}
return { myCustomAction: test };
})();
Then you can configure a custom action for MyObject
to call this myCustomAction
function with this value in the URL field:
javascript:return [object].myCustomAction('[inst]', [row_id])
Attention, due to the way the UI runs the JavaScript statement, you must only use simple quotes '
in it (this limitation does not apply to the content of the SCRIPT
resource).
The javascript statement also supports front parameter app
, grant
and obj
when the server side substitution is not required:
javascript:return obj.getName().myCustomAction(obj.getInstanceName(), obj.getRowId())
Note: These client-side custom actions are not available thru the webservices APIs.
Asynchronous action launched by the UI with tracking
Since 6.0 the method signature can get a new parameter to implement the tracking:
public String myAction(Action action, AsyncTracker tracker) {
try {
// Already running ?
if (tracker.isRunning())
return null;
// Can close the dialog on UI
tracker.setCloseable(true);
// Can minify the dialog on UI
tracker.setMinifiable(true);
// Not minimized on startup
tracker.setMinified(false);
// STOP button on UI
tracker.setStoppable(true);
// 0%
tracker.setProgress(0);
// limit tasks size in memory
tracker.setDepth(50);
// Start the tracking
tracker.start();
tracker.add("MyAction has started");
// Track the job 1
tracker.push("Job 1");
// ...
tracker.message("doing something in job 1");
// ...
tracker.message("doing something else in job 1");
tracker.error("something wrong");
// ...
tracker.pop("done");
// 20%
tracker.setProgress(20);
// Check running periodically
if (!tracker.isRunning())
return null;
// 30%
tracker.setProgress(30);
// Track the job 2
tracker.push("Job 2");
// ...
tracker.pop("done");
// ...
// Check running periodically
if (!tracker.isRunning())
return null;
// ...
}
catch (Exception e) {
// Assign the error on current task
tracker.error(e.getMessage());
}
finally {
// Stop tracking
tracker.stop();
}
return null;
}
Or a synchronous action with internal asynchronous Job:
/** Synchronous action launched by the UI with internal asynchronous Job */
public String myAction(Action action, AsyncTracker tracker) {
// Already running ?
if (tracker.isRunning())
return null;
tracker.start();
JobQueue.push("myJob", new Runnable() {
@Override
public void run() {
try {
tracker.add("MyAction has started");
tracker.push("Job 1");
// ...
tracker.message("doing something in job 1");
// ...
tracker.pop("job 1 done");
// ...
}
catch (InterruptedException e) {
tracker.message("Interrupted");
Thread.currentThread().interrupt();
}
catch (Exception e) {
// Assign the error on current task
tracker.error(e.getMessage());
}
finally {
tracker.stop();
}
}
});
return null;
}