iSeries EGL

All things EGL for the iSeries / i5 / Power System

Archive for the ‘EGL Widgets’ Category

A Conversational Dialog Box, Events and the InfoBus

leave a comment »

A Dialog Box that uses the InfoBus to Communicate User Driven Events

This post covers the creation of a dialog box and shows how to instantiate it, populate the titles and messages and how to communicate user driven events back to the parent program which launched it. The InfoBus is employed to communicate button events back to the parent program so that it may close the dialog and to act upon those events.

The Problem

The problem which this post addresses centers on the need of the application to launch a process only after asking the user if it is OK to proceed. The application launches a dialog by injecting a specified widget into a DojoDialog. When the dialog is displayed the user can select the “Cancel” or “Approve” button. At this point the event must be communicated back to the parent application so that it can A.) close the dialog and B.) either launch the process that has been approved by the user when selecting the “Proceed” button or do nothing because the user selected the “Cancel” button. The dialog box is shown below.

The dialog box.

 

The source for the dialog box and the code used by the parent application to launch it.

This section presents the source code for the dialog box along with the code used in the parent application which properly sets it up for presentation to the user.

 

The dialog box source.

Looking at the code there are two components that are established for a message title and an area for detail related messages. These are messageTextLabel and messagesTextArea The host program launching the dialog is responsible for populating these. In addition there are two functions which handle the button events. These are ApproveButton_onClick(event Event in) and CancelButton_onClick(event Event in)

package com.mig.widgets.approvedialog;

// RUI Widget

import com.ibm.egl.rui.infobus.infobus;
import com.ibm.egl.rui.widgets.GridLayout;
import com.ibm.egl.rui.widgets.GridLayoutData;
import com.ibm.egl.rui.widgets.GridLayoutLib;
import com.ibm.egl.rui.widgets.Image;
import com.ibm.egl.rui.widgets.TextLabel;
import com.ibm.egl.rui.widgets.html;
import com.mig.claimchecksapproval.libraries.ConstantsLibrary;
import com.mig.claimchecksapproval.libraries.SessionLibrary;
import com.mig.claimchecksapproval.records.GlobalDataRecord;
import com.mig.claimchecksapproval.services.ApproveChecksService;
import com.mig.widgets.waitdialog.waitdialog;
import egl.io.sql.column;
import dojo.widgets.DojoButton;
import dojo.widgets.DojoDialog;
import dojo.widgets.DojoTextArea;

//
//
handler ApproveDialog type RUIWidget{targetWidget = ui, onConstructionFunction = start, cssFile = "css/ClaimChecksApprovalProject.css", @VEWidget{category = "Custom"}}
    ui GridLayout{columns = 3, rows = 5, cellPadding = 4, children = [ GridLayout, messageTextLabel, messagesTextArea]};

    messageTextLabel TextLabel{ layoutData = new GridLayoutData{ row = 2, column = 2,
        horizontalSpan = 1, 
        horizontalAlignment = GridLayoutLib.ALIGN_CENTER }, text="TextLabel",
        fontSize = "14" };

    messagesTextArea DojoTextArea{ layoutData = new GridLayoutData{ row = 4, column = 2,
        horizontalSpan = 1 },  width = 400, height = 100,
        numColumns = 80,
        backgroundColor = "WhiteSmoke",
        id = "messagesTextArea",
        font = "courier",
        fontSize = "12" 
         };

    GridLayout GridLayout{ layoutData = new GridLayoutData{ row = 5, column = 2,
    	horizontalAlignment = GridLayoutLib.ALIGN_RIGHT }, cellPadding = 4, rows = 1, columns = 3,
    	children = [ ApproveButton, CancelButton ] };
    CancelButton DojoButton{ layoutData = new GridLayoutData{ row = 1, column = 3 }, text = "Cancel", onClick ::= CancelButton_onClick };
    ApproveButton DojoButton{ layoutData = new GridLayoutData{ row = 1, column = 2 }, text = "Approve Checks", onClick ::= ApproveButton_onClick };

          
    function start()
    end
        
    function addMessageToApproveDialog(message String in)
        messagesTextArea.value = messagesTextArea.value + message;
    end
    
    function ApproveButton_onClick(event Event in)
        InfoBus.publish(ConstantsLibrary.CONST_APPROVAL_PROCESS_APPROVE_DIALOG_APPROVE_EVENT, "noData");
    end 

    function CancelButton_onClick(event Event in)
    	InfoBus.publish(ConstantsLibrary.CONST_APPROVAL_PROCESS_APPROVE_DIALOG_CANCEL_EVENT, "noData");
    end 
   
end


Fig.1 – Source code for the dialog box (ApproveDialog.egl)

 

The code used by the parent application to present the dialog box

Shown below is the source code for the function in the parent application which launches the dialog box. It shows how it instantiates an object derived from the source presented above and how it injects the object into a DojoDialog box which simply acts as the container or frame for the object which represents its main body. It also shows how those message components are used.

    function presentApproveDialogMenuFunctionReturn(globalDataRecord GlobalDataRecord in)

        //Set title bar text and main body text message for the user instruction.
        title string = "Claim Checks Approval Process Verification";
        processMessage string = "Select 'Approve Checks' or 'Cancel'";
        
        //Set message to inform the user of an outcome of a previous step so they may select the
        //correct button.
        message string = "";        
        if (globalDataRecord.approvalStatus == ConstantsLibrary.CONST_APPROVAL_PROCESS_NOT_APPROVED)
            message = "Claim checks have not yet been approved for the day.\n";
            message = message + "You may proceed by selecting the approval or cancel button. \n";
        end
        if (globalDataRecord.approvalStatus == ConstantsLibrary.CONST_APPROVAL_PROCESS_ONCE_APPROVED)
            message = "** WARNING ! ** Claim checks have already been approved for the day! \n";
            message = message + "You may proceede by approving again or cancel.  If approved \n";
            message = message + "email notification will again be sent and the history screen \n";
            message = message + "will show another group of reports for the day.\n";            
        end
        
        //Create an instance of the ApproveDialog to be injected into a DojoDialog for presentation
        innerApproveDialog ApproveDialog =  new ApproveDialog{messageTextLabel.text = processMessage};
        innerApproveDialog.addMessageToApproveDialog(message);

        //Inject the innerApproveDialog into the DojoDialog and present it to the user.
        approveDialog = new DojoDialog { children = [ innerApproveDialog ]} ;
        approveDialog.title= title;
        approveDialog.draggable = false;
        approveDialog.showDialog();
    end 

Fig. 2 – The Service Return function that launches the dialog box.

The service return function above determines if the process has already been performed. While the process can be performed more than once, it is desirable to inform the user before proceeding. The dialog box is used present the outcome of that process to the user so that they can either cancel or proceed.

Now what?

So far, we’ve shown how a simple widget that acts as the main body for a dialog box is injected into a DojoDialog component that acts as its container. When the DojoDialog is presented to the user they can interact by selecting one of the two available buttons. The buttons then fire the appropriate event functions which we’ll focus on in the next section.

 

Using the InfoBus to provide a communication link

The parent application relies upon the InfoBus to receive information about events taking place between the user and the dialog box. To receive this information the following lines are added to the start() function in the parent application.

   //Register to the InfoBus the callback functions evoked by the ApproveDialog.
   Infobus.subscribe(ConstantsLibrary.CONST_APPROVAL_PROCESS_APPROVE_DIALOG_CANCEL_EVENT, ApproveDialogCancelButtonInfoBusCallBack);
   Infobus.subscribe(ConstantsLibrary.CONST_APPROVAL_PROCESS_APPROVE_DIALOG_APPROVE_EVENT, ApproveDialogApproveButtonInfoBusCallBack);

Fig.3 – The Parent Application. Register to the InfoBus the names of the callback functions.

See Fig. 5 for details of these functions, also in the parent application.

As seen in figure 2, the parent application “owns” the dialog meaning it holds a reference to the dialog object. When displayed the dialog is holding a conversation with the user who can select one of two buttons. No matter which button is selected, the dialog “owns” that event. There are 2 problems to be solved here, the first being the dialog cannot close itself, since it is the application which holds the object reference to the dialog. Any attempt by the dialog to close itself results in an “object not found” error. The second problem is then how to communicate the button event back to the application so that it can close the dialog, since it holds the reference to it, and then either do nothing or proceed with the process.

To accomplish this, the dialog has these button-event functions ApproveButton_onClick(event Event in) and CancelButton_onClick(event Event in), shown below.

    function ApproveButton_onClick(event Event in)
        InfoBus.publish(ConstantsLibrary.CONST_APPROVAL_PROCESS_APPROVE_DIALOG_APPROVE_EVENT, "noData");
    end 

    function CancelButton_onClick(event Event in)
    	InfoBus.publish(ConstantsLibrary.CONST_APPROVAL_PROCESS_APPROVE_DIALOG_CANCEL_EVENT, "noData");
    end 

Fig.4 – The dialog’s button-event functions

They are called when either the Approve and Cancel button are selected. These functions use the InfoBus to “publish” the need to call a function that resides in the parent application. Notice that the constants used (their values are arbitrary) are the same constants used by the parent application’s start up processing which registered the call-back functions. Those functions that are in the parent application are going to get called whenever one of the functions are called.

The constants CONST_APPROVAL_PROCESS_APPROVE_DIALOG_APPROVE_EVENT and CONST_APPROVAL_PROCESS_APPROVE_DIALOG_CANCEL_EVENT of the ConstantsLibrary are used by the Infobus.publish command to establish links back to the parent application’s ApproveDialogCancelButtonInfoBusCallBack and ApproveDialogApproveButtonInfoBusCallBack functions. The values of the constants are arbitrary in nature meaning their values don’t really matter. They simply act as a handle or a means by which a lookup can be performed so that the proper function can be called when the user selects either the “Cancel” or “Approve” button.

When the user selects either button provided by the dialog the function in the parent application is called which then closes the dialog and then proceeds with the appropriate processing.

    /**
     * This function is called when the Cancel button of the Approve Dialog is selected.
     */
    function ApproveDialogCancelButtonInfoBusCallBack(event string in, data any in)
        approveDialog.hideDialog();
        approveDialog.destroy();
    end

    /**
     *  Approve the claim checks by calling this service which will;
     *  1.  Create the PDF document containing all table data shown  
     *  2.  Writes all URL values obtained from CLM0002PF to CLM003PF
     *
     */ 
    function ApproveDialogApproveButtonInfoBusCallBack(event string in, data any in)

        //Close the Approval Dialog box and destroy the object reference.
        approveDialog.hideDialog();
        approveDialog.destroy();  

        //Show another wait dialog while the approval process is running
        message string = "Approval Processing is now executing.";
        innerWaitDialog =  new WaitDialog{messageTextLabel.text = message};
        processingDialog = new DojoDialog { children = [ innerWaitDialog ]} ;
        processingDialog.title= "Approval Processing";
        processingDialog.draggable = false;
        processingDialog.showDialog();

       :
       :  Main process occurs here
       :

    end 

Fig. 5 – The event functions for the Approve and Cancel buttons.

 

Shown here is the parent application after the user has selected the “Approve” button. As a result, another dialog is presented which indicates the process has been launched.

A Process in Progress dialog box.

 

Conclusion

This post showed how to create a somewhat reusable dialog box to be injected into a DojoDialog component which was then instantiated, initialized and presented by the parent application. The problem of communicating dialog button events back to the parent application was addressed by the use of the InfoBus which calls the appropriate functions for further processing by the parent program.

 
 

Advertisements

Written by iseriesadmin

October 18, 2012 at 8:44 am

A Multi-Select Widget

leave a comment »

Introduction

This post illustrates how to create a new widget so that it can be included into a new project or existing project.

The Widget

The widget will present a list of items from which the user may select one, some or all to be included into a working list. As they are select from the original list to be added to the working list they are removed from the list on the left.

Shown here is the widget as it appears in the designer.

The Mulit-Select widget consists of 2 ListMulti widgets and a couple of buttons.

The widget includes several functions that allow the developer to establish short descriptions that appear above each list and to populate each ListMulti widget with values. The buttons are left as they appear and let the user move the selected items from one list to another.

Here is an example of the widget as it is used by the main application after it has been fully initialized.

A fully initialized Multi-Select widget running in an application.

Below is the source code for this very short web-page. The start function does all the work of initializing. In this case the list is filled with different types of aircraft. The user may select one or more from the list at the left and then select the button with the “>” arrow which moves the selected item(s) to the list shown on the right. Modifying the list of selected items (at right) is performed the same way; select the desired items from the list at right and select the button marked as “<" and the items are placed back into the list on the left.

package com.mig.selectwidget.client;

// RUI Handler

import org.eclipse.edt.rui.widgets.GridLayout;
import org.eclipse.edt.rui.widgets.GridLayoutData;

//
//

handler Test type RUIhandler{initialUI =[ui
            ], onConstructionFunction = start, cssFile = "css/SelectIncludeWidget.css", title = "Test"}

    ui GridLayout{columns = 3, rows = 3, cellPadding = 4, children = [ selectincludewidget]};
    selectIncludeWidget SelectIncludeWidget{ layoutData = new GridLayoutData{ row = 2, column = 2 }};

    function start()
    	
    	includeItems String[] = new String[8];
    	
     	includeItems[1] = "Piper Cub";       	
     	includeItems[2] = "North American T-6G";       
     	includeItems[3] = "North American T-28";     
     	includeItems[4] = "Lockheed T-33A";       
     	includeItems[5] = "Douglas C-47D";          	
    	includeItems[6] = "Cessna 152";
    	includeItems[7] = "Cessna 172";    	
     	includeItems[8] = "Piper Archer PA-28-181";  
      	     	
     	this.selectIncludeWidget.setSelectList(includeItems);
 
 		this.selectIncludeWidget.setSelectTextLabel("Selectable Aircraft");
 		this.selectIncludeWidget.setIncludeTextLabel("Included Aircraft");
		this.selectIncludeWidget.setListWidths(180);
		this.selectIncludeWidget.setListSizes(15);
		 
    end
    
end

Fig.1 – An example of an application using the widget.

Note the start function’s use of the widget’s functions that let the developer initialize the list entries, the short descriptions that appear above each in addition to the length and width of each list. The widget’s functions are listed here.

  • setSelectList(stringArray String[]) Sets the list of selectable items shown on the left side of the widget.
  • setSelectTextLabel(labelText String) Sets the text label appearing over the selection list shown at the left of the widget.
  • setIncludeTextLabel(stringArray String[]) Sets the text label appearing over the include list shown at the right of the widget.
  • setListWidths(widths int) Sets the widths of the list windows.
  • setListSizes(sizes int) Sets the sizes of the list windows in terms of the number of entries that are visible before a scroll bar is made visible.

Below is the full source listing for the widget. To use it, simply add the code to your project and then drag-n-drop it into your page.

package com.mig.selectwidget.client;

// RUI Widget

import org.eclipse.edt.rui.widgets.GridLayout;
import dojo.widgets.DojoComboBox;
import org.eclipse.edt.rui.widgets.GridLayoutData;
import dojo.widgets.DojoButton;
import org.eclipse.edt.rui.widgets.GridLayoutLib;
import org.eclipse.edt.rui.widgets.ListMulti;
import org.eclipse.edt.rui.widgets.TextLabel;

//
//

handler SelectIncludeWidget type RUIWidget{targetWidget = ui, onConstructionFunction = start, cssFile = "css/SelectIncludeProject.css", @VEWidget{category = "Custom"}}
    ui GridLayout{columns = 3, rows = 6, cellPadding = 0, children = [ excludeSelectedButton, selectSelectedButton, includeTextLabel, selectTextLabel, selectListMulti, includeListMulti],
    	padding = 0,
    	marginLeft = 0};
    selectSelectedButton DojoButton{ layoutData = new GridLayoutData{ row = 3, column = 2,
    	horizontalAlignment = GridLayoutLib.ALIGN_CENTER,
    	verticalAlignment = GridLayoutLib.VALIGN_MIDDLE }, text = ">", onClick ::= selectSelectedButton_onClick };
    excludeSelectedButton DojoButton{ layoutData = new GridLayoutData{ row = 5, column = 2,
    	horizontalAlignment = GridLayoutLib.ALIGN_CENTER,
    	cellPadding = 0 }, text = "<", onClick ::= exludeSelectedButton_onClick };
    	
    selectListMulti ListMulti{ layoutData = new GridLayoutData{ row = 2, column = 1,
    	verticalSpan = 5 }, values = ["selectListMulti"], size = 10,
    	width = "120"  };
    includeListMulti ListMulti{ layoutData = new GridLayoutData{ row = 2, column = 3,
    	verticalSpan = 5 }, values = ["includeListMulti1"], size = 10,
    	width = "120" };


	//This will be used to preserve the developer-defined list of initial values from which
	//the user selects entries to be included in the list seen at the right of the widget.
	preserveSelectValues String[] ;
	selectTextLabel TextLabel{ layoutData = new GridLayoutData{ row = 1, column = 1 }, text="TextLabel" };
	includeTextLabel TextLabel{ layoutData = new GridLayoutData{ row = 1, column = 3 }, text="TextLabel" };
	
	
    function start()
    	includeListMulti.values = new String[];
    end
    
    function setListWidths(value int in)
    	selectListMulti.width = value;
    	includeListMulti.width = value;
    end	
    
    function setListSizes(value int in)
    	selectListMulti.size = value;
        includeListMulti.size = value;
    end
    
    function setSelectTextLabel(value String)
    	selectTextLabel.text = value;
    end
    
    function setSelectList(values String[])
    	selectListMulti.values = values;
    	
    	size int = values.getSize();
    	preserveSelectValues = new String[ ];
    	preserveSelectValues.appendAll(values);
    	
    end
     
    function setIncludeTextLabel(value String)
    	includeTextLabel.text = value;
    end   
    
    
    function getIncudedList() returns(String[])
    	return(includeListMulti.values);
    end
    
    //**********************************************************************
    //
    //
    function selectSelectedButton_onClick(event Event in)
    	    	
        //Isolate the values which are remaining in the selectListMulto to update the list.
        workSelectableValues String[]  = selectListMulti.values;
        

    	//Isolate the values which may already be in includeListMulti to preserve the list.
    	workIncludedValues String[]  = includeListMulti.values;
    	
  	
    	//Get the number of selections available in selectListMulti.
    	numberOfSelections int = selectListMulti.selection.getSize();
 
    	if (numberOfSelections > 0)
    		
    		//Iterate through the item numbers 
    		for(idx int from 1 to numberOfSelections)
    			selectEntryIndex int = selectListMulti.selection[idx];
    			selectedEntryValue string = selectListMulti.values[selectEntryIndex];
    			workIncludedValues.appendElement( selectedEntryValue );  	 
    		end
    		
    		//Process in reverse order to properly remove the entries
    		for(idx int from numberOfSelections to 1 decrement by 1)
    		  selectEntryIndex int = selectListMulti.selection[idx];	
    		  workSelectableValues.removeElement(selectEntryIndex);	
    		end
    		
    		//Update the ListMulti widgets
    		selectListMulti.values = workSelectableValues;
 			includeListMulti.values = workIncludedValues;
 			
 
 			
    	end
    end
    


    //**********************************************************************
    //
    //    
    function exludeSelectedButton_onClick(event Event in)
                
        //Isolate the values which are remaining in the selectListMulto to update the list.
        workSelectableValues String[]  = selectListMulti.values;
 
        //Isolate the values which may already be in includeListMulti to preserve the list.
        workIncludedValues String[]  = includeListMulti.values;
        
    
        //Get the number of entries available in includeListMulti.
        numberOfIncluded int = includeListMulti.selection.getSize();
 
        if (numberOfIncluded > 0)
            
            //Iterate through the item numbers 
            for(idx int from 1 to numberOfIncluded)
                includedEntryIndex int = includeListMulti.selection[idx];
                includedEntryValue string = includeListMulti.values[includedEntryIndex];
                workSelectableValues.appendElement( includedEntryValue );      
            end
            
            // Process in reverse order to properly remove the entries
            for(idx int from numberOfIncluded to 1 decrement by 1)
              selectEntryIndex int = includeListMulti.selection[idx];    
              workIncludedValues.removeElement(selectEntryIndex); 
            end
            
            //Update the ListMulti widgets
            selectListMulti.values = workSelectableValues;
            includeListMulti.values = workIncludedValues;
            
        end    	
    end
   
end

Fig.2 – The full source for the widget.

Summary

This post showed how custom widgets can be used and re-used across difference applications. Designing EGL applications in this manner can result in productivity gains by reducing development and testing times.
 
 

Written by iseriesadmin

July 29, 2012 at 5:13 pm

Posted in EGL Widgets, Rich UI

Tagged with ,