ERP/2.50/Developers Guide/How to develop a callout
Contents |
Objective
The objective of this article is to show you how to create a new callout. A callout is a piece of Javascript code associated with a particular field on a tab. This code is executed whenever the field changes. It is a type of Ajax substitute, changing parts of a tab/window without the need of refreshing it.
This is how it works:
- When the field changes the onChange Javascript event fires and an HTTP request is sent to the callout servlet within a hidden frame
- The callout servlet performs the operation(s) required (fetching data from the database, calculating something, etc) and generates Javascript code (definitions of some variables) that specifies which fields need to be changed and which values to set
- The response of this servlet is loaded into a hidden frame underneath the main tab (you cannot see this frame since its height is 0)
- A Javascript library referenced by the callout template parses the generated code and replaces the required fields with their new values generated by the servlet.
This how-to will implement the following new functionality: When entering a new product, one has the option of entering the Search Key for the product, the Name and the Category it belongs to. But what if our client wants the search key to be constructed automatically by taking the product's name, removing all spaces, appending the underscore (_) and the category name it belongs to.
For example, this way the Search Key of a product that has the Name Adidas Sneaker RXT and belongs to the Clothes Product Category would become AdidasSneakerRXT_Clothes. Let's see how this could be done using a callout.
The steps involved in creating a new callout are:
- Create the source file(s) of the callout (usually a java file and an optional xsql file).
- Define the new callout within the application dictionary (menu Application Dictionary > Setup > Callout).
- Associate this callout with a table column (Application Dictionary > Table and Column: Callout field withinin Column tab).
- Compile the window/tab(s) where this Column is used.
Module
All new developments must belong to a module that is not the core module. Please follow the How to create and package a module section to create a new module.
Create the Callout Servlet
Existing callouts are located in src/org/openbravo/erpCommon/ad_callouts. A callout Java file is similar to other Openbravo servlets and must extend HttpSecureAppServlet. As all development needs to be done as part of a module your Java file (holding the callout servlet) should be located in the src directory of a module, see here for more information.
The template used to generate the output is generally the same for all callouts, an XmlDocument template (CallOut.html + CallOut.xml located in the ad_callouts core src folder mentioned above). This document is injected with a Javascript array generated by a specific callout containing the names of the fields to be changed and their new values.
By looking at one of the existing callout servlets, one can figure out that the rough layout looks something similar to (note specific comments in the code):
// the package name corresponds to the module's manual code folder // created above package org.openbravo.howtos.ad_callouts; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.openbravo.base.secureApp.HttpSecureAppServlet; import org.openbravo.base.secureApp.VariablesSecureApp; import org.openbravo.xmlEngine.XmlDocument; // the name of the class corresponds to the filename that holds it // hence, modules/modules/org.openbravo.howtos/src/org/openbravo/howtos/ad_callouts/ProductConstructSearchKey.java. // The class must extend HttpSecureAppServlet. public class ProductConstructSearchKey extends HttpSecureAppServlet { private static final long serialVersionUID = 1L; public void init(ServletConfig config) { super.init(config); boolHist = false; } public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { VariablesSecureApp vars = new VariablesSecureApp(request); // callout calls always come in with the DEFAULT parameter if (vars.commandIn("DEFAULT")) { // parse input parameters here ... try { printPage(response, vars, ...); // pass additional parameters you // have parsed above to the printPage // rendering subroutine } catch (ServletException ex) { pageErrorCallOut(response); } } else pageError(response); } private void printPage(HttpServletResponse response, VariablesSecureApp vars, ...) throws IOException, ServletException { log4j.debug("Output: dataSheet"); // make an instance of the generic callout template where our // code will be injected XmlDocument xmlDocument = xmlEngine.readXmlTemplate( "org/openbravo/erpCommon/ad_callouts/CallOut") .createXmlDocument(); // perform any data queries or operations necessary ... // use a buffer variable to construct the generated code StringBuffer result = new StringBuffer(); // calloutName variable must be defined and contain the name of // the Java class that implements the callout result.append("var calloutName='Product_Construct_SearchKey';\n\n"); // respuesta variable must be defined and contain an array // of arrays that contains the names of the fields that need // to be changed and their corresponding new values result.append("var respuesta = new Array("); result.append("new Array(\"inpTableColumnName\", \"" + "columnValue" + "\")"); result.append(");"); // inject the generated code xmlDocument.setParameter("array", result.toString()); // which frame do the changes need to be made inside xmlDocument.setParameter("frameName", "appFrame"); response.setContentType("text/html; charset=UTF-8"); PrintWriter out = response.getWriter(); out.println(xmlDocument.print()); out.close(); } }
Keep in mind, this is only the framework for our callout that doesn't do anything just yet. Let's define the tasks that need to be performed by the callout:
- Retrieve the name of the product as entered by the user
- Retrieve the ID of the category selected from a dropdown by the user
- Query the name of the product category inside the database using the product category ID retrieved
- Strip spaces out of the product and category names
- construct the Search Key
Define the Database Query XSQL
Because the input to our callout will only be the ID of the product category and not its full name, we need to retrieve it from the database. This is done using an XSQL structure that results in a Java class that does the database connection and retrieval for us.
Common practice of developing these structures in Openbravo is that we use the file name of the servlet that will be using it and append _data.xsql for the file that will contain it. In our case, we will create the modules/modules/org.openbravo.howtos/src/org/openbravo/howtos/ad_callouts/Product_Construct_SearchKey_data.xsql. Upon compilation, Openbravo's SQLC library will convert this file to a ProductConstructSearchKeyData class that can be referenced from our callout servlet considering that we usually put it into the same package as the callout servlet itself (org.openbravo.howtos.ad_callouts).
The content of our xsql should be something like:
<?xml version="1.0" encoding="UTF-8" ?> <SqlClass name="ProductConstructSearchKeyData" package="org.openbravo.howtos.ad_callouts"> <SqlMethod name="select" type="preparedStatement" return="string"> <SqlMethodComment></SqlMethodComment> <Sql> <![CDATA[ SELECT pc.name FROM m_product_category pc WHERE pc.m_product_category_id = ? ]]> </Sql> <Parameter name="mProductCategoryId"/> </SqlMethod> </SqlClass>
Upon compilation, this XSQL file will be converted to a ProductConstructSearchKeyData.java and compiled. This class exposes the select method defined above as a static method of this class. We can now easily call it inside the callout servlet as:
String strProductCategoryName = ProductConstructSearchKeyData.select(this, strProductCategoryId);
Through this object (the servlet itself), the database connection will be obtained.
Final Callout Servlet
Finally, our callout with the tasks that it needs to perform implemented will look like:
package org.openbravo.howtos.ad_callouts; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.openbravo.base.secureApp.HttpSecureAppServlet; import org.openbravo.base.secureApp.VariablesSecureApp; import org.openbravo.utils.FormatUtilities; import org.openbravo.xmlEngine.XmlDocument; public class ProductConstructSearchKey extends HttpSecureAppServlet { private static final long serialVersionUID = 1L; public void init(ServletConfig config) { super.init(config); boolHist = false; } public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { VariablesSecureApp vars = new VariablesSecureApp(request); if (vars.commandIn("DEFAULT")) { // parse input parameters here; the names derive from the column // names of the table prepended by inp and stripped of all // underscore characters; letters following the underscore character // are capitalized; this way a database column named // M_PRODUCT_CATEGORY_ID that is shown on a tab will become // inpmProductCategoryId html field String strProductName = vars.getStringParameter("inpname"); String strProductCategoryId = vars .getStringParameter("inpmProductCategoryId"); try { if (strProductName != null && strProductCategoryId != null) printPage(response, vars, strProductName, strProductCategoryId); } catch (ServletException ex) { pageErrorCallOut(response); } } else pageError(response); } private void printPage(HttpServletResponse response, VariablesSecureApp vars, String strProductName, String strProductCategoryId) throws IOException, ServletException { log4j.debug("Output: dataSheet"); XmlDocument xmlDocument = xmlEngine.readXmlTemplate( "org/openbravo/erpCommon/ad_callouts/CallOut") .createXmlDocument(); // retrieve the actual product category name String strProductCategoryName = ProductConstructSearchKeyData.select( this, strProductCategoryId); // construct the Search Key String generatedSearchKey = FormatUtilities.replaceJS(strProductName .replaceAll(" ", "")) + "_" + strProductCategoryName.replaceAll(" ", ""); StringBuffer result = new StringBuffer(); result.append("var calloutName='ProductConstructSearchKey';\n\n"); result.append("var respuesta = new Array("); // construct the array, where the first dimension contains the name // of the field to be changed and the second one our newly generated // value result .append("new Array(\"inpvalue\", \"" + generatedSearchKey + "\")"); result.append(");"); // inject the generated code xmlDocument.setParameter("array", result.toString()); xmlDocument.setParameter("frameName", "appFrame"); response.setContentType("text/html; charset=UTF-8"); PrintWriter out = response.getWriter(); out.println(xmlDocument.print()); out.close(); } }
|
Since you are using user input to generate the output which is in Javascript format be careful of the quotations, XSS vulnerabilities and other special characters in Javascript.. To avoid these issues, use the replaceJS(string) of the utils/FormatUtilities class. This method replaces some characters that are sensitive such as “'”, “\””, “\n” and “\r” by “\\'”, “\\\””, “\\n” and “”. XSS is also addressed within Openbravo's parameter retrieval methods such as getStringParameter(), etc. |
Defining the Callout within the Application Dictionary
Using the System Administrator role navigate to Application Dictionary || Setup || Callout. Create a new record as indicated by the screenshot below:
Save and navigate to the Callout Class tab of the same window. You will notice that the Java Class Name was automatically generated for you, however, not correctly since we are developing a module and not the core. Correct it in line with your callout package/class name. See screenshot below:
Now Openbravo ERP knows to expect a callout on your harddrive according to how you just defined it.
Associating the Callout with a Column
Using the System Administrator role navigate to Application Dictionary || Tables and Columns and find the M_Product DB Table. This is the underlying table of the main tab of the Product window.
Go to Column tab, find the Name column and double click it. Scroll down to the bottom of this tab to find the Callout dropdown that should at this point be empty. Select our Product_Construct_SearchKey callout and save the record:
Do the same for the Product Category column since a change in any of them should also regenerate the Search Key.
Returning numeric values
Is important that you keep numeric values as numbers, and strings as strings. The usual JavaScript reply is like this one:
var calloutName='Application_Dictionary_CalloutName'; var respuesta = new Array(new Array("inpsomefield", "SomeStringValue"));
In this case the respuesta variable is parsed and the inpsomefield field is populated with the SomeStringValue value.
In the case of returning numeric values, remember to keep numbers as numbers.
var calloutName='Application_Dictionary_CalloutName'; var respuesta = new Array(new Array("inpsomenumericfield", 100.55));
In this case the inpsomenumericfield will be populated with the value 100.55 but after applying the correct number format, this action is made automatically by the reply parser.
Compiling the Window
Finally, for the callout to take effect, the window that uses it needs to be recompiled and deployed to Tomcat. If using Eclipse, use the eclipse.compile ant task and enter Product into the dialog that pops up. If manually compiling Openbravo, use the
ant compile.development -Dtab='Product
Important note: once the compilation has finished, restart Apache Tomcat server. |
See more on Build Tasks.
Now it is time to test the result!
The Result
Using the Openbravo Admin role, navigate to the Master Data Management || Product window. Enter a new product with Name = Adidas Sneakers RTX and leave the Name field. Notice how the Search Key changes. Then, change the Product Category to something else and see how the change is reflected inside the Search Key field.
Save.
You have now successfully created your first new callout and seen how it came to life within Openbravo ERP. Congratulations!
How to develop a new window | How to develop an alert





