View source | Discuss this page | Page history | Printable version   

ERP 2.50:Developers Guide/How to develop a callout

ERP 2.50:Developers Guide

Index

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:

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:

  1. Create the source file(s) of the callout (usually a java file and an optional xsql file).
  2. Define the new callout within the application dictionary (menu Application Dictionary > Setup > Callout).
  3. Associate this callout with a table column (Application Dictionary > Table and Column: Callout field withinin Column tab).
  4. 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.

Bulbgraph.png   Note: This article assumes you have created one according to the howto just mentioned.


Creating the Callout Servlet

Existing callouts are located in src/org/openbravo/erpCommon/ad_callouts.

There are now two ways to create a callout:

  1. the legacy way
  2. extending the SimpleCallout class

We will explain the former for you to be able to understand existing code as well at the latter that is the latter that is the recommended way.

Legacy Callouts

A callout Java file is similar to other Openbravo servlets and extends the HttpSecureAppServlet class. 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:

  1. Retrieve the name of the product as entered by the user
  2. Retrieve the ID of the category selected from a dropdown by the user
  3. Query the name of the product category inside the database using the product category ID retrieved
  4. Strip spaces out of the product and category names
  5. construct the Search Key

Defining 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.

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));
Bulbgraph.png   Do not quote your numeric values

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.


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();
    }
 }
Bulbgraph.png  

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.

Extending the SimpleCallout

If you do need to have full control of the callout servlet request and response you can use the class SimpleCallout. This class has been introduced to simplify the callout code, hide some of the internals of the callout and keep you focused on the operations required.

Theory

To develop a new callout based on this class you only have to create a new java class that extends SimpleCallout and overwrite the following method:

 protected void execute(CalloutInfo info) throws ServletException;

In this method you can develop the logic of the callout and use the info object of class CalloutInfo to access window fields, database and other methods. The most important are:

See the following class as an example of a class that currently uses SimpleCallout: SL_Project_Service. This callout simply takes the numeric value of two fields, calculates the sum and writes it into another field. This is the interesting part of the code that performs the logic:

 @Override
 protected void execute(CalloutInfo info) throws ServletException { 
 
     BigDecimal serviceSerCost = info.getBigDecimalParameter("inpservsercost");
     BigDecimal serviceOutCost = info.getBigDecimalParameter("inpservoutcost"); 
 
     BigDecimal serviceTotalCost = serviceSerCost.add(serviceOutCost);
 
     info.addResult("inpservcost", serviceTotalCost);
 }

Product Search Key Calculation using SimpleCallout

Let's take a look at the code that performs the same operation as the legacy code explained above, however, with two main distinctions:

  1. it uses the the SimpleCallout class which simplifies the code and makes it cleaner
  2. it uses the DAL (Data Access Layer) to retrieve product category data from the database as opposed to XSQL
// the package name corresponds to the module's manual code folder 
// created above
package org.openbravo.howtos.ad_callouts;
 
import javax.servlet.ServletException;
 
import org.openbravo.utils.FormatUtilities;
import org.openbravo.erpCommon.ad_callouts.SimpleCallout;
import org.openbravo.base.secureApp.VariablesSecureApp;
// classes required to retrieve product category data from the 
// database using the DAL
import org.openbravo.dal.service.OBDal;
import org.openbravo.model.common.plm.ProductCategory;
 
// 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 SimpleCallout
public class ProductConstructSearchKey extends SimpleCallout {
 
  private static final long serialVersionUID = 1L;
 
  @Override
  protected void execute(CalloutInfo info) throws ServletException {
 
    // 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 = info.getStringParameter("inpname", null);
    String strProductCategoryId = info
                    .getStringParameter("inpmProductCategoryId", null);
 
    // inject the result into the response
    info.addResult("inpvalue", getConstructedKey(info.vars, strProductName, strProductCategoryId));
  }
 
  protected String getConstructedKey(VariablesSecureApp vars,
        String strProductName, String strProductCategoryId) {
 
    // Retrieve the product category name
    final ProductCategory productCategory = OBDal.getInstance().get(ProductCategory.class,
            strProductCategoryId);
    String strProductCategoryName = productCategory.getName();
 
    // construct full key
    String generatedSearchKey = FormatUtilities.replaceJS(strProductName
                .replaceAll(" ", ""))
                + "_" + strProductCategoryName.replaceAll(" ", "");
 
    // return generated key
    return generatedSearchKey;
  }
}

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:

Bulbgraph.png  

The name of the callout should not have spaces or illegal javascript characters.

CreateCallout1.png


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:


CreateCallout2.png


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:


CreateCallout3.png


Do the same for the Product Category column since a change in any of them should also regenerate the Search Key.

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 smartbuild 

or

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!

Bulbgraph.png  

It makes development and troubleshooting callouts easier if you make the hidden callout frame visible for the time of the development.

You can do this by changing the line

<frameset cols="*" frameborder="no" border="0" framespacing="0" rows="100%,*">

to

<frameset cols="*" frameborder="no" border="0" framespacing="0" rows="95%,50">

inside the OpenbravoERP/src/org/openbravo/erpCommon/security/Login_FS.html file

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.


CreateCallout4.png


Save.

You have now successfully created your first new callout and seen how it came to life within Openbravo ERP. Congratulations!



ERP 2.50:Developers Guide/How to develop a new window | ERP 2.50:Developers Guide/How to develop an alert 

Retrieved from "http://wiki.openbravo.com/wiki/ERP_2.50:Developers_Guide/How_to_develop_a_callout"

This page has been accessed 33,180 times. This page was last modified on 14 June 2011, at 11:04. Content is available under Creative Commons Attribution-ShareAlike 2.5 Spain License.