How to Create a Navigable Link
Overview
This section explains how to create a navigable link in Etendo Classic, implementing a ClientClass. A ClientClass allows you to add visual components to a form or a row of a grid. This is useful for adding calculated fields to forms and grids, integrating elements such as buttons, links or dynamic labels.
In this case, we will implement a navigable ClientClass so that the documentNo in the Create Invoice from Orders window allows to directly open the corresponding sales order in the Sales Order window.
This section shows how to use the record and form information to get dynamic data in the grid or form.
Info
The implementation of these fields requires JavaScript knowledge.
Main Steps to Create a new ClientClass
-
Implement the ClientClass in JavaScript:
-
Create the JavaScript file with the corresponding class and place it in the appropriate directory. The convention is to place the
.js
files in:web/[module.java.package]/js
-
Register the JavaScript file (along with other static resources such as CSS files) in a ComponentProvider.
-
-
Specify the JavaScript class in the field definition:
- Configure the field in the tab/window where the functionality will be applied.
Implementing the ClientClass in JavaScript
The first step is to define the class in JavaScript in two stages:
-
Create the JavaScript class and calculate
tabId
andrecordId
. In this case, the logic is implemented in theDirectTabLink
class, which handles the opening of the window, passing the values oftabId
andrecordId
.direct-tab-link.jsisc.ClassFactory.defineClass('DirectTabLink', isc.OBGridFormLabel); isc.DirectTabLink.addProperties({ height: 1, width: 1, overflow: 'visible', // Method that subclasses must override getTabAndRecordId: function(record, callback) { callback({ tabId: null, recordId: null }); }, setRecord: function(record) { var value = record[this.field.name]; // Call the asynchronous function from the subclass this.getTabAndRecordId(record, function(result) { var tabId = result.tabId; var recordId = result.recordId; if (!value || !tabId || !recordId) { this.setContents(""); return; } // Create the inline function for onclick var linkHTML = "<a href='#' style='color:blue; text-decoration:underline;' " + "onclick='OB.Utilities.openDirectTab(\"" + tabId + "\", \"" + recordId + "\"); return false;'>" + value + "</a>"; this.setContents(linkHTML); // Force grid redraw if (this.grid && this.grid.body) { this.grid.body.markForRedraw(); } }.bind(this)); } });
-
Specific Implementation of the ClientClass
In our example, the target window is Sales Order. Since the
tabId
is fixed and does not change, it can be left as a static value.The
recordId
, on the other hand, contains the unique identifier of each order, which facilitates navigation without the need for additional calculations.sales-order-tab-link.jsisc.ClassFactory.defineClass('SalesOrderTabLink', DirectTabLink); isc.SalesOrderTabLink.addProperties({ getTabAndRecordId: function(record, callback) { var tabId = "186"; // Fixed tab ID for "Sales Order" var recordId = record.id; if (!recordId) { console.error("Error: Record ID not found in the record."); callback({ tabId: null, recordId: null }); return; } callback({ tabId: tabId, recordId: recordId }); } });
Special cases: Calculating recordId
with Java
In some cases, the recordId
information is not available directly in the record, so it must be calculated. This can be achieved through a Java class that determines the corresponding recordId
.
Example:
If the record does not have the recordId
of the commands directly, we can calculate it by calling a specific Java class:
isc.ClassFactory.defineClass('SalesOrderTabLink', DirectTabLink);
isc.SalesOrderTabLink.addProperties({
getTabAndRecordId: function(record, callback) {
var tabId = "186"; // Predefined Tab ID for "Sales Order"
var documentNo = record.documentNo; // Retrieve documentNo from the record
if (!documentNo) {
console.error("Error: documentNo not found in the record.");
callback({ tabId: null, recordId: null });
return;
}
// Call the Openbravo Action Handler to retrieve the recordId
OB.RemoteCallManager.call(
'org.openbravo.client.application.GetSalesOrderIdActionHandler', // Java Action Handler class name
{ documentNo: documentNo }, // Parameter sent to the backend
{},
function(response, data, request) {
if (data.success && data.recordId) {
callback({ tabId: tabId, recordId: data.recordId }); // Use predefined tabId
} else {
console.error("Error retrieving recordId: " + (data.errorMessage || "Invalid response"));
callback({ tabId: null, recordId: null });
}
}
);
}
});
In Java:
import org.openbravo.dal.service.OBCriteria;
import org.openbravo.dal.service.OBDal;
import org.openbravo.model.common.order.Order;
/**
* Action handler to retrieve the sales order ID based on a given document number.
*/
public class GetSalesOrderIdActionHandler extends BaseActionHandler {
private static final String DOCUMENT_NO = "documentNo";
private static final String SUCCESS = "success";
private static final String RECORD_ID = "recordId";
private static final String ERROR_MESSAGE = "errorMessage";
/**
* Executes the action to retrieve the sales order ID based on the provided document number.
*
* @param parameters A map containing execution parameters.
* @param content A JSON string containing the document number.
* @return A JSON object containing the sales order ID and status of the operation.
*/
@Override
protected JSONObject execute(Map<String, Object> parameters, String content) {
JSONObject result = new JSONObject();
try {
final JSONObject jsonData = new JSONObject(content);
if (!jsonData.has(DOCUMENT_NO) || jsonData.isNull(DOCUMENT_NO)) {
result.put(SUCCESS, false);
result.put(ERROR_MESSAGE, "The 'documentNo' parameter is required.");
return result;
}
String documentNo = jsonData.getString(DOCUMENT_NO);
OBCriteria<Order> orderOBCriteria = OBDal.getInstance().createCriteria(Order.class);
orderOBCriteria.add(Restrictions.eq(Order.PROPERTY_DOCUMENTNO, documentNo));
orderOBCriteria.setMaxResults(1);
Order order = (Order) orderOBCriteria.uniqueResult();
result.put(RECORD_ID, order.getId());
result.put(SUCCESS, true);
} catch (Exception e) {
try {
result.put(SUCCESS, false);
result.put(ERROR_MESSAGE, e.getMessage());
} catch (Exception ex) {
throw new OBException(e);
}
}
return result;
Registering the JavaScript file in the ComponentProvider
Once the JavaScript class is created, it is necessary to register it in the ComponentProvider of the corresponding module.
/** JavaScript files required for UI navigation. */
protected static final String[] JS_FILES = new String[]{
"direct-tab-link.js",
"sales-order-tab-link.js"
};
Definition of the ClientClass in the tab field (ADField)
The last step is to add the implementation in the field where the functionality will be applied and configure its ClientClass.
Final Result
As seen below, using the created link, you can directly open the linked sales order in the Sales Order window.