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 Java method.
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 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 : ""));
}
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;
}
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();
// ...
}
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;
...
}
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;
}