How to create a Manual UI Process

From InfiniteERP Wiki
Revision as of 20:42, 15 October 2018 by Wikiadmin (talk | contribs) (Created page with "== Introduction == ''Manual UI Pattern Process'' is an implememtation for the new process definition in Openbravo 3 (see also How to create a Pick and Execute Process). I...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Introduction

Manual UI Pattern Process is an implememtation for the new process definition in Openbravo 3 (see also How to create a Pick and Execute Process). It allows to code the whole UI with SmartClient, it can be used when default generated UI doesn't fit the needs of the process.

This how to will add 2 new Manual UI Processes associated with the Sales Order window.

The implementation requires development experience. See the following concept pages for background information on action handlers and javascript development:

It also makes sense to study the following page: Openbravo_3_Architecture.

Bulbgraph.png   The Manual UI Processes explained in this how to, is available from 3.0MP12

Example Module

This howto is supported by an example module which shows examples of the code shown and discussed.

The code of the example module can be downloaded from this mercurial repository: https://code.openbravo.com/erp/mods/org.openbravo.client.application.examples/

The example module is available through the Central Repository (See 'Client Application Examples'), for more information see the Examples Client Application project page.

Steps to implement the Process

Overview

In this how to we will create 2 processes that will be shown as 2 buttons in Sales Order window. Each of them implements a Manual UI Process. First of the buttons sums 1 to a new field (Total example) and the second one subtract 1 to the same field. Both can be executed for multiple records at the same time.

Manual UI Processes are implemented by a JavaScript function that is invoked when the button associated is clicked. This function is in charge of managing the whole process including UI.

Implementation

Defining the Processes

Let's create 2 similar processes: one for summing and another one for subtracting.

  • Create a new record in Process Definition window
  • Define the UI pattern: Manual
  • Set the Handler: JavaScript function to be invoked when executing the process
  • Mark Multi Record: this allows to execute the process for several records at the same time.

Adding a button to Sales Order

Create a Column

As you know, you required to have a new column to associated it to a button.

Bulbgraph.png   You can check other how-to if you are not confident with this process, e.g. How_to_add_a_field_to_a_Window_Tab

In this case we are going to create 3 new columns in C_Order table: 1 for each of the buttons and another one just to visualize the results.

  • Create the new columns in the C_Order table. PostgreSQL syntax:

<source lang="sql">ALTER TABLE c_order ADD COLUMN em_obexapp_sum_proc character varying(1); ALTER TABLE c_order ADD COLUMN em_obexapp_subs_proc character varying(1); ALTER TABLE c_order ADD COLUMN em_obexapp_total numeric; </source>

  • Go to: Tables and Columns
  • Open the C_Order record
  • Execute: Create Columns from DB process
  • Move to the Columns Tab
  • Pick the newly created em_obexapp_subs_proc column
  • Change the reference to Button
  • Pick your defined Subtract process
  • Repeat last steps for sum column and process
Create a Field
  • Go to Windows, Tabs and Fields
  • Search for Sales Order
  • Select Header tab
  • Execute Create Fields to add new fields for your columns

JavaScript Implementation

As mentioned above, Manual UI Processes are implemented by a JavaScript function. Now it is time to write that. In this how to we will focus on the JavaScript itself, to learn how to add new static JavaScript files in your module, you can read Component provider section in this how to.

In MP27 a change was done so that the grid would not load all the properties for each record, just the ones that are being shown. This means that the data related to columns that are not visible in the grid might not be available in the javascript function. Because of this, the best practice is to just send the record id, which is always available, to the backend process. There you have access to all the properties of the record.

<source lang="javascript"> OB.OBEXAPP = OB.OBEXAPP || {};

OB.OBEXAPP.Process = {

 execute: function (params, view) {
   var i, selection = params.button.contextView.viewGrid.getSelectedRecords(),
       orders = [],
       callback;
   callback = function (rpcResponse, data, rpcRequest) {
     // show result
     isc.say(OB.I18N.getLabel('Obexapp_Updated', [data.updated]));
     // refresh the whole grid after executing the process
     params.button.contextView.viewGrid.refreshGrid();
   };
   for (i = 0; i < selection.length; i++) {
     orders.push(selection[i].id);
   };
   OB.RemoteCallManager.call('org.openbravo.client.application.examples.ManualProcessActionHandler', {
     orders: orders,
     action: params.action
   }, {}, callback);
 },
 sum: function (params, view) {
   params.action = 'sum';
   OB.OBEXAPP.Process.execute(params, view);
 },
 subtract: function (params, view) {
   params.action = 'subtract';
   c.Process.execute(params, view);
 }

}; </source>

We are defining sum and subtract function within OB.OBEXAPP.Process object. Note these are the functions we previously set as handlers for the 2 processes we created. They receive as parameters params which is an object containing the button invoking the process among other things, and the view the button is in. These 2 functions call execute method.

The execute method does the following:

  • Obtains the list of selected records (remember we defined these processes to be able to be ran from several records at the same time): params.button.contextView.viewGrid.getSelectedRecords()
  • Send them to a Java action handler
  • callback function receives ActionHanlder response, prints it, and refreshes the whole grid with params.button.contextView.viewGrid.refreshGrid()
Bulbgraph.png   Note about refresh after execution. Prior to PR15Q1 the way to refresh after execution was params.button.closeProcessPopup(), this method refreshed the whole grid. In order to improve performance the behavior of this function was changed to refresh only the selected record. In case whole grid is required to be refreshed after executing the process params.button.contextView.viewGrid.refreshGrid should be invoked instead.

Java Implementation

In this example we are implementing the backend process as an ActionHandler.

As mentioned earlier you should be confident with the concept of an action handler.


<source lang="java"> package org.openbravo.client.application.examples;

import java.util.Map;

import org.codehaus.jettison.json.JSONArray; import org.codehaus.jettison.json.JSONObject; import org.openbravo.base.exception.OBException; import org.openbravo.client.kernel.BaseActionHandler; import org.openbravo.dal.service.OBDal; import org.openbravo.model.common.order.Order;

public class ManualProcessActionHandler extends BaseActionHandler {

 @Override
 protected JSONObject execute(Map<String, Object> parameters, String data) {
   try {
     final JSONObject jsonData = new JSONObject(data);
     final JSONArray orderIds = jsonData.getJSONArray("orders");
     final String action = jsonData.getString("action");
     for (int i = 0; i < orderIds.length(); i++) {
       final String orderId = orderIds.getString(i);
       // get the order
       final Order order = OBDal.getInstance().get(Order.class, orderId);
       // and add or subtract 1
       Long originalValue = order.getObexappTotal();
       if (originalValue == null) {
         originalValue = 0L;
       }
       Long finalValue;
       if ("sum".equals(action)) {
         finalValue = originalValue + 1L;
       } else {
         finalValue = originalValue - 1L;
       }
       order.setObexappTotal(finalValue);
     }
     JSONObject result = new JSONObject();
     result.put("updated", orderIds.length());
     return result;
   } catch (Exception e) {
     throw new OBException(e);
   }
 }

} </source>

This class just receives an array of sales order IDs, iterates over it adding or sustracting 1 to total field.

Testing the Process

Since you have changed the structure of some Entity by adding new columns, you need to rebuild generated classes (ant smarbuild) and restart the tomcat server.

  • After restarting, you should be able to go to the Sales Order window and see a new field and 2 new buttons that are shown when there is at least a selected record.
  • When clicking the button, the process is executed and a popup indicating how many records have been affected is shown.

Advanced Topics

Manual Parameter Window

Sometimes we may need to use a parameter window, to provide additional data to the process. For Manual UI Processes, this window have to be coded manually.

Following the previous example, we are going to create a window with:

  • A parameter: a date field
  • Two buttons: one to execute the process and the other one just to cancel and close the window.

First of all we have to create our parameter window, that will extend Openbravo OBPopup class:

<source lang="javascript"> // Define a class that extends OBPopup isc.defineClass('OBEXAPP_ParameterPopup', isc.OBPopup);

isc.OBEXAPP_ParameterPopup.addProperties({

 width: 320,
 height: 200,
 title: null,
 showMinimizeButton: false,
 showMaximizeButton: false,
 
 view: null,
 params: null,
 actionHandler: null,
 orders: null,
 
 mainform: null,
 okButton: null,
 cancelButton: null,
 
 initWidget: function () {
   // Form that contains the parameters
   this.mainform = isc.DynamicForm.create({
     numCols: 1,
     fields: [{

name: 'Date',

           title: OB.I18N.getLabel('OBEXAPP_Dialog.DATE_TITLE'),

height: 20, width: 200, type: '_id_15' //Date reference }]

   });
   // OK Button
   this.okButton = isc.OBFormButton.create({
     title: OB.I18N.getLabel('OBEXAPP_Dialog.OK_BUTTON_TITLE'),
     popup: this,
     action: function () {
       var callback = function (rpcResponse, data, rpcRequest) {
         // show result
         isc.say(OB.I18N.getLabel('Obexapp_Updated', [data.updated]));
         // close process to refresh current view
         rpcRequest.clientContext.popup.closeClick();
       };
       OB.RemoteCallManager.call(this.popup.actionHandler, {
         orders: this.popup.orders,
         action: this.popup.params.action,
         dateParam: this.popup.mainform.getField('Date').getValue(), //send the parameter to the server too
       }, {}, callback, {popup: this.popup}); 
     }
  });
  
  // Cancel Button
  this.cancelButton = isc.OBFormButton.create({
    title: OB.I18N.getLabel('OBEXAPP_Dialog.CANCEL_BUTTON_TITLE'),
    popup: this,
    action: function () {
      this.popup.closeClick();
    }
  }); 
  
  //Add the elements into a layout   
  this.items = [
    isc.VLayout.create({
      defaultLayoutAlign: "center",
      align: "center",
      width: "100%",
      layoutMargin: 10,
      membersMargin: 6,
      members: [
        isc.HLayout.create({
          defaultLayoutAlign: "center",
          align: "center",
          layoutMargin: 30,
          membersMargin: 6,
          members: this.mainform
        }), 
        isc.HLayout.create({
          defaultLayoutAlign: "center",
          align: "center",
          membersMargin: 10,
          members: [this.okButton, this.cancelButton]
        })
      ]
    })
  ];
  
  this.Super('initWidget', arguments);
 }

}); </source>

Now in our process, instead of calling directly to the action handler, we fill and open our custom parameter window first:

<source lang="javascript"> OB.OBEXAPP.Process = {

 execute: function (params, view) {
   var i, selection = params.button.contextView.viewGrid.getSelectedRecords(),
       orders = [],
       callback;
   for (i = 0; i < selection.length; i++) {
     orders.push(selection[i][OB.Constants.ID]);
   }
   
   // Create the PopUp
   isc.OBEXAPP_ParameterPopup.create({
     orders: orders,
     view: view,
     params: params,
     actionHandler: 'org.openbravo.client.application.examples.ManualProcessActionHandler'
   }).show();
 },

... </source>

And finally, in the server side, we have just to retrieve the date parameter in our action handler as it is done with the rest of the data sent:

<source lang="java">

 final String date = jsonData.getString("dateParam");

</source>

Read Only and Display Logic

Columns implementing Manual UI processes can have read only logic. In case the process supports multi record, the button will be clickable if all the selected records comply the expression.

In the same way, fields for these processes can have display logic. If the process is multi record, it will be displayed only in case all selected records satisfy the expression.

For example:

  • Lets add display logic to the subtract button, not to be shown in case the total is empty or 0, to prevent negative totals. In the field the display logic would be: <source lang="javascript">@EM_Obexapp_Total@! & @EM_Obexapp_Total@>0</source>
  • And lets put read only logic to hide sum button when total is 5. In the column for sum button, read only logic would be:<source lang="javascript">@EM_Obexapp_Total@! & @EM_Obexapp_Total@>4</source>

Security

By default, in the same way as the rest of processes, access to execute these processes is granted by access to the window containing them.

It is possible to change this behavior setting a Secured Process preference.